mirror of https://github.com/wekan/wekan
The Open Source kanban (built with Meteor). Keep variable/table/field names camelCase. For translations, only add Pull Request changes to wekan/i18n/en.i18n.json , other translations are done at https://transifex.com/wekan/wekan only.
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
157 lines
5.5 KiB
157 lines
5.5 KiB
var fs = Npm.require('fs');
|
|
var path = Npm.require('path');
|
|
var mkdirp = Npm.require('mkdirp');
|
|
//var chokidar = Npm.require('chokidar');
|
|
|
|
FS.Store.FileSystem = function(name, options) {
|
|
var self = this;
|
|
if (!(self instanceof FS.Store.FileSystem))
|
|
throw new Error('FS.Store.FileSystem missing keyword "new"');
|
|
|
|
// We allow options to be string/path empty or options.path
|
|
options = (options !== ''+options) ? options || {} : { path: options };
|
|
|
|
// Provide a default FS directory one level up from the build/bundle directory
|
|
var pathname = options.path;
|
|
if (!pathname && __meteor_bootstrap__ && __meteor_bootstrap__.serverDir) {
|
|
pathname = path.join(__meteor_bootstrap__.serverDir, '../../../cfs/files/' + name);
|
|
}
|
|
|
|
if (!pathname)
|
|
throw new Error('FS.Store.FileSystem unable to determine path');
|
|
|
|
// Check if we have '~/foo/bar'
|
|
if (pathname.split(path.sep)[0] === '~') {
|
|
var homepath = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
|
|
if (homepath) {
|
|
pathname = pathname.replace('~', homepath);
|
|
} else {
|
|
throw new Error('FS.Store.FileSystem unable to resolve "~" in path');
|
|
}
|
|
}
|
|
|
|
// Set absolute path
|
|
var absolutePath = path.resolve(pathname);
|
|
|
|
// Ensure the path exists
|
|
mkdirp.sync(absolutePath);
|
|
FS.debug && console.log(name + ' FileSystem mounted on: ' + absolutePath);
|
|
|
|
return new FS.StorageAdapter(name, options, {
|
|
typeName: 'storage.filesystem',
|
|
fileKey: function(fileObj) {
|
|
// Lookup the copy
|
|
var store = fileObj && fileObj._getInfo(name);
|
|
// If the store and key is found return the key
|
|
if (store && store.key) return store.key;
|
|
|
|
var filename = fileObj.name();
|
|
var filenameInStore = fileObj.name({store: name});
|
|
|
|
// If no store key found we resolve / generate a key
|
|
return fileObj.collectionName + '-' + fileObj._id + '-' + (filenameInStore || filename);
|
|
},
|
|
createReadStream: function(fileKey, options) {
|
|
// this is the Storage adapter scope
|
|
var filepath = path.join(absolutePath, fileKey);
|
|
|
|
// return the read stream - Options allow { start, end }
|
|
return fs.createReadStream(filepath, options);
|
|
},
|
|
createWriteStream: function(fileKey, options) {
|
|
options = options || {};
|
|
|
|
// this is the Storage adapter scope
|
|
var filepath = path.join(absolutePath, fileKey);
|
|
|
|
// Return the stream handle
|
|
var writeStream = fs.createWriteStream(filepath, options);
|
|
|
|
// The filesystem does not emit the "end" event only close - so we
|
|
// manually send the end event
|
|
writeStream.on('close', function() {
|
|
if (FS.debug) console.log('SA FileSystem - DONE!! fileKey: "' + fileKey + '"');
|
|
|
|
// Get the exact size of the stored file, so that we can pass it to onEnd/onStored.
|
|
// Since stream transforms might have altered the size, this is the best way to
|
|
// ensure we update the fileObj.copies with the correct size.
|
|
try {
|
|
// Get the stats of the file
|
|
var stats = fs.statSync(filepath);
|
|
|
|
// Emit end and return the fileKey, size, and updated date
|
|
writeStream.emit('stored', {
|
|
fileKey: fileKey,
|
|
size: stats.size,
|
|
storedAt: stats.mtime
|
|
});
|
|
|
|
} catch(err) {
|
|
// On error we emit the error on
|
|
writeStream.emit('error', err);
|
|
}
|
|
});
|
|
|
|
return writeStream;
|
|
},
|
|
remove: function(fileKey, callback) {
|
|
// this is the Storage adapter scope
|
|
var filepath = path.join(absolutePath, fileKey);
|
|
|
|
// Call node unlink file
|
|
fs.unlink(filepath, function (error, result) {
|
|
if (error && error.errno === 34) {
|
|
console.warn("SA FileSystem: Could not delete " + filepath + " because the file was not found.");
|
|
callback && callback(null);
|
|
} else {
|
|
callback && callback(error, result);
|
|
}
|
|
});
|
|
},
|
|
stats: function(fileKey, callback) {
|
|
// this is the Storage adapter scope
|
|
var filepath = path.join(absolutePath, fileKey);
|
|
if (typeof callback === 'function') {
|
|
fs.stat(filepath, callback);
|
|
} else {
|
|
return fs.statSync(filepath);
|
|
}
|
|
}
|
|
// Add this back and add the chokidar dependency back when we make this work eventually
|
|
// watch: function(callback) {
|
|
// function fileKey(filePath) {
|
|
// return filePath.replace(absolutePath, "");
|
|
// }
|
|
|
|
// FS.debug && console.log('Watching ' + absolutePath);
|
|
|
|
// // chokidar seems to be most widely used and production ready watcher
|
|
// var watcher = chokidar.watch(absolutePath, {ignored: /\/\./, ignoreInitial: true});
|
|
// watcher.on('add', Meteor.bindEnvironment(function(filePath, stats) {
|
|
// callback("change", fileKey(filePath), {
|
|
// name: path.basename(filePath),
|
|
// type: null,
|
|
// size: stats.size,
|
|
// utime: stats.mtime
|
|
// });
|
|
// }, function(err) {
|
|
// throw err;
|
|
// }));
|
|
// watcher.on('change', Meteor.bindEnvironment(function(filePath, stats) {
|
|
// callback("change", fileKey(filePath), {
|
|
// name: path.basename(filePath),
|
|
// type: null,
|
|
// size: stats.size,
|
|
// utime: stats.mtime
|
|
// });
|
|
// }, function(err) {
|
|
// throw err;
|
|
// }));
|
|
// watcher.on('unlink', Meteor.bindEnvironment(function(filePath) {
|
|
// callback("remove", fileKey(filePath));
|
|
// }, function(err) {
|
|
// throw err;
|
|
// }));
|
|
// }
|
|
});
|
|
};
|
|
|