Giter Club home page Giter Club logo

meteor-collectionfs's Introduction

CollectionFS

Looking for maintainers - please reach out! This package is to be archived due to inability to find contributors, thanks to everyone who helped make it possible.

If you're looking for an alternative, we highly recommend Meteor-Files by VeliovGroup

--

CollectionFS is a suite of Meteor packages that together provide a complete file management solution including uploading, downloading, storage, synchronization, manipulation, and copying. It supports several storage adapters for saving to the local filesystem, GridFS, or S3, and additional storage adapters can be created.

Build Status Join the chat at https://gitter.im/CollectionFS/Meteor-CollectionFS

Victor Leung wrote a great quick start guide for the most basic image uploading and displaying task.

Check out the Wiki for more information, code examples and how-tos.

  • Updated cfs:grid mongodb dependency to 2.2.4 (Meteor 1.4)

Table of Contents

Table of Contents generated with DocToc

Important Notes

Cordova Android Bug with Meteor 1.2+

Due to a bug in the Cordova Android version that is used with Meteor 1.2, you will need to add the following to your mobile-config.js or you will have problems with this package on Android devices:

App.accessRule("blob:*");

Documentation Feedback

If you have Documentation feedback/requests please post on issue 206

Installation

Only Meteor 0.9.0 and later are currently supported

$ cd <app dir>

You must add cfs:standard-packages, which is the main package:

$ meteor add cfs:standard-packages

You must add at least one storage adapter package. See the Storage Adapters section for a list of the available storage adapter packages. At least cfs:gridfs or cfs:filesystem must be added, too, even if you are not using them. The temporary store requires one of them.

$ meteor add cfs:gridfs

# OR

$ meteor add cfs:filesystem

# OR

$ meteor add cfs:s3

# OR

$ meteor add cfs:dropbox

# OR

$ meteor add iyyang:cfs-aliyun

Depending on what you need to do, you may need to add additional add-on packages. These are explained in the documentation sections to which they apply.

$ meteor add <CFS add-on package name>

Introduction

The CollectionFS package makes available two important global variables: FS.File and FS.Collection.

  • An FS.File instance wraps a file and its data on the client or server. It is similar to the browser File object (and can be created from a File object), but it has additional properties and methods. Many of its methods are reactive when the instance is returned by a call to find or findOne.
  • An FS.Collection provides a collection in which information about files can be stored. It is backed by an underlying normal Mongo.Collection instance. Most collection methods, such as find and insert are available on the FS.Collection instance. If you need to call other collection methods such as _ensureIndex, you can call them directly on the underlying Mongo.Collection instance available through myFSCollection.files.

A document from a FS.Collection is represented as a FS.File.

CollectionFS also provides an HTTP upload package that has the necessary mechanisms to upload files, track upload progress reactively, and pause and resume uploads.

Getting Started

The first step in using this package is to define a FS.Collection.

Create the FS Collection and Filestore

common.js:

Images = new FS.Collection("images", {
  stores: [new FS.Store.FileSystem("images", {path: "~/uploads"})]
});

In this example, we've defined a FS.Collection named "images", which will be a new collection in your MongoDB database with the name "cfs.images.filerecord". We've also told it to use the filesystem storage adataper and store the files in ~/uploads on the local filesystem. If you don't specify a path, a cfs/files folder in your app container (bundle directory) will be used.

Your FS.Collection and FS.Store variables do not necessarily have to be global on the client or the server, but be sure to give them the same name (the first argument in each constructor) on both the client and the server.

Adding upload permissions (insert)

To allow users to submit files to the FS Collection, you must create an allow rule in Server code:

server.js or within Meteor.isServer block:

Images.allow({
  'insert': function () {
    // add custom authentication code here
    return true;
  }
});

Initiate the upload

Now we can upload a file from the client. Here is an example of doing so from the change event handler of an HTML file input:

Template.myForm.events({
  'change .myFileInput': function(event, template) {
    var files = event.target.files;
    for (var i = 0, ln = files.length; i < ln; i++) {
      Images.insert(files[i], function (err, fileObj) {
        // Inserted new doc with ID fileObj._id, and kicked off the data upload using HTTP
      });
    }
  }
});

You can optionally make this code a bit cleaner by using a provided utility method, FS.Utility.eachFile:

Template.myForm.events({
  'change .myFileInput': function(event, template) {
    FS.Utility.eachFile(event, function(file) {
      Images.insert(file, function (err, fileObj) {
        // Inserted new doc with ID fileObj._id, and kicked off the data upload using HTTP
      });
    });
  }
});

Notice that the only thing we're doing is passing the browser-provided File object to Images.insert(). This will create a FS.File from the File, link it with the Images FS.Collection, and then immediately begin uploading the data to the server with reactive progress updates.

The insert method can directly accept a variety of different file representations as its first argument:

  • File object (client only)
  • Blob object (client only)
  • Uint8Array
  • ArrayBuffer
  • Buffer (server only)
  • A full URL that begins with "http:" or "https:"
  • A local filepath (server only)
  • A data URI string

