UNPKG

browserfs

Version:

A filesystem in your browser!

422 lines 16.4 kB
var __extends = (this && this.__extends) || (function () { var extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); exports.__esModule = true; var file_system_1 = require("../core/file_system"); var api_error_1 = require("../core/api_error"); var file_flag_1 = require("../core/file_flag"); var util_1 = require("../core/util"); var preload_file_1 = require("../generic/preload_file"); var xhr_1 = require("../generic/xhr"); var file_index_1 = require("../generic/file_index"); /** * Try to convert the given buffer into a string, and pass it to the callback. * Optimization that removes the needed try/catch into a helper function, as * this is an uncommon case. * @hidden */ function tryToString(buff, encoding, cb) { try { cb(null, buff.toString(encoding)); } catch (e) { cb(e); } } /** * A simple filesystem backed by XMLHttpRequests. You must create a directory listing using the * `make_xhrfs_index` tool provided by BrowserFS. * * If you install BrowserFS globally with `npm i -g browserfs`, you can generate a listing by * running `make_xhrfs_index` in your terminal in the directory you would like to index: * * ``` * make_xhrfs_index > index.json * ``` * * Listings objects look like the following: * * ```json * { * "home": { * "jvilk": { * "someFile.txt": null, * "someDir": { * // Empty directory * } * } * } * } * ``` * * *This example has the folder `/home/jvilk` with subfile `someFile.txt` and subfolder `someDir`.* */ var XmlHttpRequest = (function (_super) { __extends(XmlHttpRequest, _super); /** * **Deprecated. Please use XmlHttpRequest.Create() method instead to construct XmlHttpRequest objects.** * * Constructs the file system. You must provide the directory listing as a JSON object * produced by the `make_xhrfs_index` script. * * **DEPRECATED:** You may pass a URL to the file index to the constructor, which will fetch the file index * *synchronously* and may freeze up the web page. This behavior will be removed in the next major version * of BrowserFS. * * @param listingUrlOrObj index object or the path to the JSON file index generated by * `make_xhrfs_index`. * @param prefixUrl URL that is prepended to any file locations in the file index. e.g. if `prefixUrl = 'data/`, and the user wants to open the file `/foo.txt`, * the file system will fetch file `data/foo.txt`. The browser will access the file relative to the currrent webpage * URL. */ function XmlHttpRequest(listingUrlOrObj, prefixUrl, deprecateMsg) { if (prefixUrl === void 0) { prefixUrl = ''; } if (deprecateMsg === void 0) { deprecateMsg = true; } var _this = _super.call(this) || this; if (!listingUrlOrObj) { listingUrlOrObj = 'index.json'; } // prefix_url must end in a directory separator. if (prefixUrl.length > 0 && prefixUrl.charAt(prefixUrl.length - 1) !== '/') { prefixUrl = prefixUrl + '/'; } _this.prefixUrl = prefixUrl; var listing = null; if (typeof (listingUrlOrObj) === "string") { listing = _this._requestFileSync(listingUrlOrObj, 'json'); if (!listing) { throw new Error("Unable to find listing at URL: ${listingUrlOrObj}"); } } else { listing = listingUrlOrObj; } util_1.deprecationMessage(deprecateMsg, XmlHttpRequest.Name, { index: typeof (listingUrlOrObj) === "string" ? listingUrlOrObj : "file index as an object", baseUrl: prefixUrl }); _this._index = file_index_1.FileIndex.fromListing(listing); return _this; } /** * Construct an XmlHttpRequest file system backend with the given options. */ XmlHttpRequest.Create = function (opts, cb) { if (opts.index === undefined) { opts.index = "index.json"; } if (typeof (opts.index) === "string") { XmlHttpRequest.FromURL(opts.index, cb, opts.baseUrl, false); } else { cb(null, new XmlHttpRequest(opts.index, opts.baseUrl, false)); } }; XmlHttpRequest.isAvailable = function () { return typeof (XMLHttpRequest) !== "undefined" && XMLHttpRequest !== null; }; /** * **Deprecated. Please use XmlHttpRequest.Create() method instead to construct XmlHttpRequest objects.** * * Constructs an XmlHttpRequest object using the directory listing at the given URL. * Uses the base URL as the URL prefix for fetched files. * @param cb Called when the file system has been instantiated, or if an error occurs. */ XmlHttpRequest.FromURL = function (url, cb, baseUrl, deprecateMsg) { if (baseUrl === void 0) { baseUrl = url.slice(0, url.lastIndexOf('/') + 1); } if (deprecateMsg === void 0) { deprecateMsg = true; } if (deprecateMsg) { console.warn("[XmlHttpRequest] XmlHttpRequest.FromURL() is deprecated and will be removed in the next major release. Please use 'XmlHttpRequest.Create({ index: \"" + url + "\", baseUrl: \"" + baseUrl + "\" }, cb)' instead."); } xhr_1.asyncDownloadFile(url, "json", function (e, data) { if (e) { cb(e); } else { cb(null, new XmlHttpRequest(data, baseUrl, false)); } }); }; XmlHttpRequest.prototype.empty = function () { this._index.fileIterator(function (file) { file.fileData = null; }); }; XmlHttpRequest.prototype.getName = function () { return XmlHttpRequest.Name; }; XmlHttpRequest.prototype.diskSpace = function (path, cb) { // Read-only file system. We could calculate the total space, but that's not // important right now. cb(0, 0); }; XmlHttpRequest.prototype.isReadOnly = function () { return true; }; XmlHttpRequest.prototype.supportsLinks = function () { return false; }; XmlHttpRequest.prototype.supportsProps = function () { return false; }; XmlHttpRequest.prototype.supportsSynch = function () { return true; }; /** * Special XHR function: Preload the given file into the index. * @param [String] path * @param [BrowserFS.Buffer] buffer */ XmlHttpRequest.prototype.preloadFile = function (path, buffer) { var inode = this._index.getInode(path); if (file_index_1.isFileInode(inode)) { if (inode === null) { throw api_error_1.ApiError.ENOENT(path); } var stats = inode.getData(); stats.size = buffer.length; stats.fileData = buffer; } else { throw api_error_1.ApiError.EISDIR(path); } }; XmlHttpRequest.prototype.stat = function (path, isLstat, cb) { var inode = this._index.getInode(path); if (inode === null) { return cb(api_error_1.ApiError.ENOENT(path)); } var stats; if (file_index_1.isFileInode(inode)) { stats = inode.getData(); // At this point, a non-opened file will still have default stats from the listing. if (stats.size < 0) { this._requestFileSizeAsync(path, function (e, size) { if (e) { return cb(e); } stats.size = size; cb(null, stats.clone()); }); } else { cb(null, stats.clone()); } } else if (file_index_1.isDirInode(inode)) { stats = inode.getStats(); cb(null, stats); } else { cb(api_error_1.ApiError.FileError(api_error_1.ErrorCode.EINVAL, path)); } }; XmlHttpRequest.prototype.statSync = function (path, isLstat) { var inode = this._index.getInode(path); if (inode === null) { throw api_error_1.ApiError.ENOENT(path); } var stats; if (file_index_1.isFileInode(inode)) { stats = inode.getData(); // At this point, a non-opened file will still have default stats from the listing. if (stats.size < 0) { stats.size = this._requestFileSizeSync(path); } } else if (file_index_1.isDirInode(inode)) { stats = inode.getStats(); } else { throw api_error_1.ApiError.FileError(api_error_1.ErrorCode.EINVAL, path); } return stats; }; XmlHttpRequest.prototype.open = function (path, flags, mode, cb) { // INVARIANT: You can't write to files on this file system. if (flags.isWriteable()) { return cb(new api_error_1.ApiError(api_error_1.ErrorCode.EPERM, path)); } var self = this; // Check if the path exists, and is a file. var inode = this._index.getInode(path); if (inode === null) { return cb(api_error_1.ApiError.ENOENT(path)); } if (file_index_1.isFileInode(inode)) { var stats_1 = inode.getData(); switch (flags.pathExistsAction()) { case file_flag_1.ActionType.THROW_EXCEPTION: case file_flag_1.ActionType.TRUNCATE_FILE: return cb(api_error_1.ApiError.EEXIST(path)); case file_flag_1.ActionType.NOP: // Use existing file contents. // XXX: Uh, this maintains the previously-used flag. if (stats_1.fileData) { return cb(null, new preload_file_1.NoSyncFile(self, path, flags, stats_1.clone(), stats_1.fileData)); } // @todo be lazier about actually requesting the file this._requestFileAsync(path, 'buffer', function (err, buffer) { if (err) { return cb(err); } // we don't initially have file sizes stats_1.size = buffer.length; stats_1.fileData = buffer; return cb(null, new preload_file_1.NoSyncFile(self, path, flags, stats_1.clone(), buffer)); }); break; default: return cb(new api_error_1.ApiError(api_error_1.ErrorCode.EINVAL, 'Invalid FileMode object.')); } } else { return cb(api_error_1.ApiError.EISDIR(path)); } }; XmlHttpRequest.prototype.openSync = function (path, flags, mode) { // INVARIANT: You can't write to files on this file system. if (flags.isWriteable()) { throw new api_error_1.ApiError(api_error_1.ErrorCode.EPERM, path); } // Check if the path exists, and is a file. var inode = this._index.getInode(path); if (inode === null) { throw api_error_1.ApiError.ENOENT(path); } if (file_index_1.isFileInode(inode)) { var stats = inode.getData(); switch (flags.pathExistsAction()) { case file_flag_1.ActionType.THROW_EXCEPTION: case file_flag_1.ActionType.TRUNCATE_FILE: throw api_error_1.ApiError.EEXIST(path); case file_flag_1.ActionType.NOP: // Use existing file contents. // XXX: Uh, this maintains the previously-used flag. if (stats.fileData) { return new preload_file_1.NoSyncFile(this, path, flags, stats.clone(), stats.fileData); } // @todo be lazier about actually requesting the file var buffer = this._requestFileSync(path, 'buffer'); // we don't initially have file sizes stats.size = buffer.length; stats.fileData = buffer; return new preload_file_1.NoSyncFile(this, path, flags, stats.clone(), buffer); default: throw new api_error_1.ApiError(api_error_1.ErrorCode.EINVAL, 'Invalid FileMode object.'); } } else { throw api_error_1.ApiError.EISDIR(path); } }; XmlHttpRequest.prototype.readdir = function (path, cb) { try { cb(null, this.readdirSync(path)); } catch (e) { cb(e); } }; XmlHttpRequest.prototype.readdirSync = function (path) { // Check if it exists. var inode = this._index.getInode(path); if (inode === null) { throw api_error_1.ApiError.ENOENT(path); } else if (file_index_1.isDirInode(inode)) { return inode.getListing(); } else { throw api_error_1.ApiError.ENOTDIR(path); } }; /** * We have the entire file as a buffer; optimize readFile. */ XmlHttpRequest.prototype.readFile = function (fname, encoding, flag, cb) { // Wrap cb in file closing code. var oldCb = cb; // Get file. this.open(fname, flag, 0x1a4, function (err, fd) { if (err) { return cb(err); } cb = function (err, arg) { fd.close(function (err2) { if (!err) { err = err2; } return oldCb(err, arg); }); }; var fdCast = fd; var fdBuff = fdCast.getBuffer(); if (encoding === null) { cb(err, util_1.copyingSlice(fdBuff)); } else { tryToString(fdBuff, encoding, cb); } }); }; /** * Specially-optimized readfile. */ XmlHttpRequest.prototype.readFileSync = function (fname, encoding, flag) { // Get file. var fd = this.openSync(fname, flag, 0x1a4); try { var fdCast = fd; var fdBuff = fdCast.getBuffer(); if (encoding === null) { return util_1.copyingSlice(fdBuff); } return fdBuff.toString(encoding); } finally { fd.closeSync(); } }; XmlHttpRequest.prototype.getXhrPath = function (filePath) { if (filePath.charAt(0) === '/') { filePath = filePath.slice(1); } return this.prefixUrl + filePath; }; XmlHttpRequest.prototype._requestFileAsync = function (p, type, cb) { xhr_1.asyncDownloadFile(this.getXhrPath(p), type, cb); }; XmlHttpRequest.prototype._requestFileSync = function (p, type) { return xhr_1.syncDownloadFile(this.getXhrPath(p), type); }; /** * Only requests the HEAD content, for the file size. */ XmlHttpRequest.prototype._requestFileSizeAsync = function (path, cb) { xhr_1.getFileSizeAsync(this.getXhrPath(path), cb); }; XmlHttpRequest.prototype._requestFileSizeSync = function (path) { return xhr_1.getFileSizeSync(this.getXhrPath(path)); }; return XmlHttpRequest; }(file_system_1.BaseFileSystem)); XmlHttpRequest.Name = "XmlHttpRequest"; XmlHttpRequest.Options = { index: { type: ["string", "object"], optional: true, description: "URL to a file index as a JSON file or the file index object itself, generated with the make_xhrfs_index script. Defaults to `index.json`." }, baseUrl: { type: "string", optional: true, description: "Used as the URL prefix for fetched files. Default: Fetch files relative to the index." } }; exports["default"] = XmlHttpRequest; //# sourceMappingURL=XmlHttpRequest.js.map