src/vfs.js
/*
* OS.js - JavaScript Cloud/Web Desktop Platform
*
* Copyright (c) Anders Evenrud <andersevenrud@gmail.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:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer
* 2. 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
*
* 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.
*
* @author Anders Evenrud <andersevenrud@gmail.com>
* @license Simplified BSD License
*/
import {
transformReaddir,
transformArrayBuffer,
createFileIter
} from './utils/vfs';
/**
* VFS Method Options
*
* TODO: typedef
* @typedef {Object} VFSMethodOptions
*/
/**
* VFS Download Options
*
* @typedef {Object} VFSDownloadOptions
* @property {boolean} [readfile] Set to false to force backend fetch
*/
/**
* VFS File Object
*
* @typedef {Object} VFSFile
* @property {string} path
* @property {string} [filename]
* @property {boolean} [isDirectory]
* @property {boolean} [isFile]
* @property {string} [mime]
* @property {object} [stat]
*/
// Cache the capability of each mount point
let capabilityCache = {};
// Makes sure our input paths are object(s)
const pathToObject = path => ({
id: null,
...typeof path === 'string' ? {path} : path
});
// Handles directory listing result(s)
const handleDirectoryList = (path, options) => result =>
Promise.resolve(result.map(stat => createFileIter(stat)))
.then(result => transformReaddir(pathToObject(path), result, {
showHiddenFiles: options.showHiddenFiles !== false,
filter: options.filter
}));
/**
* Get vfs capabilities
*
* @param {string|VFSFile} path The path of a file
* @param {VFSMethodOptions} [options] Options
* @return {Promise<object[]>} An object of capabilities
*/
export const capabilities = (adapter, mount) => (path, options = {}) => {
const cached = capabilityCache[mount.name];
if (cached) {
return Promise.resolve(cached);
}
return adapter.capabilities(pathToObject(path), options, mount)
.then(res => {
capabilityCache[mount.name] = res;
return res;
});
};
/**
* Read a directory
*
* @param {string|VFSFile} path The path to read
* @param {VFSMethodOptions} [options] Options
* @return {Promise<object[]>} A list of files
*/
export const readdir = (adapter, mount) => (path, options = {}) =>
adapter.readdir(pathToObject(path), options, mount)
.then(handleDirectoryList(path, options));
/**
* Reads a file
*
* Available types are 'arraybuffer', 'blob', 'uri' and 'string'
*
* @param {string|VFSFile} path The path to read
* @param {string} [type=string] Return this content type
* @param {VFSMethodOptions} [options] Options
* @return {Promise<ArrayBuffer>}
*/
export const readfile = (adapter, mount) => (path, type = 'string', options = {}) =>
adapter.readfile(pathToObject(path), type, options, mount)
.then(response => transformArrayBuffer(response.body, response.mime, type));
/**
* Writes a file
* @param {string|VFSFile} path The path to write
* @param {ArrayBuffer|Blob|string} data The data
* @param {VFSMethodOptions} [options] Options
* @return {Promise<number>} File size
*/
export const writefile = (adapter, mount) => (path, data, options = {}) => {
const binary = (data instanceof ArrayBuffer || data instanceof Blob)
? data
: new Blob([data], {type: 'application/octet-stream'});
return adapter.writefile(pathToObject(path), binary, options, mount);
};
/**
* Copies a file or directory (move)
* @param {string|VFSFile} from The source (from)
* @param {string|VFSFile} to The destination (to)
* @param {VFSMethodOptions} [options] Options
* @return {Promise<boolean>}
*/
export const copy = (adapter, mount) => (from, to, options = {}) =>
adapter.copy(pathToObject(from), pathToObject(to), options, mount);
/**
* Renames a file or directory (move)
* @param {string|VFSFile} from The source (from)
* @param {string|VFSFile} to The destination (to)
* @param {VFSMethodOptions} [options] Options
* @return {Promise<boolean>}
*/
export const rename = (adapter, mount) => (from, to, options = {}) =>
adapter.rename(pathToObject(from), pathToObject(to), options, mount);
/**
* Alias of 'rename'
* @param {string|VFSFile} from The source (from)
* @param {string|VFSFile} to The destination (to)
* @param {VFSMethodOptions} [options] Options
* @return {Promise<boolean>}
*/
export const move = rename;
/**
* Creates a directory
* @param {string|VFSFile} path The path to new directory
* @param {VFSMethodOptions} [options] Options
* @return {Promise<boolean>}
*/
export const mkdir = (adapter, mount) => (path, options = {}) =>
adapter.mkdir(pathToObject(path), options, mount);
/**
* Removes a file or directory
* @param {string|VFSFile} path The path to remove
* @param {VFSMethodOptions} [options] Options
* @return {Promise<boolean>}
*/
export const unlink = (adapter, mount) => (path, options = {}) =>
adapter.unlink(pathToObject(path), options, mount);
/**
* Checks if path exists
* @param {string|VFSFile} path The path to check
* @param {VFSMethodOptions} [options] Options
* @return {Promise<boolean>}
*/
export const exists = (adapter, mount) => (path, options = {}) =>
adapter.exists(pathToObject(path), options, mount);
/**
* Gets the stats of the file or directory
* @param {string|VFSFile} path The path to check
* @param {VFSMethodOptions} [options] Options
* @return {Promise<object>}
*/
export const stat = (adapter, mount) => (path, options = {}) =>
adapter.stat(pathToObject(path), options, mount)
.then(stat => createFileIter(stat));
/**
* Gets an URL to a resource defined by file
* @param {string|VFSFile} path The file
* @param {VFSMethodOptions} [options] Options
* @return {Promise<string>}
*/
export const url = (adapter, mount) => (path, options = {}) =>
adapter.url(pathToObject(path), options, mount);
/**
* Initiates a native browser download of the file
* @param {string|VFSFile} path The file
* @param {VFSDownloadOptions} [options] Options
* @return {Promise<any>}
*/
export const download = (adapter, mount) => (path, options = {}) =>
typeof adapter.download === 'function' && options.readfile !== true
? adapter.download(pathToObject(path), options, mount)
: readfile(adapter)(path, 'blob')
.then(body => {
const filename = pathToObject(path).path.split('/').splice(-1)[0];
const url = window.URL.createObjectURL(body);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
setTimeout(() => {
window.URL.revokeObjectURL(url);
a.remove();
}, 1);
});
/**
* Searches for files and folders
* @param {string|VFSFile} root The root
* @param {string} pattern Search pattern
* @param {VFSMethodOptions} [options] Options
* @return {Promise<object[]>} A list of files
*/
export const search = (adapter, mount) => (root, pattern, options = {}) => {
if (mount.attributes && mount.attributes.searchable === false) {
return Promise.resolve([]);
}
return adapter.search(pathToObject(root), pattern, options, mount)
.then(handleDirectoryList(root, options));
};
/**
* Touches a file
* @param {string|VFSFile} path File path
* @return {Promise<boolean>}
*/
export const touch = (adapter, mount) => (path, options = {}) =>
adapter.touch(pathToObject(path), options, mount);