Added OC.Files.Client Webdav-based files client
This commit is contained in:
parent
fb3d5c7856
commit
f120846e29
|
@ -11,7 +11,7 @@ NPM="$(which npm 2>/dev/null)"
|
||||||
PREFIX="build"
|
PREFIX="build"
|
||||||
OUTPUT_DIR="build/jsdocs"
|
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"
|
if test -z "$NPM"
|
||||||
then
|
then
|
||||||
|
|
|
@ -0,0 +1,673 @@
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.port) {
|
||||||
|
// workaround in case port is null or empty
|
||||||
|
options.port = undefined;
|
||||||
|
}
|
||||||
|
var url = '';
|
||||||
|
var port = '';
|
||||||
|
if (options.useHTTPS) {
|
||||||
|
url += 'https://';
|
||||||
|
if (options.port && options.port !== 443) {
|
||||||
|
port = ':' + options.port;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
url += 'http://';
|
||||||
|
if (options.port && options.port !== 80) {
|
||||||
|
port = ':' + options.port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 + port + 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'],
|
||||||
|
/**
|
||||||
|
* Compound file id, contains fileid + server instance id
|
||||||
|
*/
|
||||||
|
[Client.NS_OWNCLOUD, 'id'],
|
||||||
|
/**
|
||||||
|
* 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 nl.sara.webdav.Client
|
||||||
|
*/
|
||||||
|
_client: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 compound file id
|
||||||
|
*
|
||||||
|
* @param {string} compoundFileId compound file id as returned by the server
|
||||||
|
*
|
||||||
|
* @return {int} local file id, stripped of the instance id
|
||||||
|
*/
|
||||||
|
_parseFileId: function(compoundFileId) {
|
||||||
|
if (!compoundFileId || compoundFileId.length < 8) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return parseInt(compoundFileId.substr(0, 8), 10);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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: this._parseFileId(props['{' + Client.NS_OWNCLOUD + '}id']),
|
||||||
|
path: OC.dirname(path) || '/',
|
||||||
|
name: OC.basename(path),
|
||||||
|
mtime: new Date(props['{' + Client.NS_DAV + '}getlastmodified']),
|
||||||
|
_props: props
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
*
|
||||||
|
* @return {Promise} promise
|
||||||
|
*/
|
||||||
|
getFolderContents: function(path, options) {
|
||||||
|
if (!path) {
|
||||||
|
path = '';
|
||||||
|
}
|
||||||
|
var self = this;
|
||||||
|
var deferred = $.Deferred();
|
||||||
|
var promise = deferred.promise();
|
||||||
|
|
||||||
|
// TODO: headers
|
||||||
|
this._client.propFind(
|
||||||
|
this._buildUrl(path),
|
||||||
|
this._getPropfindProperties(),
|
||||||
|
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} [properties] list of webdav properties to
|
||||||
|
* retrieve
|
||||||
|
*
|
||||||
|
* @return {Promise} promise
|
||||||
|
*/
|
||||||
|
getFileInfo: function(path) {
|
||||||
|
if (!path) {
|
||||||
|
path = '';
|
||||||
|
}
|
||||||
|
var self = this;
|
||||||
|
var deferred = $.Deferred();
|
||||||
|
var promise = deferred.promise();
|
||||||
|
|
||||||
|
// TODO: headers
|
||||||
|
this._client.propFind(
|
||||||
|
this._buildUrl(path),
|
||||||
|
this._getPropfindProperties(),
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
* 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) {
|
||||||
|
if (!_.isUndefined(data.id)) {
|
||||||
|
this.id = parseInt(data.id, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: normalize path
|
||||||
|
this.path = data.path || '';
|
||||||
|
this.name = data.name;
|
||||||
|
|
||||||
|
this.mtime = data.mtime;
|
||||||
|
this.etag = data.etag;
|
||||||
|
this.permissions = data.permissions;
|
||||||
|
this.size = data.size;
|
||||||
|
this.mimetype = data.mimetype || 'application/octet-stream';
|
||||||
|
this.mountType = data.mountType;
|
||||||
|
this.icon = data.icon;
|
||||||
|
this._props = data._props;
|
||||||
|
|
||||||
|
if (data.type) {
|
||||||
|
this.type = data.type;
|
||||||
|
} else if (this.mimetype === 'httpd/unix-directory') {
|
||||||
|
this.type = 'dir';
|
||||||
|
} else {
|
||||||
|
this.type = 'file';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.mimetype) {
|
||||||
|
if (this.type === 'dir') {
|
||||||
|
this.mimetype = 'httpd/unix-directory';
|
||||||
|
} else {
|
||||||
|
this.mimetype = 'application/octet-stream';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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: 'file',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
|
|
@ -1428,7 +1428,6 @@ function initCore() {
|
||||||
$('body').delegate('#app-content', 'apprendered appresized', adjustControlsWidth);
|
$('body').delegate('#app-content', 'apprendered appresized', adjustControlsWidth);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(initCore);
|
$(document).ready(initCore);
|
||||||
|
|
|
@ -86,6 +86,7 @@ window.firstDay = 0;
|
||||||
// setup dummy webroots
|
// setup dummy webroots
|
||||||
/* jshint camelcase: false */
|
/* jshint camelcase: false */
|
||||||
window.oc_debug = true;
|
window.oc_debug = true;
|
||||||
|
// FIXME: oc_webroot is supposed to be only the path!!!
|
||||||
window.oc_webroot = location.href + '/';
|
window.oc_webroot = location.href + '/';
|
||||||
window.oc_appswebroots = {
|
window.oc_appswebroots = {
|
||||||
"files": window.oc_webroot + '/apps/files/'
|
"files": window.oc_webroot + '/apps/files/'
|
||||||
|
|
|
@ -0,0 +1,712 @@
|
||||||
|
/**
|
||||||
|
* 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:999/owncloud/remote.php/webdav/';
|
||||||
|
|
||||||
|
client = new Client({
|
||||||
|
host: 'testhost',
|
||||||
|
port: 999,
|
||||||
|
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}id');
|
||||||
|
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}id');
|
||||||
|
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
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -164,15 +164,15 @@ module.exports = function(config) {
|
||||||
// need to test the core app as well ?
|
// need to test the core app as well ?
|
||||||
if (testCore) {
|
if (testCore) {
|
||||||
// core tests
|
// core tests
|
||||||
files.push(corePath + 'tests/specs/*.js');
|
files.push(corePath + 'tests/specs/**/*.js');
|
||||||
}
|
}
|
||||||
|
|
||||||
function addApp(app) {
|
function addApp(app) {
|
||||||
// if only a string was specified, expand to structure
|
// if only a string was specified, expand to structure
|
||||||
if (typeof(app) === 'string') {
|
if (typeof(app) === 'string') {
|
||||||
app = {
|
app = {
|
||||||
srcFiles: 'apps/' + app + '/js/*.js',
|
srcFiles: 'apps/' + app + '/js/**/*.js',
|
||||||
testFiles: 'apps/' + app + '/tests/js/*.js'
|
testFiles: 'apps/' + app + '/tests/js/**/*.js'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue