Skip to content
This repository has been archived by the owner on Mar 14, 2019. It is now read-only.

can I get a callback for when file upload is completed? #323

Open
ccorcos opened this issue May 23, 2014 · 36 comments
Open

can I get a callback for when file upload is completed? #323

ccorcos opened this issue May 23, 2014 · 36 comments

Comments

@ccorcos
Copy link

ccorcos commented May 23, 2014

No description provided.

@cramhead
Copy link
Contributor

It would be great to have an event for when images were finished thumbing too.

@ghost
Copy link

ghost commented May 23, 2014

See

Use it in a reactive context. Something like:

var fileId = '123456'; // Received from the insert
Deps.autorun(function (computation) {
   var fileObj = filesCollection.findOne(fileId);
   if (fileObj.hasStored('thumbnailStore')) {
      // Thumbnail is available. Do something.
      computation.stop();
   }
});

@aldeed
Copy link
Contributor

aldeed commented May 30, 2014

The latest CFS has some events on the server now. Client events coming soon, or use deps as @sanjo pointed out.

filesCollection.on('stored', function (fileObj, storeName) {
  // do something
});
filesCollection.on('uploaded', function (fileObj) {
  // do something
});
filesCollection.on('error', function (error, fileObj) {
  // this will be an upload error; to listen for store errors, listen on the stores themselves
});

Alternatively, the "stored" and "error" listeners can be used directly on a particular store. Listeners on the FS.Collection instance are called for events from all the stores used by the collection.

@lukejagodzinski
Copy link

It would be nice to have event called when all the files in the queue are uploaded not only one file. Now I have to check in the collection if there are any files without uploadedAt attribute.

@lukejagodzinski
Copy link

I have one more question. What exactly is the callback from Image.insert function?

Images.insert(fsFile, function(err, image) {
  console.log(image.url(), image.isUploaded());
});

When I try calling image.url() or image.isUploaded() I get null and false accordingly, so what is the reason of having this callback at all?

@lukejagodzinski
Copy link

If anyone wants to implement onUploaded event on the client here is the instruction:

Images.insert(fsFile, function(err, image) {
  var cursor = Images.find(image._id);

  var liveQuery = cursor.observe({
    changed: function(newImage, oldImage) {
      if (newImage.isUploaded()) {
        liveQuery.stop();

        // Call your onUploaded callback here...
      }
    }
  });
});

I can try making pull request but it would be nice if you tell me where is the file responsible for events.

@aldeed
Copy link
Contributor

aldeed commented Jan 17, 2015

The insert callback is called when the file document (metadata) is done inserting. (Same as normal collection insert.) So at that point you could store the file's id or something else. Uploading the data happens in the background so whether or not it's done uploading in that callback depends on the timing of everything, how large the file is, etc.

If you really need the url, you can do image.url({brokenIsFine: true}) and you'll get it. By default it returns null if the link would be broken, i.e., the data hasn't been fully uploaded and stored yet.

@lukejagodzinski
Copy link

Ok thanks for clarification. Actually I need information about when uploading is finished. The code which I've posted just works for me. I could try making client side events but I would need some information. I see that there are events defined for both environments. So is the only thing that needs implementation to hook into update method of collection and check whether file has already been uploaded?

@lukejagodzinski
Copy link

Actually there is uploaded event emitted here and I can hook into it. But when I check returned value of isUploaded() function in the callback then I get false. So it's not working properly. Moreover calling such event shouldn't be the task for uploaded. Because we have to have this check in any uploaded (ddp and http). There should be some abstraction class that just cuts file into the chunks and sends them to uploaded. Uploaded always works in that way. There is not possibility to send file as a whole, excluding small files.

FS.Collection is great package and I would like to work on it. There are some bugs and comments like TODO, FIXME, XXX etc. It's a lot like work in progress :) but together we can make it better. Many things needs clarification and refactoring.

@aldeed
Copy link
Contributor

aldeed commented Jan 18, 2015

@jagi, yeah, that emits when the client is finished chunking and sending the chunks, but isUploaded won't return true until the server has all chunks safely stored in temporary storage. The latter (isUploaded) is when the file is truly uploaded, so the line you reference should probably be removed. I agree it's not a good implementation and shouldn't be handled by the uploader packages.

Our plan was to try to make a generic client/server events package and use that. We already use raix:eventemitter package to have a unified event emitter across client and server. We then want to take that one step further so that an event emitted on the server can fire listeners attached on the client and vice versa. I don't know whether this would be a separate pkg on top of raix:eventemitter or just a new feature of raix:eventemitter. @raix?

Anyway, if someone can create that package/feature, then it should be easy to get the rest of the CFS events in place. There are a lot of cases where we want to emit the event on the server but listen on the client, including "uploaded".

@aldeed
Copy link
Contributor

aldeed commented Jan 18, 2015

I realized that what we're planning to do, described in my last comment, is very similar to @arunoda's streams package. My thought was to do something like new EventEmitter({key: 'foo'}) on both client and server and then that key would link them together. Client would call server method on emit, and server would publish to the client through an unmanaged (non-database) collection on emit. We would send key, eventName, and arguments down the wire. And maybe some ability to turn that off when emitting.

@arunoda, it does not look like you're maintaining the streams pkg anymore? What do you think about folding that feature into raix:eventemitter as I've described?

@arunoda
Copy link

arunoda commented Jan 18, 2015

If you guys are doing that I'd suggest to do it as a separate project. I think raix:eventemitter is providing low level eventemitter as it describe. (and I'm using it for a some projects, doesn't looking for any more features other than that)

@raix
Copy link

raix commented Jan 18, 2015

I think ddp will support triggering events on the server and have it populate to clients?

Agree, the eventemitter package should remain as slim/stable as possible, I've started to use it a lot its a very general feature - that said I'm thinking about adding something like emitOnce to simulate a state like cordovas deviceready.

The package we should create for this is "observable streams" - this could in theory be used for events and data. #216
(The client should be able to trigger same events and have it casted via the server - eg. if client uploads directly to the SA)

https://github.com/Reactive-Extensions/RxJS http://msdn.microsoft.com/en-us/data/gg577609.aspx http://reactivex.io/

The reason for stuff like this is the async nature - I've written a Kernel once that basically allows you to run functions when theres cpu time for it + Kernel.each that iterates over and handles data, again when theres cpu time for it.

File handling is heavy stuff so if we can have throttles observable streams handling up/downloads we could make sure theres time for the UI and GC = better user experience.

Theres some things to think about,

@lukejagodzinski
Copy link

But is it possible using DDP to call some method from the server to the client? I think it's not possible right now. If it will be available in the future, do you know when the meteor core team is planing to implement such feature? I agree that having possibility to invoke events in both directions would be nice.

@raix
Copy link

raix commented Jan 18, 2015

Odd, the DDP v2 card is removed from their trello card - maybe @glasser knows about this.

If we want we could implement events from/to server via sub/pub and methods - it would make sense to be able to subscribe to certain events - maybe via an in memory database on the server or the fileRecord.

Maybe have an observer to event emitter package,

@dletozeun
Copy link

Hello,

Do we have any news on this ? Jagi's code snippet looks like the fastest option for me at this time but I am wondering how the server side events (uploaded, stored, etc...) are as useful as it could be on client side. I mean, if I need to upload several files, wait for them to be all uploaded and then trigger some processing on server side, the mechanism between client and server would be quite complicated unless CollectionFS.insert could be called on server side.

At least, if we could listen to these events on client side, the whole logic would stay in client code and stay simple but imho, this code should be server side.

@0o-de-lally
Copy link

@aldeed Seems like filesCollection.on('stored') is producing some errors for me:

Error: Error storing file to the images store: Can't wait without a fiber

Are there any docs to help debug this?

@ncubica
Copy link

ncubica commented Aug 23, 2015

@jagi I don't know if somebody already found the correct way of doing this but I tried this.

           yourFsCollection.find({_id : _id}).observe({
                changed : function(file, oldFile){
                    if(file.url() !== null){
                        uploading.onFinish(file._id); //this is your callback
               }
           })

if you see I checked for file.url !== null instead of isUploaded since I was receiving an Uploaded status set to true but with an file.url === null if somebody find a more elegant solution I will glad to hear it.

@lukejagodzinski
Copy link

@ncubica whatever works for you :). There are some errors in CollectionFS like for example the isUploaded function that returns true when it shouldn't. I don't remember in details how I've avoided it but I made it work.

@okland
Copy link
Contributor

okland commented Aug 23, 2015

@jagi I added functions that add callback for file upload per store, in the PR #782 you can use it.

@fuzzybabybunny
Copy link

Hey all, so I'm trying to write a providerUpload function for the OrionJS CMS:

http://orionjs.org/docs/filesystem-providers

For my purposes,

  1. Once the file is uploaded, I need to get the URL.
  2. I then save the URL somewhere.

Just to confirm, in CollectionFS there is no way to get the URL of the file right after it is finished uploading, right?

  Images.insert(file, function (err, fileObj) {
    // Inserted new doc with ID fileObj._id, and kicked off the data upload using HTTP
  });

fileObj.url() returns null.

@okland
Copy link
Contributor

okland commented Aug 23, 2015

@fuzzybabybunny I added functions that add callback for file upload per store, in the PR #782 you can use it.

@fuzzybabybunny
Copy link

Thanks. Hopefully the PR gets accepted soon as well.

@0o-de-lally
Copy link

0o-de-lally commented Aug 25, 2015 via email

@tcastelli
Copy link

+1

@jurgenfink
Copy link

@jagi and @okland Thanks for your work on callback on client side when upload finished - works perfectly well for me - I confirm (solution from Jan. 17) - again thanks for sharing 👍
You made my day ...

@hamxiaoz
Copy link

@jurgenfink what's your collection fs and meteor version? Mine keeps telling me error.

@jurgenfink
Copy link

@hamxiaoz Good Morning Andrew,

  • Windows 10, 64 bit
  • Meteor 1.2.1
  • CFS
    • cfs:gridfs 0.0.33 GridFS storage adapter for CollectionFS
    • cfs:standard-packages 0.5.9 Filesystem for Meteor, collectionFS