Where possible, streams are used, so in general you should avoid using any of the buffer/binary options unless you have no choice, perhaps because you are generating small files in memory.

The most common usage is to pass a File object on the client or a URL on either the client or the server. Note that when you pass a URL on the client, the actual data download from that URL happens on the server, so you don't need to worry about CORS. In fact, we recommend doing all inserts on the client (managing security through allow/deny), unless you are generating the data on the server.

Using insert Properly

When you need to insert a file that's located on a client, always call myFSCollection.insert on the client. While you could define your own method, pass it the fsFile, and call myFSCollection.insert on the server, the difficulty is with getting the data from the client to the server. When you pass the fsFile to your method, only the file info is sent and not the data.

By contrast, when you do the insert directly on the client, it automatically chunks the file's data after insert, and then queues it to be sent chunk by chunk to the server. And then there is the matter of recombining all those chunks on the server and stuffing the data back into the fsFile. So doing client-side inserts actually saves you all of this complex work, and that's why we recommend it.

Calling insert on the server should be done only when you have the file somewhere on the server filesystem already or you're generating the data on the server.

After the Upload

After the server receives the FS.File and all the corresponding binary file data, it saves copies of the file in the stores that you specified.

If any storage adapters fail to save any of the copies in the designated store, the server will periodically retry saving them. After a configurable number of failed attempts at saving, the server will give up.

To configure the maximum number of save attempts, use the maxTries option when creating your store. The default is 5.

Storage Adapters

Storage adapters handle retrieving the file data and removing the file data when you delete the file. There are currently four available storage adapters, which are in separate packages. Refer to the package documentation for usage instructions.

Securing sensitive information

If you're using a storage adapter that requires sensitive information such as access keys, we recommend supplying that information using environment variables. If you instead decide to pass options to the storage adapter constructor, then be sure that you do that only in the server code (and not simply within a Meteor.isServer block).

File Manipulation

You may want to manipulate files before saving them. For example, if a user uploads a large image, you may want to reduce its resolution, crop it, compress it, etc. before allowing the storage adapter to save it. You may also want to convert to another content type or change the filename or encrypt the file. You can do all of this by defining stream transformations on a store.

Note: Transforms only work on the server-side code

transformWrite / transformRead

The most common type of transformation is a "write" transformation, that is, a function that changes the data as it is initially stored. You can define this function using the transformWrite option on any store constructor. If the transformation requires a companion transformation when the data is later read out of the store (such as encrypt/decrypt), you can define a transformRead function as well.

For illustration purposes, here is an example of a transformWrite function that doesn't do anything:

transformWrite: function(fileObj, readStream, writeStream) {
  readStream.pipe(writeStream);
}

The important thing is that you must pipe the readStream to the writeStream before returning from the function. Generally you will manipulate the stream in some way before piping it.

beforeWrite

Sometimes you also need to change a file's metadata before it is saved to a particular store. For example, you might have a transformWrite function that changes the file type, so you need a beforeWrite function that changes the extension and content type to match.

The simplest type of beforeWrite function will return an object with extension, name, or type properties. For example:

beforeWrite: function (fileObj) {
  return {
    extension: 'jpg',
    type: 'image/jpg'
  };
}

This would change the extension and type for that particular store.

Since beforeWrite is passed the fileObj, you can optionally alter that directly. For example, the following would be the same as the previous example assuming the store name is "jpegs":

beforeWrite: function (fileObj) {
  fileObj.extension('jpg', {store: "jpegs", save: false});
  fileObj.type('image/jpg', {store: "jpegs", save: false});
}

(It's best to provide the save: false option to any of the setters you call in beforeWrite.)

Image Manipulation

A common use for transformWrite is to manipulate images before saving them. To get this set up:

  1. Install GraphicsMagick or ImageMagick on your development machine and on any server that will host your app. (The free Meteor deployment servers do not have either of these, so you can't deploy to there.) These are normal operating system applications, so you have to install them using the correct method for your OS. For example, on Mac OSX you can use brew install graphicsmagick assuming you have Homebrew installed.
  2. Add the cfs:graphicsmagick Meteor package to your app: meteor add cfs:graphicsmagick

Basic Example

var createThumb = function(fileObj, readStream, writeStream) {
  // Transform the image into a 10x10px thumbnail
  gm(readStream, fileObj.name()).resize('10', '10').stream().pipe(writeStream);
};

Images = new FS.Collection("images", {
  stores: [
    new FS.Store.FileSystem("thumbs", { transformWrite: createThumb }),
    new FS.Store.FileSystem("images"),
  ],
  filter: {
    allow: {
      contentTypes: ['image/*'] //allow only images in this FS.Collection
    }
  }
});

Check out the Wiki for more examples and How-tos.

Optimizing

  • When you insert a file, a worker begins saving copies of it to all of the stores you define for the collection. The copies are saved to stores in the order you list them in the stores option array. Thus, you may want to prioritize certain stores by listing them first. For example, if you have an images collection with a thumbnail store and a large-size store, you may want to list the thumbnail store first to ensure that thumbnails appear on screen as soon as possible after inserting a new file. Or if you are storing audio files, you may want to prioritize a "sample" store over a "full-length" store.

Filtering

You may specify filters to allow (or deny) only certain content types, file extensions or file sizes in a FS.Collection with the filter option:

Images = new FS.Collection("images", {
  filter: {
    maxSize: 1048576, // in bytes
    allow: {
      contentTypes: ['image/*'],
      extensions: ['png']
    },
    deny: {
      contentTypes: ['image/*'],
      extensions: ['png']
    },
    onInvalid: function (message) {
      if (Meteor.isClient) {
        alert(message);
      } else {
        console.log(message);
      }
    }
  }
});

Alternatively, you can pass your filters object to myFSCollection.filters().

To be secure, this must be added on the server. However, you should use the filter option on the client, too, to help catch many of the disallowed uploads there and allow you to display a helpful message with your onInvalid function.

You can mix and match filtering based on extension or content types. The contentTypes array also supports "image/*" and "audio/*" and "video/*" like the "accepts" attribute on the HTML5 file input element.

If a file extension or content type matches any of those listed in allow, it is allowed. If not, it is denied. If it matches both allow and deny, it is denied. Typically, you would use only allow or only deny, but not both. If you do not pass the filter option, all files are allowed, as long as they pass the tests in your FS.Collection.allow() and FS.Collection.deny() functions.

The extension checks are used only when there is a filename. It's possible to upload a file with no name. Thus, you should generally use extension checks only in addition to content type checks, and not instead of content type checks.

The file extensions must be specified without a leading period. Extension matching is case-insensitive.

An FS.File Instance

An FS.File instance is an object with properties similar to this:

{
  _id: '',
  collectionName: '', // this property not stored in DB
  collection: collectionInstance, // this property not stored in DB
  createdByTransform: true, // this property not stored in DB
  data: data, // this property not stored in DB
  original: {
    name: '',
    size: 0,
    type: '',
    updatedAt: date
  },
  copies: {
    storeName: {
      key: '',
      name: '',
      size: 0,
      type: '',
      createdAt: date,
      updatedAt: date
    }
  },
  uploadedAt: date,
  anyUserDefinedProp: anything
}

But name, size, type, and updatedAt should be retrieved and set with the methods rather than directly accessing the props:

// get original
fileObj.name();
fileObj.extension();
fileObj.size();
fileObj.formattedSize(); // must add the "numeral" package to your project to use this method
fileObj.type();
fileObj.updatedAt();

// get for the version in a store
fileObj.name({store: 'thumbs'});
fileObj.extension({store: 'thumbs'});
fileObj.size({store: 'thumbs'});
fileObj.formattedSize({store: 'thumbs'}); // must add the "numeral" package to your project to use this method
fileObj.type({store: 'thumbs'});
fileObj.updatedAt({store: 'thumbs'});

// set original
fileObj.name('pic.png');
fileObj.extension('png');
fileObj.size(100);
fileObj.type('image/png');
fileObj.updatedAt(new Date);

// set for the version in a store
fileObj.name('pic.png', {store: 'thumbs'});
fileObj.extension('png', {store: 'thumbs'});
fileObj.size(100, {store: 'thumbs'});
fileObj.type('image/png', {store: 'thumbs'});
fileObj.updatedAt(new Date, {store: 'thumbs'});

These methods can all be used as UI helpers, too:

{{#each myFiles}}
  <p>Original name: {{this.name}}</p>
  <p>Original extension: {{this.extension}}</p>
  <p>Original type: {{this.type}}</p>
  <p>Original size: {{this.size}}</p>
  <p>Thumbnail name: {{this.name store="thumbs"}}</p>
  <p>Thumbnail extension: {{this.extension store="thumbs"}}</p>
  <p>Thumbnail type: {{this.type store="thumbs"}}</p>
  <p>Thumbnail size: {{this.size store="thumbs"}}</p>
{{/each}}

Also, rather than setting the data property directly, you should use the attachData method.

Check out the full public API for FS.File.

Storing FS.File references in your objects

NOTE: At the moment storing FS.File - References in MongoDB on the server side doesn't work. See eg. (https://github.com/CollectionFS/Meteor-cfs-ejson-file/issues/1) (#356) (meteor/meteor#1890).

_Instead store the id's of your file objects and then fetch the FS.File-Objects from your CollectionFS - Collection.

Often your files are part of another entity. You can store a reference to the file directly in the entity. You need to add cfs:ejson-file to your packages with meteor add cfs:ejson-file. Then you can do for example:

// Add file reference of the event photo to the event
var file = $('#file').get(0).files[0];
var fileObj = eventPhotos.insert(file);
events.insert({
  name: 'My Event',
  photo: fileObj
});

// Later: Retrieve the event with the photo
var event = events.findOne({name: 'My Event'});
// This loads the data of the photo into event.photo
// You can include it in your collection transform function.
event.photo.getFileRecord();

Demo app

You need to ensure that the client is subscribed to the related photo document, too. There are packages on atmosphere, such as publish-with-relations and smart-publish, that attempt to make this easy.

In simple cases, you may also be able to return an array from Meteor.publish:

Meteor.publish("memberAndPhotos", function (userId) {
  check(userId, String);
  return [
    Collections.Members.find({userId: userId}, {fields: {secretInfo: 0}}),
    Collections.Photos.find({
      $query: {'metadata.owner': userId},
      $orderby: {uploadedAt: -1}
    });
  ];
});

Security

File uploads and downloads can be secured using standard Meteor allow and deny methods. To best understand how CollectionFS security works, you must first understand that there are two ways in which a user could interact with a file:

  • She could view or edit information about the file or any custom metadata you've attached to the file record.
  • She could view or edit the actual file data.

You may find it necessary to secure file records with different criteria from that of file data. This is easy to do.

Here's an overview of the various ways of securing various aspects of files:

  • To determine who can see file metadata, such as filename, size, content type, and any custom metadata that you set, use normal Meteor publish/subscribe to publish and subscribe to an FS.Collection cursor. This does not allow the user to download the file data.
  • To determine who can download the actual file, use "download" allow/deny functions. This is a custom type of allow/deny function provided by CollectionFS. The first argument is the userId and the second argument is the FS.File being requested for download. An example:
Images.allow({
	download: function(userId, fileObj) {
		return true
	}
})
  • To determine who can set file metadata, insert files, and upload file data, use "insert" allow/deny functions.
  • To determine who can update file metadata, use "update" allow/deny functions.
  • To determine who can remove files, which removes all file data and file metadata, use "remove" allow/deny functions.

The download allow/deny functions can be thought of essentially as allowing or denying "read" access to the file. For a normal Meteor collection, "read" access is defined through pub/sub, but we don't want to send large amounts of binary file data to each client just because they subscribe to the file record. Thus with CFS, pub/sub will get you the file's metadata on the client whereas an HTTP request to the GET URL is required to view or download the file itself. The download allow/deny determines whether this HTTP request will respond with "Access Denied" or not.

Securing Based on User Information

To secure a file based on a user "owner" or "role" or some other piece of custom metadata, you must set this information on the file when originally inserting it. You can then check it in your allow/deny functions.

var fsFile = new FS.File(event.target.files[0]);
fsFile.owner = Meteor.userId();
fsCollection.insert(fsFile, function (err) {
  if (err) throw err;
});

Note that you will want to verify this owner metadata in a deny function since the client could put any user ID there.

Display an Uploaded Image

Create a helper that returns your image files:

Template.imageView.helpers({
  images: function () {
    return Images.find(); // Where Images is an FS.Collection instance
  }
});

Use the url method with an img element in your markup:

<template name="imageView">
  <div class="imageView">
    {{#each images}}
      <div>
        <a href="{{this.url}}" target="_blank"><img src="{{this.url store='thumbs' uploading='/images/uploading.gif' storing='/images/storing.gif'}}" alt="" class="thumbnail" /></a>
      </div>
    {{/each}}
  </div>
</template>

Notes:

  • {{this.url}} will assume the first store in your stores array. In this example, we're displaying the image from the "thumbs" store but wrapping it in a link that will load the image from the primary store (for example, the original image or a large image).
  • The uploading and storing options allow you to specify a static image that will be shown in place of the real image while it is being uploaded and stored. You can alternatively use if blocks like {{#if this.isUploaded}} and {{#if this.hasStored 'thumbs'}} to display something different until upload and storage is complete.
  • These helpers are actually just instance methods on the FS.File instances, so there are others you can use, such as this.isImage. See the API documentation. The url method is documented separately here.

UI Helpers

There are two types of UI helpers available. First, some of the FS.File instance methods will work when called in templates, too. These are available to you automatically and are documented here. Second, some additional useful helpers are provided in the optional cfs-ui package. These make it easy to render a delete button or an upload progress bar and more. Refer to the cfs-ui readme.

FS.File Instance Helper Methods

Some of the FS.File API methods are designed to be usable as UI helpers.

url

Returns the HTTP file URL for the current FS.File.

Use with an FS.File instance as the current context.

Specify a store attribute to get the URL for a specific store. If you don't specify the store name, the URL will be for the copy in the first defined store.

{{#each images}}
  URL: {{this.url}}
  <img src="{{this.url store='thumbnail'}}" alt="thumbnail">
{{/each}}

This is actually using the url method, which is added to the FS.File prototype by the cfs:access-point package. You can use any of the options mentioned in the API documentation, and you can call it from client and server code.

isImage

Returns true if the copy of this file in the specified store has an image content type. If the file object is unmounted or was not saved in the specified store, the content type of the original file is checked instead.

Use with an FS.File instance as the current context.

{{#if isImage}}
{{/if}}
{{#if isImage store='thumbnail'}}
{{/if}}

isAudio

Returns true if the copy of this file in the specified store has an audio content type. If the file object is unmounted or was not saved in the specified store, the content type of the original file is checked instead.

Use with an FS.File instance as the current context.

{{#if isAudio}}
{{/if}}
{{#if isAudio store='thumbnail'}}
{{/if}}

isVideo

Returns true if the copy of this file in the specified store has a video content type. If the file object is unmounted or was not saved in the specified store, the content type of the original file is checked instead.

Use with an FS.File instance as the current context.

{{#if isVideo}}
{{/if}}
{{#if isVideo store='thumbnail'}}
{{/if}}

isUploaded

Returns true if all the data for the file has been successfully received on the server. It may not have been stored yet.

Use with an FS.File instance as the current context.

{{#with fileObj}}
{{#if isUploaded}}
{{/if}}
{{/with}}

meteor-collectionfs's People

Contributors

aldeed avatar badmark avatar bryantebeek avatar comerc avatar czeslaaw avatar douglasurner avatar elbowz avatar emgee3 avatar eprochasson avatar flanamacca avatar gerwinbrunner avatar harryadel avatar kristerv avatar longuniquename avatar maomorales avatar martunta avatar michaelrokosh avatar mquandalle avatar nooitaf avatar ntziolis avatar okland avatar plopp avatar rhyslbw avatar sanjosolutions avatar sebakerckhof avatar tanis2000 avatar teachmefly avatar tosbourn avatar wursttheke avatar yatusiter avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

meteor-collectionfs's Issues

Uncaught TypeError: Object #<Object> has no method 'userId'

So I tried to implement CollectionFS for the first time.
I'm still using autopublish and the insecure package So I basically only defined
MyCollection = new CollectionFS('mycollection');

First problem is that when I try to use
MyCollection.filter({
allow: {
contentTypes: ['image/*']
}
});
Meteor throws an error
TypeError: Object [object Object] has no method 'filter'

second problem is that when I try to make an upload, I get the following error:
Uncaught TypeError: Object # has no method 'userId'

Again, I don't use any userIds yet, everything is "insecure" right now.

What am I doing wrong?

Best regards
Patrick

Add examples of fileHandlers

  • Imagemagick - resize / crop
  • pdf first page to image migth have to write a driver for that
  • Dropbox upload? (could work at meteor platform)
  • Sound
  • Video

Upload status from other users

Try out:

var lastFilesCurrentChunk[]

function isUploading(fileId) {
  var self = this;
  var fileItem = self._getItem(fileId);
  if (fileItem.complete)
     return false; //nobody is uploading since it's allready completed? (what about updating files data?)
  var answer = (self.lastFilesCurrentChunk[fileId])?(fileItem.currentChunk == self.lastFilesCurrentChunk[fileId]):false;
  //set last to current
  self.lastFilesCurrentChunk[fileId] = fileItem.currentChunk;
  return answer;
}

Add an obsever for remove

When removing a file, the chunks and files related should be deleted.

Create a serverside observer on the collectionFS handling this.
Refractor common, make a seperate client and server version

_.extend(CollectionFS.prototype, {
    find: function(arguments, options) { return this.files.find(arguments, options); },
    findOne: function(arguments, options) { return this.files.findOne(arguments, options); },
    allow: function(arguments) { return this.files.allow(arguments); },
    deny: function(arguments) { return this.files.deny(arguments); },
    fileHandlers: function(options) {
        var self = this;
        self._fileHandlers = options; // fileHandlers({ handler['name']: function() {}, ... });
    }
});

Add observer on server:

find: function(arguments, options) { 
    var query = this.files.find(arguments, options);
    var handle = query.observe({
        removed: function(doc) {
            // TODO: remove all chunks
            // TODO: remove all files related *( delete each fileUrl path begins with '/' )*
        }
    });
    return query;
},

Enable localstorage

Use all armes in sight.. Use it to contain the last 5Mb? of the file, if filesize is smaller then the whole file.
If a user wants to upload a foto just taken? or is the foto allready stored so resume is posible. investigate.

Split up the project into smaller packages?

Depends on the new package system - if it allows depending on community packages eg. via atmosphere then we should split the project up into smaller packages to improve simplicity and usabillity.

Again not all wants to use filehandlers - then why have the code as extra payload?

I'm thinking something like packages:

  • Storage Adapters gridFS + filesystem - as plugins cfs
  • Rest point for serving files - as a general package
  • File transport over DDP + EJSON object - as a general package
  • Filehandlers - as plugin for cfs
  • CollectionFS
  • UI Components for cfs

Refractor

Example and client side code needs refractoring

  • Please jshint conforming with the jshintrc
  • Make rough refractor of vars
  • Make rough refractor of files

Transform api onto file object

options: {
    blob,              // Type of node.js Buffer() 
    fileRecord: {
        chunkSize : self.chunkSize, // Default 256kb ~ 262.144 bytes
        uploadDate : Date.now(),  // Client set date
        handledAt: null,          // datetime set by Server when handled
        fileHandler:{},           // fileHandler supplied data if any
        md5 : null,               // Not yet implemented
        complete : false,         // countChunks == numChunks
        currentChunk: -1,         // Used to coordinate clients
        owner: Meteor.userId(),
        countChunks: countChunks, // Expected number of chunks
        numChunks: 0,             // number of chunks in database
        filename : file.name,     // Original filename
        length: ''+file.size,     // Issue in Meteor
        contentType : file.type,
        encoding: encoding,       // Default 'utf-8'
        metadata : (options) ? options : null,  // Custom data
    },
    destination: function, // When in filehandler
    getExtension: function,
    getBlob: function,
    getDataUrl: function,
    remove: function,
    sumFailes: 0..3 (times filehandler failed in this recovery session)
}

Images stored via server do not display correctly in browser

Okay this is an odd issue for me. Whenever I attempt to store a buffered image via the server method storeBuffer and then retrieve and pipe them to a http response, the images do not appear in the browser. Strangely, if I use the client method storeFile, and then retrieve them the same way, the images are displayed properly in the browser.

I'm not sure what's going on here. As far as I can tell the buffers are identical whether I use the client or server store method. What am I doing wrong? Here is my code:

Server Store Example

PagesFS.storeBuffer(filename, buf, {
    contentType: "image/png"
});

// Later in an express middleware function
buf = PagesFS.retrieveBuffer(image._id);
res.send(200, buf); // Browser displays a blank image

Client Store Example

PagesFS.storeFile(file);

// Later in an express middleware function on server
buf = PagesFS.retrieveBuffer(image._id);
res.send(200, buf); // Browser displays the actual image

This might be a little incomplete. Let me know what else you need to diagnose this problem.

Patch for some temp fixes and enhancements

In using this awesome file tool I ran across a couple issues that I had to address.

  1. I added a field called autopublish to the options so you can turn off autopublishing if you want to implement your own publish method - in my case it was needed to limit the number and type of files a user could see. I also added an autosubscribe option for the client.
  2. I added a numChunks field that counts the number of chunks in the db as you upload a file, I find that numChunks/totalChunks is a better reflection of a files progress for me because chunks can arrive at various times and the last currentChunk can be != totalChunks
  3. Appended the collection name to the getMissingChunk method to resolve a name collision and added an exception for the EEXISTS stat error so you can have multiple instances of CollectionFS in your app (I have three in mine so far)

Patch Below:

diff --git a/collectionFS_common.js b/collectionFS_common.js
index 0ec7fb6..60a4338 100644
--- a/collectionFS_common.js
+++ b/collectionFS_common.js
@@ -16,13 +16,17 @@
 
    //Auto subscribe
        if (Meteor.isClient) {
-           Meteor.subscribe(self._name+'.files'); //TODO: needed if nullable?
+           if(!options || !options.hasOwnProperty('autosubscribe') || options.autosubscribe) {
+               Meteor.subscribe(self._name+'.files'); //TODO: needed if nullable?
+           }
        } //EO isClient 
 
        if (Meteor.isServer) {
-         Meteor.publish(self._name+'.files', function () { //TODO: nullable? autopublish?
-           return self.files.find({});
-         });       
+           if(!options || !options.hasOwnProperty('autopublish') || options.autopublish) {
+             Meteor.publish(self._name+'.files', function () { //TODO: nullable? autopublish?
+               return self.files.find({});
+             });       
+           }
        } //EO initializesServer
 
        var methodFunc = {};
@@ -43,6 +47,8 @@
                        "data" : data,              // the chunk's payload as a BSON binary type            
                    });
 
+                   numChunks = self.chunks.find({"files_id":fileId}).count();
+
                    /* Improve chunk index integrity have a look at TODO in uploadChunk() */
                    if (cId) { //If chunk added successful
                        /*console.log('update: '+self.files.update({_id: fileId}, { $inc: { currentChunk: 1 }}));
@@ -55,11 +61,11 @@
 
                        if (complete || updateFiles)  //update file status
                            self.files.update({ _id:fileId }, { 
-                               $set: { complete: complete, currentChunk: chunkNumber+1 }
+                               $set: { complete: complete, currentChunk: chunkNumber+1, numChunks:numChunks }
                            })
                        else
                            self.files.update({ _id:fileId }, { 
-                               $set: { currentChunk: chunkNumber+1 }
+                               $set: { currentChunk: chunkNumber+1, numChunks:numChunks}
                            });
                        //** Only update currentChunk if not complete? , complete: {$ne: true}
                    } //If cId
@@ -86,7 +92,7 @@
                } //EO isServer
            }; //EO saveChunck+name
 
-           methodFunc['getMissingChunk'] = function(fileId) {
+           methodFunc['getMissingChunk'+self._name] = function(fileId) {
                console.log('getMissingChunk: '+fileRecord._id);
                var self = this;
                var fileRecord = self.files.findOne({_id: fileId});
@@ -182,6 +188,8 @@
    _.extend(CollectionFS.prototype, {
        find: function(arguments, options) { return this.files.find(arguments, options); },
        findOne: function(arguments, options) { return this.files.findOne(arguments, options); },
+       update: function(selector, modifier, options) { return this.files.update(selector, modifier, options); },
+       remove: function(selector) { return this.files.remove(selector); },
        allow: function(arguments) { return this.files.allow(arguments); },
        deny: function(arguments) { return this.files.deny(arguments); },
        fileHandlers: function(options) {
diff --git a/collectionFS_server.js b/collectionFS_server.js
index 1a97b02..c330111 100644
--- a/collectionFS_server.js
+++ b/collectionFS_server.js
@@ -63,7 +63,9 @@ var _fileHandlersFileWrite = true;
                            console.log('Path: '+self.path);
 
                        }); //EO Exists
-                       if (err) {
+                       // errno 47 is EEXISTS, which is fine
+                       if (err && err.errno != 47) {
+                           console.log(err);
                            _fileHandlersSymlinks = false;
                            //Use 'public' folder instead of uploads
                            self.cfsMainFolder = 'public';

Binary File Transfers Corrupted? Truncated?

I'm using meteorite 0.4.9, meteor 0.6.x and the latest collectionFS and when I upload any files (.jpg and .png tested) the resulting files are corrupt and shorter. Could this be an encoding issue? I did a file comparison and besides being a bit shorter the saved file also had a lot of characters that were different (in hex).

I'm hoping coffeescript doesn't have something to do with it because all my server code uses that and meteor 0.6.x got rid of global variables so it has caused some minor issues declaring collection instances.

Remove all encoding options

Seems that it should not matter how its treated internally - as long as the IO is the same.

  • Remove all encoding options
  • Check up on node specs

Error when using fresh meteor install

If the file structure doesn't have the following structure (e.g a fresh new meteor app) it will throw an error looking for .meteor/local/build/static/cfs

/server
/client
/public

Speedup the Meteor.apply?

Run some tests on that, se if anything can be done. Is it queing or is it just network lag

Checkup on the filereader.slice - pretty slow for a basic operation like that...

Add md5 validation for file integrity

In gidFS spec it speaks about attribute .md5 this is added in collectionFS but not implemented. Should be uses to verify file when resuming file upload?

Important to do when server receives client total upload confirmation?

Must close client window and re-open on server restart

For some reason when I update my server code and the meteor server restarts, the only way for me to get file uploads working again is to close my client window and re-open it. Is there some kind of JS thread on the client that is keeping the client from working when the server restarts?

Use chunkSize defined in files, the que is just the default..

Correct the use of self.chunkSize (this is a default value)
Should use fileItem.chunkSize or `fileRecord.chunkSizeas available instead. This way an app can customizechunkSize``and still load files with a different``chunkSize``

Fix in collectionFS_client and collectionFS_server

Create a server cache/ url reference

Create a server cache for completed files in the public/collectionFS._name folder.
Update fs.files record attribute: fileURL[]

Prepare abillity for special versin caching options creating converting images, docs, tts, sound, video etc.
eg. further details in server/collectionFS.server.js

CollectionFS.fileHandlers({
  //Default image cache
  handler['default']: function(fileId, blob) {
    return blob;
  },
  //Some specific
  handler['40x40']: function(fileId, blob) {
     //Some serverside image/file handling functions, user can define this
     return blob;
   },
  //Upload to remote server
  handler['remote']: function(fileId, blob) {
     //Some serverside imagick/file handling functions, user can define this
     return null;
   },
 });

The server runs caching in a fifo que by fileId handling cache request pr. file looping through the versions array, saving the blob in a collection named folder having fileId+arrayRef and content type for naming. eg.: public/contacts/d4er-ee3-eeer3-ww3.40x40.jpg
When saved the fs.files fileURL['40x40'] is updated? causing reactive update of UI

Special 'master' filehandler

Declare a filehandler named 'master', then this could trigger that the chunks would be removed from the database - all other filehandlers would get handed the master file - not the database file.

The master could do all normal stuff, changing size of image (limit data usage on filesystem)

Add package ddp file transport

Use EJSON $binary - base64 encoded when in ddp transport

  • Make an EJSON object for transferring files
  • Make sure the right data types end up in db on the server
  • Check the fileRecord spec - check that all is needed otherwise deprecate dead vars

What does that change in collectionFS?

  • The way data is transported would sometimes be a smaller footprint
  • Binary data would go directly into the database instead of binary string

Meaning:

  • It would break filehandling of old uploaded files
  • It would depend on the PR meteor version
  • Files would be obscurifyed if the Meteor is not up to date with #1001

Some bad things right? but some good stuff too, a more clean client-side code is on its way

Add example dropbox like

It would be nice with a simple example like the dropbox web interface - for showing how to use CollectionFS in a 'real' usecase' - Depends on the new client-side API to be finished.
Might do a video tutorial on how to do that

Abillity to delete files

Should be able to removeFile this includes files created by filehandlers in the fileURL

Remote method:

remove fileHandlers
remove fileChunks
remove fileFiles
return status of remove?

file handlers not showing up in local demo

This is probably a case of me not having done something simple but necessary...

In order to get the example in examples/filemanager working, I had to do mrt remove collectionFS and then mrt add collectionFS. That got it running, using mrt in the said directory.

I am able to upload and download files, but unlike in the live demo at http://collectionfs.meteor.com/, the jpg examples do not show filehandlers, they just say "no cache." I just tried deploying it and got the same result. Any idea what might be different?

Thanks!

Package CollectionFS

Refractored files to match development in the meteor/packages folder. Added smart.json and package.js

Implement correct autopublish

At the moment its on autopublish - this should handled differently, there is a Meteor constant or function returning whether or not to autopublish.
The subscribe / publish implementation is natural handled in issue for creating function aliases

Add restpoint for serving static files

Rest point for files

  • Deprecate the current usage of symlink
  • Create a restpoint cfs/collection/fileid.ext
  • Should there be a way for fetching fileRecords? (Could come natural in time - when meteor adds auto rest api)
  • Add security for users (bassed on deny/allow)
  • Add option for adding remote apikeys for other servers to access files
  • Should we have an option for uploading files from remote server?

Add test environment

tiny-test
Would be nice to get in test driven development with Meteor/node.js

EDIT: Waiting for Meteor linker to settle - currently the package tests are not in package scope - so no option for test driven development or specific testing. Havent tried Laika - maybe that would be a solution

Remove all __meteor_bootstrap__.require

Deprecate use of __meteor_bootstrap__.require replace with npm.require

Add temperary solution in server until then Meteor.engine branche gets to master:

if (!npm) {
  var npm = {
    require: __meteor_bootstrap__.require
  };
}

CollectionFS alias functions

Make alias functions find, allow, insert etc. pointing at files collection

Making usage more like a regular Meteor.Collection

Add file api via transform to files

When doing:

var a = collectionFS.find(id);

a contains the fileRecord but it would be handy if it contained functions eg.:

a.retrieveDateUrl
a.remove()
a.update()
a.retrieveBlob()
// + more on the client-side api

This way there would be no need for working with literals to define what collection the file is in - its just a file object.

The api could be transformed, the collectionFS.find - would not have the option for user to make custom transformes

Add global handlebar helpers

Usefull helpers eg. {{fileProgress}}, {{fileInQue}}, {{fileAsURL}}, {{fileURL _id}}
All using eg. fileId, param or fallback to this._id

AND UI helpers, one for the upload-loading-saveas and one for the upload a file?

How to store files on the server side?

Is it possible to use CollectionFS on the server side? So that my code on the server downloads a file, puts it into the CollectionFS, which can then be downloaded/viewed by the client?

Switch to EJSON using the binary option

Switch to EJSON for handling then binary data when sending/receiving chunks in methods:

data: { '$binary': data }

hopefully this would improve performance?

Make an option to throttle eg. max filehandlers.

It's an issue when using muliple instances of collectionFS then multiple filehandlers could spawn making the server slow - pr. default only one filehandler at a time - but maxFilehandlers could be set as an option to overwrite this behaviour.

  • Add check at collectionFS_server.js ln 119
    Check if maxFilehandlers reached otherwise spawn filehandler
  • Add inc/decrease of runningFilehandlers - In the beginning+end of workFileHandlers
  • Add option setting ( extend options with a default settings )

Check up on gridFS compliance

This is usable when addressing the whole server caching thing. Using gridFS serverside makes great sens. (saving dev time and optimizing speed)

Two things:

  1. is gridFS chunkSize locked to 256bytes? shouldn't be a problem being flexible collectionFS is easy altered, currently being 1024bytes
  2. How to make the collectionFS.que objects have attribute .length instead of the current workaround .len containing file size (why not call it filesize?)

Test the gridFS functions on the collectionFS tables .files and .chunks

Give filehandlers path, filename, extension

Do some refractoring giving the filehandler more options.

  var result = fileHandlers[func]({ fileRecord: fileRecord, blob: blob });
  /* add:
    destination: function( [extension] ) - Returns complete filename
    fileUrl: { path, extension } */

Where destination referrers to local filesystem and fileUrl referrers to url saved in database.
The filehandler can return the fileUrl if saving file using gm or using a remote host.

Add documentation for this.

CollectionFS object has no method 'filter'

For the following code under Meteor 0.6.4.1 & CollectionFS 0.2.3 I am getting

Object [object Object] has no method 'filter'

on attempting to add a filter as per the documentation.

media_items.coffee

@MediaItemsFS = new CollectionFS 'media_items'

MediaItemsFS.allow
    insert: (userId, doc) ->
        return userId && doc.owner == userId
    update: (userId, doc, fields, modifier) ->
        _.all files, (file) ->
            return userId == file.owner
    remove: (userId, doc) ->
        return false

MediaItemsFS.filter
    allow:
        contentTypes: ['image/*']

IE 9 broken

Downloading not working, guess it's Filereader, file or blob that IE breaks:
Log: Exception while delivering result of invoking 'loadChunckfilesystem'undefined

  • Write polyfill for handling files in browsers

Files mime type set to `application/octet-stream`

Files mime type set to application/octet-stream
Chrome files following warning:

Resource interpreted as Image but transferred with MIME type application/octet-stream:

Safarie doesn't show images -

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.