node-smb-server
Version:
A Pure JavaScript SMB Server Implementation
200 lines (174 loc) • 5.87 kB
JavaScript
/*
* Copyright 2016 Adobe Systems Incorporated. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
;
var util = require('util');
var logger = require('winston').loggers.get('spi');
var rqlog = require('winston').loggers.get('rq');
var Share = require('../../spi/share');
var RQTree = require('./tree');
var RQRemoteShare = require('./remoteshare');
/**
* Creates an instance of RQShare.
*
* @constructor
* @this {RQShare}
* @param {String} name share name
* @param {Object} config configuration hash
*/
var RQShare = function (name, config) {
if (!(this instanceof RQShare)) {
return new RQShare(name, config);
}
var self = this;
this.config = config || {};
this.listCache = {};
this.contentCacheTTL = typeof config.contentCacheTTL === 'number' ? config.contentCacheTTL : 30000; // default: 30s
this.remote = new RQRemoteShare(name, config);
this.waits = {};
this.downloadingFiles = {};
function forwardEvent(event) {
self.remote.on(event, function (data) {
self.emit(event, data);
});
}
forwardEvent('folderlist');
forwardEvent('longdownload');
forwardEvent('downloadstart');
forwardEvent('downloadprogress');
forwardEvent('downloadend');
forwardEvent('downloaderr');
Share.call(this, name, config);
};
// the RQShare prototype inherits from Share
util.inherits(RQShare, Share);
RQShare.prototype.notifyDownloadComplete = function (tree, path) {
var self = this;
if (self.waits[path]) {
var i;
for (i = 0; i < self.waits[path].length; i++) {
// invoke waiting callback
var waitCallback = self.waits[path][i].callback;
var waitTree = self.waits[path][i].tree;
logger.info('%s download complete, notifying waiting threads', path);
waitCallback();
}
self.waits[path] = [];
}
};
RQShare.prototype.waitOnDownload = function (tree, path, cb) {
var self = this;
if (self.isDownloading(tree, path)) {
logger.debug('%s is downloading, waiting for completion', path);
// wait for download
if (!self.waits[path]) {
self.waits[path] = [];
}
logger.info('waiting on file %s to download', path);
self.waits[path].push({ tree: tree, callback: cb });
} else {
// not downloading, return immediately
cb();
}
};
RQShare.prototype.isDownloading = function (tree, path) {
if (!tree.isTempFileName(path)) {
return this.downloadingFiles[path] ? true : false;
} else {
// temp files are never downloading
return false;
}
};
RQShare.prototype.setDownloading = function (tree, path, isDownloading) {
var wasDownloading = this.isDownloading(tree, path);
this.downloadingFiles[path] = isDownloading;
if (wasDownloading && !isDownloading) {
// the file is no longer downloading. remove lock
this.notifyDownloadComplete(tree, path);
}
};
RQShare.prototype.invalidateContentCache = function (path, deep) {
rqlog.debug('RQShare.invalidateContentCache %s', path);
if (this.remote.invalidateContentCache) {
this.remote.invalidateContentCache(path, deep);
}
this.listCache[path] = undefined;
};
RQShare.prototype.getListCache = function (path, tree, cb) {
if (this.listCache[path]) {
var now = new Date().getTime();
if (now - this.listCache[path].timestamp > this.contentCacheTTL) {
// cache is expired
rqlog.debug('RQShare.getListCache cache expired %s', path);
this.listCache[path] = undefined;
cb();
} else {
// cache is valid
var cache = this.listCache[path].files;
var addFile = function (index, files) {
if (index < cache.length) {
tree.open(cache[index], function (err, rqFile) {
if (err) {
cb(err);
} else {
files.push(rqFile);
addFile(index + 1, files);
}
});
} else {
cb(null, files);
}
};
addFile(0, []);
}
} else {
cb();
}
};
RQShare.prototype.cacheList = function (path, files) {
var names = [];
for (var i = 0; i < files.length; i++) {
names.push(files[i].getPath());
}
this.listCache[path] = {timestamp: new Date().getTime(), files: names};
};
RQShare.prototype.buildResourceUrl = function (path) {
return this.remote.buildResourceUrl(path);
};
RQShare.prototype.fetchResource = function (path, cb) {
this.remote.fetchResource(path, cb);
};
RQShare.prototype.applyRequestDefaults = function(opts, url) {
return this.remote.applyRequestDefaults(opts, url);
};
RQShare.prototype.createTree = function (remoteTree, config) {
return new RQTree(this, remoteTree, config);
};
//--------------------------------------------------------------------< Share >
/**
*
* @param {Session} session
* @param {Buffer|String} shareLevelPassword optional share-level password (may be null)
* @param {Function} cb callback called with the connect tree
* @param {SMBError} cb.error error (non-null if an error occurred)
* @param {Tree} cb.tree connected tree
*/
RQShare.prototype.connect = function (session, shareLevelPassword, cb) {
var self = this;
self.remote.connect(session, shareLevelPassword, function (err, remoteTree) {
if (err) {
cb(err);
} else {
cb(null, self.createTree(remoteTree, self.config));
}
});
};
module.exports = RQShare;