"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var CommonTypes_1 = require("./CommonTypes"); var stream_1 = require("stream"); var ContextualFileSystem_1 = require("./ContextualFileSystem"); var StandardMethods_1 = require("./StandardMethods"); var LockScope_1 = require("../../../resource/v2/lock/LockScope"); var LockType_1 = require("../../../resource/v2/lock/LockType"); var LockKind_1 = require("../../../resource/v2/lock/LockKind"); var Workflow_1 = require("../../../helper/Workflow"); var Resource_1 = require("./Resource"); var Errors_1 = require("../../../Errors"); var Path_1 = require("../Path"); var crypto = require("crypto"); var promise_1 = require("../../../helper/v2/promise"); var BufferedIsLocked = /** @class */ (function () { function BufferedIsLocked(fs, ctx, path) { this.fs = fs; this.ctx = ctx; this.path = path; this._isLocked = null; } BufferedIsLocked.prototype.isLocked = function (callback) { var _this = this; if (this._isLocked !== null) return callback(null, this._isLocked); this.fs.isLocked(this.ctx, this.path, function (e, locked) { if (e) return callback(e); _this._isLocked = locked; callback(null, locked); }); }; return BufferedIsLocked; }()); /** * File system which manage resources under its mounted path. * * @see https://github.com/OpenMarshal/npm-WebDAV-Server/wiki/Custom-File-System-%5Bv2%5D */ var FileSystem = /** @class */ (function () { function FileSystem(serializer) { this.__serializer = serializer; } /** * Get the serializer. */ FileSystem.prototype.serializer = function () { return this.__serializer; }; /** * Defines the serializer to use. * * @param serializer Serializer to use. */ FileSystem.prototype.setSerializer = function (serializer) { this.__serializer = serializer; }; /** * Tell to not serialize this file system. */ FileSystem.prototype.doNotSerialize = function () { this.__serializer = null; }; /** * Wrap the file system with the context. * * @param ctx Context of the operation. */ FileSystem.prototype.contextualize = function (ctx) { return new ContextualFileSystem_1.ContextualFileSystem(this, ctx); }; /** * Wrap the file system with the context and a resource path. * * @param ctx Context of the operation. * @param path Path of the resource. */ FileSystem.prototype.resource = function (ctx, path) { return new Resource_1.Resource(path, this, ctx); }; /** * Make a fast check if the resource exists. * If '_fastExistCheck' is not implemented, this method call 'callback'. * If '_fastExistCheck' is implemented and it returns 'false', then the 'errorCallback' is called, otherwise the 'callback' is called. * * This method will not give a true information, but just an estimate of the existence of a resource. * * @param ctx Context of the operation. * @param _path Path of the resource. * @param errorCallback Callback to call when the resource is sure to not exist. * @param callback Callback to call when the resource might exists. */ FileSystem.prototype.fastExistCheckEx = function (ctx, _path, errorCallback, callback) { if (!this._fastExistCheck) return callback(); var path = new Path_1.Path(_path); this._fastExistCheck(ctx, path, function (exists) { if (!exists) errorCallback(Errors_1.Errors.ResourceNotFound); else callback(); }); }; /** * Make a fast check if the resource exists. * If '_fastExistCheck' is not implemented, this method call 'callback'. * If '_fastExistCheck' is implemented and it returns 'false', then the 'callback' is called, otherwise the 'errorCallback' is called. * * This method will not give a true information, but just an estimate of the existence of a resource. * * @param ctx Context of the operation. * @param _path Path of the resource. * @param errorCallback Callback to call when the resource might exists. * @param callback Callback to call when the resource is sure to not exist. */ FileSystem.prototype.fastExistCheckExReverse = function (ctx, _path, errorCallback, callback) { if (!this._fastExistCheck) return callback(); var path = new Path_1.Path(_path); this._fastExistCheck(ctx, path, function (exists) { if (exists) errorCallback(Errors_1.Errors.ResourceAlreadyExists); else callback(); }); }; /** * Make a fast check if a resource exists. * This method will call '_fastExistCheck' if it is implemented or return 'true'. * * This method will not give a true information, but just an estimate of the existence of a resource. * * @param ctx Context of the operation. * @param _path Path of the resource. * @param callback Returns if the resource exists. */ FileSystem.prototype.fastExistCheck = function (ctx, _path, callback) { if (!this._fastExistCheck) return callback(true); var path = new Path_1.Path(_path); this._fastExistCheck(ctx, path, function (exists) { return callback(!!exists); }); }; FileSystem.prototype.createAsync = function (ctx, path, type, createIntermediates) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.create(ctx, path, type, createIntermediates, cb); }); }; FileSystem.prototype.create = function (ctx, _path, type, _createIntermediates, _callback) { var _this = this; var createIntermediates = promise_1.ensureValue(_callback ? _createIntermediates : undefined, false); var callbackFinal = _callback ? _callback : _createIntermediates; var path = new Path_1.Path(_path); var callback = function (e) { if (!e) _this.emit('create', ctx, path, { type: type, createIntermediates: createIntermediates }); callbackFinal(e); }; if (!this._create) return callback(Errors_1.Errors.InvalidOperation); this.emit('before-create', ctx, path, { type: type, createIntermediates: createIntermediates }); issuePrivilegeCheck(this, ctx, path, 'canWrite', callback, function () { var go = function () { ctx.server.options.storageManager.evaluateCreate(ctx, _this, path, type, function (size) { ctx.server.options.storageManager.reserve(ctx, _this, size, function (reserved) { if (!reserved) return callback(Errors_1.Errors.InsufficientStorage); _this._create(path, { context: ctx, type: type }, function (e) { if (e) ctx.server.options.storageManager.reserve(ctx, _this, -size, function () { return callback(e); }); else callback(); }); }); }); }; _this.isLocked(ctx, path, function (e, locked) { if (e || locked) return callback(locked ? Errors_1.Errors.Locked : e); _this.fastExistCheckExReverse(ctx, path, callback, function () { _this.type(ctx, path.getParent(), function (e, type) { if (e === Errors_1.Errors.ResourceNotFound) { if (!createIntermediates) return callback(Errors_1.Errors.IntermediateResourceMissing); _this.getFullPath(ctx, path, function (e, fullPath) { if (e) return callback(e); fullPath = fullPath.getParent(); ctx.getResource(fullPath, function (e, r) { if (e) return callback(e); r.create(CommonTypes_1.ResourceType.Directory, true, function (e) { if (e && e !== Errors_1.Errors.ResourceAlreadyExists) return callback(e); go(); }); }); }); return; } if (e) return callback(e); if (!type.isDirectory) return callback(Errors_1.Errors.WrongParentTypeForCreation); go(); }); }); }); }); }; /** * Get the etag of the resource. * The default etag, if '_etag' is not implemented, is to hash the last modified date information of the resource and wrap it with quotes. * * @param ctx Context of the operation. * @param _path Path of the resource. */ FileSystem.prototype.etagAsync = function (ctx, path) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.etag(ctx, path, cb); }); }; /** * Get the etag of the resource. * The default etag, if '_etag' is not implemented, is to hash the last modified date information of the resource and wrap it with quotes. * * @param ctx Context of the operation. * @param _path Path of the resource. * @param callback Returns the etag of the resource. */ FileSystem.prototype.etag = function (ctx, _path, callback) { var _this = this; var path = new Path_1.Path(_path); issuePrivilegeCheck(this, ctx, path, 'canReadProperties', callback, function () { _this.fastExistCheckEx(ctx, path, callback, function () { if (!_this._etag) return _this.lastModifiedDate(ctx, path, function (e, date) { if (e) return callback(e); date = FileSystem.neutralizeEmptyDate(date); callback(null, '"' + crypto.createHash('md5').update(date.toString()).digest('hex') + '"'); }); _this._etag(path, { context: ctx }, callback); }); }); }; FileSystem.prototype.deleteAsync = function (ctx, path, depth) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.delete(ctx, path, depth, cb); }); }; FileSystem.prototype.delete = function (ctx, _path, _depth, _callback) { var _this = this; var depth = promise_1.ensureValue(_callback ? _depth : undefined, -1); var callbackFinal = _callback ? _callback : _depth; var path = new Path_1.Path(_path); var callback = function (e) { if (!e) _this.emit('delete', ctx, path, { depth: depth }); callbackFinal(e); }; if (!this._delete) return callback(Errors_1.Errors.InvalidOperation); this.emit('before-delete', ctx, path, { depth: depth }); issuePrivilegeCheck(this, ctx, path, 'canWrite', callback, function () { _this.isLocked(ctx, path, function (e, isLocked) { if (e || isLocked) return callback(e ? e : Errors_1.Errors.Locked); _this.fastExistCheckEx(ctx, path, callback, function () { _this.size(ctx, path, function (e, contentSize) { contentSize = contentSize || 0; _this._delete(path, { context: ctx, depth: depth }, function (e) { if (!e) { _this.type(ctx, path, function (e, type) { ctx.server.options.storageManager.evaluateContent(ctx, _this, contentSize, function (reservedContentSize) { ctx.server.options.storageManager.evaluateCreate(ctx, _this, path, type, function (size) { ctx.server.options.storageManager.reserve(ctx, _this, -size - reservedContentSize, function () { callback(); }); }); }); }); } else callback(e); }); }); }); }); }); }; FileSystem.prototype.openWriteStreamAsync = function (ctx, path, mode, targetSource, estimatedSize) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.openWriteStream(ctx, path, mode, targetSource, estimatedSize, function (e, data1, data2) { return cb(e, e ? undefined : { stream: data1, created: data2 }); }); }); }; FileSystem.prototype.openWriteStream = function (ctx, _path, _mode, _targetSource, _estimatedSize, _callback) { var _this = this; var targetSource = false; for (var _i = 0, _a = [_mode, _targetSource]; _i < _a.length; _i++) { var obj = _a[_i]; if (obj && obj.constructor === Boolean) targetSource = obj; } var estimatedSize = -1; for (var _b = 0, _c = [_mode, _targetSource, _estimatedSize]; _b < _c.length; _b++) { var obj = _c[_b]; if (obj && obj.constructor === Number) estimatedSize = obj; } var callbackFinal; for (var _d = 0, _e = [_mode, _targetSource, _estimatedSize, _callback]; _d < _e.length; _d++) { var obj = _e[_d]; if (obj && obj.constructor === Function) callbackFinal = obj; } var mode = _mode && _mode.constructor === String ? _mode : 'mustExist'; var path = new Path_1.Path(_path); var created = false; var callback = function (e, stream, created) { if (!e) _this.emit('openWriteStream', ctx, path, { targetSource: targetSource, mode: mode, estimatedSize: estimatedSize, created: created, stream: stream }); callbackFinal(e, stream, created); }; if (!this._openWriteStream) return callback(Errors_1.Errors.InvalidOperation); this.emit('before-openWriteStream', ctx, path, { targetSource: targetSource, mode: mode, estimatedSize: estimatedSize, created: created }); issuePrivilegeCheck(this, ctx, path, targetSource ? 'canWriteContentSource' : 'canWriteContentTranslated', callback, function () { _this.isLocked(ctx, path, function (e, isLocked) { if (e || isLocked) return callback(e ? e : Errors_1.Errors.Locked); var finalGo = function (callback) { _this._openWriteStream(path, { context: ctx, estimatedSize: estimatedSize, targetSource: targetSource, mode: mode }, function (e, wStream) { return callback(e, wStream, created); }); }; var go = function (callback) { _this.size(ctx, path, true, function (e, size) { ctx.server.options.storageManager.evaluateContent(ctx, _this, size, function (sizeStored) { if (estimatedSize === undefined || estimatedSize === null || estimatedSize.constructor === Number && estimatedSize <= 0) { ctx.server.options.storageManager.available(ctx, _this, function (available) { if (available === -1) return finalGo(callback); if (available === 0) return callback(Errors_1.Errors.InsufficientStorage); var nb = 0; finalGo(function (e, wStream, created) { if (e) return callback(e, wStream, created); var stream = new stream_1.Transform({ transform: function (chunk, encoding, callback) { nb += chunk.length; if (nb > available) callback(Errors_1.Errors.InsufficientStorage); else callback(null, chunk, encoding); } }); stream.pipe(wStream); stream.on('finish', function () { ctx.server.options.storageManager.reserve(ctx, _this, nb, function (reserved) { if (!reserved) stream.emit('error', Errors_1.Errors.InsufficientStorage); }); }); callback(e, stream, created); }); }); } else { ctx.server.options.storageManager.evaluateContent(ctx, _this, estimatedSize, function (estimatedSizeStored) { ctx.server.options.storageManager.reserve(ctx, _this, estimatedSizeStored - sizeStored, function (reserved) { if (!reserved) return callback(Errors_1.Errors.InsufficientStorage); finalGo(callback); }); }); } }); }); }; var createAndGo = function (intermediates) { _this.create(ctx, path, CommonTypes_1.ResourceType.File, intermediates, function (e) { if (e) return callback(e); created = true; go(callback); }); }; switch (mode) { case 'mustExist': _this.fastExistCheckEx(ctx, path, callback, function () { return go(callback); }); break; case 'mustCreateIntermediates': case 'mustCreate': createAndGo(mode === 'mustCreateIntermediates'); break; case 'canCreateIntermediates': case 'canCreate': go(function (e, wStream) { if (e === Errors_1.Errors.ResourceNotFound) createAndGo(mode === 'canCreateIntermediates'); else callback(e, wStream); }); break; default: callback(Errors_1.Errors.IllegalArguments); break; } }); }); }; FileSystem.prototype.openReadStreamAsync = function (ctx, path, targetSource, estimatedSize) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.openReadStream(ctx, path, targetSource, estimatedSize, cb); }); }; FileSystem.prototype.openReadStream = function (ctx, _path, _targetSource, _estimatedSize, _callback) { var _this = this; var targetSource = promise_1.ensureValue(_targetSource.constructor === Boolean ? _targetSource : undefined, false); var estimatedSize = promise_1.ensureValue(_callback ? _estimatedSize : _estimatedSize ? _targetSource : undefined, -1); var callbackFinal = _callback ? _callback : _estimatedSize ? _estimatedSize : _targetSource; var path = new Path_1.Path(_path); var callback = function (e, stream) { if (!e) _this.emit('openReadStream', ctx, path, { targetSource: targetSource, estimatedSize: estimatedSize, stream: stream }); callbackFinal(e, stream); }; this.emit('before-openReadStream', ctx, path, { targetSource: targetSource, estimatedSize: estimatedSize }); issuePrivilegeCheck(this, ctx, path, targetSource ? 'canReadContentSource' : 'canReadContentTranslated', callback, function () { _this.fastExistCheckEx(ctx, path, callback, function () { if (!_this._openReadStream) return callback(Errors_1.Errors.InvalidOperation); _this._openReadStream(path, { context: ctx, estimatedSize: estimatedSize, targetSource: targetSource }, callback); }); }); }; FileSystem.prototype.moveAsync = function (ctx, pathFrom, pathTo, overwrite) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.move(ctx, pathFrom, pathTo, overwrite, cb); }); }; FileSystem.prototype.move = function (ctx, _pathFrom, _pathTo, _overwrite, _callback) { var _this = this; var callbackFinal = _callback ? _callback : _overwrite; var overwrite = promise_1.ensureValue(_callback ? _overwrite : undefined, false); var pathFrom = new Path_1.Path(_pathFrom); var pathTo = new Path_1.Path(_pathTo); var callback = function (e, overrided) { if (!e) _this.emit('move', ctx, pathFrom, { pathFrom: pathFrom, pathTo: pathTo, overwrite: overwrite, overrided: overrided }); callbackFinal(e, overrided); }; this.emit('before-move', ctx, pathFrom, { pathFrom: pathFrom, pathTo: pathTo, overwrite: overwrite }); issuePrivilegeCheck(this, ctx, pathFrom, 'canRead', callback, function () { issuePrivilegeCheck(_this, ctx, pathTo, 'canWrite', callback, function () { _this.isLocked(ctx, pathFrom, function (e, isLocked) { if (e || isLocked) return callback(e ? e : Errors_1.Errors.Locked); _this.isLocked(ctx, pathTo, function (e, isLocked) { if (e || isLocked) return callback(e ? e : Errors_1.Errors.Locked); var go = function () { if (_this._move) { _this._move(pathFrom, pathTo, { context: ctx, overwrite: overwrite }, callback); return; } StandardMethods_1.StandardMethods.standardMove(ctx, pathFrom, _this, pathTo, _this, overwrite, callback); }; _this.fastExistCheckEx(ctx, pathFrom, callback, function () { if (!overwrite) _this.fastExistCheckExReverse(ctx, pathTo, callback, go); else go(); }); }); }); }); }); }; FileSystem.prototype.copyAsync = function (ctx, pathFrom, pathTo, overwrite, depth) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.copy(ctx, pathFrom, pathTo, overwrite, depth); }); }; FileSystem.prototype.copy = function (ctx, _pathFrom, _pathTo, _overwrite, _depth, _callback) { var _this = this; var overwrite = promise_1.ensureValue(_overwrite.constructor === Boolean ? _overwrite : undefined, false); var depth = promise_1.ensureValue(_callback ? _depth : !_depth ? -1 : _overwrite.constructor === Number ? _overwrite : undefined, -1); var callbackFinal = _callback ? _callback : _depth ? _depth : _overwrite; var pathFrom = new Path_1.Path(_pathFrom); var pathTo = new Path_1.Path(_pathTo); var callback = function (e, overrided) { if (!e) _this.emit('copy', ctx, pathFrom, { pathTo: pathTo, overwrite: overwrite, overrided: overrided, depth: depth }); callbackFinal(e, overrided); }; this.emit('before-copy', ctx, pathFrom, { pathTo: pathTo, overwrite: overwrite, depth: depth }); issuePrivilegeCheck(this, ctx, pathFrom, 'canRead', callback, function () { issuePrivilegeCheck(_this, ctx, pathTo, 'canWrite', callback, function () { _this.isLocked(ctx, pathTo, function (e, isLocked) { if (e || isLocked) return callback(e ? e : Errors_1.Errors.Locked); var go = function () { if (_this._copy) { _this._copy(pathFrom, pathTo, { context: ctx, depth: depth, overwrite: overwrite }, callback); return; } StandardMethods_1.StandardMethods.standardCopy(ctx, pathFrom, _this, pathTo, _this, overwrite, depth, callback); }; _this.fastExistCheckEx(ctx, pathFrom, callback, function () { if (!overwrite) _this.fastExistCheckExReverse(ctx, pathTo, callback, go); else go(); }); }); }); }); }; FileSystem.prototype.renameAsync = function (ctx, pathFrom, newName, overwrite) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.rename(ctx, pathFrom, newName, overwrite, cb); }); }; FileSystem.prototype.rename = function (ctx, _pathFrom, newName, _overwrite, _callback) { var _this = this; var overwrite = promise_1.ensureValue(_callback ? _overwrite : undefined, false); var callbackFinal = _callback ? _callback : _overwrite; var pathFrom = new Path_1.Path(_pathFrom); var callback = function (e, overrided) { if (!e) _this.emit('rename', ctx, pathFrom, { newName: newName, overrided: overrided }); callbackFinal(e, overrided); }; this.emit('before-rename', ctx, pathFrom, { newName: newName }); issuePrivilegeCheck(this, ctx, pathFrom, ['canRead', 'canWrite'], callback, function () { _this.isLocked(ctx, pathFrom, function (e, isLocked) { if (e || isLocked) return callback(e ? e : Errors_1.Errors.Locked); if (pathFrom.isRoot()) { _this.getFullPath(ctx, function (e, fullPath) { if (fullPath.isRoot()) return callback(Errors_1.Errors.InvalidOperation); var newPath = fullPath.getParent().getChildPath(newName); issuePrivilegeCheck(_this, ctx, newPath, 'canWrite', callback, function () { ctx.server.getFileSystem(newPath, function (fs, _, subPath) { var go = function (overwritten) { ctx.server.setFileSystem(newPath, _this, function (successed) { if (!successed) return callback(Errors_1.Errors.InvalidOperation); ctx.server.removeFileSystem(fullPath, function () { return callback(null, overwritten); }); }); }; if (!subPath.isRoot()) { go(false); } else if (!overwrite) { callback(Errors_1.Errors.ResourceAlreadyExists); } else { ctx.server.removeFileSystem(newPath, function () { go(true); }); } }); }); }); } else { _this.fastExistCheckEx(ctx, pathFrom, callback, function () { _this.fastExistCheckExReverse(ctx, pathFrom.getParent().getChildPath(newName), callback, function () { var newPath = pathFrom.getParent().getChildPath(newName); _this.isLocked(ctx, newPath, function (e, isLocked) { if (e || isLocked) return callback(e ? e : Errors_1.Errors.Locked); issuePrivilegeCheck(_this, ctx, newPath, 'canWrite', callback, function () { if (_this._rename) { _this._rename(pathFrom, newName, { context: ctx, destinationPath: newPath }, callback); } else { _this.move(ctx, pathFrom, pathFrom.getParent().getChildPath(newName), overwrite, callback); } }); }); }); }); } }); }); }; FileSystem.prototype.mimeTypeAsync = function (ctx, path, targetSource) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.mimeType(ctx, path, targetSource, cb); }); }; FileSystem.prototype.mimeType = function (ctx, _path, _targetSource, _callback) { var _this = this; var targetSource = promise_1.ensureValue(_callback ? _targetSource : undefined, true); var callback = _callback ? _callback : _targetSource; var path = new Path_1.Path(_path); issuePrivilegeCheck(this, ctx, path, targetSource ? 'canReadContentSource' : 'canReadContentTranslated', callback, function () { _this.fastExistCheckEx(ctx, path, callback, function () { if (_this._mimeType) { _this._mimeType(path, { context: ctx, targetSource: targetSource }, callback); } else { StandardMethods_1.StandardMethods.standardMimeType(ctx, _this, path, targetSource, callback); } }); }); }; FileSystem.prototype.sizeAsync = function (ctx, path, targetSource) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.size(ctx, path, targetSource, cb); }); }; FileSystem.prototype.size = function (ctx, path, _targetSource, _callback) { var _this = this; var targetSource = promise_1.ensureValue(_callback ? _targetSource : undefined, false); var callback = _callback ? _callback : _targetSource; var pPath = new Path_1.Path(path); issuePrivilegeCheck(this, ctx, pPath, targetSource ? 'canReadContentSource' : 'canReadContentTranslated', callback, function () { _this.fastExistCheckEx(ctx, pPath, callback, function () { if (!_this._size) return callback(null, undefined); _this._size(pPath, { context: ctx, targetSource: targetSource }, callback); }); }); }; /** * Get the list of available lock kinds. * * @param ctx Context of the operation. * @param path Path of the resource. */ FileSystem.prototype.availableLocksAsync = function (ctx, path) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.availableLocks(ctx, path, cb); }); }; /** * Get the list of available lock kinds. * * @param ctx Context of the operation. * @param path Path of the resource. * @param callback Returns the list of available lock kinds. */ FileSystem.prototype.availableLocks = function (ctx, path, callback) { var _this = this; var pPath = new Path_1.Path(path); issuePrivilegeCheck(this, ctx, pPath, 'canReadLocks', callback, function () { _this.fastExistCheckEx(ctx, pPath, callback, function () { if (!_this._availableLocks) { callback(null, [ new LockKind_1.LockKind(LockScope_1.LockScope.Exclusive, LockType_1.LockType.Write), new LockKind_1.LockKind(LockScope_1.LockScope.Shared, LockType_1.LockType.Write) ]); } else { _this._availableLocks(pPath, { context: ctx }, callback); } }); }); }; /** * Get the lock manager of the resource. * * @param ctx Context of the operation. * @param path Path of the resource. */ FileSystem.prototype.lockManagerAsync = function (ctx, path) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.lockManager(ctx, path, cb); }); }; /** * Get the lock manager of the resource. * * @param ctx Context of the operation. * @param path Path of the resource. * @param callback Returns the lock manager of the resource. */ FileSystem.prototype.lockManager = function (ctx, path, callback) { var _this = this; var pPath = new Path_1.Path(path); this.fastExistCheckEx(ctx, pPath, callback, function () { _this._lockManager(pPath, { context: ctx }, function (e, lm) { if (e) return callback(e); var buffIsLocked = new BufferedIsLocked(_this, ctx, pPath); var fs = _this; var manager = { getLocksAsync: function () { return promise_1.promisifyCall(function (cb) { return manager.getLocks(cb); }); }, getLocks: function (callback) { issuePrivilegeCheck(fs, ctx, pPath, 'canReadLocks', callback, function () { lm.getLocks(callback); }); }, setLockAsync: function (lock) { return promise_1.promisifyCall(function (cb) { return manager.setLock(lock, cb); }); }, setLock: function (lock, callback) { fs.emit('before-lock-set', ctx, pPath, { lock: lock }); issuePrivilegeCheck(fs, ctx, pPath, 'canWriteLocks', callback, function () { buffIsLocked.isLocked(function (e, isLocked) { if (e || isLocked) return callback(e ? e : Errors_1.Errors.Locked); lm.setLock(lock, function (e) { if (!e) fs.emit('lock-set', ctx, pPath, { lock: lock }); callback(e); }); }); }); }, removeLockAsync: function (uuid) { return promise_1.promisifyCall(function (cb) { return manager.removeLock(uuid, cb); }); }, removeLock: function (uuid, callback) { fs.emit('before-lock-remove', ctx, pPath, { uuid: uuid }); issuePrivilegeCheck(fs, ctx, pPath, 'canWriteLocks', callback, function () { buffIsLocked.isLocked(function (e, isLocked) { if (e || isLocked) return callback(e ? e : Errors_1.Errors.Locked); lm.removeLock(uuid, function (e, removed) { if (!e) fs.emit('lock-remove', ctx, pPath, { uuid: uuid, removed: removed }); callback(e, removed); }); }); }); }, getLockAsync: function (uuid) { return promise_1.promisifyCall(function (cb) { return manager.getLock(uuid, cb); }); }, getLock: function (uuid, callback) { issuePrivilegeCheck(fs, ctx, pPath, 'canReadLocks', callback, function () { lm.getLock(uuid, callback); }); }, refreshAsync: function (uuid, timeoutSeconds) { return promise_1.promisifyCall(function (cb) { return manager.refresh(uuid, timeoutSeconds, cb); }); }, refresh: function (uuid, timeoutSeconds, callback) { fs.emit('before-lock-refresh', ctx, pPath, { uuid: uuid, timeout: timeoutSeconds }); issuePrivilegeCheck(fs, ctx, pPath, 'canWriteLocks', callback, function () { buffIsLocked.isLocked(function (e, isLocked) { if (e || isLocked) return callback(e ? e : Errors_1.Errors.Locked); lm.refresh(uuid, timeoutSeconds, function (e, lock) { if (!e) fs.emit('lock-refresh', ctx, pPath, { uuid: uuid, timeout: timeoutSeconds, lock: lock }); callback(e, lock); }); }); }); } }; callback(null, manager); }); }); }; /** * Get the property manager of the resource. * * @param ctx Context of the operation. * @param path Path of the resource. */ FileSystem.prototype.propertyManagerAsync = function (ctx, path) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.propertyManager(ctx, path, cb); }); }; /** * Get the property manager of the resource. * * @param ctx Context of the operation. * @param path Path of the resource. * @param callback Returns the property manager of the resource. */ FileSystem.prototype.propertyManager = function (ctx, path, callback) { var _this = this; var pPath = new Path_1.Path(path); this.fastExistCheckEx(ctx, pPath, callback, function () { _this._propertyManager(pPath, { context: ctx }, function (e, pm) { if (e) return callback(e); var buffIsLocked = new BufferedIsLocked(_this, ctx, pPath); var fs = _this; callback(null, { setProperty: function (name, value, attributes, callback) { fs.emit('before-property-set', ctx, pPath, { name: name, value: value, attributes: attributes }); issuePrivilegeCheck(fs, ctx, pPath, 'canWriteProperties', callback, function () { buffIsLocked.isLocked(function (e, isLocked) { if (e || isLocked) return callback(e ? e : Errors_1.Errors.Locked); pm.setProperty(name, value, attributes, function (e) { if (!e) fs.emit('property-set', ctx, pPath, { name: name, value: value, attributes: attributes }); callback(e); }); }); }); }, getProperty: function (name, callback) { issuePrivilegeCheck(fs, ctx, pPath, 'canReadProperties', callback, function () { pm.getProperty(name, callback); }); }, removeProperty: function (name, callback) { fs.emit('before-property-remove', ctx, pPath, { name: name }); issuePrivilegeCheck(fs, ctx, pPath, 'canWriteProperties', callback, function () { buffIsLocked.isLocked(function (e, isLocked) { if (e || isLocked) return callback(e ? e : Errors_1.Errors.Locked); pm.removeProperty(name, function (e) { if (!e) fs.emit('property-remove', ctx, pPath, { name: name }); callback(e); }); }); }); }, getProperties: function (callback, byCopy) { var _this = this; issuePrivilegeCheck(fs, ctx, pPath, 'canReadProperties', callback, function () { pm.getProperties(function (e, bag) { if (!bag) return callback(e, bag); ctx.server.options.storageManager.available(ctx, _this, function (availableSize) { if (availableSize === -1) return callback(e, bag); ctx.server.options.storageManager.reserved(ctx, _this, function (reservedSize) { bag['DAV:quota-available-bytes'] = { value: availableSize.toString() }; bag['DAV:quota-used-bytes'] = { value: reservedSize.toString() }; callback(e, bag); }); }); }, byCopy); }); } }); }); }); }; FileSystem.prototype.readDirAsync = function (ctx, path, retrieveExternalFiles) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.readDir(ctx, path, retrieveExternalFiles, cb); }); }; FileSystem.prototype.readDir = function (ctx, path, _retrieveExternalFiles, _callback) { var _this = this; var retrieveExternalFiles = promise_1.ensureValue(_callback ? _retrieveExternalFiles : undefined, false); var __callback = _callback ? _callback : _retrieveExternalFiles; var pPath = new Path_1.Path(path); var callback = function (e, data) { if (e) return __callback(e); if (!data) data = []; _this.getFullPath(ctx, function (e, fsFullPath) { new Workflow_1.Workflow() .each(data, function (path, cb) { _this.checkPrivilege(ctx, path, 'canReadProperties', function (e, can) { if (e) cb(e); else cb(null, can ? path : null); }); }) .error(__callback) .done(function () { return __callback(null, data.filter(function (p) { return !!p; }).map(function (p) { return p.fileName(); })); }); }); }; issuePrivilegeCheck(this, ctx, pPath, 'canReadProperties', callback, function () { _this.fastExistCheckEx(ctx, pPath, callback, function () { var next = function (base) { if (!_this._readDir) return callback(null, base); _this._readDir(pPath, { context: ctx }, function (e, paths) { if (e) return callback(e); if (paths.length === 0) return callback(null, base); if (paths[0].constructor === String) base = base.concat(paths.map(function (s) { return pPath.getChildPath(s); })); else base = base.concat(paths); callback(null, base); }); }; if (!retrieveExternalFiles) return next([]); _this.getFullPath(ctx, function (e, thisFullPath) { if (e) return callback(e); ctx.server.getChildFileSystems(thisFullPath.getChildPath(pPath), function (fss) { _this.localize(ctx, fss.map(function (f) { return f.path; }), function (e, paths) { if (e) return callback(e); next(paths); }); }); }); }); }); }; FileSystem.neutralizeEmptyDate = function (date, defaultDate) { if (!date || isNaN(date)) { if (defaultDate === undefined || defaultDate === null) defaultDate = 0; return defaultDate; } else { return date; } }; /** * Get the creation date information of a resource. * If neither '_creationDate' nor '_lastModifiedDate' are implemented, it returns 0. * If '_creationDate' is not implemented, it calls the 'lastModifiedDate' method. * Otherwise it calls the '_creationDate' method. * * @param ctx Context of the operation. * @param path Path of the resource. */ FileSystem.prototype.creationDateAsync = function (ctx, path) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.creationDate(ctx, path, cb); }); }; /** * Get the creation date information of a resource. * If neither '_creationDate' nor '_lastModifiedDate' are implemented, it returns 0. * If '_creationDate' is not implemented, it calls the 'lastModifiedDate' method. * Otherwise it calls the '_creationDate' method. * * @param ctx Context of the operation. * @param path Path of the resource. * @param callback Returns the creation date of the resource. */ FileSystem.prototype.creationDate = function (ctx, path, callback) { var _this = this; var pPath = new Path_1.Path(path); callback = FileSystem.neutralizeEmptyDateCallback(callback); issuePrivilegeCheck(this, ctx, pPath, 'canReadProperties', callback, function () { _this.fastExistCheckEx(ctx, pPath, callback, function () { if (!_this._creationDate && !_this._lastModifiedDate) return callback(null, 0); if (!_this._creationDate) return _this.lastModifiedDate(ctx, pPath, callback); _this._creationDate(pPath, { context: ctx }, callback); }); }); }; /** * Get the last modified date information of a resource. * If neither '_creationDate' nor '_lastModifiedDate' are implemented, it returns 0. * If '_lastModifiedDate' is not implemented, it calls the 'creationDate' method. * Otherwise it calls the '_lastModifiedDate' method. * * @param ctx Context of the operation. * @param path Path of the resource. */ FileSystem.prototype.lastModifiedDateAsync = function (ctx, path) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.lastModifiedDate(ctx, path, cb); }); }; /** * Get the last modified date information of a resource. * If neither '_creationDate' nor '_lastModifiedDate' are implemented, it returns 0. * If '_lastModifiedDate' is not implemented, it calls the 'creationDate' method. * Otherwise it calls the '_lastModifiedDate' method. * * @param ctx Context of the operation. * @param path Path of the resource. * @param callback Returns the last modified date of the resource. */ FileSystem.prototype.lastModifiedDate = function (ctx, path, callback) { var _this = this; var pPath = new Path_1.Path(path); callback = FileSystem.neutralizeEmptyDateCallback(callback); issuePrivilegeCheck(this, ctx, pPath, 'canReadProperties', callback, function () { _this.fastExistCheckEx(ctx, pPath, callback, function () { if (!_this._creationDate && !_this._lastModifiedDate) return callback(null, 0); if (!_this._lastModifiedDate) return _this.creationDate(ctx, pPath, callback); _this._lastModifiedDate(pPath, { context: ctx }, callback); }); }); }; /** * Get the name of the resource. * * @param ctx Context of the operation. * @param path Path of the resource. */ FileSystem.prototype.webNameAsync = function (ctx, path) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.webName(ctx, path, cb); }); }; /** * Get the name of the resource. * * @param ctx Context of the operation. * @param path Path of the resource. * @param callback Returns the name of the resource. */ FileSystem.prototype.webName = function (ctx, path, callback) { var _this = this; var pPath = new Path_1.Path(path); issuePrivilegeCheck(this, ctx, pPath, 'canReadProperties', callback, function () { _this.fastExistCheckEx(ctx, pPath, callback, function () { if (pPath.isRoot()) _this.getFullPath(ctx, function (e, pPath) { return callback(e, e ? null : pPath.fileName()); }); else callback(null, pPath.fileName()); }); }); }; /** * Get the 'displayName' information of the resource. * This value is used in the 'DAV:displayName' tag in the PROPFIND response body. * Its default behaviour is to return the result of the 'webName' method. This behaviour can be overrided by implementing the '_displayName' method. * * @param ctx Context of the operation. * @param path Path of the resource. */ FileSystem.prototype.displayNameAsync = function (ctx, path) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.displayName(ctx, path, cb); }); }; /** * Get the 'displayName' information of the resource. * This value is used in the 'DAV:displayName' tag in the PROPFIND response body. * Its default behaviour is to return the result of the 'webName' method. This behaviour can be overrided by implementing the '_displayName' method. * * @param ctx Context of the operation. * @param path Path of the resource. * @param callback Returns the 'displayName' information of the resource. */ FileSystem.prototype.displayName = function (ctx, path, callback) { var _this = this; var pPath = new Path_1.Path(path); issuePrivilegeCheck(this, ctx, pPath, 'canReadProperties', callback, function () { _this.fastExistCheckEx(ctx, pPath, callback, function () { if (!_this._displayName) return _this.webName(ctx, pPath, callback); _this._displayName(pPath, { context: ctx }, callback); }); }); }; /** * Get the type of the resource. * * @param ctx Context of the operation. * @param path Path of the resource. */ FileSystem.prototype.typeAsync = function (ctx, path) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.type(ctx, path, cb); }); }; /** * Get the type of the resource. * * @param ctx Context of the operation. * @param path Path of the resource. * @param callback Returns the type of the resource. */ FileSystem.prototype.type = function (ctx, path, callback) { var _this = this; var pPath = new Path_1.Path(path); issuePrivilegeCheck(this, ctx, pPath, 'canReadProperties', callback, function () { _this.fastExistCheckEx(ctx, pPath, callback, function () { _this._type(pPath, { context: ctx }, callback); }); }); }; FileSystem.prototype.addSubTreeAsync = function (ctx, rootPath, tree) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.addSubTree(ctx, rootPath, tree, cb); }); }; FileSystem.prototype.addSubTree = function (ctx, _rootPath, _tree, _callback) { var _this = this; var _rootPathIsPath = Path_1.Path.isPath(_rootPath); var tree = _rootPathIsPath ? _tree : _rootPath; var rootPath = _rootPathIsPath ? new Path_1.Path(_rootPath) : new Path_1.Path('/'); var callback = _callback ? _callback : _tree; callback = callback ? callback : function () { }; if (tree.constructor === CommonTypes_1.ResourceType) { this.create(ctx, rootPath, tree, callback); } else if (tree.constructor === String || tree.constructor === Buffer) { var data_1 = tree; this.openWriteStream(ctx, rootPath, 'mustCreate', true, data_1.length, function (e, w, created) { if (e) return callback(e); w.end(data_1); w.on('error', function (e) { callback(e); }); w.on('finish', function () { callback(); }); }); } else { new Workflow_1.Workflow() .each(Object.keys(tree), function (name, cb) { var value = tree[name]; var childPath = rootPath.getChildPath(name); if (value.constructor === CommonTypes_1.ResourceType || value.constructor === String || value.constructor === Buffer) { _this.addSubTree(ctx, childPath, value, cb); } else { _this.addSubTree(ctx, childPath, CommonTypes_1.ResourceType.Directory, function (e) { if (e) return cb(e); _this.addSubTree(ctx, childPath, value, cb); }); } }) .error(callback) .done(function (_) { return callback(); }); } }; FileSystem.prototype.listDeepLocksAsync = function (ctx, startPath, depth) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.listDeepLocks(ctx, startPath, depth, cb); }); }; FileSystem.prototype.listDeepLocks = function (ctx, startPath, _depth, _callback) { var _this = this; var depth = promise_1.ensureValue(_callback ? _depth : undefined, 0); var callback = _callback ? _callback : _depth; var pStartPath = new Path_1.Path(startPath); this.lockManager(ctx, pStartPath, function (e, lm) { if (e === Errors_1.Errors.ResourceNotFound) { lm = { getLocks: function (callback) { callback(null, []); } }; } else if (e) { return callback(e); } lm.getLocks(function (e, locks) { if (e === Errors_1.Errors.NotEnoughPrivilege) { locks = []; } else if (e) { return callback(e); } if (depth !== -1) locks = locks.filter(function (f) { return f.depth === -1 || f.depth >= depth; }); var go = function (fs, parentPath) { var destDepth = depth === -1 ? -1 : depth + 1; fs.listDeepLocks(ctx, parentPath, destDepth, function (e, pLocks) { if (e) return callback(e); if (locks && locks.length > 0) pLocks[pStartPath.toString()] = locks; callback(null, pLocks); }); }; if (!pStartPath.isRoot()) { go(_this, pStartPath.getParent()); } else { _this.getFullPath(ctx, function (e, fsPath) { if (e) return callback(e); if (fsPath.isRoot()) { var result = {}; if (locks && locks.length > 0) result[pStartPath.toString()] = locks; return callback(null, result); } ctx.server.getFileSystem(fsPath.getParent(), function (fs, _, subPath) { go(fs, subPath); }); }); } }); }); }; FileSystem.prototype.getFullPathAsync = function (ctx, path) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.getFullPath(ctx, path, cb); }); }; FileSystem.prototype.getFullPath = function (ctx, _path, _callback) { var path = !_path || typeof _path === 'function' ? undefined : new Path_1.Path(_path); var callback = _callback ? _callback : _path; ctx.server.getFileSystemPath(this, function (fsPath) { callback(null, path ? fsPath.getChildPath(path) : fsPath); }); }; FileSystem.prototype.localizeAsync = function (ctx, fullPath) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.localize(ctx, fullPath, cb); }); }; FileSystem.prototype.localize = function (ctx, fullPath, callback) { this.getFullPath(ctx, function (e, fsFullPath) { if (e) return callback(e); var paths = fullPath.constructor === Array ? fullPath : [fullPath]; callback(null, paths .map(function (p) { return new Path_1.Path(p); }) .map(function (p) { fsFullPath.paths.forEach(function () { return p.removeRoot(); }); return p; })); }); }; FileSystem.prototype.checkPrivilegeAsync = function (ctx, path, privileges) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.checkPrivilege(ctx, path, privileges, cb); }); }; FileSystem.prototype.checkPrivilege = function (ctx, path, privileges, callback) { var _this = this; if (privileges.constructor === String) privileges = [privileges]; this.getFullPath(ctx, path, function (e, fullPath) { _this.privilegeManager(ctx, path, function (e, privilegeManager) { if (e) return callback(e); var resource = _this.resource(ctx, new Path_1.Path(path)); privilegeManager.can(fullPath, resource, privileges, callback); }); }); }; /** * Get the privilege manager to use to authorize actions for a user. * By default, it returns the value in the server options, but it can be overrided by implementing the '_privilegeManager' method. * * @param ctx Context of the operation. * @param path Path of the resource. */ FileSystem.prototype.privilegeManagerAsync = function (ctx, path) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.privilegeManager(ctx, path, cb); }); }; /** * Get the privilege manager to use to authorize actions for a user. * By default, it returns the value in the server options, but it can be overrided by implementing the '_privilegeManager' method. * * @param ctx Context of the operation. * @param path Path of the resource. * @param callback Returns the privilege manager representing the requested resource. */ FileSystem.prototype.privilegeManager = function (ctx, path, callback) { if (!this._privilegeManager) return callback(null, ctx.server.options.privilegeManager); this._privilegeManager(new Path_1.Path(path), { context: ctx }, callback); }; FileSystem.prototype.isLockedAsync = function (ctx, path, depth) { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.isLockedAsync(ctx, path, depth); }); }; FileSystem.prototype.isLocked = function (ctx, path, _depth, _callback) { var _this = this; var callback = _callback ? _callback : _depth; var depth = typeof _depth === 'number' ? _depth : 0; if (ctx.user && ctx.user.isAdministrator) return callback(null, false); var pPath = new Path_1.Path(path); var checkThis = function () { _this._lockManager(pPath, { context: ctx }, function (e, lm) { if (e === Errors_1.Errors.ResourceNotFound) return callback(null, false); if (e) return callback(e); lm.getLocks(function (e, locks) { if (e === Errors_1.Errors.ResourceNotFound) return callback(null, false); if (e) return callback(e); locks = locks.filter(function (l) { return l.depth === -1 || l.depth >= depth; }); if (!ctx.user) return callback(null, locks.length > 0); if (locks.some(function (l) { return ctx.user.uid !== l.userUid && l.lockKind.scope.isSame(LockScope_1.LockScope.Exclusive); })) return callback(null, true); var isShared = false; for (var _i = 0, locks_1 = locks; _i < locks_1.length; _i++) { var lock = locks_1[_i]; if (lock.lockKind.scope.isSame(LockScope_1.LockScope.Shared)) { isShared = true; if (lock.userUid === ctx.user.uid) return callback(null, false); } } callback(null, isShared); }); }); }; this.getFullPath(ctx, pPath, function (e, fullPath) { if (fullPath.isRoot()) return checkThis(); ctx.server.getFileSystem(pPath.getParent(), function (fs, rootPath, subPath) { fs.isLocked(ctx, subPath, depth + 1, function (e, locked) { if (e || locked) return callback(e, locked); checkThis(); }); }); }); }; /** * Serialize the file system based on the 'this.serializer()' value. */ FileSystem.prototype.serializeAsync = function () { var _this = this; return promise_1.promisifyCall(function (cb) { return _this.serialize(cb); }); }; /** * Serialize the file system based on the 'this.serializer()' value. * * @param callback Returns the serialized data or an error. */ FileSystem.prototype.serialize = function (callback) { var serializer = this.serializer(); if (!serializer) return callback(); serializer.serialize(this, callback); }; FileSystem.prototype.on = function (ctx, event, listener) { var _this = this; var server = ctx.events ? ctx : ctx.server; server.on(event, function (ctx, fs, path) { if (fs === _this) listener(ctx, path); }); return this; }; /** * Trigger an event. * * @param event Name of the event. * @param ctx Context of the event. * @param path Path of the resource on which the event happened. */ FileSystem.prototype.emit = function (event, ctx, path, data) { ctx.server.emit(event, ctx, this, path, data); }; FileSystem.neutralizeEmptyDateCallback = function (callback) { return function (e, date) { callback(e, FileSystem.neutralizeEmptyDate(date)); }; }; return FileSystem; }()); exports.FileSystem = FileSystem; function issuePrivilegeCheck(fs, ctx, path, privilege, badCallback, goodCallback) { fs.checkPrivilege(ctx, path, privilege, function (e, can) { if (e) badCallback(e); else if (!can) badCallback(Errors_1.Errors.NotEnoughPrivilege); else goodCallback(); }); }