filesystem.server.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. var fs = Npm.require('fs');
  2. var path = Npm.require('path');
  3. var mkdirp = Npm.require('mkdirp');
  4. //var chokidar = Npm.require('chokidar');
  5. FS.Store.FileSystem = function(name, options) {
  6. var self = this;
  7. if (!(self instanceof FS.Store.FileSystem))
  8. throw new Error('FS.Store.FileSystem missing keyword "new"');
  9. // We allow options to be string/path empty or options.path
  10. options = (options !== ''+options) ? options || {} : { path: options };
  11. // Provide a default FS directory one level up from the build/bundle directory
  12. var pathname = options.path;
  13. if (!pathname && __meteor_bootstrap__ && __meteor_bootstrap__.serverDir) {
  14. pathname = path.join(__meteor_bootstrap__.serverDir, '../../../cfs/files/' + name);
  15. }
  16. if (!pathname)
  17. throw new Error('FS.Store.FileSystem unable to determine path');
  18. // Check if we have '~/foo/bar'
  19. if (pathname.split(path.sep)[0] === '~') {
  20. var homepath = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
  21. if (homepath) {
  22. pathname = pathname.replace('~', homepath);
  23. } else {
  24. throw new Error('FS.Store.FileSystem unable to resolve "~" in path');
  25. }
  26. }
  27. // Set absolute path
  28. var absolutePath = path.resolve(pathname);
  29. // Ensure the path exists
  30. mkdirp.sync(absolutePath);
  31. FS.debug && console.log(name + ' FileSystem mounted on: ' + absolutePath);
  32. return new FS.StorageAdapter(name, options, {
  33. typeName: 'storage.filesystem',
  34. fileKey: function(fileObj) {
  35. // Lookup the copy
  36. var store = fileObj && fileObj._getInfo(name);
  37. // If the store and key is found return the key
  38. if (store && store.key) return store.key;
  39. var filename = fileObj.name();
  40. var filenameInStore = fileObj.name({store: name});
  41. // If no store key found we resolve / generate a key
  42. return fileObj.collectionName + '-' + fileObj._id + '-' + (filenameInStore || filename);
  43. },
  44. createReadStream: function(fileKey, options) {
  45. // this is the Storage adapter scope
  46. var filepath = path.join(absolutePath, fileKey);
  47. // return the read stream - Options allow { start, end }
  48. return fs.createReadStream(filepath, options);
  49. },
  50. createWriteStream: function(fileKey, options) {
  51. options = options || {};
  52. // this is the Storage adapter scope
  53. var filepath = path.join(absolutePath, fileKey);
  54. // Return the stream handle
  55. var writeStream = fs.createWriteStream(filepath, options);
  56. // The filesystem does not emit the "end" event only close - so we
  57. // manually send the end event
  58. writeStream.on('close', function() {
  59. if (FS.debug) console.log('SA FileSystem - DONE!! fileKey: "' + fileKey + '"');
  60. // Get the exact size of the stored file, so that we can pass it to onEnd/onStored.
  61. // Since stream transforms might have altered the size, this is the best way to
  62. // ensure we update the fileObj.copies with the correct size.
  63. try {
  64. // Get the stats of the file
  65. var stats = fs.statSync(filepath);
  66. // Emit end and return the fileKey, size, and updated date
  67. writeStream.emit('stored', {
  68. fileKey: fileKey,
  69. size: stats.size,
  70. storedAt: stats.mtime
  71. });
  72. } catch(err) {
  73. // On error we emit the error on
  74. writeStream.emit('error', err);
  75. }
  76. });
  77. return writeStream;
  78. },
  79. remove: function(fileKey, callback) {
  80. // this is the Storage adapter scope
  81. var filepath = path.join(absolutePath, fileKey);
  82. // Call node unlink file
  83. fs.unlink(filepath, function (error, result) {
  84. if (error && error.errno === 34) {
  85. console.warn("SA FileSystem: Could not delete " + filepath + " because the file was not found.");
  86. callback && callback(null);
  87. } else {
  88. callback && callback(error, result);
  89. }
  90. });
  91. },
  92. stats: function(fileKey, callback) {
  93. // this is the Storage adapter scope
  94. var filepath = path.join(absolutePath, fileKey);
  95. if (typeof callback === 'function') {
  96. fs.stat(filepath, callback);
  97. } else {
  98. return fs.statSync(filepath);
  99. }
  100. }
  101. // Add this back and add the chokidar dependency back when we make this work eventually
  102. // watch: function(callback) {
  103. // function fileKey(filePath) {
  104. // return filePath.replace(absolutePath, "");
  105. // }
  106. // FS.debug && console.log('Watching ' + absolutePath);
  107. // // chokidar seems to be most widely used and production ready watcher
  108. // var watcher = chokidar.watch(absolutePath, {ignored: /\/\./, ignoreInitial: true});
  109. // watcher.on('add', Meteor.bindEnvironment(function(filePath, stats) {
  110. // callback("change", fileKey(filePath), {
  111. // name: path.basename(filePath),
  112. // type: null,
  113. // size: stats.size,
  114. // utime: stats.mtime
  115. // });
  116. // }, function(err) {
  117. // throw err;
  118. // }));
  119. // watcher.on('change', Meteor.bindEnvironment(function(filePath, stats) {
  120. // callback("change", fileKey(filePath), {
  121. // name: path.basename(filePath),
  122. // type: null,
  123. // size: stats.size,
  124. // utime: stats.mtime
  125. // });
  126. // }, function(err) {
  127. // throw err;
  128. // }));
  129. // watcher.on('unlink', Meteor.bindEnvironment(function(filePath) {
  130. // callback("remove", fileKey(filePath));
  131. // }, function(err) {
  132. // throw err;
  133. // }));
  134. // }
  135. });
  136. };