Merge pull request #16902 from owncloud/jsocclient
Web UI uses Webdav instead of ajax/* calls
This commit is contained in:
commit
79bbda994b
58 changed files with 4136 additions and 1622 deletions
|
@ -116,6 +116,7 @@ class FilesPlugin extends \Sabre\DAV\ServerPlugin {
|
|||
$this->server->on('afterBind', array($this, 'sendFileIdHeader'));
|
||||
$this->server->on('afterWriteContent', array($this, 'sendFileIdHeader'));
|
||||
$this->server->on('afterMethod:GET', [$this,'httpGet']);
|
||||
$this->server->on('afterMethod:GET', array($this, 'handleDownloadToken'));
|
||||
$this->server->on('afterResponse', function($request, ResponseInterface $response) {
|
||||
$body = $response->getBody();
|
||||
if (is_resource($body)) {
|
||||
|
@ -148,6 +149,32 @@ class FilesPlugin extends \Sabre\DAV\ServerPlugin {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This sets a cookie to be able to recognize the start of the download
|
||||
* the content must not be longer than 32 characters and must only contain
|
||||
* alphanumeric characters
|
||||
*
|
||||
* @param RequestInterface $request
|
||||
* @param ResponseInterface $response
|
||||
*/
|
||||
function handleDownloadToken(RequestInterface $request, ResponseInterface $response) {
|
||||
$queryParams = $request->getQueryParameters();
|
||||
|
||||
/**
|
||||
* this sets a cookie to be able to recognize the start of the download
|
||||
* the content must not be longer than 32 characters and must only contain
|
||||
* alphanumeric characters
|
||||
*/
|
||||
if (isset($queryParams['downloadStartSecret'])) {
|
||||
$token = $queryParams['downloadStartSecret'];
|
||||
if (!isset($token[32])
|
||||
&& preg_match('!^[a-zA-Z0-9]+$!', $token) === 1) {
|
||||
// FIXME: use $response->setHeader() instead
|
||||
setcookie('ocDownloadStarted', $token, time() + 20, '/');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin that adds a 'Content-Disposition: attachment' header to all files
|
||||
* delivered by SabreDAV.
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Arthur Schiwon <blizzz@owncloud.com>
|
||||
* @author Frank Karlitschek <frank@owncloud.org>
|
||||
* @author Jakob Sack <mail@jakobsack.de>
|
||||
* @author Joas Schilling <nickvergessen@owncloud.com>
|
||||
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
|
||||
* @author Lukas Reschke <lukas@owncloud.com>
|
||||
* @author Robin Appelman <icewind@owncloud.com>
|
||||
* @author Thomas Müller <thomas.mueller@tmit.eu>
|
||||
* @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/>
|
||||
*
|
||||
*/
|
||||
OCP\JSON::checkLoggedIn();
|
||||
OCP\JSON::callCheck();
|
||||
\OC::$server->getSession()->close();
|
||||
|
||||
|
||||
// Get data
|
||||
$dir = isset($_POST['dir']) ? (string)$_POST['dir'] : '';
|
||||
$allFiles = isset($_POST["allfiles"]) ? (string)$_POST["allfiles"] : false;
|
||||
|
||||
// delete all files in dir ?
|
||||
if ($allFiles === 'true') {
|
||||
$files = array();
|
||||
$fileList = \OC\Files\Filesystem::getDirectoryContent($dir);
|
||||
foreach ($fileList as $fileInfo) {
|
||||
$files[] = $fileInfo['name'];
|
||||
}
|
||||
} else {
|
||||
$files = isset($_POST["file"]) ? (string)$_POST["file"] : (string)$_POST["files"];
|
||||
$files = json_decode($files);
|
||||
}
|
||||
$filesWithError = '';
|
||||
|
||||
$success = true;
|
||||
|
||||
//Now delete
|
||||
foreach ($files as $file) {
|
||||
try {
|
||||
if (\OC\Files\Filesystem::file_exists($dir . '/' . $file) &&
|
||||
!(\OC\Files\Filesystem::isDeletable($dir . '/' . $file) &&
|
||||
\OC\Files\Filesystem::unlink($dir . '/' . $file))
|
||||
) {
|
||||
$filesWithError .= $file . "\n";
|
||||
$success = false;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$filesWithError .= $file . "\n";
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
|
||||
// get array with updated storage stats (e.g. max file size) after upload
|
||||
try {
|
||||
$storageStats = \OCA\Files\Helper::buildFileStorageStatistics($dir);
|
||||
} catch(\OCP\Files\NotFoundException $e) {
|
||||
OCP\JSON::error(['data' => ['message' => 'File not found']]);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($success) {
|
||||
OCP\JSON::success(array("data" => array_merge(array("dir" => $dir, "files" => $files), $storageStats)));
|
||||
} else {
|
||||
OCP\JSON::error(array("data" => array_merge(array("message" => "Could not delete:\n" . $filesWithError), $storageStats)));
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Björn Schießle <schiessle@owncloud.com>
|
||||
* @author Frank Karlitschek <frank@owncloud.org>
|
||||
* @author Georg Ehrke <georg@owncloud.com>
|
||||
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
|
||||
* @author Lukas Reschke <lukas@owncloud.com>
|
||||
* @author Robin Appelman <icewind@owncloud.com>
|
||||
* @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/>
|
||||
*
|
||||
*/
|
||||
OCP\JSON::checkLoggedIn();
|
||||
OCP\JSON::callCheck();
|
||||
\OC::$server->getSession()->close();
|
||||
|
||||
// Get data
|
||||
$dir = isset($_POST['dir']) ? (string)$_POST['dir'] : '';
|
||||
$file = isset($_POST['file']) ? (string)$_POST['file'] : '';
|
||||
$target = isset($_POST['target']) ? rawurldecode((string)$_POST['target']) : '';
|
||||
|
||||
$l = \OC::$server->getL10N('files');
|
||||
|
||||
if(\OC\Files\Filesystem::file_exists($target . '/' . $file)) {
|
||||
OCP\JSON::error(array("data" => array( "message" => $l->t("Could not move %s - File with this name already exists", array($file)) )));
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($target != '' || strtolower($file) != 'shared') {
|
||||
$targetFile = \OC\Files\Filesystem::normalizePath($target . '/' . $file);
|
||||
$sourceFile = \OC\Files\Filesystem::normalizePath($dir . '/' . $file);
|
||||
try {
|
||||
if(\OC\Files\Filesystem::rename($sourceFile, $targetFile)) {
|
||||
OCP\JSON::success(array("data" => array( "dir" => $dir, "files" => $file )));
|
||||
} else {
|
||||
OCP\JSON::error(array("data" => array( "message" => $l->t("Could not move %s", array($file)) )));
|
||||
}
|
||||
} catch (\OCP\Files\NotPermittedException $e) {
|
||||
OCP\JSON::error(array("data" => array( "message" => $l->t("Permission denied") )));
|
||||
} catch (\Exception $e) {
|
||||
OCP\JSON::error(array("data" => array( "message" => $e->getMessage())));
|
||||
}
|
||||
}else{
|
||||
OCP\JSON::error(array("data" => array( "message" => $l->t("Could not move %s", array($file)) )));
|
||||
}
|
|
@ -1,103 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Andreas Fischer <bantu@owncloud.com>
|
||||
* @author Georg Ehrke <georg@owncloud.com>
|
||||
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
|
||||
* @author Lukas Reschke <lukas@owncloud.com>
|
||||
* @author Robin Appelman <icewind@owncloud.com>
|
||||
* @author Thomas Müller <thomas.mueller@tmit.eu>
|
||||
* @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/>
|
||||
*
|
||||
*/
|
||||
// Init owncloud
|
||||
global $eventSource;
|
||||
|
||||
\OCP\JSON::checkLoggedIn();
|
||||
\OCP\JSON::callCheck();
|
||||
|
||||
\OC::$server->getSession()->close();
|
||||
|
||||
// Get the params
|
||||
$dir = isset( $_REQUEST['dir'] ) ? '/'.trim((string)$_REQUEST['dir'], '/\\') : '';
|
||||
$fileName = isset( $_REQUEST['filename'] ) ? trim((string)$_REQUEST['filename'], '/\\') : '';
|
||||
|
||||
$l10n = \OC::$server->getL10N('files');
|
||||
|
||||
$result = array(
|
||||
'success' => false,
|
||||
'data' => NULL
|
||||
);
|
||||
|
||||
try {
|
||||
\OC\Files\Filesystem::getView()->verifyPath($dir, $fileName);
|
||||
} catch (\OCP\Files\InvalidPathException $ex) {
|
||||
$result['data'] = [
|
||||
'message' => $ex->getMessage()];
|
||||
OCP\JSON::error($result);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!\OC\Files\Filesystem::file_exists($dir . '/')) {
|
||||
$result['data'] = array('message' => (string)$l10n->t(
|
||||
'The target folder has been moved or deleted.'),
|
||||
'code' => 'targetnotfound'
|
||||
);
|
||||
OCP\JSON::error($result);
|
||||
exit();
|
||||
}
|
||||
|
||||
$target = $dir.'/'.$fileName;
|
||||
|
||||
if (\OC\Files\Filesystem::file_exists($target)) {
|
||||
$result['data'] = array('message' => (string)$l10n->t(
|
||||
'The name %s is already used in the folder %s. Please choose a different name.',
|
||||
array($fileName, $dir))
|
||||
);
|
||||
OCP\JSON::error($result);
|
||||
exit();
|
||||
}
|
||||
|
||||
$success = false;
|
||||
$templateManager = OC_Helper::getFileTemplateManager();
|
||||
$mimeType = OC_Helper::getMimetypeDetector()->detectPath($target);
|
||||
$content = $templateManager->getTemplate($mimeType);
|
||||
|
||||
try {
|
||||
if($content) {
|
||||
$success = \OC\Files\Filesystem::file_put_contents($target, $content);
|
||||
} else {
|
||||
$success = \OC\Files\Filesystem::touch($target);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$result = [
|
||||
'success' => false,
|
||||
'data' => [
|
||||
'message' => $e->getMessage()
|
||||
]
|
||||
];
|
||||
OCP\JSON::error($result);
|
||||
exit();
|
||||
}
|
||||
|
||||
if($success) {
|
||||
$meta = \OC\Files\Filesystem::getFileInfo($target);
|
||||
OCP\JSON::success(array('data' => \OCA\Files\Helper::formatFileInfo($meta)));
|
||||
return;
|
||||
}
|
||||
|
||||
OCP\JSON::error(array('data' => array( 'message' => $l10n->t('Error when creating the file') )));
|
|
@ -1,99 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Arthur Schiwon <blizzz@owncloud.com>
|
||||
* @author Björn Schießle <schiessle@owncloud.com>
|
||||
* @author Frank Karlitschek <frank@owncloud.org>
|
||||
* @author Georg Ehrke <georg@owncloud.com>
|
||||
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
|
||||
* @author Lukas Reschke <lukas@owncloud.com>
|
||||
* @author Robin Appelman <icewind@owncloud.com>
|
||||
* @author Thomas Müller <thomas.mueller@tmit.eu>
|
||||
* @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/>
|
||||
*
|
||||
*/
|
||||
// Init owncloud
|
||||
|
||||
|
||||
OCP\JSON::checkLoggedIn();
|
||||
OCP\JSON::callCheck();
|
||||
\OC::$server->getSession()->close();
|
||||
|
||||
// Get the params
|
||||
$dir = isset($_POST['dir']) ? (string)$_POST['dir'] : '';
|
||||
$folderName = isset($_POST['foldername']) ?(string) $_POST['foldername'] : '';
|
||||
|
||||
$l10n = \OC::$server->getL10N('files');
|
||||
|
||||
$result = array(
|
||||
'success' => false,
|
||||
'data' => NULL
|
||||
);
|
||||
|
||||
try {
|
||||
\OC\Files\Filesystem::getView()->verifyPath($dir, $folderName);
|
||||
} catch (\OCP\Files\InvalidPathException $ex) {
|
||||
$result['data'] = [
|
||||
'message' => $ex->getMessage()];
|
||||
OCP\JSON::error($result);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!\OC\Files\Filesystem::file_exists($dir . '/')) {
|
||||
$result['data'] = array('message' => (string)$l10n->t(
|
||||
'The target folder has been moved or deleted.'),
|
||||
'code' => 'targetnotfound'
|
||||
);
|
||||
OCP\JSON::error($result);
|
||||
exit();
|
||||
}
|
||||
|
||||
$target = $dir . '/' . $folderName;
|
||||
|
||||
if (\OC\Files\Filesystem::file_exists($target)) {
|
||||
$result['data'] = array('message' => $l10n->t(
|
||||
'The name %s is already used in the folder %s. Please choose a different name.',
|
||||
array($folderName, $dir))
|
||||
);
|
||||
OCP\JSON::error($result);
|
||||
exit();
|
||||
}
|
||||
|
||||
try {
|
||||
if(\OC\Files\Filesystem::mkdir($target)) {
|
||||
if ( $dir !== '/') {
|
||||
$path = $dir.'/'.$folderName;
|
||||
} else {
|
||||
$path = '/'.$folderName;
|
||||
}
|
||||
$meta = \OC\Files\Filesystem::getFileInfo($path);
|
||||
$meta['type'] = 'dir'; // missing ?!
|
||||
OCP\JSON::success(array('data' => \OCA\Files\Helper::formatFileInfo($meta)));
|
||||
exit();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$result = [
|
||||
'success' => false,
|
||||
'data' => [
|
||||
'message' => $e->getMessage()
|
||||
]
|
||||
];
|
||||
OCP\JSON::error($result);
|
||||
exit();
|
||||
}
|
||||
|
||||
OCP\JSON::error(array('data' => array( 'message' => $l10n->t('Error when creating the folder') )));
|
|
@ -1,58 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Christopher Schäpers <kondou@ts.unde.re>
|
||||
* @author Frank Karlitschek <frank@owncloud.org>
|
||||
* @author Jakob Sack <mail@jakobsack.de>
|
||||
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
|
||||
* @author Lukas Reschke <lukas@owncloud.com>
|
||||
* @author Morris Jobke <hey@morrisjobke.de>
|
||||
* @author Robin Appelman <icewind@owncloud.com>
|
||||
* @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/>
|
||||
*
|
||||
*/
|
||||
|
||||
OCP\JSON::checkLoggedIn();
|
||||
OCP\JSON::callCheck();
|
||||
\OC::$server->getSession()->close();
|
||||
|
||||
$l10n = \OC::$server->getL10N('files');
|
||||
|
||||
$files = new \OCA\Files\App(
|
||||
\OC\Files\Filesystem::getView(),
|
||||
\OC::$server->getL10N('files')
|
||||
);
|
||||
try {
|
||||
$result = $files->rename(
|
||||
isset($_GET['dir']) ? (string)$_GET['dir'] : '',
|
||||
isset($_GET['file']) ? (string)$_GET['file'] : '',
|
||||
isset($_GET['newname']) ? (string)$_GET['newname'] : ''
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
$result = [
|
||||
'success' => false,
|
||||
'data' => [
|
||||
'message' => $e->getMessage()
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
if($result['success'] === true){
|
||||
OCP\JSON::success(['data' => $result['data']]);
|
||||
} else {
|
||||
OCP\JSON::error(['data' => $result['data']]);
|
||||
}
|
|
@ -41,7 +41,6 @@ OCP\JSON::setContentTypeHeader('text/plain');
|
|||
// If not, check the login.
|
||||
// If no token is sent along, rely on login only
|
||||
|
||||
$allowedPermissions = \OCP\Constants::PERMISSION_ALL;
|
||||
$errorCode = null;
|
||||
|
||||
$l = \OC::$server->getL10N('files');
|
||||
|
@ -60,8 +59,6 @@ if (empty($_POST['dirToken'])) {
|
|||
|
||||
\OC_User::setIncognitoMode(true);
|
||||
|
||||
// return only read permissions for public upload
|
||||
$allowedPermissions = \OCP\Constants::PERMISSION_READ;
|
||||
$publicDirectory = !empty($_POST['subdir']) ? (string)$_POST['subdir'] : '/';
|
||||
|
||||
$linkItem = OCP\Share::getShareByToken((string)$_POST['dirToken']);
|
||||
|
@ -207,7 +204,7 @@ if (\OC\Files\Filesystem::isValidPath($dir) === true) {
|
|||
$data['originalname'] = $files['name'][$i];
|
||||
$data['uploadMaxFilesize'] = $maxUploadFileSize;
|
||||
$data['maxHumanFilesize'] = $maxHumanFileSize;
|
||||
$data['permissions'] = $meta['permissions'] & $allowedPermissions;
|
||||
$data['permissions'] = $meta['permissions'];
|
||||
$data['directory'] = $returnedDir;
|
||||
$result[] = $data;
|
||||
}
|
||||
|
@ -234,7 +231,7 @@ if (\OC\Files\Filesystem::isValidPath($dir) === true) {
|
|||
$data['originalname'] = $files['name'][$i];
|
||||
$data['uploadMaxFilesize'] = $maxUploadFileSize;
|
||||
$data['maxHumanFilesize'] = $maxHumanFileSize;
|
||||
$data['permissions'] = $meta['permissions'] & $allowedPermissions;
|
||||
$data['permissions'] = $meta['permissions'];
|
||||
$data['directory'] = $returnedDir;
|
||||
$result[] = $data;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<shipped>true</shipped>
|
||||
<standalone/>
|
||||
<default_enable/>
|
||||
<version>1.3.0</version>
|
||||
<version>1.4.0</version>
|
||||
<types>
|
||||
<filesystem/>
|
||||
</types>
|
||||
|
|
|
@ -119,6 +119,8 @@ class ViewController extends Controller {
|
|||
* @throws \OCP\Files\NotFoundException
|
||||
*/
|
||||
public function index($dir = '', $view = '') {
|
||||
$nav = new \OCP\Template('files', 'appnavigation', '');
|
||||
|
||||
// Load the files we need
|
||||
\OCP\Util::addStyle('files', 'files');
|
||||
\OCP\Util::addStyle('files', 'upload');
|
||||
|
@ -169,8 +171,6 @@ class ViewController extends Controller {
|
|||
// FIXME: Make non static
|
||||
$storageInfo = $this->getStorageInfo();
|
||||
|
||||
$nav = new \OCP\Template('files', 'appnavigation', '');
|
||||
|
||||
\OCA\Files\App::getNavigationManager()->add(
|
||||
[
|
||||
'id' => 'favorites',
|
||||
|
|
|
@ -71,7 +71,8 @@
|
|||
folderDropOptions: folderDropOptions,
|
||||
fileActions: fileActions,
|
||||
allowLegacyActions: true,
|
||||
scrollTo: urlParams.scrollto
|
||||
scrollTo: urlParams.scrollto,
|
||||
filesClient: OC.Files.getClient()
|
||||
}
|
||||
);
|
||||
this.files.initialize();
|
||||
|
|
|
@ -92,7 +92,7 @@
|
|||
// folder in the files app instead of opening it directly
|
||||
fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) {
|
||||
OCA.Files.App.setActiveView('files', {silent: true});
|
||||
OCA.Files.App.fileList.changeDirectory(context.$file.attr('data-path') + '/' + filename, true, true);
|
||||
OCA.Files.App.fileList.changeDirectory(OC.joinPaths(context.$file.attr('data-path'), filename), true, true);
|
||||
});
|
||||
fileActions.setDefault('dir', 'Open');
|
||||
return fileActions;
|
||||
|
|
|
@ -575,7 +575,8 @@
|
|||
},
|
||||
actionHandler: function (filename, context) {
|
||||
var dir = context.dir || context.fileList.getCurrentDirectory();
|
||||
var url = context.fileList.getDownloadUrl(filename, dir);
|
||||
var isDir = context.$file.attr('data-type') === 'dir';
|
||||
var url = context.fileList.getDownloadUrl(filename, dir, isDir);
|
||||
|
||||
var downloadFileaction = $(context.$file).find('.fileactions .action-download');
|
||||
|
||||
|
@ -611,10 +612,7 @@
|
|||
|
||||
this.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) {
|
||||
var dir = context.$file.attr('data-path') || context.fileList.getCurrentDirectory();
|
||||
if (dir !== '/') {
|
||||
dir = dir + '/';
|
||||
}
|
||||
context.fileList.changeDirectory(dir + filename);
|
||||
context.fileList.changeDirectory(OC.joinPaths(dir, filename));
|
||||
});
|
||||
|
||||
this.registerAction({
|
||||
|
|
|
@ -22,11 +22,12 @@
|
|||
*
|
||||
* @param $el container element with existing markup for the #controls
|
||||
* and a table
|
||||
* @param [options] map of options, see other parameters
|
||||
* @param [options.scrollContainer] scrollable container, defaults to $(window)
|
||||
* @param [options.dragOptions] drag options, disabled by default
|
||||
* @param [options.folderDropOptions] folder drop options, disabled by default
|
||||
* @param [options.detailsViewEnabled=true] whether to enable details view
|
||||
* @param {Object} [options] map of options, see other parameters
|
||||
* @param {Object} [options.scrollContainer] scrollable container, defaults to $(window)
|
||||
* @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 {OC.Files.Client} [options.filesClient] files client to use
|
||||
*/
|
||||
var FileList = function($el, options) {
|
||||
this.initialize($el, options);
|
||||
|
@ -73,6 +74,13 @@
|
|||
*/
|
||||
_detailsView: null,
|
||||
|
||||
/**
|
||||
* Files client instance
|
||||
*
|
||||
* @type OC.Files.Client
|
||||
*/
|
||||
filesClient: null,
|
||||
|
||||
/**
|
||||
* Whether the file list was initialized already.
|
||||
* @type boolean
|
||||
|
@ -92,10 +100,17 @@
|
|||
* Array of files in the current folder.
|
||||
* The entries are of file data.
|
||||
*
|
||||
* @type Array.<Object>
|
||||
* @type Array.<OC.Files.FileInfo>
|
||||
*/
|
||||
files: [],
|
||||
|
||||
/**
|
||||
* Current directory entry
|
||||
*
|
||||
* @type OC.Files.FileInfo
|
||||
*/
|
||||
dirInfo: null,
|
||||
|
||||
/**
|
||||
* File actions handler, defaults to OCA.Files.FileActions
|
||||
* @type OCA.Files.FileActions
|
||||
|
@ -149,7 +164,7 @@
|
|||
* When false, clicking on a table header will call reload().
|
||||
* When true, clicking on a table header will simply resort the list.
|
||||
*/
|
||||
_clientSideSort: false,
|
||||
_clientSideSort: true,
|
||||
|
||||
/**
|
||||
* Current directory
|
||||
|
@ -170,6 +185,7 @@
|
|||
* @param options.dragOptions drag options, disabled by default
|
||||
* @param options.folderDropOptions folder drop options, disabled by default
|
||||
* @param options.scrollTo name of file to scroll to after the first load
|
||||
* @param {OC.Files.Client} [options.filesClient] files API client
|
||||
* @private
|
||||
*/
|
||||
initialize: function($el, options) {
|
||||
|
@ -185,6 +201,12 @@
|
|||
if (options.folderDropOptions) {
|
||||
this._folderDropOptions = options.folderDropOptions;
|
||||
}
|
||||
if (options.filesClient) {
|
||||
this.filesClient = options.filesClient;
|
||||
} else {
|
||||
// default client if not specified
|
||||
this.filesClient = OC.Files.getClient();
|
||||
}
|
||||
|
||||
this.$el = $el;
|
||||
if (options.id) {
|
||||
|
@ -209,6 +231,8 @@
|
|||
this.files = [];
|
||||
this._selectedFiles = {};
|
||||
this._selectionSummary = new OCA.Files.FileSummary();
|
||||
// dummy root dir info
|
||||
this.dirInfo = new OC.Files.FileInfo({});
|
||||
|
||||
this.fileSummary = this._createSummary();
|
||||
|
||||
|
@ -359,7 +383,7 @@
|
|||
var highlightState = $tr.hasClass('highlighted');
|
||||
$tr = self.updateRow(
|
||||
$tr,
|
||||
_.extend({isPreviewAvailable: true}, model.toJSON()),
|
||||
model.toJSON(),
|
||||
{updateSummary: true, silent: false, animate: true}
|
||||
);
|
||||
$tr.toggleClass('highlighted', highlightState);
|
||||
|
@ -618,7 +642,7 @@
|
|||
};
|
||||
|
||||
OCA.Files.FileActions.updateFileActionSpinner(downloadFileaction, true);
|
||||
OCA.Files.Files.handleDownload(this.getDownloadUrl(files, dir), disableLoadingState);
|
||||
OCA.Files.Files.handleDownload(this.getDownloadUrl(files, dir, true), disableLoadingState);
|
||||
return false;
|
||||
},
|
||||
|
||||
|
@ -894,16 +918,39 @@
|
|||
self.$el.closest('#app-content').trigger(jQuery.Event('apprendered'));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the icon URL matching the given file info
|
||||
*
|
||||
* @param {OC.Files.FileInfo} fileInfo file info
|
||||
*
|
||||
* @return {string} icon URL
|
||||
*/
|
||||
_getIconUrl: function(fileInfo) {
|
||||
var mimeType = fileInfo.mimetype || 'application/octet-stream';
|
||||
if (mimeType === 'httpd/unix-directory') {
|
||||
// use default folder icon
|
||||
if (fileInfo.mountType === 'shared' || fileInfo.mountType === 'shared-root') {
|
||||
return OC.MimeType.getIconUrl('dir-shared');
|
||||
} else if (fileInfo.mountType === 'external-root') {
|
||||
return OC.MimeType.getIconUrl('dir-external');
|
||||
}
|
||||
return OC.MimeType.getIconUrl('dir');
|
||||
}
|
||||
return OC.MimeType.getIconUrl(mimeType);
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a new table row element using the given file data.
|
||||
* @param {OCA.Files.FileInfo} fileData file info attributes
|
||||
* @param {OC.Files.FileInfo} fileData file info attributes
|
||||
* @param options map of attributes
|
||||
* @return new tr element (not appended to the table)
|
||||
*/
|
||||
_createRow: function(fileData, options) {
|
||||
var td, simpleSize, basename, extension, sizeColor,
|
||||
icon = OC.MimeType.getIconUrl(fileData.mimetype),
|
||||
icon = fileData.icon || this._getIconUrl(fileData),
|
||||
name = fileData.name,
|
||||
// TODO: get rid of type, only use mime type
|
||||
type = fileData.type || 'file',
|
||||
mtime = parseInt(fileData.mtime, 10),
|
||||
mime = fileData.mimetype,
|
||||
|
@ -943,6 +990,14 @@
|
|||
}
|
||||
|
||||
if (fileData.mountType) {
|
||||
// FIXME: HACK: detect shared-root
|
||||
if (fileData.mountType === 'shared' && this.dirInfo.mountType !== 'shared') {
|
||||
// if parent folder isn't share, assume the displayed folder is a share root
|
||||
fileData.mountType = 'shared-root';
|
||||
} else if (fileData.mountType === 'external' && this.dirInfo.mountType !== 'external') {
|
||||
// if parent folder isn't external, assume the displayed folder is the external storage root
|
||||
fileData.mountType = 'external-root';
|
||||
}
|
||||
tr.attr('data-mounttype', fileData.mountType);
|
||||
}
|
||||
|
||||
|
@ -953,24 +1008,16 @@
|
|||
path = this.getCurrentDirectory();
|
||||
}
|
||||
|
||||
if (type === 'dir') {
|
||||
// use default folder icon
|
||||
icon = icon || OC.imagePath('core', 'filetypes/folder');
|
||||
}
|
||||
else {
|
||||
icon = icon || OC.imagePath('core', 'filetypes/file');
|
||||
}
|
||||
|
||||
// filename td
|
||||
td = $('<td class="filename"></td>');
|
||||
|
||||
|
||||
// linkUrl
|
||||
if (type === 'dir') {
|
||||
if (mime === 'httpd/unix-directory') {
|
||||
linkUrl = this.linkTo(path + '/' + name);
|
||||
}
|
||||
else {
|
||||
linkUrl = this.getDownloadUrl(name, path);
|
||||
linkUrl = this.getDownloadUrl(name, path, type === 'dir');
|
||||
}
|
||||
if (this._allowSelection) {
|
||||
td.append(
|
||||
|
@ -996,7 +1043,7 @@
|
|||
basename = '';
|
||||
extension = name;
|
||||
// split extension from filename for non dirs
|
||||
} else if (type !== 'dir' && name.indexOf('.') !== -1) {
|
||||
} else if (mime !== 'httpd/unix-directory' && name.indexOf('.') !== -1) {
|
||||
basename = name.substr(0, name.lastIndexOf('.'));
|
||||
extension = name.substr(name.lastIndexOf('.'));
|
||||
} else {
|
||||
|
@ -1018,7 +1065,7 @@
|
|||
nameSpan.tooltip({placement: 'right'});
|
||||
}
|
||||
// dirs can show the number of uploaded files
|
||||
if (type === 'dir') {
|
||||
if (mime !== 'httpd/unix-directory') {
|
||||
linkElem.append($('<span></span>').attr({
|
||||
'class': 'uploadtext',
|
||||
'currentUploads': 0
|
||||
|
@ -1074,7 +1121,7 @@
|
|||
* Adds an entry to the files array and also into the DOM
|
||||
* in a sorted manner.
|
||||
*
|
||||
* @param {OCA.Files.FileInfo} fileData map of file attributes
|
||||
* @param {OC.Files.FileInfo} fileData map of file attributes
|
||||
* @param {Object} [options] map of attributes
|
||||
* @param {boolean} [options.updateSummary] true to update the summary
|
||||
* after adding (default), false otherwise. Defaults to true.
|
||||
|
@ -1147,7 +1194,7 @@
|
|||
* Creates a new row element based on the given attributes
|
||||
* and returns it.
|
||||
*
|
||||
* @param {OCA.Files.FileInfo} fileData map of file attributes
|
||||
* @param {OC.Files.FileInfo} fileData map of file attributes
|
||||
* @param {Object} [options] map of attributes
|
||||
* @param {int} [options.index] index at which to insert the element
|
||||
* @param {boolean} [options.updateSummary] true to update the summary
|
||||
|
@ -1182,7 +1229,7 @@
|
|||
filenameTd.draggable(this._dragOptions);
|
||||
}
|
||||
// allow dropping on folders
|
||||
if (this._folderDropOptions && fileData.type === 'dir') {
|
||||
if (this._folderDropOptions && mime === 'httpd/unix-directory') {
|
||||
filenameTd.droppable(this._folderDropOptions);
|
||||
}
|
||||
|
||||
|
@ -1193,7 +1240,7 @@
|
|||
// display actions
|
||||
this.fileActions.display(filenameTd, !options.silent, this);
|
||||
|
||||
if (fileData.isPreviewAvailable && mime !== 'httpd/unix-directory') {
|
||||
if (mime !== 'httpd/unix-directory') {
|
||||
var iconDiv = filenameTd.find('.thumbnail');
|
||||
// lazy load / newly inserted td ?
|
||||
// the typeof check ensures that the default value of animate is true
|
||||
|
@ -1329,6 +1376,13 @@
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns list of webdav properties to request
|
||||
*/
|
||||
_getWebdavProperties: function() {
|
||||
return this.filesClient.getPropfindProperties();
|
||||
},
|
||||
|
||||
/**
|
||||
* Reloads the file list using ajax call
|
||||
*
|
||||
|
@ -1343,17 +1397,12 @@
|
|||
this._currentFileModel = null;
|
||||
this.$el.find('.select-all').prop('checked', false);
|
||||
this.showMask();
|
||||
if (this._reloadCall) {
|
||||
this._reloadCall.abort();
|
||||
}
|
||||
this._reloadCall = $.ajax({
|
||||
url: this.getAjaxUrl('list'),
|
||||
data: {
|
||||
dir : this.getCurrentDirectory(),
|
||||
sort: this._sort,
|
||||
sortdirection: this._sortDirection
|
||||
this._reloadCall = this.filesClient.getFolderContents(
|
||||
this.getCurrentDirectory(), {
|
||||
includeParent: true,
|
||||
properties: this._getWebdavProperties()
|
||||
}
|
||||
});
|
||||
);
|
||||
if (this._detailsView) {
|
||||
// close sidebar
|
||||
this._updateDetailsView(null);
|
||||
|
@ -1361,24 +1410,19 @@
|
|||
var callBack = this.reloadCallback.bind(this);
|
||||
return this._reloadCall.then(callBack, callBack);
|
||||
},
|
||||
reloadCallback: function(result) {
|
||||
reloadCallback: function(status, result) {
|
||||
delete this._reloadCall;
|
||||
this.hideMask();
|
||||
|
||||
if (!result || result.status === 'error') {
|
||||
// if the error is not related to folder we're trying to load, reload the page to handle logout etc
|
||||
if (result.data.error === 'authentication_error' ||
|
||||
result.data.error === 'token_expired' ||
|
||||
result.data.error === 'application_not_enabled'
|
||||
) {
|
||||
OC.redirect(OC.generateUrl('apps/files'));
|
||||
}
|
||||
OC.Notification.showTemporary(result.data.message);
|
||||
if (status === 401) {
|
||||
// TODO: append current URL to be able to get back after logging in again
|
||||
OC.redirect(OC.generateUrl('apps/files'));
|
||||
OC.Notification.show(result);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Firewall Blocked request?
|
||||
if (result.status === 403) {
|
||||
if (status === 403) {
|
||||
// Go home
|
||||
this.changeDirectory('/');
|
||||
OC.Notification.showTemporary(t('files', 'This operation is forbidden'));
|
||||
|
@ -1386,32 +1430,49 @@
|
|||
}
|
||||
|
||||
// Did share service die or something else fail?
|
||||
if (result.status === 500) {
|
||||
if (status === 500) {
|
||||
// Go home
|
||||
this.changeDirectory('/');
|
||||
OC.Notification.showTemporary(t('files', 'This directory is unavailable, please check the logs or contact the administrator'));
|
||||
OC.Notification.showTemporary(
|
||||
t('files', 'This directory is unavailable, please check the logs or contact the administrator')
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result.status === 404) {
|
||||
if (status === 503) {
|
||||
// Go home
|
||||
if (this.getCurrentDirectory() !== '/') {
|
||||
this.changeDirectory('/');
|
||||
// TODO: read error message from exception
|
||||
OC.Notification.showTemporary(
|
||||
t('files', 'Storage not available')
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (status === 404) {
|
||||
// go back home
|
||||
this.changeDirectory('/');
|
||||
return false;
|
||||
}
|
||||
// aborted ?
|
||||
if (result.status === 0){
|
||||
if (status === 0){
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: should rather return upload file size through
|
||||
// the files list ajax call
|
||||
// TODO: parse remaining quota from PROPFIND response
|
||||
this.updateStorageStatistics(true);
|
||||
|
||||
if (result.data.permissions) {
|
||||
this.setDirectoryPermissions(result.data.permissions);
|
||||
// first entry is the root
|
||||
this.dirInfo = result.shift();
|
||||
|
||||
if (this.dirInfo.permissions) {
|
||||
this.setDirectoryPermissions(this.dirInfo.permissions);
|
||||
}
|
||||
|
||||
this.setFiles(result.data.files);
|
||||
result.sort(this._sortComparator);
|
||||
this.setFiles(result);
|
||||
return true;
|
||||
},
|
||||
|
||||
|
@ -1419,12 +1480,15 @@
|
|||
OCA.Files.Files.updateStorageStatistics(this.getCurrentDirectory(), force);
|
||||
},
|
||||
|
||||
/**
|
||||
* @deprecated do not use nor override
|
||||
*/
|
||||
getAjaxUrl: function(action, params) {
|
||||
return OCA.Files.Files.getAjaxUrl(action, params);
|
||||
},
|
||||
|
||||
getDownloadUrl: function(files, dir) {
|
||||
return OCA.Files.Files.getDownloadUrl(files, dir || this.getCurrentDirectory());
|
||||
getDownloadUrl: function(files, dir, isDir) {
|
||||
return OCA.Files.Files.getDownloadUrl(files, dir || this.getCurrentDirectory(), isDir);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1489,8 +1553,6 @@
|
|||
if (etag){
|
||||
// use etag as cache buster
|
||||
urlSpec.c = etag;
|
||||
} else {
|
||||
console.warn('OCA.Files.FileList.lazyLoadPreview(): missing etag argument');
|
||||
}
|
||||
|
||||
previewURL = self.generatePreviewUrl(urlSpec);
|
||||
|
@ -1514,6 +1576,9 @@
|
|||
img.src = previewURL;
|
||||
},
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
setDirectoryPermissions: function(permissions) {
|
||||
var isCreatable = (permissions & OC.PERMISSION_CREATE) !== 0;
|
||||
this.$el.find('#permissions').val(permissions);
|
||||
|
@ -1610,7 +1675,7 @@
|
|||
* fileData should be inserted, considering the current
|
||||
* sorting
|
||||
*
|
||||
* @param {OCA.Files.FileInfo} fileData file info
|
||||
* @param {OC.Files.FileInfo} fileData file info
|
||||
*/
|
||||
_findInsertionIndex: function(fileData) {
|
||||
var index = 0;
|
||||
|
@ -1628,6 +1693,9 @@
|
|||
move: function(fileNames, targetPath) {
|
||||
var self = this;
|
||||
var dir = this.getCurrentDirectory();
|
||||
if (dir.charAt(dir.length - 1) !== '/') {
|
||||
dir += '/';
|
||||
}
|
||||
var target = OC.basename(targetPath);
|
||||
if (!_.isArray(fileNames)) {
|
||||
fileNames = [fileNames];
|
||||
|
@ -1635,46 +1703,42 @@
|
|||
_.each(fileNames, function(fileName) {
|
||||
var $tr = self.findFileEl(fileName);
|
||||
self.showFileBusyState($tr, true);
|
||||
// TODO: improve performance by sending all file names in a single call
|
||||
$.post(
|
||||
OC.filePath('files', 'ajax', 'move.php'),
|
||||
{
|
||||
dir: dir,
|
||||
file: fileName,
|
||||
target: targetPath
|
||||
},
|
||||
function(result) {
|
||||
if (result) {
|
||||
if (result.status === 'success') {
|
||||
// if still viewing the same directory
|
||||
if (self.getCurrentDirectory() === dir) {
|
||||
// recalculate folder size
|
||||
var oldFile = self.findFileEl(target);
|
||||
var newFile = self.findFileEl(fileName);
|
||||
var oldSize = oldFile.data('size');
|
||||
var newSize = oldSize + newFile.data('size');
|
||||
oldFile.data('size', newSize);
|
||||
oldFile.find('td.filesize').text(OC.Util.humanFileSize(newSize));
|
||||
if (targetPath.charAt(targetPath.length - 1) !== '/') {
|
||||
// make sure we move the files into the target dir,
|
||||
// not overwrite it
|
||||
targetPath = targetPath + '/';
|
||||
}
|
||||
self.filesClient.move(dir + fileName, targetPath + fileName)
|
||||
.done(function() {
|
||||
// if still viewing the same directory
|
||||
if (OC.joinPaths(self.getCurrentDirectory(), '/') === dir) {
|
||||
// recalculate folder size
|
||||
var oldFile = self.findFileEl(target);
|
||||
var newFile = self.findFileEl(fileName);
|
||||
var oldSize = oldFile.data('size');
|
||||
var newSize = oldSize + newFile.data('size');
|
||||
oldFile.data('size', newSize);
|
||||
oldFile.find('td.filesize').text(OC.Util.humanFileSize(newSize));
|
||||
|
||||
// TODO: also update entry in FileList.files
|
||||
|
||||
self.remove(fileName);
|
||||
}
|
||||
} else {
|
||||
OC.Notification.hide();
|
||||
if (result.status === 'error' && result.data.message) {
|
||||
OC.Notification.showTemporary(result.data.message);
|
||||
}
|
||||
else {
|
||||
OC.Notification.showTemporary(t('files', 'Error moving file.'));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
OC.dialogs.alert(t('files', 'Error moving file'), t('files', 'Error'));
|
||||
// TODO: also update entry in FileList.files
|
||||
self.remove(fileName);
|
||||
}
|
||||
})
|
||||
.fail(function(status) {
|
||||
if (status === 412) {
|
||||
// TODO: some day here we should invoke the conflict dialog
|
||||
OC.Notification.showTemporary(
|
||||
t('files', 'Could not move "{file}", target exists', {file: fileName})
|
||||
);
|
||||
} else {
|
||||
OC.Notification.showTemporary(
|
||||
t('files', 'Could not move "{file}"', {file: fileName})
|
||||
);
|
||||
}
|
||||
})
|
||||
.always(function() {
|
||||
self.showFileBusyState($tr, false);
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
},
|
||||
|
@ -1700,16 +1764,16 @@
|
|||
* Triggers file rename input field for the given file name.
|
||||
* If the user enters a new name, the file will be renamed.
|
||||
*
|
||||
* @param oldname file name of the file to rename
|
||||
* @param oldName file name of the file to rename
|
||||
*/
|
||||
rename: function(oldname) {
|
||||
rename: function(oldName) {
|
||||
var self = this;
|
||||
var tr, td, input, form;
|
||||
tr = this.findFileEl(oldname);
|
||||
tr = this.findFileEl(oldName);
|
||||
var oldFileInfo = this.files[tr.index()];
|
||||
tr.data('renaming',true);
|
||||
td = tr.children('td.filename');
|
||||
input = $('<input type="text" class="filename"/>').val(oldname);
|
||||
input = $('<input type="text" class="filename"/>').val(oldName);
|
||||
form = $('<form></form>');
|
||||
form.append(input);
|
||||
td.children('a.name').hide();
|
||||
|
@ -1724,11 +1788,11 @@
|
|||
input.selectRange(0, len);
|
||||
var checkInput = function () {
|
||||
var filename = input.val();
|
||||
if (filename !== oldname) {
|
||||
if (filename !== oldName) {
|
||||
// Files.isFileNameValid(filename) throws an exception itself
|
||||
OCA.Files.Files.isFileNameValid(filename);
|
||||
if (self.inList(filename)) {
|
||||
throw t('files', '{new_name} already exists', {new_name: filename});
|
||||
throw t('files', '{newName} already exists', {newName: filename});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
@ -1741,6 +1805,12 @@
|
|||
td.children('a.name').show();
|
||||
}
|
||||
|
||||
function updateInList(fileInfo) {
|
||||
self.updateRow(tr, fileInfo);
|
||||
self._updateDetailsView(fileInfo.name, false);
|
||||
}
|
||||
|
||||
// TODO: too many nested blocks, move parts into functions
|
||||
form.submit(function(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
@ -1753,7 +1823,7 @@
|
|||
input.tooltip('hide');
|
||||
form.remove();
|
||||
|
||||
if (newName !== oldname) {
|
||||
if (newName !== oldName) {
|
||||
checkInput();
|
||||
// mark as loading (temp element)
|
||||
self.showFileBusyState(tr, true);
|
||||
|
@ -1765,34 +1835,45 @@
|
|||
td.find('a.name span.nametext').text(basename);
|
||||
td.children('a.name').show();
|
||||
|
||||
$.ajax({
|
||||
url: OC.filePath('files','ajax','rename.php'),
|
||||
data: {
|
||||
dir : tr.attr('data-path') || self.getCurrentDirectory(),
|
||||
newname: newName,
|
||||
file: oldname
|
||||
},
|
||||
success: function(result) {
|
||||
var fileInfo;
|
||||
if (!result || result.status === 'error') {
|
||||
OC.dialogs.alert(result.data.message, t('files', 'Could not rename file'));
|
||||
fileInfo = oldFileInfo;
|
||||
if (result.data.code === 'sourcenotfound') {
|
||||
self.remove(result.data.newname, {updateSummary: true});
|
||||
return;
|
||||
}
|
||||
var path = tr.attr('data-path') || self.getCurrentDirectory();
|
||||
self.filesClient.move(OC.joinPaths(path, oldName), OC.joinPaths(path, newName))
|
||||
.done(function() {
|
||||
oldFileInfo.name = newName;
|
||||
updateInList(oldFileInfo);
|
||||
})
|
||||
.fail(function(status) {
|
||||
// TODO: 409 means current folder does not exist, redirect ?
|
||||
if (status === 404) {
|
||||
// source not found, so remove it from the list
|
||||
OC.Notification.showTemporary(
|
||||
t(
|
||||
'files',
|
||||
'Could not rename "{fileName}", it does not exist any more',
|
||||
{fileName: oldName}
|
||||
)
|
||||
);
|
||||
self.remove(newName, {updateSummary: true});
|
||||
return;
|
||||
} else if (status === 412) {
|
||||
// target exists
|
||||
OC.Notification.showTemporary(
|
||||
t(
|
||||
'files',
|
||||
'The name "{targetName}" is already used in the folder "{dir}". Please choose a different name.',
|
||||
{
|
||||
targetName: newName,
|
||||
dir: self.getCurrentDirectory()
|
||||
}
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// restore the item to its previous state
|
||||
OC.Notification.showTemporary(
|
||||
t('files', 'Could not rename "{fileName}"', {fileName: oldName})
|
||||
);
|
||||
}
|
||||
else {
|
||||
fileInfo = result.data;
|
||||
}
|
||||
// reinsert row
|
||||
self.files.splice(tr.index(), 1);
|
||||
tr.remove();
|
||||
tr = self.add(fileInfo, {updateSummary: false, silent: true});
|
||||
self.$fileList.trigger($.Event('fileActionsReady', {fileList: self, $files: $(tr)}));
|
||||
self._updateDetailsView(fileInfo.name, false);
|
||||
}
|
||||
});
|
||||
updateInList(oldFileInfo);
|
||||
});
|
||||
} else {
|
||||
// add back the old file info when cancelled
|
||||
self.files.splice(tr.index(), 1);
|
||||
|
@ -1849,32 +1930,48 @@
|
|||
var promise = deferred.promise();
|
||||
|
||||
OCA.Files.Files.isFileNameValid(name);
|
||||
name = this.getUniqueName(name);
|
||||
|
||||
if (this.lastAction) {
|
||||
this.lastAction();
|
||||
}
|
||||
|
||||
$.post(
|
||||
OC.generateUrl('/apps/files/ajax/newfile.php'),
|
||||
{
|
||||
dir: this.getCurrentDirectory(),
|
||||
filename: name
|
||||
},
|
||||
function(result) {
|
||||
if (result.status === 'success') {
|
||||
self.add(result.data, {animate: true, scrollTo: true});
|
||||
deferred.resolve(result.status, result.data);
|
||||
} else {
|
||||
if (result.data && result.data.message) {
|
||||
OC.Notification.showTemporary(result.data.message);
|
||||
} else {
|
||||
OC.Notification.showTemporary(t('core', 'Could not create file'));
|
||||
}
|
||||
deferred.reject(result.status, result.data);
|
||||
name = this.getUniqueName(name);
|
||||
var targetPath = this.getCurrentDirectory() + '/' + name;
|
||||
|
||||
self.filesClient.putFileContents(
|
||||
targetPath,
|
||||
'',
|
||||
{
|
||||
contentType: 'text/plain',
|
||||
overwrite: true
|
||||
}
|
||||
}
|
||||
);
|
||||
)
|
||||
.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);
|
||||
});
|
||||
})
|
||||
.fail(function(status) {
|
||||
if (status === 412) {
|
||||
OC.Notification.showTemporary(
|
||||
t('files', 'Could not create file "{file}" because it already exists', {file: name})
|
||||
);
|
||||
} else {
|
||||
OC.Notification.showTemporary(t('files', 'Could not create file "{file}"', {file: name}));
|
||||
}
|
||||
deferred.reject(status);
|
||||
});
|
||||
|
||||
return promise;
|
||||
},
|
||||
|
@ -1895,32 +1992,58 @@
|
|||
var promise = deferred.promise();
|
||||
|
||||
OCA.Files.Files.isFileNameValid(name);
|
||||
name = this.getUniqueName(name);
|
||||
|
||||
if (this.lastAction) {
|
||||
this.lastAction();
|
||||
}
|
||||
|
||||
$.post(
|
||||
OC.generateUrl('/apps/files/ajax/newfolder.php'),
|
||||
{
|
||||
dir: this.getCurrentDirectory(),
|
||||
foldername: name
|
||||
},
|
||||
function(result) {
|
||||
if (result.status === 'success') {
|
||||
self.add(result.data, {animate: true, scrollTo: true});
|
||||
deferred.resolve(result.status, result.data);
|
||||
name = this.getUniqueName(name);
|
||||
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);
|
||||
});
|
||||
})
|
||||
.fail(function(createStatus) {
|
||||
// method not allowed, folder might exist already
|
||||
if (createStatus === 405) {
|
||||
self.filesClient.getFileInfo(
|
||||
targetPath, {
|
||||
properties: self._getWebdavProperties()
|
||||
}
|
||||
)
|
||||
.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})
|
||||
);
|
||||
// still consider a failure
|
||||
deferred.reject(createStatus, data);
|
||||
})
|
||||
.fail(function() {
|
||||
OC.Notification.showTemporary(
|
||||
t('files', 'Could not create folder "{dir}"', {dir: name})
|
||||
);
|
||||
deferred.reject(status);
|
||||
});
|
||||
} else {
|
||||
if (result.data && result.data.message) {
|
||||
OC.Notification.showTemporary(result.data.message);
|
||||
} else {
|
||||
OC.Notification.showTemporary(t('core', 'Could not create folder'));
|
||||
}
|
||||
deferred.reject(result.status);
|
||||
OC.Notification.showTemporary(t('files', 'Could not create folder "{dir}"', {dir: name}));
|
||||
deferred.reject(createStatus);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return promise;
|
||||
},
|
||||
|
@ -1981,76 +2104,59 @@
|
|||
*/
|
||||
do_delete:function(files, dir) {
|
||||
var self = this;
|
||||
var params;
|
||||
if (files && files.substr) {
|
||||
files=[files];
|
||||
}
|
||||
if (!files) {
|
||||
// delete all files in directory
|
||||
files = _.pluck(this.files, 'name');
|
||||
}
|
||||
if (files) {
|
||||
this.showFileBusyState(files, true);
|
||||
for (var i=0; i<files.length; i++) {
|
||||
}
|
||||
}
|
||||
// Finish any existing actions
|
||||
if (this.lastAction) {
|
||||
this.lastAction();
|
||||
}
|
||||
|
||||
params = {
|
||||
dir: dir || this.getCurrentDirectory()
|
||||
};
|
||||
if (files) {
|
||||
params.files = JSON.stringify(files);
|
||||
}
|
||||
else {
|
||||
// no files passed, delete all in current dir
|
||||
params.allfiles = true;
|
||||
// show spinner for all files
|
||||
this.showFileBusyState(this.$fileList.find('tr'), true);
|
||||
dir = dir || this.getCurrentDirectory();
|
||||
|
||||
function removeFromList(file) {
|
||||
var fileEl = self.remove(file, {updateSummary: false});
|
||||
// FIXME: not sure why we need this after the
|
||||
// element isn't even in the DOM any more
|
||||
fileEl.find('.selectCheckBox').prop('checked', false);
|
||||
fileEl.removeClass('selected');
|
||||
self.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')});
|
||||
// TODO: this info should be returned by the ajax call!
|
||||
self.updateEmptyContent();
|
||||
self.fileSummary.update();
|
||||
self.updateSelectionSummary();
|
||||
// FIXME: don't repeat this, do it once all files are done
|
||||
self.updateStorageStatistics();
|
||||
}
|
||||
|
||||
$.post(OC.filePath('files', 'ajax', 'delete.php'),
|
||||
params,
|
||||
function(result) {
|
||||
if (result.status === 'success') {
|
||||
if (params.allfiles) {
|
||||
self.setFiles([]);
|
||||
}
|
||||
else {
|
||||
$.each(files,function(index,file) {
|
||||
var fileEl = self.remove(file, {updateSummary: false});
|
||||
// FIXME: not sure why we need this after the
|
||||
// element isn't even in the DOM any more
|
||||
fileEl.find('.selectCheckBox').prop('checked', false);
|
||||
fileEl.removeClass('selected');
|
||||
self.fileSummary.remove({type: fileEl.attr('data-type'), size: fileEl.attr('data-size')});
|
||||
});
|
||||
}
|
||||
// TODO: this info should be returned by the ajax call!
|
||||
self.updateEmptyContent();
|
||||
self.fileSummary.update();
|
||||
self.updateSelectionSummary();
|
||||
self.updateStorageStatistics();
|
||||
// in case there was a "storage full" permanent notification
|
||||
OC.Notification.hide();
|
||||
_.each(files, function(file) {
|
||||
self.filesClient.remove(dir + '/' + file)
|
||||
.done(function() {
|
||||
removeFromList(file);
|
||||
})
|
||||
.fail(function(status) {
|
||||
if (status === 404) {
|
||||
// the file already did not exist, remove it from the list
|
||||
removeFromList(file);
|
||||
} else {
|
||||
if (result.status === 'error' && result.data.message) {
|
||||
OC.Notification.showTemporary(result.data.message);
|
||||
}
|
||||
else {
|
||||
OC.Notification.showTemporary(t('files', 'Error deleting file.'));
|
||||
}
|
||||
if (params.allfiles) {
|
||||
// reload the page as we don't know what files were deleted
|
||||
// and which ones remain
|
||||
self.reload();
|
||||
}
|
||||
else {
|
||||
$.each(files,function(index,file) {
|
||||
self.showFileBusyState(file, false);
|
||||
});
|
||||
}
|
||||
// only reset the spinner for that one file
|
||||
OC.Notification.showTemporary(
|
||||
t('files', 'Error deleting file "{fileName}".', {fileName: file}),
|
||||
{timeout: 10}
|
||||
);
|
||||
var deleteAction = self.findFileEl(file).find('.action.delete');
|
||||
deleteAction.removeClass('icon-loading-small').addClass('icon-delete');
|
||||
self.showFileBusyState(files, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Creates the file summary section
|
||||
|
@ -2659,8 +2765,8 @@
|
|||
* Compares two file infos by name, making directories appear
|
||||
* first.
|
||||
*
|
||||
* @param {OCA.Files.FileInfo} fileInfo1 file info
|
||||
* @param {OCA.Files.FileInfo} fileInfo2 file info
|
||||
* @param {OC.Files.FileInfo} fileInfo1 file info
|
||||
* @param {OC.Files.FileInfo} fileInfo2 file info
|
||||
* @return {int} -1 if the first file must appear before the second one,
|
||||
* 0 if they are identify, 1 otherwise.
|
||||
*/
|
||||
|
@ -2676,8 +2782,8 @@
|
|||
/**
|
||||
* Compares two file infos by size.
|
||||
*
|
||||
* @param {OCA.Files.FileInfo} fileInfo1 file info
|
||||
* @param {OCA.Files.FileInfo} fileInfo2 file info
|
||||
* @param {OC.Files.FileInfo} fileInfo1 file info
|
||||
* @param {OC.Files.FileInfo} fileInfo2 file info
|
||||
* @return {int} -1 if the first file must appear before the second one,
|
||||
* 0 if they are identify, 1 otherwise.
|
||||
*/
|
||||
|
@ -2687,8 +2793,8 @@
|
|||
/**
|
||||
* Compares two file infos by timestamp.
|
||||
*
|
||||
* @param {OCA.Files.FileInfo} fileInfo1 file info
|
||||
* @param {OCA.Files.FileInfo} fileInfo2 file info
|
||||
* @param {OC.Files.FileInfo} fileInfo1 file info
|
||||
* @param {OC.Files.FileInfo} fileInfo2 file info
|
||||
* @return {int} -1 if the first file must appear before the second one,
|
||||
* 0 if they are identify, 1 otherwise.
|
||||
*/
|
||||
|
@ -2700,23 +2806,14 @@
|
|||
/**
|
||||
* File info attributes.
|
||||
*
|
||||
* @todo make this a real class in the future
|
||||
* @typedef {Object} OCA.Files.FileInfo
|
||||
* @typedef {Object} OC.Files.FileInfo
|
||||
*
|
||||
* @lends OC.Files.FileInfo
|
||||
*
|
||||
* @deprecated use OC.Files.FileInfo instead
|
||||
*
|
||||
* @property {int} id file id
|
||||
* @property {String} name file name
|
||||
* @property {String} [path] file path, defaults to the list's current path
|
||||
* @property {String} mimetype mime type
|
||||
* @property {String} type "file" for files or "dir" for directories
|
||||
* @property {int} permissions file permissions
|
||||
* @property {int} mtime modification time in milliseconds
|
||||
* @property {boolean} [isShareMountPoint] whether the file is a share mount
|
||||
* point
|
||||
* @property {boolean} [isPreviewAvailable] whether a preview is available
|
||||
* for the given file type
|
||||
* @property {String} [icon] path to the mime type icon
|
||||
* @property {String} etag etag of the file
|
||||
*/
|
||||
OCA.Files.FileInfo = OC.Files.FileInfo;
|
||||
|
||||
OCA.Files.FileList = FileList;
|
||||
})();
|
||||
|
|
|
@ -136,13 +136,27 @@
|
|||
|
||||
/**
|
||||
* Returns the download URL of the given file(s)
|
||||
* @param filename string or array of file names to download
|
||||
* @param dir optional directory in which the file name is, defaults to the current directory
|
||||
* @param {string} filename string or array of file names to download
|
||||
* @param {string} [dir] optional directory in which the file name is, defaults to the current directory
|
||||
* @param {bool} [isDir=false] whether the given filename is a directory and might need a special URL
|
||||
*/
|
||||
getDownloadUrl: function(filename, dir) {
|
||||
if ($.isArray(filename)) {
|
||||
getDownloadUrl: function(filename, dir, isDir) {
|
||||
if (!_.isArray(filename) && !isDir) {
|
||||
var pathSections = dir.split('/');
|
||||
pathSections.push(filename);
|
||||
var encodedPath = '';
|
||||
_.each(pathSections, function(section) {
|
||||
if (section !== '') {
|
||||
encodedPath += '/' + encodeURIComponent(section);
|
||||
}
|
||||
});
|
||||
return OC.linkToRemoteBase('webdav') + encodedPath;
|
||||
}
|
||||
|
||||
if (_.isArray(filename)) {
|
||||
filename = JSON.stringify(filename);
|
||||
}
|
||||
|
||||
var params = {
|
||||
dir: dir,
|
||||
files: filename
|
||||
|
@ -193,7 +207,7 @@
|
|||
*/
|
||||
lazyLoadPreview : function(path, mime, ready, width, height, etag) {
|
||||
console.warn('DEPRECATED: please use lazyLoadPreview() from an OCA.Files.FileList instance');
|
||||
return OCA.Files.App.fileList.lazyLoadPreview({
|
||||
return FileList.lazyLoadPreview({
|
||||
path: path,
|
||||
mime: mime,
|
||||
callback: ready,
|
||||
|
@ -356,8 +370,10 @@ scanFiles.scanning=false;
|
|||
|
||||
// TODO: move to FileList
|
||||
var createDragShadow = function(event) {
|
||||
// FIXME: inject file list instance somehow
|
||||
/* global FileList, Files */
|
||||
|
||||
//select dragged file
|
||||
var FileList = OCA.Files.App.fileList;
|
||||
var isDragSelected = $(event.target).parents('tr').find('td input:first').prop('checked');
|
||||
if (!isDragSelected) {
|
||||
//select dragged file
|
||||
|
@ -394,7 +410,7 @@ var createDragShadow = function(event) {
|
|||
.css('background-image', 'url(' + OC.imagePath('core', 'filetypes/folder.png') + ')');
|
||||
} else {
|
||||
var path = dir + '/' + elem.name;
|
||||
OCA.Files.App.files.lazyLoadPreview(path, elem.mime, function(previewpath) {
|
||||
Files.lazyLoadPreview(path, elem.mimetype, function(previewpath) {
|
||||
newtr.find('td.filename')
|
||||
.css('background-image', 'url(' + previewpath + ')');
|
||||
}, null, null, elem.etag);
|
||||
|
@ -441,7 +457,7 @@ var folderDropOptions = {
|
|||
hoverClass: "canDrop",
|
||||
drop: function( event, ui ) {
|
||||
// don't allow moving a file into a selected folder
|
||||
var FileList = OCA.Files.App.fileList;
|
||||
/* global FileList */
|
||||
if ($(event.target).parents('tr').find('td input:first').prop('checked') === true) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -161,6 +161,38 @@
|
|||
fileInfo.tags = tags;
|
||||
return fileInfo;
|
||||
};
|
||||
|
||||
var NS_OC = 'http://owncloud.org/ns';
|
||||
|
||||
var oldGetWebdavProperties = fileList._getWebdavProperties;
|
||||
fileList._getWebdavProperties = function() {
|
||||
var props = oldGetWebdavProperties.apply(this, arguments);
|
||||
props.push('{' + NS_OC + '}tags');
|
||||
props.push('{' + NS_OC + '}favorite');
|
||||
return props;
|
||||
};
|
||||
|
||||
fileList.filesClient.addFileInfoParser(function(response) {
|
||||
var data = {};
|
||||
var props = response.propStat[0].properties;
|
||||
var tags = props['{' + NS_OC + '}tags'];
|
||||
var favorite = props['{' + NS_OC + '}favorite'];
|
||||
if (tags && tags.length) {
|
||||
tags = _.chain(tags).filter(function(xmlvalue) {
|
||||
return (xmlvalue.namespaceURI === NS_OC && xmlvalue.nodeName.split(':')[1] === 'tag');
|
||||
}).map(function(xmlvalue) {
|
||||
return xmlvalue.textContent || xmlvalue.text;
|
||||
}).value();
|
||||
}
|
||||
if (tags) {
|
||||
data.tags = tags;
|
||||
}
|
||||
if (favorite && parseInt(favorite, 10) !== 0) {
|
||||
data.tags = data.tags || [];
|
||||
data.tags.push(OC.TAG_FAVORITE);
|
||||
}
|
||||
return data;
|
||||
});
|
||||
},
|
||||
|
||||
attach: function(fileList) {
|
||||
|
|
|
@ -28,108 +28,22 @@
|
|||
namespace OCA\Files;
|
||||
|
||||
class App {
|
||||
/**
|
||||
* @var \OC_L10N
|
||||
*/
|
||||
private $l10n;
|
||||
|
||||
/**
|
||||
* @var \OCP\INavigationManager
|
||||
*/
|
||||
private static $navigationManager;
|
||||
|
||||
/**
|
||||
* @var \OC\Files\View
|
||||
*/
|
||||
private $view;
|
||||
|
||||
public function __construct($view, $l10n) {
|
||||
$this->view = $view;
|
||||
$this->l10n = $l10n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the app's navigation manager
|
||||
*
|
||||
* @return \OCP\INavigationManager
|
||||
*/
|
||||
public static function getNavigationManager() {
|
||||
// TODO: move this into a service in the Application class
|
||||
if (self::$navigationManager === null) {
|
||||
self::$navigationManager = new \OC\NavigationManager();
|
||||
}
|
||||
return self::$navigationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* rename a file
|
||||
*
|
||||
* @param string $dir
|
||||
* @param string $oldname
|
||||
* @param string $newname
|
||||
* @return array
|
||||
*/
|
||||
public function rename($dir, $oldname, $newname) {
|
||||
$result = array(
|
||||
'success' => false,
|
||||
'data' => NULL
|
||||
);
|
||||
|
||||
try {
|
||||
// check if the new name is conform to file name restrictions
|
||||
$this->view->verifyPath($dir, $newname);
|
||||
} catch (\OCP\Files\InvalidPathException $ex) {
|
||||
$result['data'] = array(
|
||||
'message' => $this->l10n->t($ex->getMessage()),
|
||||
'code' => 'invalidname',
|
||||
);
|
||||
return $result;
|
||||
}
|
||||
|
||||
$normalizedOldPath = \OC\Files\Filesystem::normalizePath($dir . '/' . $oldname);
|
||||
$normalizedNewPath = \OC\Files\Filesystem::normalizePath($dir . '/' . $newname);
|
||||
|
||||
// rename to non-existing folder is denied
|
||||
if (!$this->view->file_exists($normalizedOldPath)) {
|
||||
$result['data'] = array(
|
||||
'message' => $this->l10n->t('%s could not be renamed as it has been deleted', array($oldname)),
|
||||
'code' => 'sourcenotfound',
|
||||
'oldname' => $oldname,
|
||||
'newname' => $newname,
|
||||
);
|
||||
}else if (!$this->view->file_exists($dir)) {
|
||||
$result['data'] = array('message' => (string)$this->l10n->t(
|
||||
'The target folder has been moved or deleted.',
|
||||
array($dir)),
|
||||
'code' => 'targetnotfound'
|
||||
);
|
||||
// rename to existing file is denied
|
||||
} else if ($this->view->file_exists($normalizedNewPath)) {
|
||||
|
||||
$result['data'] = array(
|
||||
'message' => $this->l10n->t(
|
||||
"The name %s is already used in the folder %s. Please choose a different name.",
|
||||
array($newname, $dir))
|
||||
);
|
||||
} else if (
|
||||
// rename to "." is denied
|
||||
$newname !== '.' and
|
||||
// THEN try to rename
|
||||
$this->view->rename($normalizedOldPath, $normalizedNewPath)
|
||||
) {
|
||||
// successful rename
|
||||
$meta = $this->view->getFileInfo($normalizedNewPath);
|
||||
$meta = \OCA\Files\Helper::populateTags(array($meta));
|
||||
$fileInfo = \OCA\Files\Helper::formatFileInfo(current($meta));
|
||||
$fileInfo['path'] = dirname($normalizedNewPath);
|
||||
$result['success'] = true;
|
||||
$result['data'] = $fileInfo;
|
||||
} else {
|
||||
// rename failed
|
||||
$result['data'] = array(
|
||||
'message' => $this->l10n->t('%s could not be renamed', array($oldname))
|
||||
);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -139,9 +139,6 @@ class Helper {
|
|||
$entry['parentId'] = $i['parent'];
|
||||
$entry['mtime'] = $i['mtime'] * 1000;
|
||||
// only pick out the needed attributes
|
||||
if (\OC::$server->getPreviewManager()->isAvailable($i)) {
|
||||
$entry['isPreviewAvailable'] = true;
|
||||
}
|
||||
$entry['name'] = $i->getName();
|
||||
$entry['permissions'] = $i['permissions'];
|
||||
$entry['mimetype'] = $i['mimetype'];
|
||||
|
|
|
@ -1,16 +1,5 @@
|
|||
<div id="controls">
|
||||
<div class="actions creatable hidden">
|
||||
<?php /*
|
||||
Only show upload button for public page
|
||||
*/ ?>
|
||||
<?php if(isset($_['dirToken'])):?>
|
||||
<div id="upload" class="button upload"
|
||||
title="<?php isset($_['uploadMaxHumanFilesize']) ? p($l->t('Upload (max. %s)', array($_['uploadMaxHumanFilesize']))) : '' ?>">
|
||||
<label for="file_upload_start" class="svg icon-upload">
|
||||
<span class="hidden-visually"><?php p($l->t('Upload'))?></span>
|
||||
</label>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div id="uploadprogresswrapper">
|
||||
<div id="uploadprogressbar"></div>
|
||||
<button class="stop icon-close" style="display:none">
|
||||
|
|
|
@ -1,232 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Björn Schießle <schiessle@owncloud.com>
|
||||
* @author Christopher Schäpers <kondou@ts.unde.re>
|
||||
* @author Joas Schilling <nickvergessen@owncloud.com>
|
||||
* @author Morris Jobke <hey@morrisjobke.de>
|
||||
* @author Robin Appelman <icewind@owncloud.com>
|
||||
* @author Thomas Müller <thomas.mueller@tmit.eu>
|
||||
* @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/>
|
||||
*
|
||||
*/
|
||||
|
||||
class Test_OC_Files_App_Rename extends \Test\TestCase {
|
||||
private static $user;
|
||||
|
||||
/**
|
||||
* @var PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
private $viewMock;
|
||||
|
||||
/**
|
||||
* @var \OCA\Files\App
|
||||
*/
|
||||
private $files;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// mock OC_L10n
|
||||
if (!self::$user) {
|
||||
self::$user = uniqid();
|
||||
}
|
||||
\OC_User::createUser(self::$user, 'password');
|
||||
$this->loginAsUser(self::$user);
|
||||
|
||||
$l10nMock = $this->getMock('\OC_L10N', array('t'), array(), '', false);
|
||||
$l10nMock->expects($this->any())
|
||||
->method('t')
|
||||
->will($this->returnArgument(0));
|
||||
$viewMock = $this->getMock('\OC\Files\View', array('rename', 'normalizePath', 'getFileInfo', 'file_exists'), array(), '', false);
|
||||
$viewMock->expects($this->any())
|
||||
->method('normalizePath')
|
||||
->will($this->returnArgument(0));
|
||||
$viewMock->expects($this->any())
|
||||
->method('rename')
|
||||
->will($this->returnValue(true));
|
||||
$this->viewMock = $viewMock;
|
||||
$this->files = new \OCA\Files\App($viewMock, $l10nMock);
|
||||
}
|
||||
|
||||
protected function tearDown() {
|
||||
$result = \OC_User::deleteUser(self::$user);
|
||||
$this->assertTrue($result);
|
||||
|
||||
$this->logout();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* test rename of file/folder
|
||||
*/
|
||||
function testRenameFolder() {
|
||||
$dir = '/';
|
||||
$oldname = 'oldname';
|
||||
$newname = 'newname';
|
||||
|
||||
$this->viewMock->expects($this->any())
|
||||
->method('file_exists')
|
||||
->with($this->anything())
|
||||
->will($this->returnValueMap(array(
|
||||
array('/', true),
|
||||
array('/oldname', true)
|
||||
)));
|
||||
|
||||
|
||||
$this->viewMock->expects($this->any())
|
||||
->method('getFileInfo')
|
||||
->will($this->returnValue(new \OC\Files\FileInfo(
|
||||
'/new_name',
|
||||
new \OC\Files\Storage\Local(array('datadir' => '/')),
|
||||
'/',
|
||||
array(
|
||||
'fileid' => 123,
|
||||
'type' => 'dir',
|
||||
'mimetype' => 'httpd/unix-directory',
|
||||
'mtime' => 0,
|
||||
'permissions' => 31,
|
||||
'size' => 18,
|
||||
'etag' => 'abcdef',
|
||||
'directory' => '/',
|
||||
'name' => 'new_name',
|
||||
), null)));
|
||||
|
||||
$result = $this->files->rename($dir, $oldname, $newname);
|
||||
|
||||
$this->assertTrue($result['success']);
|
||||
$this->assertEquals(123, $result['data']['id']);
|
||||
$this->assertEquals('new_name', $result['data']['name']);
|
||||
$this->assertEquals(18, $result['data']['size']);
|
||||
$this->assertEquals('httpd/unix-directory', $result['data']['mimetype']);
|
||||
$this->assertEquals('abcdef', $result['data']['etag']);
|
||||
$this->assertFalse(isset($result['data']['tags']));
|
||||
$this->assertEquals('/', $result['data']['path']);
|
||||
}
|
||||
|
||||
/**
|
||||
* test rename of file with tag
|
||||
*/
|
||||
function testRenameFileWithTag() {
|
||||
$taggerMock = $this->getMock('\OCP\ITags');
|
||||
$taggerMock->expects($this->any())
|
||||
->method('getTagsForObjects')
|
||||
->with(array(123))
|
||||
->will($this->returnValue(array(123 => array('tag1', 'tag2'))));
|
||||
$tagManagerMock = $this->getMock('\OCP\ITagManager');
|
||||
$tagManagerMock->expects($this->any())
|
||||
->method('load')
|
||||
->with('files')
|
||||
->will($this->returnValue($taggerMock));
|
||||
$oldTagManager = \OC::$server->query('TagManager');
|
||||
\OC::$server->registerService('TagManager', function ($c) use ($tagManagerMock) {
|
||||
return $tagManagerMock;
|
||||
});
|
||||
|
||||
$dir = '/';
|
||||
$oldname = 'oldname.txt';
|
||||
$newname = 'newname.txt';
|
||||
|
||||
$this->viewMock->expects($this->any())
|
||||
->method('file_exists')
|
||||
->with($this->anything())
|
||||
->will($this->returnValueMap(array(
|
||||
array('/', true),
|
||||
array('/oldname.txt', true)
|
||||
)));
|
||||
|
||||
|
||||
$this->viewMock->expects($this->any())
|
||||
->method('getFileInfo')
|
||||
->will($this->returnValue(new \OC\Files\FileInfo(
|
||||
'/new_name.txt',
|
||||
new \OC\Files\Storage\Local(array('datadir' => '/')),
|
||||
'/',
|
||||
array(
|
||||
'fileid' => 123,
|
||||
'type' => 'file',
|
||||
'mimetype' => 'text/plain',
|
||||
'mtime' => 0,
|
||||
'permissions' => 31,
|
||||
'size' => 18,
|
||||
'etag' => 'abcdef',
|
||||
'directory' => '/',
|
||||
'name' => 'new_name.txt',
|
||||
), null)));
|
||||
|
||||
$result = $this->files->rename($dir, $oldname, $newname);
|
||||
|
||||
$this->assertTrue($result['success']);
|
||||
$this->assertEquals(123, $result['data']['id']);
|
||||
$this->assertEquals('new_name.txt', $result['data']['name']);
|
||||
$this->assertEquals(18, $result['data']['size']);
|
||||
$this->assertEquals('text/plain', $result['data']['mimetype']);
|
||||
$this->assertEquals('abcdef', $result['data']['etag']);
|
||||
$this->assertEquals(array('tag1', 'tag2'), $result['data']['tags']);
|
||||
$this->assertEquals('/', $result['data']['path']);
|
||||
|
||||
\OC::$server->registerService('TagManager', function ($c) use ($oldTagManager) {
|
||||
return $oldTagManager;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Test rename inside a folder that doesn't exist any more
|
||||
*/
|
||||
function testRenameInNonExistingFolder() {
|
||||
$dir = '/unexist';
|
||||
$oldname = 'oldname';
|
||||
$newname = 'newname';
|
||||
|
||||
$this->viewMock->expects($this->at(0))
|
||||
->method('file_exists')
|
||||
->with('/unexist/oldname')
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$this->viewMock->expects($this->any())
|
||||
->method('getFileInfo')
|
||||
->will($this->returnValue(array(
|
||||
'fileid' => 123,
|
||||
'type' => 'dir',
|
||||
'mimetype' => 'httpd/unix-directory',
|
||||
'size' => 18,
|
||||
'etag' => 'abcdef',
|
||||
'directory' => '/unexist',
|
||||
'name' => 'new_name',
|
||||
)));
|
||||
|
||||
$result = $this->files->rename($dir, $oldname, $newname);
|
||||
|
||||
$this->assertFalse($result['success']);
|
||||
$this->assertEquals('sourcenotfound', $result['data']['code']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test move to invalid name
|
||||
*/
|
||||
function testRenameToInvalidName() {
|
||||
$dir = '/';
|
||||
$oldname = 'oldname';
|
||||
$newname = 'abc\\';
|
||||
|
||||
$result = $this->files->rename($dir, $oldname, $newname);
|
||||
|
||||
$this->assertFalse($result['success']);
|
||||
$this->assertEquals('File name contains at least one invalid character', $result['data']['message']);
|
||||
$this->assertEquals('invalidname', $result['data']['code']);
|
||||
}
|
||||
}
|
|
@ -100,8 +100,7 @@ describe('OCA.Files.FavoritesFileList tests', function() {
|
|||
expect($tr.attr('data-mtime')).toEqual('11111000');
|
||||
expect($tr.find('a.name').attr('href')).toEqual(
|
||||
OC.webroot +
|
||||
'/index.php/apps/files/ajax/download.php' +
|
||||
'?dir=%2Fsomedir&files=test.txt'
|
||||
'/remote.php/webdav/somedir/test.txt'
|
||||
);
|
||||
expect($tr.find('.nametext').text().trim()).toEqual('test.txt');
|
||||
});
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
*
|
||||
*/
|
||||
|
||||
/* global FileList */
|
||||
|
||||
describe('OC.Upload tests', function() {
|
||||
var $dummyUploader;
|
||||
var testFile;
|
||||
|
|
|
@ -584,7 +584,7 @@ describe('OCA.Files.FileActions tests', function() {
|
|||
expect(busyStub.calledWith('testName.txt', true)).toEqual(true);
|
||||
expect(handleDownloadStub.calledOnce).toEqual(true);
|
||||
expect(handleDownloadStub.getCall(0).args[0]).toEqual(
|
||||
OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=testName.txt'
|
||||
OC.webroot + '/remote.php/webdav/subdir/testName.txt'
|
||||
);
|
||||
busyStub.reset();
|
||||
handleDownloadStub.yield();
|
||||
|
|
|
@ -237,8 +237,8 @@ describe('OCA.Files.FileActionsMenu tests', function() {
|
|||
expect(redirectStub.calledOnce).toEqual(true);
|
||||
expect(redirectStub.getCall(0).args[0]).toContain(
|
||||
OC.webroot +
|
||||
'/index.php/apps/files/ajax/download.php' +
|
||||
'?dir=%2Fsubdir&files=testName.txt');
|
||||
'/remote.php/webdav/subdir/testName.txt'
|
||||
);
|
||||
redirectStub.restore();
|
||||
});
|
||||
it('takes the file\'s path into account when clicking download', function() {
|
||||
|
@ -269,8 +269,7 @@ describe('OCA.Files.FileActionsMenu tests', function() {
|
|||
|
||||
expect(redirectStub.calledOnce).toEqual(true);
|
||||
expect(redirectStub.getCall(0).args[0]).toContain(
|
||||
OC.webroot + '/index.php/apps/files/ajax/download.php' +
|
||||
'?dir=%2Fanotherpath%2Fthere&files=testName.txt'
|
||||
OC.webroot + '/remote.php/webdav/anotherpath/there/testName.txt'
|
||||
);
|
||||
redirectStub.restore();
|
||||
});
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -76,11 +76,11 @@ describe('OCA.Files.Files tests', function() {
|
|||
describe('getDownloadUrl', function() {
|
||||
it('returns the ajax download URL when filename and dir specified', function() {
|
||||
var url = Files.getDownloadUrl('test file.txt', '/subdir');
|
||||
expect(url).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2Fsubdir&files=test%20file.txt');
|
||||
expect(url).toEqual(OC.webroot + '/remote.php/webdav/subdir/test%20file.txt');
|
||||
});
|
||||
it('returns the ajax download URL when filename and root dir specific', function() {
|
||||
it('returns the webdav download URL when filename and root dir specified', function() {
|
||||
var url = Files.getDownloadUrl('test file.txt', '/');
|
||||
expect(url).toEqual(OC.webroot + '/index.php/apps/files/ajax/download.php?dir=%2F&files=test%20file.txt');
|
||||
expect(url).toEqual(OC.webroot + '/remote.php/webdav/test%20file.txt');
|
||||
});
|
||||
it('returns the ajax download URL when multiple files specified', function() {
|
||||
var url = Files.getDownloadUrl(['test file.txt', 'abc.txt'], '/subdir');
|
||||
|
|
|
@ -54,7 +54,7 @@ OCA.External.App = {
|
|||
// folder in the files app instead of opening it directly
|
||||
fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) {
|
||||
OCA.Files.App.setActiveView('files', {silent: true});
|
||||
OCA.Files.App.fileList.changeDirectory(context.$file.attr('data-path') + '/' + filename, true, true);
|
||||
OCA.Files.App.fileList.changeDirectory(OC.joinPaths(context.$file.attr('data-path'), filename), true, true);
|
||||
});
|
||||
fileActions.setDefault('dir', 'Open');
|
||||
return fileActions;
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Joas Schilling <nickvergessen@owncloud.com>
|
||||
* @author Lukas Reschke <lukas@owncloud.com>
|
||||
* @author Morris Jobke <hey@morrisjobke.de>
|
||||
* @author Roeland Jago Douma <rullzer@owncloud.com>
|
||||
* @author Thomas Müller <thomas.mueller@tmit.eu>
|
||||
* @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/>
|
||||
*
|
||||
*/
|
||||
|
||||
OCP\JSON::checkAppEnabled('files_sharing');
|
||||
|
||||
if(!isset($_GET['t'])){
|
||||
\OC_Response::setStatus(\OC_Response::STATUS_BAD_REQUEST);
|
||||
\OCP\Util::writeLog('core-preview', 'No token parameter was passed', \OCP\Util::DEBUG);
|
||||
exit;
|
||||
}
|
||||
|
||||
$token = $_GET['t'];
|
||||
|
||||
$password = null;
|
||||
if (isset($_POST['password'])) {
|
||||
$password = $_POST['password'];
|
||||
}
|
||||
|
||||
$relativePath = null;
|
||||
if (isset($_GET['dir'])) {
|
||||
$relativePath = $_GET['dir'];
|
||||
}
|
||||
|
||||
$sortAttribute = isset( $_GET['sort'] ) ? $_GET['sort'] : 'name';
|
||||
$sortDirection = isset( $_GET['sortdirection'] ) ? ($_GET['sortdirection'] === 'desc') : false;
|
||||
|
||||
$data = \OCA\Files_Sharing\Helper::setupFromToken($token, $relativePath, $password);
|
||||
|
||||
$linkItem = $data['linkItem'];
|
||||
// Load the files
|
||||
$dir = $data['realPath'];
|
||||
|
||||
$dir = \OC\Files\Filesystem::normalizePath($dir);
|
||||
if (!\OC\Files\Filesystem::is_dir($dir . '/')) {
|
||||
\OC_Response::setStatus(\OC_Response::STATUS_NOT_FOUND);
|
||||
\OCP\JSON::error(array('success' => false));
|
||||
exit();
|
||||
}
|
||||
|
||||
$data = array();
|
||||
|
||||
// make filelist
|
||||
$files = \OCA\Files\Helper::getFiles($dir, $sortAttribute, $sortDirection);
|
||||
|
||||
$formattedFiles = array();
|
||||
foreach ($files as $file) {
|
||||
$entry = \OCA\Files\Helper::formatFileInfo($file);
|
||||
// for now
|
||||
unset($entry['directory']);
|
||||
// do not disclose share owner
|
||||
unset($entry['shareOwner']);
|
||||
// do not disclose if something is a remote shares
|
||||
unset($entry['mountType']);
|
||||
unset($entry['icon']);
|
||||
$entry['permissions'] = \OCP\Constants::PERMISSION_READ;
|
||||
$formattedFiles[] = $entry;
|
||||
}
|
||||
|
||||
$data['directory'] = $relativePath;
|
||||
$data['files'] = $formattedFiles;
|
||||
$data['dirToken'] = $linkItem['token'];
|
||||
|
||||
$permissions = $linkItem['permissions'];
|
||||
|
||||
// if globally disabled
|
||||
if (\OC::$server->getAppConfig()->getValue('core', 'shareapi_allow_public_upload', 'yes') === 'no') {
|
||||
// only allow reading
|
||||
$permissions = \OCP\Constants::PERMISSION_READ;
|
||||
}
|
||||
|
||||
$data['permissions'] = $permissions;
|
||||
|
||||
OCP\JSON::success(array('data' => $data));
|
|
@ -142,7 +142,7 @@ OCA.Sharing.App = {
|
|||
// folder in the files app instead of opening it directly
|
||||
fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) {
|
||||
OCA.Files.App.setActiveView('files', {silent: true});
|
||||
OCA.Files.App.fileList.changeDirectory(context.$file.attr('data-path') + '/' + filename, true, true);
|
||||
OCA.Files.App.fileList.changeDirectory(OC.joinPaths(context.$file.attr('data-path'), filename), true, true);
|
||||
});
|
||||
fileActions.setDefault('dir', 'Open');
|
||||
return fileActions;
|
||||
|
|
|
@ -48,8 +48,20 @@ OCA.Sharing.PublicApp = {
|
|||
this._initialized = true;
|
||||
this.initialDir = $('#dir').val();
|
||||
|
||||
var token = $('#sharingToken').val();
|
||||
|
||||
// file list mode ?
|
||||
if ($el.find('#filestable').length) {
|
||||
var filesClient = new OC.Files.Client({
|
||||
host: OC.getHost(),
|
||||
port: OC.getPort(),
|
||||
userName: token,
|
||||
// note: password not be required, the endpoint
|
||||
// will recognize previous validation from the session
|
||||
root: OC.getRootPath() + '/public.php/webdav',
|
||||
useHTTPS: OC.getProtocol() === 'https'
|
||||
});
|
||||
|
||||
this.fileList = new OCA.Files.FileList(
|
||||
$el,
|
||||
{
|
||||
|
@ -58,7 +70,8 @@ OCA.Sharing.PublicApp = {
|
|||
dragOptions: dragOptions,
|
||||
folderDropOptions: folderDropOptions,
|
||||
fileActions: fileActions,
|
||||
detailsViewEnabled: false
|
||||
detailsViewEnabled: false,
|
||||
filesClient: filesClient
|
||||
}
|
||||
);
|
||||
this.files = OCA.Files.Files;
|
||||
|
@ -88,7 +101,6 @@ OCA.Sharing.PublicApp = {
|
|||
|
||||
|
||||
// dynamically load image previews
|
||||
var token = $('#sharingToken').val();
|
||||
var bottomMargin = 350;
|
||||
var previewWidth = Math.ceil($(window).width() * window.devicePixelRatio);
|
||||
var previewHeight = Math.ceil(($(window).height() - bottomMargin) * window.devicePixelRatio);
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
if (fileData.shareOwner) {
|
||||
tr.attr('data-share-owner', fileData.shareOwner);
|
||||
// user should always be able to rename a mount point
|
||||
if (fileData.isShareMountPoint) {
|
||||
if (fileData.mountType === 'shared-root') {
|
||||
tr.attr('data-permissions', fileData.permissions | OC.PERMISSION_UPDATE);
|
||||
}
|
||||
}
|
||||
|
@ -68,6 +68,26 @@
|
|||
return fileInfo;
|
||||
};
|
||||
|
||||
var NS_OC = 'http://owncloud.org/ns';
|
||||
|
||||
var oldGetWebdavProperties = fileList._getWebdavProperties;
|
||||
fileList._getWebdavProperties = function() {
|
||||
var props = oldGetWebdavProperties.apply(this, arguments);
|
||||
props.push('{' + NS_OC + '}owner-display-name');
|
||||
return props;
|
||||
};
|
||||
|
||||
fileList.filesClient.addFileInfoParser(function(response) {
|
||||
var data = {};
|
||||
var props = response.propStat[0].properties;
|
||||
var permissionsProp = props['{' + NS_OC + '}permissions'];
|
||||
|
||||
if (permissionsProp && permissionsProp.indexOf('S') >= 0) {
|
||||
data.shareOwner = props['{' + NS_OC + '}owner-display-name'];
|
||||
}
|
||||
return data;
|
||||
});
|
||||
|
||||
// use delegate to catch the case with multiple file lists
|
||||
fileList.$el.on('fileActionsReady', function(ev){
|
||||
var fileList = ev.fileList;
|
||||
|
|
|
@ -231,6 +231,7 @@
|
|||
files = _.chain(files)
|
||||
// convert share data to file data
|
||||
.map(function(share) {
|
||||
// TODO: use OC.Files.FileInfo
|
||||
var file = {
|
||||
id: share.file_source,
|
||||
icon: OC.MimeType.getIconUrl(share.mimetype),
|
||||
|
@ -242,9 +243,6 @@
|
|||
}
|
||||
else {
|
||||
file.type = 'file';
|
||||
if (share.isPreviewAvailable) {
|
||||
file.isPreviewAvailable = true;
|
||||
}
|
||||
}
|
||||
file.share = {
|
||||
id: share.id,
|
||||
|
|
|
@ -21,11 +21,13 @@
|
|||
|
||||
describe('OCA.Sharing.PublicApp tests', function() {
|
||||
var App = OCA.Sharing.PublicApp;
|
||||
var hostStub, protocolStub, webrootStub;
|
||||
var $preview;
|
||||
var fileListIn;
|
||||
var fileListOut;
|
||||
|
||||
beforeEach(function() {
|
||||
protocolStub = sinon.stub(OC, 'getProtocol').returns('https');
|
||||
hostStub = sinon.stub(OC, 'getHost').returns('example.com');
|
||||
webrootStub = sinon.stub(OC, 'getRootPath').returns('/owncloud');
|
||||
$preview = $('<div id="preview"></div>');
|
||||
$('#testArea').append($preview);
|
||||
$preview.append(
|
||||
|
@ -35,6 +37,12 @@ describe('OCA.Sharing.PublicApp tests', function() {
|
|||
);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
protocolStub.restore();
|
||||
hostStub.restore();
|
||||
webrootStub.restore();
|
||||
});
|
||||
|
||||
describe('File list', function() {
|
||||
// TODO: this should be moved to a separate file once the PublicFileList is extracted from public.js
|
||||
beforeEach(function() {
|
||||
|
@ -78,6 +86,12 @@ describe('OCA.Sharing.PublicApp tests', function() {
|
|||
App._initialized = false;
|
||||
});
|
||||
|
||||
it('Uses public webdav endpoint', function() {
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
expect(fakeServer.requests[0].method).toEqual('PROPFIND');
|
||||
expect(fakeServer.requests[0].url).toEqual('https://sh4tok@example.com/owncloud/public.php/webdav/subdir');
|
||||
});
|
||||
|
||||
describe('Download Url', function() {
|
||||
var fileList;
|
||||
|
||||
|
|
|
@ -166,8 +166,7 @@ describe('OCA.Sharing.FileList tests', function() {
|
|||
expect($tr.attr('data-share-id')).toEqual('7');
|
||||
expect($tr.find('a.name').attr('href')).toEqual(
|
||||
OC.webroot +
|
||||
'/index.php/apps/files/ajax/download.php' +
|
||||
'?dir=%2Flocal%20path&files=local%20name.txt'
|
||||
'/remote.php/webdav/local%20path/local%20name.txt'
|
||||
);
|
||||
expect($tr.find('.nametext').text().trim()).toEqual('local name.txt');
|
||||
|
||||
|
@ -185,8 +184,7 @@ describe('OCA.Sharing.FileList tests', function() {
|
|||
expect($tr.attr('data-share-id')).toEqual('8');
|
||||
expect($tr.find('a.name').attr('href')).toEqual(
|
||||
OC.webroot +
|
||||
'/index.php/apps/files/ajax/download.php' +
|
||||
'?dir=%2F&files=b.txt'
|
||||
'/remote.php/webdav/b.txt'
|
||||
);
|
||||
expect($tr.find('.nametext').text().trim()).toEqual('b.txt');
|
||||
});
|
||||
|
@ -338,8 +336,7 @@ describe('OCA.Sharing.FileList tests', function() {
|
|||
expect($tr.attr('data-share-id')).toEqual('7');
|
||||
expect($tr.find('a.name').attr('href')).toEqual(
|
||||
OC.webroot +
|
||||
'/index.php/apps/files/ajax/download.php' +
|
||||
'?dir=%2Flocal%20path&files=local%20name.txt'
|
||||
'/remote.php/webdav/local%20path/local%20name.txt'
|
||||
);
|
||||
expect($tr.find('.nametext').text().trim()).toEqual('local name.txt');
|
||||
});
|
||||
|
@ -429,9 +426,8 @@ describe('OCA.Sharing.FileList tests', function() {
|
|||
expect($tr.attr('data-share-owner')).not.toBeDefined();
|
||||
expect($tr.attr('data-share-id')).toEqual('7');
|
||||
expect($tr.find('a.name').attr('href')).toEqual(
|
||||
OC.webroot +
|
||||
'/index.php/apps/files/ajax/download.php' +
|
||||
'?dir=%2Flocal%20path&files=local%20name.txt');
|
||||
OC.webroot + '/remote.php/webdav/local%20path/local%20name.txt'
|
||||
);
|
||||
|
||||
expect($tr.find('.nametext').text().trim()).toEqual('local name.txt');
|
||||
});
|
||||
|
@ -498,9 +494,7 @@ describe('OCA.Sharing.FileList tests', function() {
|
|||
expect($tr.attr('data-share-owner')).not.toBeDefined();
|
||||
expect($tr.attr('data-share-id')).toEqual('7,8,9');
|
||||
expect($tr.find('a.name').attr('href')).toEqual(
|
||||
OC.webroot +
|
||||
'/index.php/apps/files/ajax/download.php' +
|
||||
'?dir=%2Flocal%20path&files=local%20name.txt'
|
||||
OC.webroot + '/remote.php/webdav/local%20path/local%20name.txt'
|
||||
);
|
||||
expect($tr.find('.nametext').text().trim()).toEqual('local name.txt');
|
||||
});
|
||||
|
@ -592,9 +586,8 @@ describe('OCA.Sharing.FileList tests', function() {
|
|||
expect($tr.attr('data-share-owner')).not.toBeDefined();
|
||||
expect($tr.attr('data-share-id')).toEqual('7');
|
||||
expect($tr.find('a.name').attr('href')).toEqual(
|
||||
OC.webroot +
|
||||
'/index.php/apps/files/ajax/download.php' +
|
||||
'?dir=%2Flocal%20path&files=local%20name.txt');
|
||||
OC.webroot + '/remote.php/webdav/local%20path/local%20name.txt'
|
||||
);
|
||||
|
||||
expect($tr.find('.nametext').text().trim()).toEqual('local name.txt');
|
||||
});
|
||||
|
@ -634,8 +627,7 @@ describe('OCA.Sharing.FileList tests', function() {
|
|||
expect($tr.attr('data-share-id')).toEqual('7');
|
||||
expect($tr.find('a.name').attr('href')).toEqual(
|
||||
OC.webroot +
|
||||
'/index.php/apps/files/ajax/download.php' +
|
||||
'?dir=%2Flocal%20path&files=local%20name.txt');
|
||||
'/remote.php/webdav/local%20path/local%20name.txt');
|
||||
|
||||
expect($tr.find('.nametext').text().trim()).toEqual('local name.txt');
|
||||
});
|
||||
|
|
|
@ -38,10 +38,7 @@ OCA.Trashbin.App = {
|
|||
var fileActions = new OCA.Files.FileActions();
|
||||
fileActions.register('dir', 'Open', OC.PERMISSION_READ, '', function (filename, context) {
|
||||
var dir = context.fileList.getCurrentDirectory();
|
||||
if (dir !== '/') {
|
||||
dir = dir + '/';
|
||||
}
|
||||
context.fileList.changeDirectory(dir + filename);
|
||||
context.fileList.changeDirectory(OC.joinPaths(dir, filename));
|
||||
});
|
||||
|
||||
fileActions.setDefault('dir', 'Open');
|
||||
|
|
|
@ -283,7 +283,77 @@
|
|||
|
||||
isSelectedDeletable: function() {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reloads the file list using ajax call
|
||||
*
|
||||
* @return ajax call object
|
||||
*/
|
||||
reload: function() {
|
||||
this._selectedFiles = {};
|
||||
this._selectionSummary.clear();
|
||||
this.$el.find('.select-all').prop('checked', false);
|
||||
this.showMask();
|
||||
if (this._reloadCall) {
|
||||
this._reloadCall.abort();
|
||||
}
|
||||
this._reloadCall = $.ajax({
|
||||
url: this.getAjaxUrl('list'),
|
||||
data: {
|
||||
dir : this.getCurrentDirectory(),
|
||||
sort: this._sort,
|
||||
sortdirection: this._sortDirection
|
||||
}
|
||||
});
|
||||
var callBack = this.reloadCallback.bind(this);
|
||||
return this._reloadCall.then(callBack, callBack);
|
||||
},
|
||||
reloadCallback: function(result) {
|
||||
delete this._reloadCall;
|
||||
this.hideMask();
|
||||
|
||||
if (!result || result.status === 'error') {
|
||||
// if the error is not related to folder we're trying to load, reload the page to handle logout etc
|
||||
if (result.data.error === 'authentication_error' ||
|
||||
result.data.error === 'token_expired' ||
|
||||
result.data.error === 'application_not_enabled'
|
||||
) {
|
||||
OC.redirect(OC.generateUrl('apps/files'));
|
||||
}
|
||||
OC.Notification.show(result.data.message);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Firewall Blocked request?
|
||||
if (result.status === 403) {
|
||||
// Go home
|
||||
this.changeDirectory('/');
|
||||
OC.Notification.show(t('files', 'This operation is forbidden'));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Did share service die or something else fail?
|
||||
if (result.status === 500) {
|
||||
// Go home
|
||||
this.changeDirectory('/');
|
||||
OC.Notification.show(t('files', 'This directory is unavailable, please check the logs or contact the administrator'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result.status === 404) {
|
||||
// go back home
|
||||
this.changeDirectory('/');
|
||||
return false;
|
||||
}
|
||||
// aborted ?
|
||||
if (result.status === 0){
|
||||
return true;
|
||||
}
|
||||
|
||||
this.setFiles(result.data.files);
|
||||
return true;
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@
|
|||
"strengthify": "0.4.2",
|
||||
"underscore": "~1.8.0",
|
||||
"bootstrap": "~3.3.5",
|
||||
"backbone": "~1.2.1"
|
||||
"backbone": "~1.2.1",
|
||||
"davclient.js": "https://github.com/evert/davclient.js.git",
|
||||
"es6-promise": "https://github.com/jakearchibald/es6-promise.git#~2.3.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ NPM="$(which npm 2>/dev/null)"
|
|||
PREFIX="build"
|
||||
OUTPUT_DIR="build/jsdocs"
|
||||
|
||||
JS_FILES="core/js/*.js apps/*/js/*.js"
|
||||
JS_FILES="core/js/*.js core/js/**/*.js apps/*/js/*.js"
|
||||
|
||||
if test -z "$NPM"
|
||||
then
|
||||
|
|
|
@ -8,7 +8,9 @@
|
|||
"handlebars/handlebars.js",
|
||||
"blueimp-md5/js/md5.js",
|
||||
"bootstrap/js/tooltip.js",
|
||||
"backbone/backbone.js"
|
||||
"backbone/backbone.js",
|
||||
"es6-promise/dist/es6-promise.js",
|
||||
"davclient.js/lib/client.js"
|
||||
],
|
||||
"libraries": [
|
||||
"jquery-showpassword.js",
|
||||
|
@ -39,6 +41,8 @@
|
|||
"setupchecks.js",
|
||||
"../search/js/search.js",
|
||||
"mimetype.js",
|
||||
"mimetypelist.js"
|
||||
"mimetypelist.js",
|
||||
"files/fileinfo.js",
|
||||
"files/client.js"
|
||||
]
|
||||
}
|
||||
|
|
691
core/js/files/client.js
Normal file
691
core/js/files/client.js
Normal file
|
@ -0,0 +1,691 @@
|
|||
/*
|
||||
* Copyright (c) 2015
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3
|
||||
* or later.
|
||||
*
|
||||
* See the COPYING-README file.
|
||||
*
|
||||
*/
|
||||
|
||||
/* global dav */
|
||||
|
||||
(function(OC, FileInfo) {
|
||||
/**
|
||||
* @class OC.Files.Client
|
||||
* @classdesc Client to access files on the server
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {String} options.host host name
|
||||
* @param {int} [options.port] port
|
||||
* @param {boolean} [options.useHTTPS] whether to use https
|
||||
* @param {String} [options.root] root path
|
||||
* @param {String} [options.userName] user name
|
||||
* @param {String} [options.password] password
|
||||
*
|
||||
* @since 8.2
|
||||
*/
|
||||
var Client = function(options) {
|
||||
this._root = options.root;
|
||||
if (this._root.charAt(this._root.length - 1) === '/') {
|
||||
this._root = this._root.substr(0, this._root.length - 1);
|
||||
}
|
||||
|
||||
var url = 'http://';
|
||||
if (options.useHTTPS) {
|
||||
url = 'https://';
|
||||
}
|
||||
var credentials = '';
|
||||
if (options.userName) {
|
||||
credentials += encodeURIComponent(options.userName);
|
||||
}
|
||||
if (options.password) {
|
||||
credentials += ':' + encodeURIComponent(options.password);
|
||||
}
|
||||
if (credentials.length > 0) {
|
||||
url += credentials + '@';
|
||||
}
|
||||
|
||||
url += options.host + this._root;
|
||||
this._defaultHeaders = options.defaultHeaders || {'X-Requested-With': 'XMLHttpRequest'};
|
||||
this._baseUrl = url;
|
||||
this._client = new dav.Client({
|
||||
baseUrl: this._baseUrl,
|
||||
xmlNamespaces: {
|
||||
'DAV:': 'd',
|
||||
'http://owncloud.org/ns': 'oc'
|
||||
}
|
||||
});
|
||||
this._client.xhrProvider = _.bind(this._xhrProvider, this);
|
||||
};
|
||||
|
||||
Client.NS_OWNCLOUD = 'http://owncloud.org/ns';
|
||||
Client.NS_DAV = 'DAV:';
|
||||
Client._PROPFIND_PROPERTIES = [
|
||||
/**
|
||||
* Modified time
|
||||
*/
|
||||
[Client.NS_DAV, 'getlastmodified'],
|
||||
/**
|
||||
* Etag
|
||||
*/
|
||||
[Client.NS_DAV, 'getetag'],
|
||||
/**
|
||||
* Mime type
|
||||
*/
|
||||
[Client.NS_DAV, 'getcontenttype'],
|
||||
/**
|
||||
* Resource type "collection" for folders, empty otherwise
|
||||
*/
|
||||
[Client.NS_DAV, 'resourcetype'],
|
||||
/**
|
||||
* File id
|
||||
*/
|
||||
[Client.NS_OWNCLOUD, 'fileid'],
|
||||
/**
|
||||
* Letter-coded permissions
|
||||
*/
|
||||
[Client.NS_OWNCLOUD, 'permissions'],
|
||||
//[Client.NS_OWNCLOUD, 'downloadURL'],
|
||||
/**
|
||||
* Folder sizes
|
||||
*/
|
||||
[Client.NS_OWNCLOUD, 'size'],
|
||||
/**
|
||||
* File sizes
|
||||
*/
|
||||
[Client.NS_DAV, 'getcontentlength']
|
||||
];
|
||||
|
||||
/**
|
||||
* @memberof OC.Files
|
||||
*/
|
||||
Client.prototype = {
|
||||
|
||||
/**
|
||||
* Root path of the Webdav endpoint
|
||||
*
|
||||
* @type string
|
||||
*/
|
||||
_root: null,
|
||||
|
||||
/**
|
||||
* Client from the library
|
||||
*
|
||||
* @type dav.Client
|
||||
*/
|
||||
_client: null,
|
||||
|
||||
/**
|
||||
* Array of file info parsing functions.
|
||||
*
|
||||
* @type Array<OC.Files.Client~parseFileInfo>
|
||||
*/
|
||||
_fileInfoParsers: [],
|
||||
|
||||
/**
|
||||
* Returns the configured XHR provider for davclient
|
||||
* @return {XMLHttpRequest}
|
||||
*/
|
||||
_xhrProvider: function() {
|
||||
var headers = this._defaultHeaders;
|
||||
var xhr = new XMLHttpRequest();
|
||||
var oldOpen = xhr.open;
|
||||
// override open() method to add headers
|
||||
xhr.open = function() {
|
||||
var result = oldOpen.apply(this, arguments);
|
||||
_.each(headers, function(value, key) {
|
||||
xhr.setRequestHeader(key, value);
|
||||
});
|
||||
return result;
|
||||
};
|
||||
return xhr;
|
||||
},
|
||||
|
||||
/**
|
||||
* Prepends the base url to the given path sections
|
||||
*
|
||||
* @param {...String} path sections
|
||||
*
|
||||
* @return {String} base url + joined path, any leading or trailing slash
|
||||
* will be kept
|
||||
*/
|
||||
_buildUrl: function() {
|
||||
var path = this._buildPath.apply(this, arguments);
|
||||
if (path.charAt([path.length - 1]) === '/') {
|
||||
path = path.substr(0, path.length - 1);
|
||||
}
|
||||
if (path.charAt(0) === '/') {
|
||||
path = path.substr(1);
|
||||
}
|
||||
return this._baseUrl + '/' + path;
|
||||
},
|
||||
|
||||
/**
|
||||
* Append the path to the root and also encode path
|
||||
* sections
|
||||
*
|
||||
* @param {...String} path sections
|
||||
*
|
||||
* @return {String} joined path, any leading or trailing slash
|
||||
* will be kept
|
||||
*/
|
||||
_buildPath: function() {
|
||||
var path = OC.joinPaths.apply(this, arguments);
|
||||
var sections = path.split('/');
|
||||
var i;
|
||||
for (i = 0; i < sections.length; i++) {
|
||||
sections[i] = encodeURIComponent(sections[i]);
|
||||
}
|
||||
path = sections.join('/');
|
||||
return path;
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse headers string into a map
|
||||
*
|
||||
* @param {string} headersString headers list as string
|
||||
*
|
||||
* @return {Object.<String,Array>} map of header name to header contents
|
||||
*/
|
||||
_parseHeaders: function(headersString) {
|
||||
var headerRows = headersString.split('\n');
|
||||
var headers = {};
|
||||
for (var i = 0; i < headerRows.length; i++) {
|
||||
var sepPos = headerRows[i].indexOf(':');
|
||||
if (sepPos < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var headerName = headerRows[i].substr(0, sepPos);
|
||||
var headerValue = headerRows[i].substr(sepPos + 2);
|
||||
|
||||
if (!headers[headerName]) {
|
||||
// make it an array
|
||||
headers[headerName] = [];
|
||||
}
|
||||
|
||||
headers[headerName].push(headerValue);
|
||||
}
|
||||
return headers;
|
||||
},
|
||||
|
||||
/**
|
||||
* Parses the etag response which is in double quotes.
|
||||
*
|
||||
* @param {string} etag etag value in double quotes
|
||||
*
|
||||
* @return {string} etag without double quotes
|
||||
*/
|
||||
_parseEtag: function(etag) {
|
||||
if (etag.charAt(0) === '"') {
|
||||
return etag.split('"')[1];
|
||||
}
|
||||
return etag;
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse Webdav result
|
||||
*
|
||||
* @param {Object} response XML object
|
||||
*
|
||||
* @return {Array.<FileInfo>} array of file info
|
||||
*/
|
||||
_parseFileInfo: function(response) {
|
||||
var path = response.href;
|
||||
if (path.substr(0, this._root.length) === this._root) {
|
||||
path = path.substr(this._root.length);
|
||||
}
|
||||
|
||||
if (path.charAt(path.length - 1) === '/') {
|
||||
path = path.substr(0, path.length - 1);
|
||||
}
|
||||
|
||||
path = '/' + decodeURIComponent(path);
|
||||
|
||||
if (response.propStat.length === 1 && response.propStat[0].status !== 200) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var props = response.propStat[0].properties;
|
||||
|
||||
var data = {
|
||||
id: props['{' + Client.NS_OWNCLOUD + '}fileid'],
|
||||
path: OC.dirname(path) || '/',
|
||||
name: OC.basename(path),
|
||||
mtime: new Date(props['{' + Client.NS_DAV + '}getlastmodified'])
|
||||
};
|
||||
|
||||
var etagProp = props['{' + Client.NS_DAV + '}getetag'];
|
||||
if (!_.isUndefined(etagProp)) {
|
||||
data.etag = this._parseEtag(etagProp);
|
||||
}
|
||||
|
||||
var sizeProp = props['{' + Client.NS_DAV + '}getcontentlength'];
|
||||
if (!_.isUndefined(sizeProp)) {
|
||||
data.size = parseInt(sizeProp, 10);
|
||||
}
|
||||
|
||||
sizeProp = props['{' + Client.NS_OWNCLOUD + '}size'];
|
||||
if (!_.isUndefined(sizeProp)) {
|
||||
data.size = parseInt(sizeProp, 10);
|
||||
}
|
||||
|
||||
var contentType = props['{' + Client.NS_DAV + '}getcontenttype'];
|
||||
if (!_.isUndefined(contentType)) {
|
||||
data.mimetype = contentType;
|
||||
}
|
||||
|
||||
var resType = props['{' + Client.NS_DAV + '}resourcetype'];
|
||||
var isFile = true;
|
||||
if (!data.mimetype && resType) {
|
||||
var xmlvalue = resType[0];
|
||||
if (xmlvalue.namespaceURI === Client.NS_DAV && xmlvalue.nodeName.split(':')[1] === 'collection') {
|
||||
data.mimetype = 'httpd/unix-directory';
|
||||
isFile = false;
|
||||
}
|
||||
}
|
||||
|
||||
data.permissions = OC.PERMISSION_READ;
|
||||
var permissionProp = props['{' + Client.NS_OWNCLOUD + '}permissions'];
|
||||
if (!_.isUndefined(permissionProp)) {
|
||||
var permString = permissionProp || '';
|
||||
data.mountType = null;
|
||||
for (var i = 0; i < permString.length; i++) {
|
||||
var c = permString.charAt(i);
|
||||
switch (c) {
|
||||
// FIXME: twisted permissions
|
||||
case 'C':
|
||||
case 'K':
|
||||
data.permissions |= OC.PERMISSION_CREATE;
|
||||
if (!isFile) {
|
||||
data.permissions |= OC.PERMISSION_UPDATE;
|
||||
}
|
||||
break;
|
||||
case 'W':
|
||||
if (isFile) {
|
||||
// also add create permissions
|
||||
data.permissions |= OC.PERMISSION_CREATE;
|
||||
}
|
||||
data.permissions |= OC.PERMISSION_UPDATE;
|
||||
break;
|
||||
case 'D':
|
||||
data.permissions |= OC.PERMISSION_DELETE;
|
||||
break;
|
||||
case 'R':
|
||||
data.permissions |= OC.PERMISSION_SHARE;
|
||||
break;
|
||||
case 'M':
|
||||
if (!data.mountType) {
|
||||
// TODO: how to identify external-root ?
|
||||
data.mountType = 'external';
|
||||
}
|
||||
break;
|
||||
case 'S':
|
||||
// TODO: how to identify shared-root ?
|
||||
data.mountType = 'shared';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// extend the parsed data using the custom parsers
|
||||
_.each(this._fileInfoParsers, function(parserFunction) {
|
||||
_.extend(data, parserFunction(response) || {});
|
||||
});
|
||||
|
||||
return new FileInfo(data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse Webdav multistatus
|
||||
*
|
||||
* @param {Array} responses
|
||||
*/
|
||||
_parseResult: function(responses) {
|
||||
var self = this;
|
||||
return _.map(responses, function(response) {
|
||||
return self._parseFileInfo(response);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns whether the given status code means success
|
||||
*
|
||||
* @param {int} status status code
|
||||
*
|
||||
* @return true if status code is between 200 and 299 included
|
||||
*/
|
||||
_isSuccessStatus: function(status) {
|
||||
return status >= 200 && status <= 299;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the default PROPFIND properties to use during a call.
|
||||
*
|
||||
* @return {Array.<Object>} array of properties
|
||||
*/
|
||||
getPropfindProperties: function() {
|
||||
if (!this._propfindProperties) {
|
||||
this._propfindProperties = _.map(Client._PROPFIND_PROPERTIES, function(propDef) {
|
||||
return '{' + propDef[0] + '}' + propDef[1];
|
||||
});
|
||||
}
|
||||
return this._propfindProperties;
|
||||
},
|
||||
|
||||
/**
|
||||
* Lists the contents of a directory
|
||||
*
|
||||
* @param {String} path path to retrieve
|
||||
* @param {Object} [options] options
|
||||
* @param {boolean} [options.includeParent=false] set to true to keep
|
||||
* the parent folder in the result list
|
||||
* @param {Array} [options.properties] list of Webdav properties to retrieve
|
||||
*
|
||||
* @return {Promise} promise
|
||||
*/
|
||||
getFolderContents: function(path, options) {
|
||||
if (!path) {
|
||||
path = '';
|
||||
}
|
||||
options = options || {};
|
||||
var self = this;
|
||||
var deferred = $.Deferred();
|
||||
var promise = deferred.promise();
|
||||
var properties;
|
||||
if (_.isUndefined(options.properties)) {
|
||||
properties = this.getPropfindProperties();
|
||||
} else {
|
||||
properties = options.properties;
|
||||
}
|
||||
|
||||
// TODO: headers
|
||||
this._client.propFind(
|
||||
this._buildUrl(path),
|
||||
properties,
|
||||
1
|
||||
).then(function(result) {
|
||||
if (self._isSuccessStatus(result.status)) {
|
||||
var results = self._parseResult(result.body);
|
||||
if (!options || !options.includeParent) {
|
||||
// remove root dir, the first entry
|
||||
results.shift();
|
||||
}
|
||||
deferred.resolve(result.status, results);
|
||||
} else {
|
||||
deferred.reject(result.status);
|
||||
}
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the file info of a given path.
|
||||
*
|
||||
* @param {String} path path
|
||||
* @param {Array} [options.properties] list of Webdav properties to retrieve
|
||||
*
|
||||
* @return {Promise} promise
|
||||
*/
|
||||
getFileInfo: function(path, options) {
|
||||
if (!path) {
|
||||
path = '';
|
||||
}
|
||||
options = options || {};
|
||||
var self = this;
|
||||
var deferred = $.Deferred();
|
||||
var promise = deferred.promise();
|
||||
var properties;
|
||||
if (_.isUndefined(options.properties)) {
|
||||
properties = this.getPropfindProperties();
|
||||
} else {
|
||||
properties = options.properties;
|
||||
}
|
||||
|
||||
// TODO: headers
|
||||
this._client.propFind(
|
||||
this._buildUrl(path),
|
||||
properties,
|
||||
0
|
||||
).then(
|
||||
function(result) {
|
||||
if (self._isSuccessStatus(result.status)) {
|
||||
deferred.resolve(result.status, self._parseResult([result.body])[0]);
|
||||
} else {
|
||||
deferred.reject(result.status);
|
||||
}
|
||||
}
|
||||
);
|
||||
return promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the contents of the given file.
|
||||
*
|
||||
* @param {String} path path to file
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
getFileContents: function(path) {
|
||||
if (!path) {
|
||||
throw 'Missing argument "path"';
|
||||
}
|
||||
var self = this;
|
||||
var deferred = $.Deferred();
|
||||
var promise = deferred.promise();
|
||||
|
||||
this._client.request(
|
||||
'GET',
|
||||
this._buildUrl(path),
|
||||
this._defaultHeaders
|
||||
).then(
|
||||
function(result) {
|
||||
if (self._isSuccessStatus(result.status)) {
|
||||
deferred.resolve(result.status, result.body);
|
||||
} else {
|
||||
deferred.reject(result.status);
|
||||
}
|
||||
}
|
||||
);
|
||||
return promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Puts the given data into the given file.
|
||||
*
|
||||
* @param {String} path path to file
|
||||
* @param {String} body file body
|
||||
* @param {Object} [options]
|
||||
* @param {String} [options.contentType='text/plain'] content type
|
||||
* @param {bool} [options.overwrite=true] whether to overwrite an existing file
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
putFileContents: function(path, body, options) {
|
||||
if (!path) {
|
||||
throw 'Missing argument "path"';
|
||||
}
|
||||
var self = this;
|
||||
var deferred = $.Deferred();
|
||||
var promise = deferred.promise();
|
||||
options = options || {};
|
||||
var headers = _.extend({}, this._defaultHeaders);
|
||||
var contentType = 'text/plain';
|
||||
if (options.contentType) {
|
||||
contentType = options.contentType;
|
||||
}
|
||||
|
||||
headers['Content-Type'] = contentType;
|
||||
|
||||
if (_.isUndefined(options.overwrite) || options.overwrite) {
|
||||
// will trigger 412 precondition failed if a file already exists
|
||||
headers['If-None-Match'] = '*';
|
||||
}
|
||||
|
||||
this._client.request(
|
||||
'PUT',
|
||||
this._buildUrl(path),
|
||||
headers,
|
||||
body || ''
|
||||
).then(
|
||||
function(result) {
|
||||
if (self._isSuccessStatus(result.status)) {
|
||||
deferred.resolve(result.status);
|
||||
} else {
|
||||
deferred.reject(result.status);
|
||||
}
|
||||
}
|
||||
);
|
||||
return promise;
|
||||
},
|
||||
|
||||
_simpleCall: function(method, path) {
|
||||
if (!path) {
|
||||
throw 'Missing argument "path"';
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var deferred = $.Deferred();
|
||||
var promise = deferred.promise();
|
||||
|
||||
this._client.request(
|
||||
method,
|
||||
this._buildUrl(path),
|
||||
this._defaultHeaders
|
||||
).then(
|
||||
function(result) {
|
||||
if (self._isSuccessStatus(result.status)) {
|
||||
deferred.resolve(result.status);
|
||||
} else {
|
||||
deferred.reject(result.status);
|
||||
}
|
||||
}
|
||||
);
|
||||
return promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates a directory
|
||||
*
|
||||
* @param {String} path path to create
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
createDirectory: function(path) {
|
||||
return this._simpleCall('MKCOL', path);
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes a file or directory
|
||||
*
|
||||
* @param {String} path path to delete
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
remove: function(path) {
|
||||
return this._simpleCall('DELETE', path);
|
||||
},
|
||||
|
||||
/**
|
||||
* Moves path to another path
|
||||
*
|
||||
* @param {String} path path to move
|
||||
* @param {String} destinationPath destination path
|
||||
* @param {boolean} [allowOverwrite=false] true to allow overwriting,
|
||||
* false otherwise
|
||||
*
|
||||
* @return {Promise} promise
|
||||
*/
|
||||
move: function(path, destinationPath, allowOverwrite) {
|
||||
if (!path) {
|
||||
throw 'Missing argument "path"';
|
||||
}
|
||||
if (!destinationPath) {
|
||||
throw 'Missing argument "destinationPath"';
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var deferred = $.Deferred();
|
||||
var promise = deferred.promise();
|
||||
var headers =
|
||||
_.extend({
|
||||
'Destination' : this._buildUrl(destinationPath)
|
||||
}, this._defaultHeaders);
|
||||
|
||||
if (!allowOverwrite) {
|
||||
headers['Overwrite'] = 'F';
|
||||
}
|
||||
|
||||
this._client.request(
|
||||
'MOVE',
|
||||
this._buildUrl(path),
|
||||
headers
|
||||
).then(
|
||||
function(response) {
|
||||
if (self._isSuccessStatus(response.status)) {
|
||||
deferred.resolve(response.status);
|
||||
} else {
|
||||
deferred.reject(response.status);
|
||||
}
|
||||
}
|
||||
);
|
||||
return promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a file info parser function
|
||||
*
|
||||
* @param {OC.Files.Client~parseFileInfo>}
|
||||
*/
|
||||
addFileInfoParser: function(parserFunction) {
|
||||
this._fileInfoParsers.push(parserFunction);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* File info parser function
|
||||
*
|
||||
* This function receives a list of Webdav properties as input and
|
||||
* should return a hash array of parsed properties, if applicable.
|
||||
*
|
||||
* @callback OC.Files.Client~parseFileInfo
|
||||
* @param {Object} XML Webdav properties
|
||||
* @return {Array} array of parsed property values
|
||||
*/
|
||||
|
||||
if (!OC.Files) {
|
||||
/**
|
||||
* @namespace OC.Files
|
||||
*
|
||||
* @since 8.2
|
||||
*/
|
||||
OC.Files = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default instance of the files client
|
||||
*
|
||||
* @return {OC.Files.Client} default client
|
||||
*
|
||||
* @since 8.2
|
||||
*/
|
||||
OC.Files.getClient = function() {
|
||||
if (OC.Files._defaultClient) {
|
||||
return OC.Files._defaultClient;
|
||||
}
|
||||
|
||||
var client = new OC.Files.Client({
|
||||
host: OC.getHost(),
|
||||
port: OC.getPort(),
|
||||
root: OC.linkToRemoteBase('webdav'),
|
||||
useHTTPS: OC.getProtocol() === 'https'
|
||||
});
|
||||
OC.Files._defaultClient = client;
|
||||
return client;
|
||||
};
|
||||
|
||||
OC.Files.Client = Client;
|
||||
})(OC, OC.Files.FileInfo);
|
||||
|
138
core/js/files/fileinfo.js
Normal file
138
core/js/files/fileinfo.js
Normal file
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright (c) 2015
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3
|
||||
* or later.
|
||||
*
|
||||
* See the COPYING-README file.
|
||||
*
|
||||
*/
|
||||
|
||||
(function(OC) {
|
||||
|
||||
/**
|
||||
* @class OC.Files.FileInfo
|
||||
* @classdesc File information
|
||||
*
|
||||
* @param {Object} data file data, see attributes for details
|
||||
*
|
||||
* @since 8.2
|
||||
*/
|
||||
var FileInfo = function(data) {
|
||||
var self = this;
|
||||
_.each(data, function(value, key) {
|
||||
if (!_.isFunction(value)) {
|
||||
self[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
if (!_.isUndefined(this.id)) {
|
||||
this.id = parseInt(data.id, 10);
|
||||
}
|
||||
|
||||
// TODO: normalize path
|
||||
this.path = data.path || '';
|
||||
|
||||
if (this.type === 'dir') {
|
||||
this.mimetype = 'httpd/unix-directory';
|
||||
} else {
|
||||
this.mimetype = this.mimetype || 'application/octet-stream';
|
||||
}
|
||||
|
||||
if (!this.type) {
|
||||
if (this.mimetype === 'httpd/unix-directory') {
|
||||
this.type = 'dir';
|
||||
} else {
|
||||
this.type = 'file';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @memberof OC.Files
|
||||
*/
|
||||
FileInfo.prototype = {
|
||||
/**
|
||||
* File id
|
||||
*
|
||||
* @type int
|
||||
*/
|
||||
id: null,
|
||||
|
||||
/**
|
||||
* File name
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
name: null,
|
||||
|
||||
/**
|
||||
* Path leading to the file, without the file name,
|
||||
* and with a leading slash.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
path: null,
|
||||
|
||||
/**
|
||||
* Mime type
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
mimetype: null,
|
||||
|
||||
/**
|
||||
* Icon URL.
|
||||
*
|
||||
* Can be used to override the mime type icon.
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
icon: null,
|
||||
|
||||
/**
|
||||
* File type. 'file' for files, 'dir' for directories.
|
||||
*
|
||||
* @type String
|
||||
* @deprecated rely on mimetype instead
|
||||
*/
|
||||
type: null,
|
||||
|
||||
/**
|
||||
* Permissions.
|
||||
*
|
||||
* @see OC#PERMISSION_ALL for permissions
|
||||
* @type int
|
||||
*/
|
||||
permissions: null,
|
||||
|
||||
/**
|
||||
* Modification time
|
||||
*
|
||||
* @type int
|
||||
*/
|
||||
mtime: null,
|
||||
|
||||
/**
|
||||
* Etag
|
||||
*
|
||||
* @type String
|
||||
*/
|
||||
etag: null,
|
||||
|
||||
/**
|
||||
* Mount type.
|
||||
*
|
||||
* One of null, "external-root", "shared" or "shared-root"
|
||||
*
|
||||
* @type string
|
||||
*/
|
||||
mountType: null
|
||||
};
|
||||
|
||||
if (!OC.Files) {
|
||||
OC.Files = {};
|
||||
}
|
||||
OC.Files.FileInfo = FileInfo;
|
||||
})(OC);
|
||||
|
169
core/js/files/iedavclient.js
Normal file
169
core/js/files/iedavclient.js
Normal file
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* Copyright (c) 2015
|
||||
*
|
||||
* This file is licensed under the Affero General Public License version 3
|
||||
* or later.
|
||||
*
|
||||
* See the COPYING-README file.
|
||||
*
|
||||
*/
|
||||
|
||||
/* global dav */
|
||||
(function(dav) {
|
||||
|
||||
/**
|
||||
* Override davclient.js methods with IE-compatible logic
|
||||
*/
|
||||
dav.Client.prototype = _.extend({}, dav.Client.prototype, {
|
||||
|
||||
/**
|
||||
* Generates a propFind request.
|
||||
*
|
||||
* @param {string} url Url to do the propfind request on
|
||||
* @param {Array} properties List of properties to retrieve.
|
||||
* @return {Promise}
|
||||
*/
|
||||
propFind : function(url, properties, depth) {
|
||||
|
||||
if(typeof depth == "undefined") {
|
||||
depth = 0;
|
||||
}
|
||||
|
||||
var headers = {
|
||||
Depth : depth,
|
||||
'Content-Type' : 'application/xml; charset=utf-8'
|
||||
};
|
||||
|
||||
var body =
|
||||
'<?xml version="1.0"?>\n' +
|
||||
'<d:propfind ';
|
||||
|
||||
var namespace;
|
||||
for (namespace in this.xmlNamespaces) {
|
||||
body += ' xmlns:' + this.xmlNamespaces[namespace] + '="' + namespace + '"';
|
||||
}
|
||||
body += '>\n' +
|
||||
' <d:prop>\n';
|
||||
|
||||
for(var ii in properties) {
|
||||
var propText = properties[ii];
|
||||
if (typeof propText !== 'string') {
|
||||
// can happen on IE8
|
||||
continue;
|
||||
}
|
||||
var property = this.parseClarkNotation(properties[ii]);
|
||||
if (this.xmlNamespaces[property.namespace]) {
|
||||
body+=' <' + this.xmlNamespaces[property.namespace] + ':' + property.name + ' />\n';
|
||||
} else {
|
||||
body+=' <x:' + property.name + ' xmlns:x="' + property.namespace + '" />\n';
|
||||
}
|
||||
|
||||
}
|
||||
body+=' </d:prop>\n';
|
||||
body+='</d:propfind>';
|
||||
|
||||
return this.request('PROPFIND', url, headers, body).then(
|
||||
function(result) {
|
||||
var elements = this.parseMultiStatus(result.xhr.responseXML);
|
||||
var response;
|
||||
if (depth===0) {
|
||||
response = {
|
||||
status: result.status,
|
||||
body: elements[0]
|
||||
};
|
||||
} else {
|
||||
response = {
|
||||
status: result.status,
|
||||
body: elements
|
||||
};
|
||||
}
|
||||
return response;
|
||||
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
},
|
||||
|
||||
|
||||
_getElementsByTagName: function(node, name, resolver) {
|
||||
var parts = name.split(':');
|
||||
var tagName = parts[1];
|
||||
var namespace = resolver(parts[0]);
|
||||
if (node.getElementsByTagNameNS) {
|
||||
return node.getElementsByTagNameNS(namespace, tagName);
|
||||
}
|
||||
return node.getElementsByTagName(name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Parses a multi-status response body.
|
||||
*
|
||||
* @param {string} xmlBody
|
||||
* @param {Array}
|
||||
*/
|
||||
parseMultiStatus : function(doc) {
|
||||
|
||||
var result = [];
|
||||
var resolver = function(foo) {
|
||||
var ii;
|
||||
for(ii in this.xmlNamespaces) {
|
||||
if (this.xmlNamespaces[ii] === foo) {
|
||||
return ii;
|
||||
}
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
var responses = this._getElementsByTagName(doc, 'd:response', resolver);
|
||||
var i;
|
||||
for (i = 0; i < responses.length; i++) {
|
||||
var responseNode = responses[i];
|
||||
var response = {
|
||||
href : null,
|
||||
propStat : []
|
||||
};
|
||||
|
||||
var hrefNode = this._getElementsByTagName(responseNode, 'd:href', resolver)[0];
|
||||
|
||||
response.href = hrefNode.textContent || hrefNode.text;
|
||||
|
||||
var propStatNodes = this._getElementsByTagName(responseNode, 'd:propstat', resolver);
|
||||
var j = 0;
|
||||
|
||||
for (j = 0; j < propStatNodes.length; j++) {
|
||||
var propStatNode = propStatNodes[j];
|
||||
var statusNode = this._getElementsByTagName(propStatNode, 'd:status', resolver)[0];
|
||||
|
||||
var propStat = {
|
||||
status : statusNode.textContent || statusNode.text,
|
||||
properties : []
|
||||
};
|
||||
|
||||
var propNode = this._getElementsByTagName(propStatNode, 'd:prop', resolver)[0];
|
||||
if (!propNode) {
|
||||
continue;
|
||||
}
|
||||
var k = 0;
|
||||
for (k = 0; k < propNode.childNodes.length; k++) {
|
||||
var prop = propNode.childNodes[k];
|
||||
var value = prop.textContent || prop.text;
|
||||
if (prop.childNodes && prop.childNodes.length > 0 && prop.childNodes[0].nodeType === 1) {
|
||||
value = prop.childNodes;
|
||||
}
|
||||
propStat.properties['{' + prop.namespaceURI + '}' + (prop.localName || prop.baseName)] = value;
|
||||
|
||||
}
|
||||
response.propStat.push(propStat);
|
||||
}
|
||||
|
||||
result.push(response);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
})(dav);
|
||||
|
|
@ -1428,7 +1428,6 @@ function initCore() {
|
|||
$('body').delegate('#app-content', 'apprendered appresized', adjustControlsWidth);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$(document).ready(initCore);
|
||||
|
|
|
@ -759,7 +759,7 @@ var OCdialogs = {
|
|||
filename: entry.name,
|
||||
date: OC.Util.relativeModifiedDate(entry.mtime)
|
||||
});
|
||||
if (entry.isPreviewAvailable) {
|
||||
if (entry.type === 'file') {
|
||||
var urlSpec = {
|
||||
file: dir + '/' + entry.name
|
||||
};
|
||||
|
|
|
@ -86,6 +86,7 @@ window.firstDay = 0;
|
|||
// setup dummy webroots
|
||||
/* jshint camelcase: false */
|
||||
window.oc_debug = true;
|
||||
// FIXME: oc_webroot is supposed to be only the path!!!
|
||||
window.oc_webroot = location.href + '/';
|
||||
window.oc_appswebroots = {
|
||||
"files": window.oc_webroot + '/apps/files/'
|
||||
|
|
711
core/js/tests/specs/files/clientSpec.js
Normal file
711
core/js/tests/specs/files/clientSpec.js
Normal file
|
@ -0,0 +1,711 @@
|
|||
/**
|
||||
* ownCloud
|
||||
*
|
||||
* @author Vincent Petry
|
||||
* @copyright 2015 Vincent Petry <pvince81@owncloud.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This library 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 along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
describe('OC.Files.Client tests', function() {
|
||||
var Client = OC.Files.Client;
|
||||
var baseUrl;
|
||||
var client;
|
||||
|
||||
beforeEach(function() {
|
||||
baseUrl = 'https://testhost/owncloud/remote.php/webdav/';
|
||||
|
||||
client = new Client({
|
||||
host: 'testhost',
|
||||
root: '/owncloud/remote.php/webdav',
|
||||
useHTTPS: true
|
||||
});
|
||||
});
|
||||
afterEach(function() {
|
||||
client = null;
|
||||
});
|
||||
|
||||
/**
|
||||
* Send an status response and check that the given
|
||||
* promise gets its success handler called with the error
|
||||
* status code
|
||||
*
|
||||
* @param {Promise} promise promise
|
||||
* @param {int} status status to test
|
||||
*/
|
||||
function respondAndCheckStatus(promise, status) {
|
||||
var successHandler = sinon.stub();
|
||||
var failHandler = sinon.stub();
|
||||
promise.done(successHandler);
|
||||
promise.fail(failHandler);
|
||||
|
||||
fakeServer.requests[0].respond(
|
||||
status,
|
||||
{'Content-Type': 'application/xml'},
|
||||
''
|
||||
);
|
||||
|
||||
promise.then(function() {
|
||||
expect(successHandler.calledOnce).toEqual(true);
|
||||
expect(successHandler.getCall(0).args[0]).toEqual(status);
|
||||
|
||||
expect(failHandler.notCalled).toEqual(true);
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an error response and check that the given
|
||||
* promise gets its fail handler called with the error
|
||||
* status code
|
||||
*
|
||||
* @param {Promise} promise promise object
|
||||
* @param {int} status error status to test
|
||||
*/
|
||||
function respondAndCheckError(promise, status) {
|
||||
var successHandler = sinon.stub();
|
||||
var failHandler = sinon.stub();
|
||||
promise.done(successHandler);
|
||||
promise.fail(failHandler);
|
||||
|
||||
fakeServer.requests[0].respond(
|
||||
status,
|
||||
{'Content-Type': 'application/xml'},
|
||||
''
|
||||
);
|
||||
|
||||
promise.then(function() {
|
||||
expect(failHandler.calledOnce).toEqual(true);
|
||||
expect(failHandler.calledWith(status)).toEqual(true);
|
||||
|
||||
expect(successHandler.notCalled).toEqual(true);
|
||||
|
||||
fulfill();
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of request properties parsed from the given request body.
|
||||
*
|
||||
* @param {string} requestBody request XML
|
||||
*
|
||||
* @return {Array.<String>} array of request properties in the format
|
||||
* "{NS:}propname"
|
||||
*/
|
||||
function getRequestedProperties(requestBody) {
|
||||
var doc = (new window.DOMParser()).parseFromString(
|
||||
requestBody,
|
||||
'application/xml'
|
||||
);
|
||||
var propRoots = doc.getElementsByTagNameNS('DAV:', 'prop');
|
||||
var propsList = propRoots.item(0).childNodes;
|
||||
return _.map(propsList, function(propNode) {
|
||||
return '{' + propNode.namespaceURI + '}' + propNode.localName;
|
||||
});
|
||||
}
|
||||
|
||||
function makePropBlock(props) {
|
||||
var s = '<d:prop>\n';
|
||||
|
||||
_.each(props, function(value, key) {
|
||||
s += '<' + key + '>' + value + '</' + key + '>\n';
|
||||
});
|
||||
|
||||
return s + '</d:prop>\n';
|
||||
}
|
||||
|
||||
function makeResponseBlock(href, props, failedProps) {
|
||||
var s = '<d:response>\n';
|
||||
s += '<d:href>' + href + '</d:href>\n';
|
||||
s += '<d:propstat>\n';
|
||||
s += makePropBlock(props);
|
||||
s += '<d:status>HTTP/1.1 200 OK</d:status>';
|
||||
s += '</d:propstat>\n';
|
||||
if (failedProps) {
|
||||
s += '<d:propstat>\n';
|
||||
_.each(failedProps, function(prop) {
|
||||
s += '<' + prop + '/>\n';
|
||||
});
|
||||
s += '<d:status>HTTP/1.1 404 Not Found</d:status>\n';
|
||||
s += '</d:propstat>\n';
|
||||
}
|
||||
return s + '</d:response>\n';
|
||||
}
|
||||
|
||||
describe('file listing', function() {
|
||||
|
||||
var folderContentsXml =
|
||||
'<?xml version="1.0" encoding="utf-8"?>' +
|
||||
'<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:oc="http://owncloud.org/ns">' +
|
||||
makeResponseBlock(
|
||||
'/owncloud/remote.php/webdav/path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/',
|
||||
{
|
||||
'd:getlastmodified': 'Fri, 10 Jul 2015 10:00:05 GMT',
|
||||
'd:getetag': '"56cfcabd79abb"',
|
||||
'd:resourcetype': '<d:collection/>',
|
||||
'oc:id': '00000011oc2d13a6a068',
|
||||
'oc:permissions': 'RDNVCK',
|
||||
'oc:size': 120
|
||||
},
|
||||
[
|
||||
'd:getcontenttype',
|
||||
'd:getcontentlength'
|
||||
]
|
||||
) +
|
||||
makeResponseBlock(
|
||||
'/owncloud/remote.php/webdav/path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/One.txt',
|
||||
{
|
||||
'd:getlastmodified': 'Fri, 10 Jul 2015 13:38:05 GMT',
|
||||
'd:getetag': '"559fcabd79a38"',
|
||||
'd:getcontenttype': 'text/plain',
|
||||
'd:getcontentlength': 250,
|
||||
'd:resourcetype': '',
|
||||
'oc:id': '00000051oc2d13a6a068',
|
||||
'oc:permissions': 'RDNVW'
|
||||
},
|
||||
[
|
||||
'oc:size',
|
||||
]
|
||||
) +
|
||||
makeResponseBlock(
|
||||
'/owncloud/remote.php/webdav/path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/sub',
|
||||
{
|
||||
'd:getlastmodified': 'Fri, 10 Jul 2015 14:00:00 GMT',
|
||||
'd:getetag': '"66cfcabd79abb"',
|
||||
'd:resourcetype': '<d:collection/>',
|
||||
'oc:id': '00000015oc2d13a6a068',
|
||||
'oc:permissions': 'RDNVCK',
|
||||
'oc:size': 100
|
||||
},
|
||||
[
|
||||
'd:getcontenttype',
|
||||
'd:getcontentlength'
|
||||
]
|
||||
) +
|
||||
'</d:multistatus>';
|
||||
|
||||
it('sends PROPFIND with explicit properties to get file list', function() {
|
||||
client.getFolderContents('path/to space/文件夹');
|
||||
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
expect(fakeServer.requests[0].method).toEqual('PROPFIND');
|
||||
expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9');
|
||||
expect(fakeServer.requests[0].requestHeaders.Depth).toEqual(1);
|
||||
|
||||
var props = getRequestedProperties(fakeServer.requests[0].requestBody);
|
||||
expect(props).toContain('{DAV:}getlastmodified');
|
||||
expect(props).toContain('{DAV:}getcontentlength');
|
||||
expect(props).toContain('{DAV:}getcontenttype');
|
||||
expect(props).toContain('{DAV:}getetag');
|
||||
expect(props).toContain('{DAV:}resourcetype');
|
||||
expect(props).toContain('{http://owncloud.org/ns}fileid');
|
||||
expect(props).toContain('{http://owncloud.org/ns}size');
|
||||
expect(props).toContain('{http://owncloud.org/ns}permissions');
|
||||
});
|
||||
it('sends PROPFIND to base url when empty path given', function() {
|
||||
client.getFolderContents('');
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
expect(fakeServer.requests[0].url).toEqual(baseUrl);
|
||||
});
|
||||
it('sends PROPFIND to base url when root path given', function() {
|
||||
client.getFolderContents('/');
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
expect(fakeServer.requests[0].url).toEqual(baseUrl);
|
||||
});
|
||||
it('parses the result list into a FileInfo array', function() {
|
||||
var promise = client.getFolderContents('path/to space/文件夹');
|
||||
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
|
||||
fakeServer.requests[0].respond(
|
||||
207,
|
||||
{'Content-Type': 'application/xml'},
|
||||
folderContentsXml
|
||||
);
|
||||
|
||||
promise.then(function(status, response) {
|
||||
expect(status).toEqual(207);
|
||||
expect(_.isArray(response)).toEqual(true);
|
||||
|
||||
expect(response.length).toEqual(2);
|
||||
|
||||
// file entry
|
||||
var info = response[0];
|
||||
expect(info instanceof OC.Files.FileInfo).toEqual(true);
|
||||
expect(info.id).toEqual(51);
|
||||
expect(info.path).toEqual('/path/to space/文件夹');
|
||||
expect(info.name).toEqual('One.txt');
|
||||
expect(info.permissions).toEqual(31);
|
||||
expect(info.size).toEqual(250);
|
||||
expect(info.mtime.getTime()).toEqual(1436535485000);
|
||||
expect(info.mimetype).toEqual('text/plain');
|
||||
expect(info.etag).toEqual('559fcabd79a38');
|
||||
|
||||
// sub entry
|
||||
info = response[1];
|
||||
expect(info instanceof OC.Files.FileInfo).toEqual(true);
|
||||
expect(info.id).toEqual(15);
|
||||
expect(info.path).toEqual('/path/to space/文件夹');
|
||||
expect(info.name).toEqual('sub');
|
||||
expect(info.permissions).toEqual(31);
|
||||
expect(info.size).toEqual(100);
|
||||
expect(info.mtime.getTime()).toEqual(1436536800000);
|
||||
expect(info.mimetype).toEqual('httpd/unix-directory');
|
||||
expect(info.etag).toEqual('66cfcabd79abb');
|
||||
});
|
||||
return promise.promise();
|
||||
});
|
||||
it('returns parent node in result if specified', function() {
|
||||
var promise = client.getFolderContents('path/to space/文件夹', {includeParent: true});
|
||||
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
|
||||
fakeServer.requests[0].respond(
|
||||
207,
|
||||
{'Content-Type': 'application/xml'},
|
||||
folderContentsXml
|
||||
);
|
||||
|
||||
promise.then(function(status, response) {
|
||||
expect(status).toEqual(207);
|
||||
expect(_.isArray(response)).toEqual(true);
|
||||
|
||||
expect(response.length).toEqual(3);
|
||||
|
||||
// root entry
|
||||
var info = response[0];
|
||||
expect(info instanceof OC.Files.FileInfo).toEqual(true);
|
||||
expect(info.id).toEqual(11);
|
||||
expect(info.path).toEqual('/path/to space');
|
||||
expect(info.name).toEqual('文件夹');
|
||||
expect(info.permissions).toEqual(31);
|
||||
expect(info.size).toEqual(120);
|
||||
expect(info.mtime.getTime()).toEqual(1436522405000);
|
||||
expect(info.mimetype).toEqual('httpd/unix-directory');
|
||||
expect(info.etag).toEqual('56cfcabd79abb');
|
||||
|
||||
// the two other entries follow
|
||||
expect(response[1].id).toEqual(51);
|
||||
expect(response[2].id).toEqual(15);
|
||||
});
|
||||
|
||||
return promise;
|
||||
});
|
||||
it('rejects promise when an error occurred', function() {
|
||||
var promise = client.getFolderContents('path/to space/文件夹', {includeParent: true});
|
||||
return respondAndCheckError(promise, 404);
|
||||
});
|
||||
it('throws exception if arguments are missing', function() {
|
||||
// TODO
|
||||
});
|
||||
});
|
||||
|
||||
describe('file info', function() {
|
||||
var responseXml =
|
||||
'<?xml version="1.0" encoding="utf-8"?>' +
|
||||
'<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:oc="http://owncloud.org/ns">' +
|
||||
makeResponseBlock(
|
||||
'/owncloud/remote.php/webdav/path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/',
|
||||
{
|
||||
'd:getlastmodified': 'Fri, 10 Jul 2015 10:00:05 GMT',
|
||||
'd:getetag': '"56cfcabd79abb"',
|
||||
'd:resourcetype': '<d:collection/>',
|
||||
'oc:id': '00000011oc2d13a6a068',
|
||||
'oc:permissions': 'RDNVCK',
|
||||
'oc:size': 120
|
||||
},
|
||||
[
|
||||
'd:getcontenttype',
|
||||
'd:getcontentlength'
|
||||
]
|
||||
) +
|
||||
'</d:multistatus>';
|
||||
|
||||
it('sends PROPFIND with zero depth to get single file info', function() {
|
||||
client.getFileInfo('path/to space/文件夹');
|
||||
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
expect(fakeServer.requests[0].method).toEqual('PROPFIND');
|
||||
expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9');
|
||||
expect(fakeServer.requests[0].requestHeaders.Depth).toEqual(0);
|
||||
|
||||
var props = getRequestedProperties(fakeServer.requests[0].requestBody);
|
||||
expect(props).toContain('{DAV:}getlastmodified');
|
||||
expect(props).toContain('{DAV:}getcontentlength');
|
||||
expect(props).toContain('{DAV:}getcontenttype');
|
||||
expect(props).toContain('{DAV:}getetag');
|
||||
expect(props).toContain('{DAV:}resourcetype');
|
||||
expect(props).toContain('{http://owncloud.org/ns}fileid');
|
||||
expect(props).toContain('{http://owncloud.org/ns}size');
|
||||
expect(props).toContain('{http://owncloud.org/ns}permissions');
|
||||
});
|
||||
it('parses the result into a FileInfo', function() {
|
||||
var promise = client.getFileInfo('path/to space/文件夹');
|
||||
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
|
||||
fakeServer.requests[0].respond(
|
||||
207,
|
||||
{'Content-Type': 'application/xml'},
|
||||
responseXml
|
||||
);
|
||||
|
||||
promise.then(function(status, response) {
|
||||
expect(status).toEqual(207);
|
||||
expect(_.isArray(response)).toEqual(false);
|
||||
|
||||
var info = response;
|
||||
expect(info instanceof OC.Files.FileInfo).toEqual(true);
|
||||
expect(info.id).toEqual(11);
|
||||
expect(info.path).toEqual('/path/to space');
|
||||
expect(info.name).toEqual('文件夹');
|
||||
expect(info.permissions).toEqual(31);
|
||||
expect(info.size).toEqual(120);
|
||||
expect(info.mtime.getTime()).toEqual(1436522405000);
|
||||
expect(info.mimetype).toEqual('httpd/unix-directory');
|
||||
expect(info.etag).toEqual('56cfcabd79abb');
|
||||
});
|
||||
|
||||
return promise;
|
||||
});
|
||||
it('properly parses entry inside root', function() {
|
||||
var responseXml =
|
||||
'<?xml version="1.0" encoding="utf-8"?>' +
|
||||
'<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:oc="http://owncloud.org/ns">' +
|
||||
makeResponseBlock(
|
||||
'/owncloud/remote.php/webdav/in%20root',
|
||||
{
|
||||
'd:getlastmodified': 'Fri, 10 Jul 2015 10:00:05 GMT',
|
||||
'd:getetag': '"56cfcabd79abb"',
|
||||
'd:resourcetype': '<d:collection/>',
|
||||
'oc:id': '00000011oc2d13a6a068',
|
||||
'oc:permissions': 'RDNVCK',
|
||||
'oc:size': 120
|
||||
},
|
||||
[
|
||||
'd:getcontenttype',
|
||||
'd:getcontentlength'
|
||||
]
|
||||
) +
|
||||
'</d:multistatus>';
|
||||
|
||||
var promise = client.getFileInfo('in root');
|
||||
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
|
||||
fakeServer.requests[0].respond(
|
||||
207,
|
||||
{'Content-Type': 'application/xml'},
|
||||
responseXml
|
||||
);
|
||||
|
||||
promise.then(function(status, response) {
|
||||
expect(status).toEqual(207);
|
||||
expect(_.isArray(response)).toEqual(false);
|
||||
|
||||
var info = response;
|
||||
expect(info instanceof OC.Files.FileInfo).toEqual(true);
|
||||
expect(info.id).toEqual(11);
|
||||
expect(info.path).toEqual('/');
|
||||
expect(info.name).toEqual('in root');
|
||||
expect(info.permissions).toEqual(31);
|
||||
expect(info.size).toEqual(120);
|
||||
expect(info.mtime.getTime()).toEqual(1436522405000);
|
||||
expect(info.mimetype).toEqual('httpd/unix-directory');
|
||||
expect(info.etag).toEqual('56cfcabd79abb');
|
||||
});
|
||||
|
||||
return promise;
|
||||
});
|
||||
it('rejects promise when an error occurred', function() {
|
||||
var promise = client.getFileInfo('path/to space/文件夹');
|
||||
return respondAndCheckError(promise, 404);
|
||||
});
|
||||
it('throws exception if arguments are missing', function() {
|
||||
// TODO
|
||||
});
|
||||
});
|
||||
|
||||
describe('permissions', function() {
|
||||
|
||||
function getFileInfoWithPermission(webdavPerm, isFile) {
|
||||
var props = {
|
||||
'd:getlastmodified': 'Fri, 10 Jul 2015 13:38:05 GMT',
|
||||
'd:getetag': '"559fcabd79a38"',
|
||||
'd:getcontentlength': 250,
|
||||
'oc:id': '00000051oc2d13a6a068',
|
||||
'oc:permissions': webdavPerm,
|
||||
};
|
||||
|
||||
if (isFile) {
|
||||
props['d:getcontenttype'] = 'text/plain';
|
||||
} else {
|
||||
props['d:resourcetype'] = '<d:collection/>';
|
||||
}
|
||||
|
||||
var responseXml =
|
||||
'<?xml version="1.0" encoding="utf-8"?>' +
|
||||
'<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:oc="http://owncloud.org/ns">' +
|
||||
makeResponseBlock(
|
||||
'/owncloud/remote.php/webdav/file.txt',
|
||||
props
|
||||
) +
|
||||
'</d:multistatus>';
|
||||
var promise = client.getFileInfo('file.txt');
|
||||
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
fakeServer.requests[0].respond(
|
||||
207,
|
||||
{'Content-Type': 'application/xml'},
|
||||
responseXml
|
||||
);
|
||||
|
||||
fakeServer.restore();
|
||||
fakeServer = sinon.fakeServer.create();
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
function testPermission(permission, isFile, expectedPermissions) {
|
||||
var promise = getFileInfoWithPermission(permission, isFile);
|
||||
promise.then(function(result) {
|
||||
expect(result.permissions).toEqual(expectedPermissions);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
function testMountType(permission, isFile, expectedMountType) {
|
||||
var promise = getFileInfoWithPermission(permission, isFile);
|
||||
promise.then(function(result) {
|
||||
expect(result.mountType).toEqual(expectedMountType);
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
it('properly parses file permissions', function() {
|
||||
// permission, isFile, expectedPermissions
|
||||
var testCases = [
|
||||
['', true, OC.PERMISSION_READ],
|
||||
['C', true, OC.PERMISSION_READ | OC.PERMISSION_CREATE],
|
||||
['K', true, OC.PERMISSION_READ | OC.PERMISSION_CREATE],
|
||||
['W', true, OC.PERMISSION_READ | OC.PERMISSION_CREATE | OC.PERMISSION_UPDATE],
|
||||
['D', true, OC.PERMISSION_READ | OC.PERMISSION_DELETE],
|
||||
['R', true, OC.PERMISSION_READ | OC.PERMISSION_SHARE],
|
||||
['CKWDR', true, OC.PERMISSION_ALL]
|
||||
];
|
||||
return Promise.all(
|
||||
_.map(testCases, function(testCase) {
|
||||
return testPermission.apply(testCase);
|
||||
})
|
||||
);
|
||||
});
|
||||
it('properly parses folder permissions', function() {
|
||||
var testCases = [
|
||||
['', false, OC.PERMISSION_READ],
|
||||
['C', false, OC.PERMISSION_READ | OC.PERMISSION_CREATE | OC.PERMISSION_UPDATE],
|
||||
['K', false, OC.PERMISSION_READ | OC.PERMISSION_CREATE | OC.PERMISSION_UPDATE],
|
||||
['W', false, OC.PERMISSION_READ | OC.PERMISSION_UPDATE],
|
||||
['D', false, OC.PERMISSION_READ | OC.PERMISSION_DELETE],
|
||||
['R', false, OC.PERMISSION_READ | OC.PERMISSION_SHARE],
|
||||
['CKWDR', false, OC.PERMISSION_ALL]
|
||||
];
|
||||
|
||||
return Promise.all(
|
||||
_.map(testCases, function(testCase) {
|
||||
return testPermission.apply(testCase);
|
||||
})
|
||||
);
|
||||
});
|
||||
it('properly parses mount types', function() {
|
||||
var testCases = [
|
||||
['CKWDR', false, null],
|
||||
['M', false, 'external'],
|
||||
['S', false, 'shared'],
|
||||
['SM', false, 'shared']
|
||||
];
|
||||
|
||||
return Promise.all(
|
||||
_.map(testCases, function(testCase) {
|
||||
return testMountType.apply(testCase);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('get file contents', function() {
|
||||
it('returns file contents', function() {
|
||||
var promise = client.getFileContents('path/to space/文件夹/One.txt');
|
||||
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
expect(fakeServer.requests[0].method).toEqual('GET');
|
||||
expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/One.txt');
|
||||
|
||||
fakeServer.requests[0].respond(
|
||||
200,
|
||||
{'Content-Type': 'text/plain'},
|
||||
'some contents'
|
||||
);
|
||||
|
||||
promise.then(function(status, response) {
|
||||
expect(status).toEqual(200);
|
||||
expect(response).toEqual('some contents');
|
||||
});
|
||||
|
||||
return promise;
|
||||
});
|
||||
it('rejects promise when an error occurred', function() {
|
||||
var promise = client.getFileContents('path/to space/文件夹/One.txt');
|
||||
return respondAndCheckError(promise, 409);
|
||||
});
|
||||
it('throws exception if arguments are missing', function() {
|
||||
// TODO
|
||||
});
|
||||
});
|
||||
|
||||
describe('put file contents', function() {
|
||||
it('sends PUT with file contents', function() {
|
||||
var promise = client.putFileContents(
|
||||
'path/to space/文件夹/One.txt',
|
||||
'some contents'
|
||||
);
|
||||
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
expect(fakeServer.requests[0].method).toEqual('PUT');
|
||||
expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/One.txt');
|
||||
expect(fakeServer.requests[0].requestBody).toEqual('some contents');
|
||||
expect(fakeServer.requests[0].requestHeaders['If-None-Match']).toEqual('*');
|
||||
expect(fakeServer.requests[0].requestHeaders['Content-Type']).toEqual('text/plain;charset=utf-8');
|
||||
|
||||
return respondAndCheckStatus(promise, 201);
|
||||
});
|
||||
it('sends PUT with file contents with headers matching options', function() {
|
||||
var promise = client.putFileContents(
|
||||
'path/to space/文件夹/One.txt',
|
||||
'some contents',
|
||||
{
|
||||
overwrite: false,
|
||||
contentType: 'text/markdown'
|
||||
}
|
||||
);
|
||||
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
expect(fakeServer.requests[0].method).toEqual('PUT');
|
||||
expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/One.txt');
|
||||
expect(fakeServer.requests[0].requestBody).toEqual('some contents');
|
||||
expect(fakeServer.requests[0].requestHeaders['If-None-Match']).not.toBeDefined();
|
||||
expect(fakeServer.requests[0].requestHeaders['Content-Type']).toEqual('text/markdown;charset=utf-8');
|
||||
|
||||
return respondAndCheckStatus(promise, 201);
|
||||
});
|
||||
it('rejects promise when an error occurred', function() {
|
||||
var promise = client.putFileContents(
|
||||
'path/to space/文件夹/One.txt',
|
||||
'some contents'
|
||||
);
|
||||
return respondAndCheckError(promise, 409);
|
||||
});
|
||||
it('throws exception if arguments are missing', function() {
|
||||
// TODO
|
||||
});
|
||||
});
|
||||
|
||||
describe('create directory', function() {
|
||||
it('sends MKCOL with specified path', function() {
|
||||
var promise = client.createDirectory('path/to space/文件夹/new dir');
|
||||
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
expect(fakeServer.requests[0].method).toEqual('MKCOL');
|
||||
expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9/new%20dir');
|
||||
|
||||
return respondAndCheckStatus(promise, 201);
|
||||
});
|
||||
it('rejects promise when an error occurred', function() {
|
||||
var promise = client.createDirectory('path/to space/文件夹/new dir');
|
||||
return respondAndCheckError(promise, 404);
|
||||
});
|
||||
it('throws exception if arguments are missing', function() {
|
||||
// TODO
|
||||
});
|
||||
});
|
||||
|
||||
describe('deletion', function() {
|
||||
it('sends DELETE with specified path', function() {
|
||||
var promise = client.remove('path/to space/文件夹');
|
||||
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
expect(fakeServer.requests[0].method).toEqual('DELETE');
|
||||
expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9');
|
||||
|
||||
return respondAndCheckStatus(promise, 201);
|
||||
});
|
||||
it('rejects promise when an error occurred', function() {
|
||||
var promise = client.remove('path/to space/文件夹');
|
||||
return respondAndCheckError(promise, 404);
|
||||
});
|
||||
it('throws exception if arguments are missing', function() {
|
||||
// TODO
|
||||
});
|
||||
});
|
||||
|
||||
describe('move', function() {
|
||||
it('sends MOVE with specified paths with fail on overwrite by default', function() {
|
||||
var promise = client.move(
|
||||
'path/to space/文件夹',
|
||||
'path/to space/anotherdir/文件夹'
|
||||
);
|
||||
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
expect(fakeServer.requests[0].method).toEqual('MOVE');
|
||||
expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9');
|
||||
expect(fakeServer.requests[0].requestHeaders.Destination)
|
||||
.toEqual(baseUrl + 'path/to%20space/anotherdir/%E6%96%87%E4%BB%B6%E5%A4%B9');
|
||||
expect(fakeServer.requests[0].requestHeaders.Overwrite)
|
||||
.toEqual('F');
|
||||
|
||||
return respondAndCheckStatus(promise, 201);
|
||||
});
|
||||
it('sends MOVE with silent overwrite mode when specified', function() {
|
||||
var promise = client.move(
|
||||
'path/to space/文件夹',
|
||||
'path/to space/anotherdir/文件夹',
|
||||
{allowOverwrite: true}
|
||||
);
|
||||
|
||||
expect(fakeServer.requests.length).toEqual(1);
|
||||
expect(fakeServer.requests[0].method).toEqual('MOVE');
|
||||
expect(fakeServer.requests[0].url).toEqual(baseUrl + 'path/to%20space/%E6%96%87%E4%BB%B6%E5%A4%B9');
|
||||
expect(fakeServer.requests[0].requestHeaders.Destination)
|
||||
.toEqual(baseUrl + 'path/to%20space/anotherdir/%E6%96%87%E4%BB%B6%E5%A4%B9');
|
||||
expect(fakeServer.requests[0].requestHeaders.Overwrite)
|
||||
.not.toBeDefined();
|
||||
|
||||
return respondAndCheckStatus(promise, 201);
|
||||
});
|
||||
it('rejects promise when an error occurred', function() {
|
||||
var promise = client.move(
|
||||
'path/to space/文件夹',
|
||||
'path/to space/anotherdir/文件夹',
|
||||
{allowOverwrite: true}
|
||||
);
|
||||
return respondAndCheckError(promise, 404);
|
||||
});
|
||||
it('throws exception if arguments are missing', function() {
|
||||
// TODO
|
||||
});
|
||||
});
|
||||
});
|
11
core/vendor/.gitignore
vendored
11
core/vendor/.gitignore
vendored
|
@ -122,3 +122,14 @@ bootstrap/js/*
|
|||
|
||||
# backbone
|
||||
backbone/backbone-min*
|
||||
|
||||
# davclient.js
|
||||
davclient.js/**
|
||||
!davclient.js/lib/*
|
||||
!davclient.js/LICENSE
|
||||
|
||||
# es6-promise
|
||||
es6-promise/**
|
||||
!es6-promise/LICENSE
|
||||
!es6-promise/dist/es6-promise.js
|
||||
|
||||
|
|
27
core/vendor/davclient.js/LICENSE
vendored
Normal file
27
core/vendor/davclient.js/LICENSE
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
Copyright (C) 2013-2014 fruux GmbH (https://fruux.com/)
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name Sabre nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
296
core/vendor/davclient.js/lib/client.js
vendored
Normal file
296
core/vendor/davclient.js/lib/client.js
vendored
Normal file
|
@ -0,0 +1,296 @@
|
|||
if (typeof dav == 'undefined') { dav = {}; };
|
||||
|
||||
dav.Client = function(options) {
|
||||
var i;
|
||||
for(i in options) {
|
||||
this[i] = options[i];
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
dav.Client.prototype = {
|
||||
|
||||
baseUrl : null,
|
||||
|
||||
userName : null,
|
||||
|
||||
password : null,
|
||||
|
||||
|
||||
xmlNamespaces : {
|
||||
'DAV:' : 'd'
|
||||
},
|
||||
|
||||
/**
|
||||
* Generates a propFind request.
|
||||
*
|
||||
* @param {string} url Url to do the propfind request on
|
||||
* @param {Array} properties List of properties to retrieve.
|
||||
* @return {Promise}
|
||||
*/
|
||||
propFind : function(url, properties, depth) {
|
||||
|
||||
if(typeof depth == "undefined") {
|
||||
depth = 0;
|
||||
}
|
||||
|
||||
var headers = {
|
||||
Depth : depth,
|
||||
'Content-Type' : 'application/xml; charset=utf-8'
|
||||
};
|
||||
|
||||
var body =
|
||||
'<?xml version="1.0"?>\n' +
|
||||
'<d:propfind ';
|
||||
var namespace;
|
||||
for (namespace in this.xmlNamespaces) {
|
||||
body += ' xmlns:' + this.xmlNamespaces[namespace] + '="' + namespace + '"';
|
||||
}
|
||||
body += '>\n' +
|
||||
' <d:prop>\n';
|
||||
|
||||
for(var ii in properties) {
|
||||
|
||||
var property = this.parseClarkNotation(properties[ii]);
|
||||
if (this.xmlNamespaces[property.namespace]) {
|
||||
body+=' <' + this.xmlNamespaces[property.namespace] + ':' + property.name + ' />\n';
|
||||
} else {
|
||||
body+=' <x:' + property.name + ' xmlns:x="' + property.namespace + '" />\n';
|
||||
}
|
||||
|
||||
}
|
||||
body+=' </d:prop>\n';
|
||||
body+='</d:propfind>';
|
||||
|
||||
return this.request('PROPFIND', url, headers, body).then(
|
||||
function(result) {
|
||||
|
||||
var resultBody = this.parseMultiStatus(result.body);
|
||||
if (depth===0) {
|
||||
return {
|
||||
status: result.status,
|
||||
body: resultBody[0],
|
||||
xhr: result.xhr
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
status: result.status,
|
||||
body: resultBody,
|
||||
xhr: result.xhr
|
||||
};
|
||||
}
|
||||
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Performs a HTTP request, and returns a Promise
|
||||
*
|
||||
* @param {string} method HTTP method
|
||||
* @param {string} url Relative or absolute url
|
||||
* @param {Object} headers HTTP headers as an object.
|
||||
* @param {string} body HTTP request body.
|
||||
* @return {Promise}
|
||||
*/
|
||||
request : function(method, url, headers, body) {
|
||||
|
||||
var xhr = this.xhrProvider();
|
||||
|
||||
if (this.userName) {
|
||||
headers['Authorization'] = 'Basic ' + btoa(this.userName + ':' + this.password);
|
||||
// xhr.open(method, this.resolveUrl(url), true, this.userName, this.password);
|
||||
}
|
||||
xhr.open(method, this.resolveUrl(url), true);
|
||||
var ii;
|
||||
for(ii in headers) {
|
||||
xhr.setRequestHeader(ii, headers[ii]);
|
||||
}
|
||||
xhr.send(body);
|
||||
|
||||
return new Promise(function(fulfill, reject) {
|
||||
|
||||
xhr.onreadystatechange = function() {
|
||||
|
||||
if (xhr.readyState !== 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
fulfill({
|
||||
body: xhr.response,
|
||||
status: xhr.status,
|
||||
xhr: xhr
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
xhr.ontimeout = function() {
|
||||
|
||||
reject(new Error('Timeout exceeded'));
|
||||
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an XMLHttpRequest object.
|
||||
*
|
||||
* This is in its own method, so it can be easily overridden.
|
||||
*
|
||||
* @return {XMLHttpRequest}
|
||||
*/
|
||||
xhrProvider : function() {
|
||||
|
||||
return new XMLHttpRequest();
|
||||
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Parses a multi-status response body.
|
||||
*
|
||||
* @param {string} xmlBody
|
||||
* @param {Array}
|
||||
*/
|
||||
parseMultiStatus : function(xmlBody) {
|
||||
|
||||
var parser = new DOMParser();
|
||||
var doc = parser.parseFromString(xmlBody, "application/xml");
|
||||
|
||||
var resolver = function(foo) {
|
||||
var ii;
|
||||
for(ii in this.xmlNamespaces) {
|
||||
if (this.xmlNamespaces[ii] === foo) {
|
||||
return ii;
|
||||
}
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
var responseIterator = doc.evaluate('/d:multistatus/d:response', doc, resolver, XPathResult.ANY_TYPE, null);
|
||||
|
||||
var result = [];
|
||||
var responseNode = responseIterator.iterateNext();
|
||||
|
||||
while(responseNode) {
|
||||
|
||||
var response = {
|
||||
href : null,
|
||||
propStat : []
|
||||
};
|
||||
|
||||
response.href = doc.evaluate('string(d:href)', responseNode, resolver, XPathResult.ANY_TYPE, null).stringValue;
|
||||
|
||||
var propStatIterator = doc.evaluate('d:propstat', responseNode, resolver, XPathResult.ANY_TYPE, null);
|
||||
var propStatNode = propStatIterator.iterateNext();
|
||||
|
||||
while(propStatNode) {
|
||||
|
||||
var propStat = {
|
||||
status : doc.evaluate('string(d:status)', propStatNode, resolver, XPathResult.ANY_TYPE, null).stringValue,
|
||||
properties : [],
|
||||
};
|
||||
|
||||
var propIterator = doc.evaluate('d:prop/*', propStatNode, resolver, XPathResult.ANY_TYPE, null);
|
||||
|
||||
var propNode = propIterator.iterateNext();
|
||||
while(propNode) {
|
||||
var content = propNode.textContent;
|
||||
if (propNode.childNodes && propNode.childNodes.length > 0 && propNode.childNodes[0].nodeType === 1) {
|
||||
content = propNode.childNodes;
|
||||
}
|
||||
|
||||
propStat.properties['{' + propNode.namespaceURI + '}' + propNode.localName] = content;
|
||||
propNode = propIterator.iterateNext();
|
||||
|
||||
}
|
||||
response.propStat.push(propStat);
|
||||
propStatNode = propStatIterator.iterateNext();
|
||||
|
||||
|
||||
}
|
||||
|
||||
result.push(response);
|
||||
responseNode = responseIterator.iterateNext();
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes a relative url, and maps it to an absolute url, using the baseUrl
|
||||
*
|
||||
* @param {string} url
|
||||
* @return {string}
|
||||
*/
|
||||
resolveUrl : function(url) {
|
||||
|
||||
// Note: this is rudamentary.. not sure yet if it handles every case.
|
||||
if (/^https?:\/\//i.test(url)) {
|
||||
// absolute
|
||||
return url;
|
||||
}
|
||||
|
||||
var baseParts = this.parseUrl(this.baseUrl);
|
||||
if (url.charAt('/')) {
|
||||
// Url starts with a slash
|
||||
return baseParts.root + url;
|
||||
}
|
||||
|
||||
// Url does not start with a slash, we need grab the base url right up until the last slash.
|
||||
var newUrl = baseParts.root + '/';
|
||||
if (baseParts.path.lastIndexOf('/')!==-1) {
|
||||
newUrl = newUrl = baseParts.path.subString(0, baseParts.path.lastIndexOf('/')) + '/';
|
||||
}
|
||||
newUrl+=url;
|
||||
return url;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Parses a url and returns its individual components.
|
||||
*
|
||||
* @param {String} url
|
||||
* @return {Object}
|
||||
*/
|
||||
parseUrl : function(url) {
|
||||
|
||||
var parts = url.match(/^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/);
|
||||
var result = {
|
||||
url : parts[0],
|
||||
scheme : parts[1],
|
||||
host : parts[3],
|
||||
port : parts[4],
|
||||
path : parts[5],
|
||||
query : parts[6],
|
||||
fragment : parts[7],
|
||||
};
|
||||
result.root =
|
||||
result.scheme + '://' +
|
||||
result.host +
|
||||
(result.port ? ':' + result.port : '');
|
||||
|
||||
return result;
|
||||
|
||||
},
|
||||
|
||||
parseClarkNotation : function(propertyName) {
|
||||
|
||||
var result = propertyName.match(/^{([^}]+)}(.*)$/);
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
name : result[2],
|
||||
namespace : result[1]
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
40
core/vendor/es6-promise/.bower.json
vendored
Normal file
40
core/vendor/es6-promise/.bower.json
vendored
Normal file
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"name": "es6-promise",
|
||||
"namespace": "Promise",
|
||||
"version": "2.3.0",
|
||||
"description": "A polyfill for ES6-style Promises, tracking rsvp",
|
||||
"authors": [
|
||||
"Stefan Penner <stefan.penner@gmail.com>"
|
||||
],
|
||||
"main": "dist/es6-promise.js",
|
||||
"keywords": [
|
||||
"promise"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/jakearchibald/ES6-Promises.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/jakearchibald/ES6-Promises/issues"
|
||||
},
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests",
|
||||
"vendor",
|
||||
"tasks"
|
||||
],
|
||||
"homepage": "https://github.com/jakearchibald/es6-promise",
|
||||
"_release": "2.3.0",
|
||||
"_resolution": {
|
||||
"type": "version",
|
||||
"tag": "2.3.0",
|
||||
"commit": "fcbab11a1a981eb2290bfff89017cb764335a2a5"
|
||||
},
|
||||
"_source": "https://github.com/jakearchibald/es6-promise.git",
|
||||
"_target": "~2.3.0",
|
||||
"_originalSource": "https://github.com/jakearchibald/es6-promise.git",
|
||||
"_direct": true
|
||||
}
|
11
core/vendor/es6-promise/.npmignore
vendored
Normal file
11
core/vendor/es6-promise/.npmignore
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
/node_modules/
|
||||
/tmp
|
||||
/tasks
|
||||
/test
|
||||
/vendor
|
||||
/.jshintrc
|
||||
/.npmignore
|
||||
/.travis.yml
|
||||
/Gruntfile.js
|
||||
/component.json
|
||||
/index.html
|
17
core/vendor/es6-promise/.release.json
vendored
Normal file
17
core/vendor/es6-promise/.release.json
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"non-interactive": true,
|
||||
"dry-run": false,
|
||||
"verbose": false,
|
||||
"force": false,
|
||||
"pkgFiles": ["package.json", "bower.json"],
|
||||
"increment": "patch",
|
||||
"commitMessage": "Release %s",
|
||||
"tagName": "%s",
|
||||
"tagAnnotation": "Release %s",
|
||||
"buildCommand": "npm run-script build-all",
|
||||
"distRepo": "git@github.com:components/rsvp.js.git",
|
||||
"distStageDir": "tmp/stage",
|
||||
"distBase": "dist",
|
||||
"distFiles": ["**/*", "../package.json", "../bower.json"],
|
||||
"publish": false
|
||||
}
|
11
core/vendor/es6-promise/.spmignore
vendored
Normal file
11
core/vendor/es6-promise/.spmignore
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
/node_modules/
|
||||
/tmp
|
||||
/tasks
|
||||
/test
|
||||
/vendor
|
||||
/.jshintrc
|
||||
/.npmignore
|
||||
/.travis.yml
|
||||
/Gruntfile.js
|
||||
/component.json
|
||||
/index.html
|
19
core/vendor/es6-promise/LICENSE
vendored
Normal file
19
core/vendor/es6-promise/LICENSE
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
972
core/vendor/es6-promise/dist/es6-promise.js
vendored
Normal file
972
core/vendor/es6-promise/dist/es6-promise.js
vendored
Normal file
|
@ -0,0 +1,972 @@
|
|||
/*!
|
||||
* @overview es6-promise - a tiny implementation of Promises/A+.
|
||||
* @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
|
||||
* @license Licensed under MIT license
|
||||
* See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE
|
||||
* @version 2.3.0
|
||||
*/
|
||||
|
||||
(function() {
|
||||
"use strict";
|
||||
function lib$es6$promise$utils$$objectOrFunction(x) {
|
||||
return typeof x === 'function' || (typeof x === 'object' && x !== null);
|
||||
}
|
||||
|
||||
function lib$es6$promise$utils$$isFunction(x) {
|
||||
return typeof x === 'function';
|
||||
}
|
||||
|
||||
function lib$es6$promise$utils$$isMaybeThenable(x) {
|
||||
return typeof x === 'object' && x !== null;
|
||||
}
|
||||
|
||||
var lib$es6$promise$utils$$_isArray;
|
||||
if (!Array.isArray) {
|
||||
lib$es6$promise$utils$$_isArray = function (x) {
|
||||
return Object.prototype.toString.call(x) === '[object Array]';
|
||||
};
|
||||
} else {
|
||||
lib$es6$promise$utils$$_isArray = Array.isArray;
|
||||
}
|
||||
|
||||
var lib$es6$promise$utils$$isArray = lib$es6$promise$utils$$_isArray;
|
||||
var lib$es6$promise$asap$$len = 0;
|
||||
var lib$es6$promise$asap$$toString = {}.toString;
|
||||
var lib$es6$promise$asap$$vertxNext;
|
||||
var lib$es6$promise$asap$$customSchedulerFn;
|
||||
|
||||
var lib$es6$promise$asap$$asap = function asap(callback, arg) {
|
||||
lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len] = callback;
|
||||
lib$es6$promise$asap$$queue[lib$es6$promise$asap$$len + 1] = arg;
|
||||
lib$es6$promise$asap$$len += 2;
|
||||
if (lib$es6$promise$asap$$len === 2) {
|
||||
// If len is 2, that means that we need to schedule an async flush.
|
||||
// If additional callbacks are queued before the queue is flushed, they
|
||||
// will be processed by this flush that we are scheduling.
|
||||
if (lib$es6$promise$asap$$customSchedulerFn) {
|
||||
lib$es6$promise$asap$$customSchedulerFn(lib$es6$promise$asap$$flush);
|
||||
} else {
|
||||
lib$es6$promise$asap$$scheduleFlush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function lib$es6$promise$asap$$setScheduler(scheduleFn) {
|
||||
lib$es6$promise$asap$$customSchedulerFn = scheduleFn;
|
||||
}
|
||||
|
||||
function lib$es6$promise$asap$$setAsap(asapFn) {
|
||||
lib$es6$promise$asap$$asap = asapFn;
|
||||
}
|
||||
|
||||
var lib$es6$promise$asap$$browserWindow = (typeof window !== 'undefined') ? window : undefined;
|
||||
var lib$es6$promise$asap$$browserGlobal = lib$es6$promise$asap$$browserWindow || {};
|
||||
var lib$es6$promise$asap$$BrowserMutationObserver = lib$es6$promise$asap$$browserGlobal.MutationObserver || lib$es6$promise$asap$$browserGlobal.WebKitMutationObserver;
|
||||
var lib$es6$promise$asap$$isNode = typeof process !== 'undefined' && {}.toString.call(process) === '[object process]';
|
||||
|
||||
// test for web worker but not in IE10
|
||||
var lib$es6$promise$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' &&
|
||||
typeof importScripts !== 'undefined' &&
|
||||
typeof MessageChannel !== 'undefined';
|
||||
|
||||
// node
|
||||
function lib$es6$promise$asap$$useNextTick() {
|
||||
var nextTick = process.nextTick;
|
||||
// node version 0.10.x displays a deprecation warning when nextTick is used recursively
|
||||
// setImmediate should be used instead instead
|
||||
var version = process.versions.node.match(/^(?:(\d+)\.)?(?:(\d+)\.)?(\*|\d+)$/);
|
||||
if (Array.isArray(version) && version[1] === '0' && version[2] === '10') {
|
||||
nextTick = setImmediate;
|
||||
}
|
||||
return function() {
|
||||
nextTick(lib$es6$promise$asap$$flush);
|
||||
};
|
||||
}
|
||||
|
||||
// vertx
|
||||
function lib$es6$promise$asap$$useVertxTimer() {
|
||||
return function() {
|
||||
lib$es6$promise$asap$$vertxNext(lib$es6$promise$asap$$flush);
|
||||
};
|
||||
}
|
||||
|
||||
function lib$es6$promise$asap$$useMutationObserver() {
|
||||
var iterations = 0;
|
||||
var observer = new lib$es6$promise$asap$$BrowserMutationObserver(lib$es6$promise$asap$$flush);
|
||||
var node = document.createTextNode('');
|
||||
observer.observe(node, { characterData: true });
|
||||
|
||||
return function() {
|
||||
node.data = (iterations = ++iterations % 2);
|
||||
};
|
||||
}
|
||||
|
||||
// web worker
|
||||
function lib$es6$promise$asap$$useMessageChannel() {
|
||||
var channel = new MessageChannel();
|
||||
channel.port1.onmessage = lib$es6$promise$asap$$flush;
|
||||
return function () {
|
||||
channel.port2.postMessage(0);
|
||||
};
|
||||
}
|
||||
|
||||
function lib$es6$promise$asap$$useSetTimeout() {
|
||||
return function() {
|
||||
setTimeout(lib$es6$promise$asap$$flush, 1);
|
||||
};
|
||||
}
|
||||
|
||||
var lib$es6$promise$asap$$queue = new Array(1000);
|
||||
function lib$es6$promise$asap$$flush() {
|
||||
for (var i = 0; i < lib$es6$promise$asap$$len; i+=2) {
|
||||
var callback = lib$es6$promise$asap$$queue[i];
|
||||
var arg = lib$es6$promise$asap$$queue[i+1];
|
||||
|
||||
callback(arg);
|
||||
|
||||
lib$es6$promise$asap$$queue[i] = undefined;
|
||||
lib$es6$promise$asap$$queue[i+1] = undefined;
|
||||
}
|
||||
|
||||
lib$es6$promise$asap$$len = 0;
|
||||
}
|
||||
|
||||
function lib$es6$promise$asap$$attemptVertex() {
|
||||
try {
|
||||
var r = require;
|
||||
var vertx = r('vertx');
|
||||
lib$es6$promise$asap$$vertxNext = vertx.runOnLoop || vertx.runOnContext;
|
||||
return lib$es6$promise$asap$$useVertxTimer();
|
||||
} catch(e) {
|
||||
return lib$es6$promise$asap$$useSetTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
var lib$es6$promise$asap$$scheduleFlush;
|
||||
// Decide what async method to use to triggering processing of queued callbacks:
|
||||
if (lib$es6$promise$asap$$isNode) {
|
||||
lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useNextTick();
|
||||
} else if (lib$es6$promise$asap$$BrowserMutationObserver) {
|
||||
lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMutationObserver();
|
||||
} else if (lib$es6$promise$asap$$isWorker) {
|
||||
lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useMessageChannel();
|
||||
} else if (lib$es6$promise$asap$$browserWindow === undefined && typeof require === 'function') {
|
||||
lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$attemptVertex();
|
||||
} else {
|
||||
lib$es6$promise$asap$$scheduleFlush = lib$es6$promise$asap$$useSetTimeout();
|
||||
}
|
||||
|
||||
function lib$es6$promise$$internal$$noop() {}
|
||||
|
||||
var lib$es6$promise$$internal$$PENDING = void 0;
|
||||
var lib$es6$promise$$internal$$FULFILLED = 1;
|
||||
var lib$es6$promise$$internal$$REJECTED = 2;
|
||||
|
||||
var lib$es6$promise$$internal$$GET_THEN_ERROR = new lib$es6$promise$$internal$$ErrorObject();
|
||||
|
||||
function lib$es6$promise$$internal$$selfFullfillment() {
|
||||
return new TypeError("You cannot resolve a promise with itself");
|
||||
}
|
||||
|
||||
function lib$es6$promise$$internal$$cannotReturnOwn() {
|
||||
return new TypeError('A promises callback cannot return that same promise.');
|
||||
}
|
||||
|
||||
function lib$es6$promise$$internal$$getThen(promise) {
|
||||
try {
|
||||
return promise.then;
|
||||
} catch(error) {
|
||||
lib$es6$promise$$internal$$GET_THEN_ERROR.error = error;
|
||||
return lib$es6$promise$$internal$$GET_THEN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
function lib$es6$promise$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) {
|
||||
try {
|
||||
then.call(value, fulfillmentHandler, rejectionHandler);
|
||||
} catch(e) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
function lib$es6$promise$$internal$$handleForeignThenable(promise, thenable, then) {
|
||||
lib$es6$promise$asap$$asap(function(promise) {
|
||||
var sealed = false;
|
||||
var error = lib$es6$promise$$internal$$tryThen(then, thenable, function(value) {
|
||||
if (sealed) { return; }
|
||||
sealed = true;
|
||||
if (thenable !== value) {
|
||||
lib$es6$promise$$internal$$resolve(promise, value);
|
||||
} else {
|
||||
lib$es6$promise$$internal$$fulfill(promise, value);
|
||||
}
|
||||
}, function(reason) {
|
||||
if (sealed) { return; }
|
||||
sealed = true;
|
||||
|
||||
lib$es6$promise$$internal$$reject(promise, reason);
|
||||
}, 'Settle: ' + (promise._label || ' unknown promise'));
|
||||
|
||||
if (!sealed && error) {
|
||||
sealed = true;
|
||||
lib$es6$promise$$internal$$reject(promise, error);
|
||||
}
|
||||
}, promise);
|
||||
}
|
||||
|
||||
function lib$es6$promise$$internal$$handleOwnThenable(promise, thenable) {
|
||||
if (thenable._state === lib$es6$promise$$internal$$FULFILLED) {
|
||||
lib$es6$promise$$internal$$fulfill(promise, thenable._result);
|
||||
} else if (thenable._state === lib$es6$promise$$internal$$REJECTED) {
|
||||
lib$es6$promise$$internal$$reject(promise, thenable._result);
|
||||
} else {
|
||||
lib$es6$promise$$internal$$subscribe(thenable, undefined, function(value) {
|
||||
lib$es6$promise$$internal$$resolve(promise, value);
|
||||
}, function(reason) {
|
||||
lib$es6$promise$$internal$$reject(promise, reason);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function lib$es6$promise$$internal$$handleMaybeThenable(promise, maybeThenable) {
|
||||
if (maybeThenable.constructor === promise.constructor) {
|
||||
lib$es6$promise$$internal$$handleOwnThenable(promise, maybeThenable);
|
||||
} else {
|
||||
var then = lib$es6$promise$$internal$$getThen(maybeThenable);
|
||||
|
||||
if (then === lib$es6$promise$$internal$$GET_THEN_ERROR) {
|
||||
lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$GET_THEN_ERROR.error);
|
||||
} else if (then === undefined) {
|
||||
lib$es6$promise$$internal$$fulfill(promise, maybeThenable);
|
||||
} else if (lib$es6$promise$utils$$isFunction(then)) {
|
||||
lib$es6$promise$$internal$$handleForeignThenable(promise, maybeThenable, then);
|
||||
} else {
|
||||
lib$es6$promise$$internal$$fulfill(promise, maybeThenable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function lib$es6$promise$$internal$$resolve(promise, value) {
|
||||
if (promise === value) {
|
||||
lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$selfFullfillment());
|
||||
} else if (lib$es6$promise$utils$$objectOrFunction(value)) {
|
||||
lib$es6$promise$$internal$$handleMaybeThenable(promise, value);
|
||||
} else {
|
||||
lib$es6$promise$$internal$$fulfill(promise, value);
|
||||
}
|
||||
}
|
||||
|
||||
function lib$es6$promise$$internal$$publishRejection(promise) {
|
||||
if (promise._onerror) {
|
||||
promise._onerror(promise._result);
|
||||
}
|
||||
|
||||
lib$es6$promise$$internal$$publish(promise);
|
||||
}
|
||||
|
||||
function lib$es6$promise$$internal$$fulfill(promise, value) {
|
||||
if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; }
|
||||
|
||||
promise._result = value;
|
||||
promise._state = lib$es6$promise$$internal$$FULFILLED;
|
||||
|
||||
if (promise._subscribers.length !== 0) {
|
||||
lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publish, promise);
|
||||
}
|
||||
}
|
||||
|
||||
function lib$es6$promise$$internal$$reject(promise, reason) {
|
||||
if (promise._state !== lib$es6$promise$$internal$$PENDING) { return; }
|
||||
promise._state = lib$es6$promise$$internal$$REJECTED;
|
||||
promise._result = reason;
|
||||
|
||||
lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publishRejection, promise);
|
||||
}
|
||||
|
||||
function lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection) {
|
||||
var subscribers = parent._subscribers;
|
||||
var length = subscribers.length;
|
||||
|
||||
parent._onerror = null;
|
||||
|
||||
subscribers[length] = child;
|
||||
subscribers[length + lib$es6$promise$$internal$$FULFILLED] = onFulfillment;
|
||||
subscribers[length + lib$es6$promise$$internal$$REJECTED] = onRejection;
|
||||
|
||||
if (length === 0 && parent._state) {
|
||||
lib$es6$promise$asap$$asap(lib$es6$promise$$internal$$publish, parent);
|
||||
}
|
||||
}
|
||||
|
||||
function lib$es6$promise$$internal$$publish(promise) {
|
||||
var subscribers = promise._subscribers;
|
||||
var settled = promise._state;
|
||||
|
||||
if (subscribers.length === 0) { return; }
|
||||
|
||||
var child, callback, detail = promise._result;
|
||||
|
||||
for (var i = 0; i < subscribers.length; i += 3) {
|
||||
child = subscribers[i];
|
||||
callback = subscribers[i + settled];
|
||||
|
||||
if (child) {
|
||||
lib$es6$promise$$internal$$invokeCallback(settled, child, callback, detail);
|
||||
} else {
|
||||
callback(detail);
|
||||
}
|
||||
}
|
||||
|
||||
promise._subscribers.length = 0;
|
||||
}
|
||||
|
||||
function lib$es6$promise$$internal$$ErrorObject() {
|
||||
this.error = null;
|
||||
}
|
||||
|
||||
var lib$es6$promise$$internal$$TRY_CATCH_ERROR = new lib$es6$promise$$internal$$ErrorObject();
|
||||
|
||||
function lib$es6$promise$$internal$$tryCatch(callback, detail) {
|
||||
try {
|
||||
return callback(detail);
|
||||
} catch(e) {
|
||||
lib$es6$promise$$internal$$TRY_CATCH_ERROR.error = e;
|
||||
return lib$es6$promise$$internal$$TRY_CATCH_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
function lib$es6$promise$$internal$$invokeCallback(settled, promise, callback, detail) {
|
||||
var hasCallback = lib$es6$promise$utils$$isFunction(callback),
|
||||
value, error, succeeded, failed;
|
||||
|
||||
if (hasCallback) {
|
||||
value = lib$es6$promise$$internal$$tryCatch(callback, detail);
|
||||
|
||||
if (value === lib$es6$promise$$internal$$TRY_CATCH_ERROR) {
|
||||
failed = true;
|
||||
error = value.error;
|
||||
value = null;
|
||||
} else {
|
||||
succeeded = true;
|
||||
}
|
||||
|
||||
if (promise === value) {
|
||||
lib$es6$promise$$internal$$reject(promise, lib$es6$promise$$internal$$cannotReturnOwn());
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
value = detail;
|
||||
succeeded = true;
|
||||
}
|
||||
|
||||
if (promise._state !== lib$es6$promise$$internal$$PENDING) {
|
||||
// noop
|
||||
} else if (hasCallback && succeeded) {
|
||||
lib$es6$promise$$internal$$resolve(promise, value);
|
||||
} else if (failed) {
|
||||
lib$es6$promise$$internal$$reject(promise, error);
|
||||
} else if (settled === lib$es6$promise$$internal$$FULFILLED) {
|
||||
lib$es6$promise$$internal$$fulfill(promise, value);
|
||||
} else if (settled === lib$es6$promise$$internal$$REJECTED) {
|
||||
lib$es6$promise$$internal$$reject(promise, value);
|
||||
}
|
||||
}
|
||||
|
||||
function lib$es6$promise$$internal$$initializePromise(promise, resolver) {
|
||||
try {
|
||||
resolver(function resolvePromise(value){
|
||||
lib$es6$promise$$internal$$resolve(promise, value);
|
||||
}, function rejectPromise(reason) {
|
||||
lib$es6$promise$$internal$$reject(promise, reason);
|
||||
});
|
||||
} catch(e) {
|
||||
lib$es6$promise$$internal$$reject(promise, e);
|
||||
}
|
||||
}
|
||||
|
||||
function lib$es6$promise$enumerator$$Enumerator(Constructor, input) {
|
||||
var enumerator = this;
|
||||
|
||||
enumerator._instanceConstructor = Constructor;
|
||||
enumerator.promise = new Constructor(lib$es6$promise$$internal$$noop);
|
||||
|
||||
if (enumerator._validateInput(input)) {
|
||||
enumerator._input = input;
|
||||
enumerator.length = input.length;
|
||||
enumerator._remaining = input.length;
|
||||
|
||||
enumerator._init();
|
||||
|
||||
if (enumerator.length === 0) {
|
||||
lib$es6$promise$$internal$$fulfill(enumerator.promise, enumerator._result);
|
||||
} else {
|
||||
enumerator.length = enumerator.length || 0;
|
||||
enumerator._enumerate();
|
||||
if (enumerator._remaining === 0) {
|
||||
lib$es6$promise$$internal$$fulfill(enumerator.promise, enumerator._result);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lib$es6$promise$$internal$$reject(enumerator.promise, enumerator._validationError());
|
||||
}
|
||||
}
|
||||
|
||||
lib$es6$promise$enumerator$$Enumerator.prototype._validateInput = function(input) {
|
||||
return lib$es6$promise$utils$$isArray(input);
|
||||
};
|
||||
|
||||
lib$es6$promise$enumerator$$Enumerator.prototype._validationError = function() {
|
||||
return new Error('Array Methods must be provided an Array');
|
||||
};
|
||||
|
||||
lib$es6$promise$enumerator$$Enumerator.prototype._init = function() {
|
||||
this._result = new Array(this.length);
|
||||
};
|
||||
|
||||
var lib$es6$promise$enumerator$$default = lib$es6$promise$enumerator$$Enumerator;
|
||||
|
||||
lib$es6$promise$enumerator$$Enumerator.prototype._enumerate = function() {
|
||||
var enumerator = this;
|
||||
|
||||
var length = enumerator.length;
|
||||
var promise = enumerator.promise;
|
||||
var input = enumerator._input;
|
||||
|
||||
for (var i = 0; promise._state === lib$es6$promise$$internal$$PENDING && i < length; i++) {
|
||||
enumerator._eachEntry(input[i], i);
|
||||
}
|
||||
};
|
||||
|
||||
lib$es6$promise$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) {
|
||||
var enumerator = this;
|
||||
var c = enumerator._instanceConstructor;
|
||||
|
||||
if (lib$es6$promise$utils$$isMaybeThenable(entry)) {
|
||||
if (entry.constructor === c && entry._state !== lib$es6$promise$$internal$$PENDING) {
|
||||
entry._onerror = null;
|
||||
enumerator._settledAt(entry._state, i, entry._result);
|
||||
} else {
|
||||
enumerator._willSettleAt(c.resolve(entry), i);
|
||||
}
|
||||
} else {
|
||||
enumerator._remaining--;
|
||||
enumerator._result[i] = entry;
|
||||
}
|
||||
};
|
||||
|
||||
lib$es6$promise$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) {
|
||||
var enumerator = this;
|
||||
var promise = enumerator.promise;
|
||||
|
||||
if (promise._state === lib$es6$promise$$internal$$PENDING) {
|
||||
enumerator._remaining--;
|
||||
|
||||
if (state === lib$es6$promise$$internal$$REJECTED) {
|
||||
lib$es6$promise$$internal$$reject(promise, value);
|
||||
} else {
|
||||
enumerator._result[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (enumerator._remaining === 0) {
|
||||
lib$es6$promise$$internal$$fulfill(promise, enumerator._result);
|
||||
}
|
||||
};
|
||||
|
||||
lib$es6$promise$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) {
|
||||
var enumerator = this;
|
||||
|
||||
lib$es6$promise$$internal$$subscribe(promise, undefined, function(value) {
|
||||
enumerator._settledAt(lib$es6$promise$$internal$$FULFILLED, i, value);
|
||||
}, function(reason) {
|
||||
enumerator._settledAt(lib$es6$promise$$internal$$REJECTED, i, reason);
|
||||
});
|
||||
};
|
||||
function lib$es6$promise$promise$all$$all(entries) {
|
||||
return new lib$es6$promise$enumerator$$default(this, entries).promise;
|
||||
}
|
||||
var lib$es6$promise$promise$all$$default = lib$es6$promise$promise$all$$all;
|
||||
function lib$es6$promise$promise$race$$race(entries) {
|
||||
/*jshint validthis:true */
|
||||
var Constructor = this;
|
||||
|
||||
var promise = new Constructor(lib$es6$promise$$internal$$noop);
|
||||
|
||||
if (!lib$es6$promise$utils$$isArray(entries)) {
|
||||
lib$es6$promise$$internal$$reject(promise, new TypeError('You must pass an array to race.'));
|
||||
return promise;
|
||||
}
|
||||
|
||||
var length = entries.length;
|
||||
|
||||
function onFulfillment(value) {
|
||||
lib$es6$promise$$internal$$resolve(promise, value);
|
||||
}
|
||||
|
||||
function onRejection(reason) {
|
||||
lib$es6$promise$$internal$$reject(promise, reason);
|
||||
}
|
||||
|
||||
for (var i = 0; promise._state === lib$es6$promise$$internal$$PENDING && i < length; i++) {
|
||||
lib$es6$promise$$internal$$subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection);
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
||||
var lib$es6$promise$promise$race$$default = lib$es6$promise$promise$race$$race;
|
||||
function lib$es6$promise$promise$resolve$$resolve(object) {
|
||||
/*jshint validthis:true */
|
||||
var Constructor = this;
|
||||
|
||||
if (object && typeof object === 'object' && object.constructor === Constructor) {
|
||||
return object;
|
||||
}
|
||||
|
||||
var promise = new Constructor(lib$es6$promise$$internal$$noop);
|
||||
lib$es6$promise$$internal$$resolve(promise, object);
|
||||
return promise;
|
||||
}
|
||||
var lib$es6$promise$promise$resolve$$default = lib$es6$promise$promise$resolve$$resolve;
|
||||
function lib$es6$promise$promise$reject$$reject(reason) {
|
||||
/*jshint validthis:true */
|
||||
var Constructor = this;
|
||||
var promise = new Constructor(lib$es6$promise$$internal$$noop);
|
||||
lib$es6$promise$$internal$$reject(promise, reason);
|
||||
return promise;
|
||||
}
|
||||
var lib$es6$promise$promise$reject$$default = lib$es6$promise$promise$reject$$reject;
|
||||
|
||||
var lib$es6$promise$promise$$counter = 0;
|
||||
|
||||
function lib$es6$promise$promise$$needsResolver() {
|
||||
throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
|
||||
}
|
||||
|
||||
function lib$es6$promise$promise$$needsNew() {
|
||||
throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
|
||||
}
|
||||
|
||||
var lib$es6$promise$promise$$default = lib$es6$promise$promise$$Promise;
|
||||
/**
|
||||
Promise objects represent the eventual result of an asynchronous operation. The
|
||||
primary way of interacting with a promise is through its `then` method, which
|
||||
registers callbacks to receive either a promise's eventual value or the reason
|
||||
why the promise cannot be fulfilled.
|
||||
|
||||
Terminology
|
||||
-----------
|
||||
|
||||
- `promise` is an object or function with a `then` method whose behavior conforms to this specification.
|
||||
- `thenable` is an object or function that defines a `then` method.
|
||||
- `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
|
||||
- `exception` is a value that is thrown using the throw statement.
|
||||
- `reason` is a value that indicates why a promise was rejected.
|
||||
- `settled` the final resting state of a promise, fulfilled or rejected.
|
||||
|
||||
A promise can be in one of three states: pending, fulfilled, or rejected.
|
||||
|
||||
Promises that are fulfilled have a fulfillment value and are in the fulfilled
|
||||
state. Promises that are rejected have a rejection reason and are in the
|
||||
rejected state. A fulfillment value is never a thenable.
|
||||
|
||||
Promises can also be said to *resolve* a value. If this value is also a
|
||||
promise, then the original promise's settled state will match the value's
|
||||
settled state. So a promise that *resolves* a promise that rejects will
|
||||
itself reject, and a promise that *resolves* a promise that fulfills will
|
||||
itself fulfill.
|
||||
|
||||
|
||||
Basic Usage:
|
||||
------------
|
||||
|
||||
```js
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
// on success
|
||||
resolve(value);
|
||||
|
||||
// on failure
|
||||
reject(reason);
|
||||
});
|
||||
|
||||
promise.then(function(value) {
|
||||
// on fulfillment
|
||||
}, function(reason) {
|
||||
// on rejection
|
||||
});
|
||||
```
|
||||
|
||||
Advanced Usage:
|
||||
---------------
|
||||
|
||||
Promises shine when abstracting away asynchronous interactions such as
|
||||
`XMLHttpRequest`s.
|
||||
|
||||
```js
|
||||
function getJSON(url) {
|
||||
return new Promise(function(resolve, reject){
|
||||
var xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open('GET', url);
|
||||
xhr.onreadystatechange = handler;
|
||||
xhr.responseType = 'json';
|
||||
xhr.setRequestHeader('Accept', 'application/json');
|
||||
xhr.send();
|
||||
|
||||
function handler() {
|
||||
if (this.readyState === this.DONE) {
|
||||
if (this.status === 200) {
|
||||
resolve(this.response);
|
||||
} else {
|
||||
reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
getJSON('/posts.json').then(function(json) {
|
||||
// on fulfillment
|
||||
}, function(reason) {
|
||||
// on rejection
|
||||
});
|
||||
```
|
||||
|
||||
Unlike callbacks, promises are great composable primitives.
|
||||
|
||||
```js
|
||||
Promise.all([
|
||||
getJSON('/posts'),
|
||||
getJSON('/comments')
|
||||
]).then(function(values){
|
||||
values[0] // => postsJSON
|
||||
values[1] // => commentsJSON
|
||||
|
||||
return values;
|
||||
});
|
||||
```
|
||||
|
||||
@class Promise
|
||||
@param {function} resolver
|
||||
Useful for tooling.
|
||||
@constructor
|
||||
*/
|
||||
function lib$es6$promise$promise$$Promise(resolver) {
|
||||
this._id = lib$es6$promise$promise$$counter++;
|
||||
this._state = undefined;
|
||||
this._result = undefined;
|
||||
this._subscribers = [];
|
||||
|
||||
if (lib$es6$promise$$internal$$noop !== resolver) {
|
||||
if (!lib$es6$promise$utils$$isFunction(resolver)) {
|
||||
lib$es6$promise$promise$$needsResolver();
|
||||
}
|
||||
|
||||
if (!(this instanceof lib$es6$promise$promise$$Promise)) {
|
||||
lib$es6$promise$promise$$needsNew();
|
||||
}
|
||||
|
||||
lib$es6$promise$$internal$$initializePromise(this, resolver);
|
||||
}
|
||||
}
|
||||
|
||||
lib$es6$promise$promise$$Promise.all = lib$es6$promise$promise$all$$default;
|
||||
lib$es6$promise$promise$$Promise.race = lib$es6$promise$promise$race$$default;
|
||||
lib$es6$promise$promise$$Promise.resolve = lib$es6$promise$promise$resolve$$default;
|
||||
lib$es6$promise$promise$$Promise.reject = lib$es6$promise$promise$reject$$default;
|
||||
lib$es6$promise$promise$$Promise._setScheduler = lib$es6$promise$asap$$setScheduler;
|
||||
lib$es6$promise$promise$$Promise._setAsap = lib$es6$promise$asap$$setAsap;
|
||||
lib$es6$promise$promise$$Promise._asap = lib$es6$promise$asap$$asap;
|
||||
|
||||
lib$es6$promise$promise$$Promise.prototype = {
|
||||
constructor: lib$es6$promise$promise$$Promise,
|
||||
|
||||
/**
|
||||
The primary way of interacting with a promise is through its `then` method,
|
||||
which registers callbacks to receive either a promise's eventual value or the
|
||||
reason why the promise cannot be fulfilled.
|
||||
|
||||
```js
|
||||
findUser().then(function(user){
|
||||
// user is available
|
||||
}, function(reason){
|
||||
// user is unavailable, and you are given the reason why
|
||||
});
|
||||
```
|
||||
|
||||
Chaining
|
||||
--------
|
||||
|
||||
The return value of `then` is itself a promise. This second, 'downstream'
|
||||
promise is resolved with the return value of the first promise's fulfillment
|
||||
or rejection handler, or rejected if the handler throws an exception.
|
||||
|
||||
```js
|
||||
findUser().then(function (user) {
|
||||
return user.name;
|
||||
}, function (reason) {
|
||||
return 'default name';
|
||||
}).then(function (userName) {
|
||||
// If `findUser` fulfilled, `userName` will be the user's name, otherwise it
|
||||
// will be `'default name'`
|
||||
});
|
||||
|
||||
findUser().then(function (user) {
|
||||
throw new Error('Found user, but still unhappy');
|
||||
}, function (reason) {
|
||||
throw new Error('`findUser` rejected and we're unhappy');
|
||||
}).then(function (value) {
|
||||
// never reached
|
||||
}, function (reason) {
|
||||
// if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
|
||||
// If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
|
||||
});
|
||||
```
|
||||
If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
|
||||
|
||||
```js
|
||||
findUser().then(function (user) {
|
||||
throw new PedagogicalException('Upstream error');
|
||||
}).then(function (value) {
|
||||
// never reached
|
||||
}).then(function (value) {
|
||||
// never reached
|
||||
}, function (reason) {
|
||||
// The `PedgagocialException` is propagated all the way down to here
|
||||
});
|
||||
```
|
||||
|
||||
Assimilation
|
||||
------------
|
||||
|
||||
Sometimes the value you want to propagate to a downstream promise can only be
|
||||
retrieved asynchronously. This can be achieved by returning a promise in the
|
||||
fulfillment or rejection handler. The downstream promise will then be pending
|
||||
until the returned promise is settled. This is called *assimilation*.
|
||||
|
||||
```js
|
||||
findUser().then(function (user) {
|
||||
return findCommentsByAuthor(user);
|
||||
}).then(function (comments) {
|
||||
// The user's comments are now available
|
||||
});
|
||||
```
|
||||
|
||||
If the assimliated promise rejects, then the downstream promise will also reject.
|
||||
|
||||
```js
|
||||
findUser().then(function (user) {
|
||||
return findCommentsByAuthor(user);
|
||||
}).then(function (comments) {
|
||||
// If `findCommentsByAuthor` fulfills, we'll have the value here
|
||||
}, function (reason) {
|
||||
// If `findCommentsByAuthor` rejects, we'll have the reason here
|
||||
});
|
||||
```
|
||||
|
||||
Simple Example
|
||||
--------------
|
||||
|
||||
Synchronous Example
|
||||
|
||||
```javascript
|
||||
var result;
|
||||
|
||||
try {
|
||||
result = findResult();
|
||||
// success
|
||||
} catch(reason) {
|
||||
// failure
|
||||
}
|
||||
```
|
||||
|
||||
Errback Example
|
||||
|
||||
```js
|
||||
findResult(function(result, err){
|
||||
if (err) {
|
||||
// failure
|
||||
} else {
|
||||
// success
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Promise Example;
|
||||
|
||||
```javascript
|
||||
findResult().then(function(result){
|
||||
// success
|
||||
}, function(reason){
|
||||
// failure
|
||||
});
|
||||
```
|
||||
|
||||
Advanced Example
|
||||
--------------
|
||||
|
||||
Synchronous Example
|
||||
|
||||
```javascript
|
||||
var author, books;
|
||||
|
||||
try {
|
||||
author = findAuthor();
|
||||
books = findBooksByAuthor(author);
|
||||
// success
|
||||
} catch(reason) {
|
||||
// failure
|
||||
}
|
||||
```
|
||||
|
||||
Errback Example
|
||||
|
||||
```js
|
||||
|
||||
function foundBooks(books) {
|
||||
|
||||
}
|
||||
|
||||
function failure(reason) {
|
||||
|
||||
}
|
||||
|
||||
findAuthor(function(author, err){
|
||||
if (err) {
|
||||
failure(err);
|
||||
// failure
|
||||
} else {
|
||||
try {
|
||||
findBoooksByAuthor(author, function(books, err) {
|
||||
if (err) {
|
||||
failure(err);
|
||||
} else {
|
||||
try {
|
||||
foundBooks(books);
|
||||
} catch(reason) {
|
||||
failure(reason);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch(error) {
|
||||
failure(err);
|
||||
}
|
||||
// success
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Promise Example;
|
||||
|
||||
```javascript
|
||||
findAuthor().
|
||||
then(findBooksByAuthor).
|
||||
then(function(books){
|
||||
// found books
|
||||
}).catch(function(reason){
|
||||
// something went wrong
|
||||
});
|
||||
```
|
||||
|
||||
@method then
|
||||
@param {Function} onFulfilled
|
||||
@param {Function} onRejected
|
||||
Useful for tooling.
|
||||
@return {Promise}
|
||||
*/
|
||||
then: function(onFulfillment, onRejection) {
|
||||
var parent = this;
|
||||
var state = parent._state;
|
||||
|
||||
if (state === lib$es6$promise$$internal$$FULFILLED && !onFulfillment || state === lib$es6$promise$$internal$$REJECTED && !onRejection) {
|
||||
return this;
|
||||
}
|
||||
|
||||
var child = new this.constructor(lib$es6$promise$$internal$$noop);
|
||||
var result = parent._result;
|
||||
|
||||
if (state) {
|
||||
var callback = arguments[state - 1];
|
||||
lib$es6$promise$asap$$asap(function(){
|
||||
lib$es6$promise$$internal$$invokeCallback(state, child, callback, result);
|
||||
});
|
||||
} else {
|
||||
lib$es6$promise$$internal$$subscribe(parent, child, onFulfillment, onRejection);
|
||||
}
|
||||
|
||||
return child;
|
||||
},
|
||||
|
||||
/**
|
||||
`catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
|
||||
as the catch block of a try/catch statement.
|
||||
|
||||
```js
|
||||
function findAuthor(){
|
||||
throw new Error('couldn't find that author');
|
||||
}
|
||||
|
||||
// synchronous
|
||||
try {
|
||||
findAuthor();
|
||||
} catch(reason) {
|
||||
// something went wrong
|
||||
}
|
||||
|
||||
// async with promises
|
||||
findAuthor().catch(function(reason){
|
||||
// something went wrong
|
||||
});
|
||||
```
|
||||
|
||||
@method catch
|
||||
@param {Function} onRejection
|
||||
Useful for tooling.
|
||||
@return {Promise}
|
||||
*/
|
||||
'catch': function(onRejection) {
|
||||
return this.then(null, onRejection);
|
||||
}
|
||||
};
|
||||
function lib$es6$promise$polyfill$$polyfill() {
|
||||
var local;
|
||||
|
||||
if (typeof global !== 'undefined') {
|
||||
local = global;
|
||||
} else if (typeof self !== 'undefined') {
|
||||
local = self;
|
||||
} else {
|
||||
try {
|
||||
local = Function('return this')();
|
||||
} catch (e) {
|
||||
throw new Error('polyfill failed because global object is unavailable in this environment');
|
||||
}
|
||||
}
|
||||
|
||||
var P = local.Promise;
|
||||
|
||||
if (P && Object.prototype.toString.call(P.resolve()) === '[object Promise]' && !P.cast) {
|
||||
return;
|
||||
}
|
||||
|
||||
local.Promise = lib$es6$promise$promise$$default;
|
||||
}
|
||||
var lib$es6$promise$polyfill$$default = lib$es6$promise$polyfill$$polyfill;
|
||||
|
||||
var lib$es6$promise$umd$$ES6Promise = {
|
||||
'Promise': lib$es6$promise$promise$$default,
|
||||
'polyfill': lib$es6$promise$polyfill$$default
|
||||
};
|
||||
|
||||
/* global define:true module:true window: true */
|
||||
if (typeof define === 'function' && define['amd']) {
|
||||
define(function() { return lib$es6$promise$umd$$ES6Promise; });
|
||||
} else if (typeof module !== 'undefined' && module['exports']) {
|
||||
module['exports'] = lib$es6$promise$umd$$ES6Promise;
|
||||
} else if (typeof this !== 'undefined') {
|
||||
this['ES6Promise'] = lib$es6$promise$umd$$ES6Promise;
|
||||
}
|
||||
|
||||
lib$es6$promise$polyfill$$default();
|
||||
}).call(this);
|
||||
|
|
@ -42,7 +42,7 @@ use OCP\Security\ISecureRandom;
|
|||
*/
|
||||
class Request implements \ArrayAccess, \Countable, IRequest {
|
||||
|
||||
const USER_AGENT_IE = '/MSIE/';
|
||||
const USER_AGENT_IE = '/(MSIE)|(Trident)/';
|
||||
const USER_AGENT_IE_8 = '/MSIE 8.0/';
|
||||
// Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent
|
||||
const USER_AGENT_ANDROID_MOBILE_CHROME = '#Android.*Chrome/[.0-9]*#';
|
||||
|
@ -674,6 +674,9 @@ class Request implements \ArrayAccess, \Countable, IRequest {
|
|||
* @return bool true if at least one of the given agent matches, false otherwise
|
||||
*/
|
||||
public function isUserAgent(array $agent) {
|
||||
if (!isset($this->server['HTTP_USER_AGENT'])) {
|
||||
return false;
|
||||
}
|
||||
foreach ($agent as $regex) {
|
||||
if (preg_match($regex, $this->server['HTTP_USER_AGENT'])) {
|
||||
return true;
|
||||
|
|
|
@ -143,7 +143,9 @@ class OC_Template extends \OC\Template\Base {
|
|||
OC_Util::addStyle("jquery.ocdialog");
|
||||
OC_Util::addScript("compatibility", null, true);
|
||||
OC_Util::addScript("placeholders", null, true);
|
||||
|
||||
OC_Util::addScript('files/fileinfo');
|
||||
OC_Util::addScript('files/client');
|
||||
|
||||
// Add the stuff we need always
|
||||
// following logic will import all vendor libraries that are
|
||||
// specified in core/js/core.json
|
||||
|
@ -158,7 +160,12 @@ class OC_Template extends \OC\Template\Base {
|
|||
} else {
|
||||
throw new \Exception('Cannot read core/js/core.json');
|
||||
}
|
||||
|
||||
|
||||
if (\OC::$server->getRequest()->isUserAgent([\OC\AppFramework\Http\Request::USER_AGENT_IE])) {
|
||||
// shim for the davclient.js library
|
||||
\OCP\Util::addScript('files/iedavclient');
|
||||
}
|
||||
|
||||
self::$initTemplateEngineFirstRun = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -164,15 +164,15 @@ module.exports = function(config) {
|
|||
// need to test the core app as well ?
|
||||
if (testCore) {
|
||||
// core tests
|
||||
files.push(corePath + 'tests/specs/*.js');
|
||||
files.push(corePath + 'tests/specs/**/*.js');
|
||||
}
|
||||
|
||||
function addApp(app) {
|
||||
// if only a string was specified, expand to structure
|
||||
if (typeof(app) === 'string') {
|
||||
app = {
|
||||
srcFiles: 'apps/' + app + '/js/*.js',
|
||||
testFiles: 'apps/' + app + '/tests/js/*.js'
|
||||
srcFiles: 'apps/' + app + '/js/**/*.js',
|
||||
testFiles: 'apps/' + app + '/tests/js/**/*.js'
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -693,19 +693,36 @@ class RequestTest extends \Test\TestCase {
|
|||
*/
|
||||
public function testUserAgent($testAgent, $userAgent, $matches) {
|
||||
$request = new Request(
|
||||
[
|
||||
'server' => [
|
||||
'HTTP_USER_AGENT' => $testAgent,
|
||||
]
|
||||
],
|
||||
$this->secureRandom,
|
||||
$this->config,
|
||||
$this->stream
|
||||
[
|
||||
'server' => [
|
||||
'HTTP_USER_AGENT' => $testAgent,
|
||||
]
|
||||
],
|
||||
$this->secureRandom,
|
||||
$this->config,
|
||||
$this->stream
|
||||
);
|
||||
|
||||
$this->assertSame($matches, $request->isUserAgent($userAgent));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider userAgentProvider
|
||||
* @param string $testAgent
|
||||
* @param array $userAgent
|
||||
* @param bool $matches
|
||||
*/
|
||||
public function testUndefinedUserAgent($testAgent, $userAgent, $matches) {
|
||||
$request = new Request(
|
||||
[],
|
||||
$this->secureRandom,
|
||||
$this->config,
|
||||
$this->stream
|
||||
);
|
||||
|
||||
$this->assertFalse($request->isUserAgent($userAgent));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue