Merge pull request #16902 from owncloud/jsocclient

Web UI uses Webdav instead of ajax/* calls
This commit is contained in:
Thomas Müller 2015-11-23 09:38:01 +01:00
commit 79bbda994b
58 changed files with 4136 additions and 1622 deletions

View file

@ -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.

View file

@ -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)));
}

View file

@ -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)) )));
}

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

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

View file

@ -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']]);
}

View file

@ -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;
}

View file

@ -8,7 +8,7 @@
<shipped>true</shipped>
<standalone/>
<default_enable/>
<version>1.3.0</version>
<version>1.4.0</version>
<types>
<filesystem/>
</types>

View file

@ -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',

View file

@ -71,7 +71,8 @@
folderDropOptions: folderDropOptions,
fileActions: fileActions,
allowLegacyActions: true,
scrollTo: urlParams.scrollto
scrollTo: urlParams.scrollto,
filesClient: OC.Files.getClient()
}
);
this.files.initialize();

View file

@ -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;

View file

@ -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({

View file

@ -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;
})();

View file

@ -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;
}

View file

@ -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) {

View file

@ -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;
}
}

View file

@ -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'];

View file

@ -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">

View file

@ -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']);
}
}

View file

@ -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');
});

View file

@ -19,6 +19,8 @@
*
*/
/* global FileList */
describe('OC.Upload tests', function() {
var $dummyUploader;
var testFile;

View file

@ -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();

View file

@ -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

View file

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

View file

@ -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;

View file

@ -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));

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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,

View file

@ -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;

View file

@ -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');
});

View file

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

View file

@ -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;
},
});

View file

@ -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"
}
}

View file

@ -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

View file

@ -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
View 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
View 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);

View 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);

View file

@ -1428,7 +1428,6 @@ function initCore() {
$('body').delegate('#app-content', 'apprendered appresized', adjustControlsWidth);
}
}
$(document).ready(initCore);

View file

@ -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
};

View file

@ -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/'

View 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
});
});
});

View file

@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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.

View 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);

View file

@ -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;

View file

@ -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;
}

View file

@ -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'
};
}

View file

@ -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
*/