Selaa lähdekoodia

Split AttachmentStoreStrategy classes to new FileStoreStrategy classes

Martin Filser 3 vuotta sitten
vanhempi
sitoutus
883beba9ed

+ 2 - 2
client/components/cards/attachments.js

@@ -147,11 +147,11 @@ BlazeComponent.extendComponent({
           Popup.back();
         },
         'click .js-move-storage-fs'() {
-          Meteor.call('moveToStorage', this.data()._id, "fs");
+          Meteor.call('moveAttachmentToStorage', this.data()._id, "fs");
           Popup.back();
         },
         'click .js-move-storage-gridfs'() {
-          Meteor.call('moveToStorage', this.data()._id, "gridfs");
+          Meteor.call('moveAttachmentToStorage', this.data()._id, "gridfs");
           Popup.back();
         },
       }

+ 15 - 35
models/attachments.js

@@ -1,8 +1,17 @@
 import { Meteor } from 'meteor/meteor';
 import { FilesCollection } from 'meteor/ostrio:files';
+import { createBucket } from './lib/grid/createBucket';
 import fs from 'fs';
 import path from 'path';
-import AttachmentStoreStrategy from '/models/lib/attachmentStoreStrategy';
+import { AttachmentStoreStrategyFilesystem, AttachmentStoreStrategyGridFs} from '/models/lib/attachmentStoreStrategy';
+import FileStoreStrategyFactory, {moveToStorage} from '/models/lib/fileStoreStrategy';
+
+let attachmentBucket;
+if (Meteor.isServer) {
+  attachmentBucket = createBucket('attachments');
+}
+
+const fileStoreStrategyFactory = new FileStoreStrategyFactory(AttachmentStoreStrategyFilesystem, AttachmentStoreStrategyGridFs, attachmentBucket);
 
 // XXX Enforce a schema for the Attachments FilesCollection
 // see: https://github.com/VeliovGroup/Meteor-Files/wiki/Schema
@@ -26,17 +35,17 @@ Attachments = new FilesCollection({
   },
   onAfterUpload(fileObj) {
     Object.keys(fileObj.versions).forEach(versionName => {
-      AttachmentStoreStrategy.getFileStrategy(this, fileObj, versionName).onAfterUpload();
+      fileStoreStrategyFactory.getFileStrategy(this, fileObj, versionName).onAfterUpload();
     })
   },
   interceptDownload(http, fileObj, versionName) {
-    const ret = AttachmentStoreStrategy.getFileStrategy(this, fileObj, versionName).interceptDownload(http);
+    const ret = fileStoreStrategyFactory.getFileStrategy(this, fileObj, versionName).interceptDownload(http);
     return ret;
   },
   onAfterRemove(files) {
     files.forEach(fileObj => {
       Object.keys(fileObj.versions).forEach(versionName => {
-        AttachmentStoreStrategy.getFileStrategy(this, fileObj, versionName).onAfterRemove();
+        fileStoreStrategyFactory.getFileStrategy(this, fileObj, versionName).onAfterRemove();
       });
     });
   },
@@ -67,41 +76,12 @@ if (Meteor.isServer) {
   });
 
   Meteor.methods({
-    moveToStorage(fileObjId, storageDestination) {
+    moveAttachmentToStorage(fileObjId, storageDestination) {
       check(fileObjId, String);
       check(storageDestination, String);
 
       const fileObj = Attachments.findOne({_id: fileObjId});
-
-      Object.keys(fileObj.versions).forEach(versionName => {
-        const strategyRead = AttachmentStoreStrategy.getFileStrategy(this, fileObj, versionName);
-        const strategyWrite = AttachmentStoreStrategy.getFileStrategy(this, fileObj, versionName, storageDestination);
-
-        if (strategyRead.constructor.name != strategyWrite.constructor.name) {
-          const readStream = strategyRead.getReadStream();
-          const writeStream = strategyWrite.getWriteStream();
-
-          writeStream.on('error', error => {
-            console.error('[writeStream error]: ', error, fileObjId);
-          });
-
-          readStream.on('error', error => {
-            console.error('[readStream error]: ', error, fileObjId);
-          });
-
-          writeStream.on('finish', Meteor.bindEnvironment((finishedData) => {
-            strategyWrite.writeStreamFinished(finishedData);
-          }));
-
-          // https://forums.meteor.com/t/meteor-code-must-always-run-within-a-fiber-try-wrapping-callbacks-that-you-pass-to-non-meteor-libraries-with-meteor-bindenvironmen/40099/8
-          readStream.on('end', Meteor.bindEnvironment(() => {
-            Attachments.update({ _id: fileObj._id }, { $set: { [`versions.${versionName}.storage`]: strategyWrite.getStorageName() } });
-            strategyRead.unlink();
-          }));
-
-          readStream.pipe(writeStream);
-        }
-      });
+      moveToStorage(fileObj, storageDestination, fileStoreStrategyFactory);
     },
   });
 

+ 16 - 188
models/lib/attachmentStoreStrategy.js

@@ -1,9 +1,5 @@
 import fs from 'fs';
-import { createBucket } from './grid/createBucket';
-import { createObjectId } from './grid/createObjectId';
-import { createOnAfterUpload } from './fsHooks/createOnAfterUpload';
-import { createInterceptDownload } from './fsHooks/createInterceptDownload';
-import { createOnAfterRemove } from './fsHooks/createOnAfterRemove';
+import FileStoreStrategy, {FileStoreStrategyFilesystem, FileStoreStrategyGridFs} from './fileStoreStrategy'
 
 const insertActivity = (fileObj, activityType) =>
   Activities.insert({
@@ -18,27 +14,22 @@ const insertActivity = (fileObj, activityType) =>
     swimlaneId: fileObj.meta.swimlaneId,
   });
 
-let attachmentBucket;
-if (Meteor.isServer) {
-  attachmentBucket = createBucket('attachments');
-}
-
-/** Strategy to store attachments */
-class AttachmentStoreStrategy {
+/** Strategy to store attachments at GridFS (MongoDB) */
+export class AttachmentStoreStrategyGridFs extends FileStoreStrategyGridFs {
 
   /** constructor
+   * @param gridFsBucket use this GridFS Bucket
    * @param filesCollection the current FilesCollection instance
    * @param fileObj the current file object
    * @param versionName the current version
    */
-  constructor(filesCollection, fileObj, versionName) {
-    this.filesCollection = filesCollection;
-    this.fileObj = fileObj;
-    this.versionName = versionName;
+  constructor(gridFsBucket, filesCollection, fileObj, versionName) {
+    super(gridFsBucket, filesCollection, fileObj, versionName);
   }
 
   /** after successfull upload */
   onAfterUpload() {
+    super.onAfterUpload();
     // If the attachment doesn't have a source field or its source is different than import
     if (!this.fileObj.meta.source || this.fileObj.meta.source !== 'import') {
       // Add activity about adding the attachment
@@ -46,64 +37,15 @@ class AttachmentStoreStrategy {
     }
   }
 
-  /** download the file
-   * @param http the current http request
-   */
-  interceptDownload(http) {
-  }
-
   /** after file remove */
   onAfterRemove() {
+    super.onAfterRemove();
     insertActivity(this.fileObj, 'deleteAttachment');
   }
-
-  /** returns a read stream
-   * @return the read stream
-   */
-  getReadStream() {
-  }
-
-  /** returns a write stream
-   * @return the write stream
-   */
-  getWriteStream() {
-  }
-
-  /** writing finished
-   * @param finishedData the data of the write stream finish event
-   */
-  writeStreamFinished(finishedData) {
-  }
-
-  /** remove the file */
-  unlink() {
-  }
-
-  /** return the storage name
-   * @return the storage name
-   */
-  getStorageName() {
-  }
-
-  static getFileStrategy(filesCollection, fileObj, versionName, storage) {
-    if (!storage) {
-      storage = fileObj.versions[versionName].storage || "gridfs";
-    }
-    let ret;
-    if (["fs", "gridfs"].includes(storage)) {
-      if (storage == "fs") {
-        ret = new AttachmentStoreStrategyFilesystem(filesCollection, fileObj, versionName);
-      } else if (storage == "gridfs") {
-        ret = new AttachmentStoreStrategyGridFs(filesCollection, fileObj, versionName);
-      }
-    }
-    console.log("getFileStrategy: ", ret.constructor.name);
-    return ret;
-  }
 }
 
-/** Strategy to store attachments at GridFS (MongoDB) */
-class AttachmentStoreStrategyGridFs extends AttachmentStoreStrategy {
+/** Strategy to store attachments at filesystem */
+export class AttachmentStoreStrategyFilesystem extends FileStoreStrategyFilesystem {
 
   /** constructor
    * @param filesCollection the current FilesCollection instance
@@ -116,131 +58,17 @@ class AttachmentStoreStrategyGridFs extends AttachmentStoreStrategy {
 
   /** after successfull upload */
   onAfterUpload() {
-    createOnAfterUpload(this.filesCollection, attachmentBucket, this.fileObj, this.versionName);
     super.onAfterUpload();
-  }
-
-  /** download the file
-   * @param http the current http request
-   */
-  interceptDownload(http) {
-    const ret = createInterceptDownload(this.filesCollection, attachmentBucket, this.fileObj, http, this.versionName);
-    return ret;
+    // If the attachment doesn't have a source field or its source is different than import
+    if (!this.fileObj.meta.source || this.fileObj.meta.source !== 'import') {
+      // Add activity about adding the attachment
+      insertActivity(this.fileObj, 'addAttachment');
+    }
   }
 
   /** after file remove */
   onAfterRemove() {
-    this.unlink();
     super.onAfterRemove();
-  }
-
-  /** returns a read stream
-   * @return the read stream
-   */
-  getReadStream() {
-    const gridFsFileId = (this.fileObj.versions[this.versionName].meta || {})
-      .gridFsFileId;
-    let ret;
-    if (gridFsFileId) {
-      const gfsId = createObjectId({ gridFsFileId });
-      ret = attachmentBucket.openDownloadStream(gfsId);
-    }
-    return ret;
-  }
-
-  /** returns a write stream
-   * @return the write stream
-   */
-  getWriteStream() {
-    const fileObj = this.fileObj;
-    const versionName = this.versionName;
-    const metadata = { ...fileObj.meta, versionName, fileId: fileObj._id };
-    const ret = attachmentBucket.openUploadStream(this.fileObj.name, {
-      contentType: fileObj.type || 'binary/octet-stream',
-      metadata,
-    });
-    return ret;
-  }
-
-  /** writing finished
-   * @param finishedData the data of the write stream finish event
-   */
-  writeStreamFinished(finishedData) {
-    const gridFsFileIdName = this.getGridFsFileIdName();
-    Attachments.update({ _id: this.fileObj._id }, { $set: { [gridFsFileIdName]: finishedData._id.toHexString(), } });
-  }
-
-  /** remove the file */
-  unlink() {
-    createOnAfterRemove(this.filesCollection, attachmentBucket, this.fileObj, this.versionName);
-    const gridFsFileIdName = this.getGridFsFileIdName();
-    Attachments.update({ _id: this.fileObj._id }, { $unset: { [gridFsFileIdName]: 1 } });
-  }
-
-  /** return the storage name
-   * @return the storage name
-   */
-  getStorageName() {
-    return "gridfs";
-  }
-
-  /** returns the property name of gridFsFileId
-   * @return the property name of gridFsFileId
-   */
-  getGridFsFileIdName() {
-    const ret = `versions.${this.versionName}.meta.gridFsFileId`;
-    return ret;
-  }
-}
-
-/** Strategy to store attachments at filesystem */
-class AttachmentStoreStrategyFilesystem extends AttachmentStoreStrategy {
-
-  /** constructor
-   * @param filesCollection the current FilesCollection instance
-   * @param fileObj the current file object
-   * @param versionName the current version
-   */
-  constructor(filesCollection, fileObj, versionName) {
-    super(filesCollection, fileObj, versionName);
-  }
-
-  /** returns a read stream
-   * @return the read stream
-   */
-  getReadStream() {
-    const ret = fs.createReadStream(this.fileObj.versions[this.versionName].path)
-    return ret;
-  }
-
-  /** returns a write stream
-   * @return the write stream
-   */
-  getWriteStream() {
-    const newFileName = this.fileObj.name;
-    const filePath = this.fileObj.versions[this.versionName].path;
-    const ret = fs.createWriteStream(filePath);
-    return ret;
-  }
-
-  /** writing finished
-   * @param finishedData the data of the write stream finish event
-   */
-  writeStreamFinished(finishedData) {
-  }
-
-  /** remove the file */
-  unlink() {
-    const filePath = this.fileObj.versions[this.versionName].path;
-    fs.unlink(filePath, () => {});
-  }
-
-  /** return the storage name
-   * @return the storage name
-   */
-  getStorageName() {
-    return "fs";
+    insertActivity(this.fileObj, 'deleteAttachment');
   }
 }
-
-export default AttachmentStoreStrategy;

+ 277 - 0
models/lib/fileStoreStrategy.js

@@ -0,0 +1,277 @@
+import fs from 'fs';
+import { createObjectId } from './grid/createObjectId';
+import { createOnAfterUpload } from './fsHooks/createOnAfterUpload';
+import { createInterceptDownload } from './fsHooks/createInterceptDownload';
+import { createOnAfterRemove } from './fsHooks/createOnAfterRemove';
+
+/** Factory for FileStoreStrategy */
+export default class FileStoreStrategyFactory {
+
+  /** constructor
+   * @param classFileStoreStrategyFilesystem use this strategy for filesystem storage
+   * @param classFileStoreStrategyGridFs use this strategy for GridFS storage
+   * @param gridFsBucket use this GridFS Bucket as GridFS Storage
+   */
+  constructor(classFileStoreStrategyFilesystem, classFileStoreStrategyGridFs, gridFsBucket) {
+    this.classFileStoreStrategyFilesystem = classFileStoreStrategyFilesystem;
+    this.classFileStoreStrategyGridFs = classFileStoreStrategyGridFs;
+    this.gridFsBucket = gridFsBucket;
+  }
+
+  /** returns the right FileStoreStrategy
+   * @param filesCollection the current FilesCollection instance
+   * @param fileObj the current file object
+   * @param versionName the current version
+   * @param use this storage, or if not set, get the storage from fileObj
+   */
+  getFileStrategy(filesCollection, fileObj, versionName, storage) {
+    if (!storage) {
+      storage = fileObj.versions[versionName].storage || "gridfs";
+    }
+    let ret;
+    if (["fs", "gridfs"].includes(storage)) {
+      if (storage == "fs") {
+        ret = new this.classFileStoreStrategyFilesystem(filesCollection, fileObj, versionName);
+      } else if (storage == "gridfs") {
+        ret = new this.classFileStoreStrategyGridFs(this.gridFsBucket, filesCollection, fileObj, versionName);
+      }
+    }
+    return ret;
+  }
+}
+
+/** Strategy to store files */
+class FileStoreStrategy {
+
+  /** constructor
+   * @param filesCollection the current FilesCollection instance
+   * @param fileObj the current file object
+   * @param versionName the current version
+   */
+  constructor(filesCollection, fileObj, versionName) {
+    this.filesCollection = filesCollection;
+    this.fileObj = fileObj;
+    this.versionName = versionName;
+  }
+
+  /** after successfull upload */
+  onAfterUpload() {
+  }
+
+  /** download the file
+   * @param http the current http request
+   */
+  interceptDownload(http) {
+  }
+
+  /** after file remove */
+  onAfterRemove() {
+  }
+
+  /** returns a read stream
+   * @return the read stream
+   */
+  getReadStream() {
+  }
+
+  /** returns a write stream
+   * @return the write stream
+   */
+  getWriteStream() {
+  }
+
+  /** writing finished
+   * @param finishedData the data of the write stream finish event
+   */
+  writeStreamFinished(finishedData) {
+  }
+
+  /** remove the file */
+  unlink() {
+  }
+
+  /** return the storage name
+   * @return the storage name
+   */
+  getStorageName() {
+  }
+}
+
+/** Strategy to store attachments at GridFS (MongoDB) */
+export class FileStoreStrategyGridFs extends FileStoreStrategy {
+
+  /** constructor
+   * @param gridFsBucket use this GridFS Bucket
+   * @param filesCollection the current FilesCollection instance
+   * @param fileObj the current file object
+   * @param versionName the current version
+   */
+  constructor(gridFsBucket, filesCollection, fileObj, versionName) {
+    super(filesCollection, fileObj, versionName);
+    this.gridFsBucket = gridFsBucket;
+  }
+
+  /** after successfull upload */
+  onAfterUpload() {
+    createOnAfterUpload(this.filesCollection, this.gridFsBucket, this.fileObj, this.versionName);
+    super.onAfterUpload();
+  }
+
+  /** download the file
+   * @param http the current http request
+   */
+  interceptDownload(http) {
+    const ret = createInterceptDownload(this.filesCollection, this.gridFsBucket, this.fileObj, http, this.versionName);
+    return ret;
+  }
+
+  /** after file remove */
+  onAfterRemove() {
+    this.unlink();
+    super.onAfterRemove();
+  }
+
+  /** returns a read stream
+   * @return the read stream
+   */
+  getReadStream() {
+    const gridFsFileId = (this.fileObj.versions[this.versionName].meta || {})
+      .gridFsFileId;
+    let ret;
+    if (gridFsFileId) {
+      const gfsId = createObjectId({ gridFsFileId });
+      ret = this.gridFsBucket.openDownloadStream(gfsId);
+    }
+    return ret;
+  }
+
+  /** returns a write stream
+   * @return the write stream
+   */
+  getWriteStream() {
+    const fileObj = this.fileObj;
+    const versionName = this.versionName;
+    const metadata = { ...fileObj.meta, versionName, fileId: fileObj._id };
+    const ret = this.gridFsBucket.openUploadStream(this.fileObj.name, {
+      contentType: fileObj.type || 'binary/octet-stream',
+      metadata,
+    });
+    return ret;
+  }
+
+  /** writing finished
+   * @param finishedData the data of the write stream finish event
+   */
+  writeStreamFinished(finishedData) {
+    const gridFsFileIdName = this.getGridFsFileIdName();
+    Attachments.update({ _id: this.fileObj._id }, { $set: { [gridFsFileIdName]: finishedData._id.toHexString(), } });
+  }
+
+  /** remove the file */
+  unlink() {
+    createOnAfterRemove(this.filesCollection, this.gridFsBucket, this.fileObj, this.versionName);
+    const gridFsFileIdName = this.getGridFsFileIdName();
+    Attachments.update({ _id: this.fileObj._id }, { $unset: { [gridFsFileIdName]: 1 } });
+  }
+
+  /** return the storage name
+   * @return the storage name
+   */
+  getStorageName() {
+    return "gridfs";
+  }
+
+  /** returns the property name of gridFsFileId
+   * @return the property name of gridFsFileId
+   */
+  getGridFsFileIdName() {
+    const ret = `versions.${this.versionName}.meta.gridFsFileId`;
+    return ret;
+  }
+}
+
+/** Strategy to store attachments at filesystem */
+export class FileStoreStrategyFilesystem extends FileStoreStrategy {
+
+  /** constructor
+   * @param filesCollection the current FilesCollection instance
+   * @param fileObj the current file object
+   * @param versionName the current version
+   */
+  constructor(filesCollection, fileObj, versionName) {
+    super(filesCollection, fileObj, versionName);
+  }
+
+  /** returns a read stream
+   * @return the read stream
+   */
+  getReadStream() {
+    const ret = fs.createReadStream(this.fileObj.versions[this.versionName].path)
+    return ret;
+  }
+
+  /** returns a write stream
+   * @return the write stream
+   */
+  getWriteStream() {
+    const filePath = this.fileObj.versions[this.versionName].path;
+    const ret = fs.createWriteStream(filePath);
+    return ret;
+  }
+
+  /** writing finished
+   * @param finishedData the data of the write stream finish event
+   */
+  writeStreamFinished(finishedData) {
+  }
+
+  /** remove the file */
+  unlink() {
+    const filePath = this.fileObj.versions[this.versionName].path;
+    fs.unlink(filePath, () => {});
+  }
+
+  /** return the storage name
+   * @return the storage name
+   */
+  getStorageName() {
+    return "fs";
+  }
+}
+
+/** move the fileObj to another storage
+ * @param fileObj move this fileObj to another storage
+ * @param storageDestination the storage destination (fs or gridfs)
+ * @param fileStoreStrategyFactory get FileStoreStrategy from this factory
+ */
+export const moveToStorage = function(fileObj, storageDestination, fileStoreStrategyFactory) {
+  Object.keys(fileObj.versions).forEach(versionName => {
+    const strategyRead = fileStoreStrategyFactory.getFileStrategy(this, fileObj, versionName);
+    const strategyWrite = fileStoreStrategyFactory.getFileStrategy(this, fileObj, versionName, storageDestination);
+
+    if (strategyRead.constructor.name != strategyWrite.constructor.name) {
+      const readStream = strategyRead.getReadStream();
+      const writeStream = strategyWrite.getWriteStream();
+
+      writeStream.on('error', error => {
+        console.error('[writeStream error]: ', error, fileObjId);
+      });
+
+      readStream.on('error', error => {
+        console.error('[readStream error]: ', error, fileObjId);
+      });
+
+      writeStream.on('finish', Meteor.bindEnvironment((finishedData) => {
+        strategyWrite.writeStreamFinished(finishedData);
+      }));
+
+      // https://forums.meteor.com/t/meteor-code-must-always-run-within-a-fiber-try-wrapping-callbacks-that-you-pass-to-non-meteor-libraries-with-meteor-bindenvironmen/40099/8
+      readStream.on('end', Meteor.bindEnvironment(() => {
+        Attachments.update({ _id: fileObj._id }, { $set: { [`versions.${versionName}.storage`]: strategyWrite.getStorageName() } });
+        strategyRead.unlink();
+      }));
+
+      readStream.pipe(writeStream);
+    }
+  });
+};