Armelias
Armelias

Reputation: 289

How to save a file -in a folder- using FileSystem and CollectionFS ? (yeah, really.)

I think I'm missing something. I have read a lot of posts/examples and I can't save images on my system (I work locally).

What is my goal ?

I'm trying to save a file submitted by the user in a folder (server-side). Does it sound easy ? Maybe.

What's the issue ?

Short answer : I can't figure out how to save the file in my folder. Do you want more information ?

The story of a file upload

I have read that to use the path parameter like new FS.Store.FileSystem("thumb", { path: "/public/images/user/avatar" }) , I have to declare my collection server-side. But when I call Avatars.insert() (Avatars is the name of my collection), it seems like it doesn't exists. This makes sense because this collection exists only on the server.

So I've tried to declare the collection both server-side and client-side (I've read some examples about that) and that works ! The file is correctly added to MongoDB, but my folder is still empty (I'm not sure but I think this is because Avatars.insert() is called client-side so the collection used is the client-side one, the one which cannot take path parameter).

But no problem ! I've created 2 Meteor methods (one client-side and one server-side) called "updateAvatarFile". With this "trick", I'm able to do Meteor.call("updateAvatarFile", field.files[0]), which calls both server-side and client-side methods. So I can do some UI stuff in the client-side one and upload the file in the other. But I can't pass the file as a parameter.

field.files[0] contains the file client-side but server-side it's an empty object. My question is : How can I upload a file ?

I can't do it client-side (because I can't use path parameter) but I can pass the file to the server. I'm sure that I'm missing something but I can't figure what.

Here is how I go :

// /client/views/templates/settings.js
Template.settings.events({
   'submit #updateAvatar': function (e, template) {
        e.preventDefault();
        const field = document.getElementsByName('avatar')[0];
        Meteor.call('updateAvatarFile', field.files[0]);
    }
});

// /client/lib/clientMethods.js
Meteor.methods({
    'updateAvatarFile': function (file) {
        // blabla
    }
});

// /server/lib/serverMethods.js
Meteor.methods({
    'updateAvatarFile': function (file) {
        Avatars.insert(file, function (err, fileObj) {
            if (err) {
                console.log(err);
            } else {
                console.log(fileObj);
            }
        });
    }
});

// /server/collections/serverAvatarCollection.js
Avatars = new FS.Collection("avatars", {
    stores: [
        new FS.Store.FileSystem("original", { path: "/public/images/user/avatar" }),
        new FS.Store.FileSystem("thumb", { path: "/public/images/user/avatar" })
    ],
    filter: {
        maxSize: 1000000, //1Mo
        allow: { contentTypes: ['image/*'] }
    },
    onInvalid: function (message) {
        //throw new Meteor.Error(403, message);
    }
});

// /client/collections/clientAvatarCollection.js
// (this one is actually in a comment block)
Avatars = new FS.Collection("avatars", {
    stores: [
        new FS.Store.FileSystem("original"),
        new FS.Store.FileSystem("thumb")
    ],
    filter: {
        maxSize: 1000000, //1Mo
        allow: { contentTypes: ['image/*'] }
    },
    onInvalid: function (message) {
        alert(message);
    }
});

I've also tried to insert the file with the client-side method but I've got the same result (the file is added to MongoDB but not saved into a folder).

Using different path values didn't work either.


EDIT : Or maybe I'm trying to use the wrong package ? To my mind, transform a picture to chunks and save them into MongoDB sounds really weird and bad. Do you have any adivces ?


EDIT 2 : answer to Michel Floyd (sorry about that, the character limit is annoying).

First, thanks for your answer !

1. At the moment, I'm just trying Meteor so I have both autopublish and insecure installed. Not publishing/subscribing to my collection cannot cause an issue, is that right ?

2. Before your answer I've tried to set up a collection available for both server and client by putting my avatarCollection.js in /collections. I was thinking that path which doesn't contains server or client are automatically available for the two sides. So what is the difference between /collections and /lib ? (I know that all files in a "lib" folder are loaded first). Is it a bad practice to put collections in /collections ? Maybe should I create a /lib/collections folder ?

3. (the last point, sorry for the long comment) I've tried what you advised above but it doesn't seems to work (or I am doing something wrong, again ><). When I use Avatars.insert(), CollectionFS don't save the file on my local storage. I've also checked the root of my HDD in case CollectionFS interpreted / to be the root of my machine but it doesn't. In the other hand, CollectionFS have created 4 collections in MongoDB (cfs._tempstore.chunks, cfs.avatars.filerecord, cfs_gridfs._tempstore.chunks and cfs_gridfs._tempstore.files) - the gridfs is weird. I have GridFS installed but I use FileSystem -. Those tables are not empty. That's why I think CollectionFS split my file into chunks and save them in MongoDB.

Upvotes: 3

Views: 3046

Answers (1)

Michel Floyd
Michel Floyd

Reputation: 20226

You're generally on the right track. CollectionFS uses storage adapters to deal with actual file storage. You can put files on S3, gridFS, or your local file system as you're trying to do. Putting the file contents in Mongo directly is usually avoided.

Firstly, define your collection:

Avatars = new FS.Collection("avatars", {
    stores: [
        new FS.Store.FileSystem("original", { path: "/public/images/user/avatar" }),
        new FS.Store.FileSystem("thumb", { path: "/public/images/user/avatar" })
    ],
    filter: {
        maxSize: 1000000, //1Mo
        allow: { contentTypes: ['image/*'] }
    },
    onInvalid: function (message) {
        //throw new Meteor.Error(403, message);
    }
});

in /lib! This will make it available to both the server and the client.

Secondly, make sure you publish your avatars collection from the server and subscribe to it from the client. I don't see any publish/subscribe code in your question. You need it.

Thirdly, if you just do:

Avatars.insert(...);

on the client with a file then CollectionFS then CollectionFS will take care of storing it for you. The thing is, it won't be instantly available. It can take a little while for the actual upload and storage to happen. You can look at fileObj.isUploaded for example to see if the file is ready.

Upvotes: 1

Related Questions