Server (example: yourapp.js)

Meteor.publish('images', function(){
  return Images.find();
});

Meteor.methods({
  updateClientFoto: function(imagesURL, id){
    return Clients.update({_id: id}, {$set: {stored:true, imageurl:imagesURL}});
  },
  deleteClientFoto: function(id){
    return Clients.update({_id: id}, {$set: {stored:true}, $unset: {imageurl:""}});
  },
});

Client (example: clients.js)

Meteor.methods({  
  updateClientFoto: function(imagesURL, id){
    return Clients.update({_id: id}, {$set: {stored:false, imageurl:imagesURL}});
  },
  deleteClientFoto: function(id){
    return Clients.update({_id: id}, {$set: {stored:false}, $unset: {imageurl:""}});
  },
});

Template.clients.events ({
    'change #addCurrentClientFoto': function(event, template) {
        FS.Utility.eachFile(event, function(file) {
            Images.insert(file, function (err, fileObj) {
                if (err){

                    // handle error

                } else {

                  // handle success depending what you need to do

                  $('#ImageSync').addClass('visible'); // spinner icon, visible while not synced

                  var cursor = Images.find(fileObj._id);
                  var liveQuery = cursor.observe({
                    changed: function(newImage, oldImage) {
                      if (newImage.isUploaded()) {
                        liveQuery.stop();
                        // Call your onUploaded callback here...
                        var imagesURL = "/cfs/files/images/" + fileObj._id;
                        var id = Session.get('currentClientID');

                        //now save the imageURL into client doc, as file is now awaylable on server (uploaded)

                        Meteor.call('updateClientFoto', imagesURL, id, function(err){
                            if(err){
                                console.log(err.reason);
                            } else {
                                $('#ImageSync').removeClass('visible'); // spinner icon, now saved on server
                            };
                        });
                      }
                    }
                  });
                }
            });
        });
    },
 });

(Side-Note: I repeat same Meteor.method both on server-side and client-side, with only difference of setting my stored field on false on client side, and true on server side. Hence, we take advantage of Meteor's latency compensation and can see when data is synced with server side)

Client (example: clients.html)

<template name='clients'>
           <input type='file' class='myFileInput' id='addCurrentClientFoto' />

            <div class='sync' id='ImageSync' style='position:absolute; top:52px; left:52px; width:24px; height:24px'>
              <i class='icon ion-loading-c'></i>
            </div>

</template>

Client and Server (example: collection.js in lib-folder within root, lib for priority in loading)

var imageStore = new FS.Store.GridFS("images");

Images = new FS.Collection("images", {
 stores: [imageStore]
});

Images.deny({
 insert: function(){
 return false;
 },
 update: function(){
 return false;
 },
 remove: function(){
 return false;
 },
 download: function(){
 return false;
 }
 });

Images.allow({
 insert: function(){
 return true;
 },
 update: function(){
 return true;
 },
 remove: function(){
 return true;
 },
 download: function(){
 return true;
 }
});

Hope this helps

@talha-asad
Copy link

So after more than 1.5 years, this is still like this with hacks ...

@MilosStanic
Copy link

Being unable to get image (file) url immediately after upload is finished, i.e. not knowing when upload is finished, is a deal breaker.
I'd like to appeal to the authors to implement @okland's PR so we can have a fully functioning upload mechanism.

@jlukic
Copy link

jlukic commented Mar 28, 2016

Love CFS, hate the difficulty in getting anything to work without hacks / intervals galore.

@kainisoft
Copy link

Try to this:

let fileObj = Files.findOne(_id);
fileObj.on('stored', Meteor.bindEnvironment(function() {
      // do something
}));

@neutron92
Copy link

please where i have to put this trigger?

filesCollection.on('stored', function (fileObj, storeName)

@kainisoft
Copy link

kainisoft commented Jun 3, 2016

It's working only on server side

// Client
Template.some_template.events({
    'change #file'( event ) {
        FS.Utility.eachFile(event, function ( file ) {
            Images.insert(file, function (err, fileObj) {
                if (err) throw err;

                Meteor.call('someMethod', fileObj._id);
            });
        });
    }
});

// Server
Meteor.methods({
    someMethods( fileId ) {
        var file = Images.findOne(fileId);
        file.once('stored', Meteor.bindEnvironment(function() {
            // do something
        }));
    }
});

On client you may use isUploade

@GlebDolzhikov
Copy link

GlebDolzhikov commented Aug 26, 2016

My version for catch id's during multiple upload...

       let fileList = [];
        let promises = [];
        FS.Utility.eachFile(event, function(file) {
            promises.push(new Promise(function(res,rej){
                Images.insert(file, function (err, fileObj) {
                    fileList.push(fileObj._id);
                    res();
                })
            }))
        });
        Promise.all(promises).then(()=>{
                Meteor.call('setInfoImages',fileList,tpl.data._id)
            }
        )

@boostbob
Copy link

uploaded event worked for me.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests