566 lines
20 KiB
JavaScript
566 lines
20 KiB
JavaScript
"use strict";
|
|
var __extends = (this && this.__extends) || (function () {
|
|
var extendStatics = function (d, b) {
|
|
extendStatics = Object.setPrototypeOf ||
|
|
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
|
return extendStatics(d, b);
|
|
};
|
|
return function (d, b) {
|
|
if (typeof b !== "function" && b !== null)
|
|
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
|
extendStatics(d, b);
|
|
function __() { this.constructor = d; }
|
|
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
};
|
|
})();
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.DropboxFile = void 0;
|
|
var preload_file_1 = require("../generic/preload_file");
|
|
var file_system_1 = require("../core/file_system");
|
|
var node_fs_stats_1 = require("../core/node_fs_stats");
|
|
var api_error_1 = require("../core/api_error");
|
|
var util_1 = require("../core/util");
|
|
var dropbox_bridge_1 = require("dropbox_bridge");
|
|
var setImmediate_1 = require("../generic/setImmediate");
|
|
var path_1 = require("path");
|
|
/**
|
|
* Dropbox paths do not begin with a /, they just begin with a folder at the root node.
|
|
* Here, we strip the `/`.
|
|
* @param p An absolute path
|
|
*/
|
|
function FixPath(p) {
|
|
if (p === '/') {
|
|
return '';
|
|
}
|
|
else {
|
|
return p;
|
|
}
|
|
}
|
|
/**
|
|
* HACK: Dropbox errors are FUBAR'd sometimes.
|
|
* @url https://github.com/dropbox/dropbox-sdk-js/issues/146
|
|
* @param e
|
|
*/
|
|
function ExtractTheFuckingError(e) {
|
|
var obj = e.error;
|
|
if (obj['.tag']) {
|
|
// Everything is OK.
|
|
return obj;
|
|
}
|
|
else if (obj['error']) {
|
|
// Terrible nested object bug.
|
|
var obj2 = obj.error;
|
|
if (obj2['.tag']) {
|
|
return obj2;
|
|
}
|
|
else if (obj2['reason'] && obj2['reason']['.tag']) {
|
|
return obj2.reason;
|
|
}
|
|
else {
|
|
return obj2;
|
|
}
|
|
}
|
|
else if (typeof (obj) === 'string') {
|
|
// Might be a fucking JSON object error.
|
|
try {
|
|
var obj2 = JSON.parse(obj);
|
|
if (obj2['error'] && obj2['error']['reason'] && obj2['error']['reason']['.tag']) {
|
|
return obj2.error.reason;
|
|
}
|
|
}
|
|
catch (e) {
|
|
// Nope. Give up.
|
|
}
|
|
}
|
|
return obj;
|
|
}
|
|
/**
|
|
* Returns a user-facing error message given an error.
|
|
*
|
|
* HACK: Dropbox error messages sometimes lack a `user_message` field.
|
|
* Sometimes, they are even strings. Ugh.
|
|
* @url https://github.com/dropbox/dropbox-sdk-js/issues/146
|
|
* @url https://github.com/dropbox/dropbox-sdk-js/issues/145
|
|
* @url https://github.com/dropbox/dropbox-sdk-js/issues/144
|
|
* @param err An error.
|
|
*/
|
|
function GetErrorMessage(err) {
|
|
if (err['user_message']) {
|
|
return err.user_message.text;
|
|
}
|
|
else if (err['error_summary']) {
|
|
return err.error_summary;
|
|
}
|
|
else if (typeof (err.error) === "string") {
|
|
return err.error;
|
|
}
|
|
else if (typeof (err.error) === "object") {
|
|
// DROPBOX BUG: Sometimes, error is a nested error.
|
|
return GetErrorMessage(err.error);
|
|
}
|
|
else {
|
|
throw new Error("Dropbox's servers gave us a garbage error message: ".concat(JSON.stringify(err)));
|
|
}
|
|
}
|
|
function LookupErrorToError(err, p, msg) {
|
|
switch (err['.tag']) {
|
|
case 'malformed_path':
|
|
return new api_error_1.ApiError(api_error_1.ErrorCode.EBADF, msg, p);
|
|
case 'not_found':
|
|
return api_error_1.ApiError.ENOENT(p);
|
|
case 'not_file':
|
|
return api_error_1.ApiError.EISDIR(p);
|
|
case 'not_folder':
|
|
return api_error_1.ApiError.ENOTDIR(p);
|
|
case 'restricted_content':
|
|
return api_error_1.ApiError.EPERM(p);
|
|
case 'other':
|
|
default:
|
|
return new api_error_1.ApiError(api_error_1.ErrorCode.EIO, msg, p);
|
|
}
|
|
}
|
|
function WriteErrorToError(err, p, msg) {
|
|
switch (err['.tag']) {
|
|
case 'malformed_path':
|
|
case 'disallowed_name':
|
|
return new api_error_1.ApiError(api_error_1.ErrorCode.EBADF, msg, p);
|
|
case 'conflict':
|
|
case 'no_write_permission':
|
|
case 'team_folder':
|
|
return api_error_1.ApiError.EPERM(p);
|
|
case 'insufficient_space':
|
|
return new api_error_1.ApiError(api_error_1.ErrorCode.ENOSPC, msg);
|
|
case 'other':
|
|
default:
|
|
return new api_error_1.ApiError(api_error_1.ErrorCode.EIO, msg, p);
|
|
}
|
|
}
|
|
function FilesDeleteWrapped(client, p, cb) {
|
|
var arg = {
|
|
path: FixPath(p)
|
|
};
|
|
client.filesDeleteV2(arg)
|
|
.then(function () {
|
|
cb();
|
|
}).catch(function (e) {
|
|
var err = ExtractTheFuckingError(e);
|
|
switch (err['.tag']) {
|
|
case 'path_lookup':
|
|
cb(LookupErrorToError(err.path_lookup, p, GetErrorMessage(e)));
|
|
break;
|
|
case 'path_write':
|
|
cb(WriteErrorToError(err.path_write, p, GetErrorMessage(e)));
|
|
break;
|
|
case 'too_many_write_operations':
|
|
setTimeout(function () { return FilesDeleteWrapped(client, p, cb); }, 500 + (300 * (Math.random())));
|
|
break;
|
|
case 'other':
|
|
default:
|
|
cb(new api_error_1.ApiError(api_error_1.ErrorCode.EIO, GetErrorMessage(e), p));
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
var DropboxFile = /** @class */ (function (_super) {
|
|
__extends(DropboxFile, _super);
|
|
function DropboxFile(_fs, _path, _flag, _stat, contents) {
|
|
return _super.call(this, _fs, _path, _flag, _stat, contents) || this;
|
|
}
|
|
DropboxFile.prototype.sync = function (cb) {
|
|
this._fs._syncFile(this.getPath(), this.getBuffer(), cb);
|
|
};
|
|
DropboxFile.prototype.close = function (cb) {
|
|
this.sync(cb);
|
|
};
|
|
return DropboxFile;
|
|
}(preload_file_1.default));
|
|
exports.DropboxFile = DropboxFile;
|
|
/**
|
|
* A read/write file system backed by Dropbox cloud storage.
|
|
*
|
|
* Uses the Dropbox V2 API, and the 2.x JS SDK.
|
|
*/
|
|
var DropboxFileSystem = /** @class */ (function (_super) {
|
|
__extends(DropboxFileSystem, _super);
|
|
function DropboxFileSystem(client) {
|
|
var _this = _super.call(this) || this;
|
|
_this._client = client;
|
|
return _this;
|
|
}
|
|
/**
|
|
* Creates a new DropboxFileSystem instance with the given options.
|
|
* Must be given an *authenticated* Dropbox client from 2.x JS SDK.
|
|
*/
|
|
DropboxFileSystem.Create = function (opts, cb) {
|
|
cb(null, new DropboxFileSystem(opts.client));
|
|
};
|
|
DropboxFileSystem.isAvailable = function () {
|
|
// Checks if the Dropbox library is loaded.
|
|
return typeof dropbox_bridge_1.Dropbox !== 'undefined';
|
|
};
|
|
DropboxFileSystem.prototype.getName = function () {
|
|
return DropboxFileSystem.Name;
|
|
};
|
|
DropboxFileSystem.prototype.isReadOnly = function () {
|
|
return false;
|
|
};
|
|
// Dropbox doesn't support symlinks, properties, or synchronous calls
|
|
// TODO: does it???
|
|
DropboxFileSystem.prototype.supportsSymlinks = function () {
|
|
return false;
|
|
};
|
|
DropboxFileSystem.prototype.supportsProps = function () {
|
|
return false;
|
|
};
|
|
DropboxFileSystem.prototype.supportsSynch = function () {
|
|
return false;
|
|
};
|
|
/**
|
|
* Deletes *everything* in the file system. Mainly intended for unit testing!
|
|
* @param mainCb Called when operation completes.
|
|
*/
|
|
DropboxFileSystem.prototype.empty = function (mainCb) {
|
|
var _this = this;
|
|
this.readdir('/', function (e, paths) {
|
|
if (paths) {
|
|
var next_1 = function (e) {
|
|
if (paths.length === 0) {
|
|
mainCb();
|
|
}
|
|
else {
|
|
FilesDeleteWrapped(_this._client, paths.shift(), next_1);
|
|
}
|
|
};
|
|
next_1();
|
|
}
|
|
else {
|
|
mainCb(e);
|
|
}
|
|
});
|
|
};
|
|
DropboxFileSystem.prototype.rename = function (oldPath, newPath, cb) {
|
|
var _this = this;
|
|
// Dropbox doesn't let you rename things over existing things, but POSIX does.
|
|
// So, we need to see if newPath exists...
|
|
this.stat(newPath, false, function (e, stats) {
|
|
var rename = function () {
|
|
var relocationArg = {
|
|
from_path: FixPath(oldPath),
|
|
to_path: FixPath(newPath)
|
|
};
|
|
_this._client.filesMoveV2(relocationArg)
|
|
.then(function () { return cb(); })
|
|
.catch(function (e) {
|
|
var err = ExtractTheFuckingError(e);
|
|
switch (err['.tag']) {
|
|
case 'from_lookup':
|
|
cb(LookupErrorToError(err.from_lookup, oldPath, GetErrorMessage(e)));
|
|
break;
|
|
case 'from_write':
|
|
cb(WriteErrorToError(err.from_write, oldPath, GetErrorMessage(e)));
|
|
break;
|
|
case 'to':
|
|
cb(WriteErrorToError(err.to, newPath, GetErrorMessage(e)));
|
|
break;
|
|
case 'cant_copy_shared_folder':
|
|
case 'cant_nest_shared_folder':
|
|
cb(new api_error_1.ApiError(api_error_1.ErrorCode.EPERM, GetErrorMessage(e), oldPath));
|
|
break;
|
|
case 'cant_move_folder_into_itself':
|
|
case 'duplicated_or_nested_paths':
|
|
cb(new api_error_1.ApiError(api_error_1.ErrorCode.EBADF, GetErrorMessage(e), oldPath));
|
|
break;
|
|
case 'too_many_files':
|
|
cb(new api_error_1.ApiError(api_error_1.ErrorCode.ENOSPC, GetErrorMessage(e), oldPath));
|
|
break;
|
|
case 'other':
|
|
default:
|
|
cb(new api_error_1.ApiError(api_error_1.ErrorCode.EIO, GetErrorMessage(e), oldPath));
|
|
break;
|
|
}
|
|
});
|
|
};
|
|
if (e) {
|
|
// Doesn't exist. Proceed!
|
|
rename();
|
|
}
|
|
else if (oldPath === newPath) {
|
|
// NOP if the path exists. Error if it doesn't exist.
|
|
if (e) {
|
|
cb(api_error_1.ApiError.ENOENT(newPath));
|
|
}
|
|
else {
|
|
cb();
|
|
}
|
|
}
|
|
else if (stats && stats.isDirectory()) {
|
|
// Exists, is a directory. Cannot rename over an existing directory.
|
|
cb(api_error_1.ApiError.EISDIR(newPath));
|
|
}
|
|
else {
|
|
// Exists, is a file, and differs from oldPath. Delete and rename.
|
|
_this.unlink(newPath, function (e) {
|
|
if (e) {
|
|
cb(e);
|
|
}
|
|
else {
|
|
rename();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
};
|
|
DropboxFileSystem.prototype.stat = function (path, isLstat, cb) {
|
|
if (path === '/') {
|
|
// Dropbox doesn't support querying the root directory.
|
|
(0, setImmediate_1.default)(function () {
|
|
cb(null, new node_fs_stats_1.default(node_fs_stats_1.FileType.DIRECTORY, 4096));
|
|
});
|
|
return;
|
|
}
|
|
var arg = {
|
|
path: FixPath(path)
|
|
};
|
|
this._client.filesGetMetadata(arg).then(function (ref) {
|
|
switch (ref['.tag']) {
|
|
case 'file':
|
|
var fileMetadata = ref;
|
|
// TODO: Parse time fields.
|
|
cb(null, new node_fs_stats_1.default(node_fs_stats_1.FileType.FILE, fileMetadata.size));
|
|
break;
|
|
case 'folder':
|
|
cb(null, new node_fs_stats_1.default(node_fs_stats_1.FileType.DIRECTORY, 4096));
|
|
break;
|
|
case 'deleted':
|
|
cb(api_error_1.ApiError.ENOENT(path));
|
|
break;
|
|
default:
|
|
// Unknown.
|
|
break;
|
|
}
|
|
}).catch(function (e) {
|
|
var err = ExtractTheFuckingError(e);
|
|
switch (err['.tag']) {
|
|
case 'path':
|
|
cb(LookupErrorToError(err.path, path, GetErrorMessage(e)));
|
|
break;
|
|
default:
|
|
cb(new api_error_1.ApiError(api_error_1.ErrorCode.EIO, GetErrorMessage(e), path));
|
|
break;
|
|
}
|
|
});
|
|
};
|
|
DropboxFileSystem.prototype.openFile = function (path, flags, cb) {
|
|
var _this = this;
|
|
var downloadArg = {
|
|
path: FixPath(path)
|
|
};
|
|
this._client.filesDownload(downloadArg).then(function (res) {
|
|
var b = res.fileBlob;
|
|
var fr = new FileReader();
|
|
fr.onload = function () {
|
|
var ab = fr.result;
|
|
cb(null, new DropboxFile(_this, path, flags, new node_fs_stats_1.default(node_fs_stats_1.FileType.FILE, ab.byteLength), (0, util_1.arrayBuffer2Buffer)(ab)));
|
|
};
|
|
fr.readAsArrayBuffer(b);
|
|
}).catch(function (e) {
|
|
var err = ExtractTheFuckingError(e);
|
|
switch (err['.tag']) {
|
|
case 'path':
|
|
var dpError = err;
|
|
cb(LookupErrorToError(dpError.path, path, GetErrorMessage(e)));
|
|
break;
|
|
case 'other':
|
|
default:
|
|
cb(new api_error_1.ApiError(api_error_1.ErrorCode.EIO, GetErrorMessage(e), path));
|
|
break;
|
|
}
|
|
});
|
|
};
|
|
DropboxFileSystem.prototype.createFile = function (p, flags, mode, cb) {
|
|
var _this = this;
|
|
var fileData = Buffer.alloc(0);
|
|
var blob = new Blob([(0, util_1.buffer2ArrayBuffer)(fileData)], { type: "octet/stream" });
|
|
var commitInfo = {
|
|
contents: blob,
|
|
path: FixPath(p)
|
|
};
|
|
this._client.filesUpload(commitInfo).then(function (metadata) {
|
|
cb(null, new DropboxFile(_this, p, flags, new node_fs_stats_1.default(node_fs_stats_1.FileType.FILE, 0), fileData));
|
|
}).catch(function (e) {
|
|
var err = ExtractTheFuckingError(e);
|
|
// HACK: Casting to 'any' since tag can be 'too_many_write_operations'.
|
|
switch (err['.tag']) {
|
|
case 'path':
|
|
var upError = err;
|
|
cb(WriteErrorToError(upError.path.reason, p, GetErrorMessage(e)));
|
|
break;
|
|
case 'too_many_write_operations':
|
|
// Retry in (500, 800) ms.
|
|
setTimeout(function () { return _this.createFile(p, flags, mode, cb); }, 500 + (300 * (Math.random())));
|
|
break;
|
|
case 'other':
|
|
default:
|
|
cb(new api_error_1.ApiError(api_error_1.ErrorCode.EIO, GetErrorMessage(e), p));
|
|
break;
|
|
}
|
|
});
|
|
};
|
|
/**
|
|
* Delete a file
|
|
*/
|
|
DropboxFileSystem.prototype.unlink = function (path, cb) {
|
|
var _this = this;
|
|
// Must be a file. Check first.
|
|
this.stat(path, false, function (e, stat) {
|
|
if (stat) {
|
|
if (stat.isDirectory()) {
|
|
cb(api_error_1.ApiError.EISDIR(path));
|
|
}
|
|
else {
|
|
FilesDeleteWrapped(_this._client, path, cb);
|
|
}
|
|
}
|
|
else {
|
|
cb(e);
|
|
}
|
|
});
|
|
};
|
|
/**
|
|
* Delete a directory
|
|
*/
|
|
DropboxFileSystem.prototype.rmdir = function (path, cb) {
|
|
var _this = this;
|
|
this.readdir(path, function (e, paths) {
|
|
if (paths) {
|
|
if (paths.length > 0) {
|
|
cb(api_error_1.ApiError.ENOTEMPTY(path));
|
|
}
|
|
else {
|
|
FilesDeleteWrapped(_this._client, path, cb);
|
|
}
|
|
}
|
|
else {
|
|
cb(e);
|
|
}
|
|
});
|
|
};
|
|
/**
|
|
* Create a directory
|
|
*/
|
|
DropboxFileSystem.prototype.mkdir = function (p, mode, cb) {
|
|
var _this = this;
|
|
// Dropbox's create_folder is recursive. Check if parent exists.
|
|
var parent = (0, path_1.dirname)(p);
|
|
this.stat(parent, false, function (e, stats) {
|
|
if (e) {
|
|
cb(e);
|
|
}
|
|
else if (stats && !stats.isDirectory()) {
|
|
cb(api_error_1.ApiError.ENOTDIR(parent));
|
|
}
|
|
else {
|
|
var arg = {
|
|
path: FixPath(p)
|
|
};
|
|
_this._client.filesCreateFolderV2(arg).then(function () { return cb(); }).catch(function (e) {
|
|
var err = ExtractTheFuckingError(e);
|
|
if (err['.tag'] === "too_many_write_operations") {
|
|
// Retry in a bit.
|
|
setTimeout(function () { return _this.mkdir(p, mode, cb); }, 500 + (300 * (Math.random())));
|
|
}
|
|
else {
|
|
cb(WriteErrorToError(ExtractTheFuckingError(e).path, p, GetErrorMessage(e)));
|
|
}
|
|
});
|
|
}
|
|
});
|
|
};
|
|
/**
|
|
* Get the names of the files in a directory
|
|
*/
|
|
DropboxFileSystem.prototype.readdir = function (path, cb) {
|
|
var _this = this;
|
|
var arg = {
|
|
path: FixPath(path)
|
|
};
|
|
this._client.filesListFolder(arg).then(function (res) {
|
|
ContinueReadingDir(_this._client, path, res, [], cb);
|
|
}).catch(function (e) {
|
|
ProcessListFolderError(e, path, cb);
|
|
});
|
|
};
|
|
/**
|
|
* (Internal) Syncs file to Dropbox.
|
|
*/
|
|
DropboxFileSystem.prototype._syncFile = function (p, d, cb) {
|
|
var _this = this;
|
|
var blob = new Blob([(0, util_1.buffer2ArrayBuffer)(d)], { type: "octet/stream" });
|
|
var arg = {
|
|
contents: blob,
|
|
path: FixPath(p),
|
|
mode: {
|
|
'.tag': 'overwrite'
|
|
}
|
|
};
|
|
this._client.filesUpload(arg).then(function () {
|
|
cb();
|
|
}).catch(function (e) {
|
|
var err = ExtractTheFuckingError(e);
|
|
switch (err['.tag']) {
|
|
case 'path':
|
|
var upError = err;
|
|
cb(WriteErrorToError(upError.path.reason, p, GetErrorMessage(e)));
|
|
break;
|
|
case 'too_many_write_operations':
|
|
setTimeout(function () { return _this._syncFile(p, d, cb); }, 500 + (300 * (Math.random())));
|
|
break;
|
|
case 'other':
|
|
default:
|
|
cb(new api_error_1.ApiError(api_error_1.ErrorCode.EIO, GetErrorMessage(e), p));
|
|
break;
|
|
}
|
|
});
|
|
};
|
|
DropboxFileSystem.Name = "DropboxV2";
|
|
DropboxFileSystem.Options = {
|
|
client: {
|
|
type: "object",
|
|
description: "An *authenticated* Dropbox client. Must be from the 2.5.x JS SDK."
|
|
}
|
|
};
|
|
return DropboxFileSystem;
|
|
}(file_system_1.BaseFileSystem));
|
|
exports.default = DropboxFileSystem;
|
|
function ProcessListFolderError(e, path, cb) {
|
|
var err = ExtractTheFuckingError(e);
|
|
switch (err['.tag']) {
|
|
case 'path':
|
|
var pathError = err;
|
|
cb(LookupErrorToError(pathError.path, path, GetErrorMessage(e)));
|
|
break;
|
|
case 'other':
|
|
default:
|
|
cb(new api_error_1.ApiError(api_error_1.ErrorCode.EIO, GetErrorMessage(e), path));
|
|
break;
|
|
}
|
|
}
|
|
function ContinueReadingDir(client, path, res, previousEntries, cb) {
|
|
var newEntries = res.entries.map(function (e) { return e.path_display; }).filter(Boolean);
|
|
var entries = previousEntries.concat(newEntries);
|
|
if (!res.has_more) {
|
|
cb(null, entries);
|
|
}
|
|
else {
|
|
var arg = {
|
|
cursor: res.cursor
|
|
};
|
|
client.filesListFolderContinue(arg).then(function (res) {
|
|
ContinueReadingDir(client, path, res, entries, cb);
|
|
}).catch(function (e) {
|
|
ProcessListFolderError(e, path, cb);
|
|
});
|
|
}
|
|
}
|
|
//# sourceMappingURL=Dropbox.js.map
|