Meteor-Community-Packages / Meteor-CollectionFS

Reactive file manager for Meteor
MIT License
1.05k stars 237 forks source link

How to get a URL for a file that has been uploaded as a base64-String? #803

Open derwaldgeist opened 9 years ago

derwaldgeist commented 9 years ago

My use case: I'm using the mdg:camera package to get an image from my mobile device. This image is returned as a standard base64-String, as it could be used directly in an image's src attribute.

For performance reasons, I would like to retrieve the image from GridFS using an HTTP URL instead. The upload is working fine, but if I'm retrieving the image and call .url() on the returned File object, the URL is null. I assumed the URL would be set automatically in the case of base64 strings. Obviously, this is not the case.

Is there a way to convert the base64 string into a valid File object and upload it as such instead?

I have tried

var newFile = new FS.File(theBase64String);
newFile.name('upload.jpg');
Images.insert(newFile, function(...) { ...

to get a "real" FS.File before uploading (instead of uploading the base64 string directly), and also set a name for it, but still on later retrieval the .url() is null.

In the database, I can see that for "real" file uploads (using an HTML upload form element), the file record contains a "copies" entry, but for the File that had been created based on the base64String it does not.

Another mystery: After reading some of the Github issues for CollectionFS, I found out that one can call .url with an additional parameter like this: .url({brokenIsFine: true}) to get a URL for the file before it has been stored on the server side. Strange thing is: Yes, this call returns a URL for my base64 strings; but this URL never becomes valid, even if I wait some time and reload the browser. So, this is a kind of broken forever URL. It is also unclear to me, why this .url() call should even differ, since I am only calling the .url() function after the callback from insert has been executed. This is because the callback will add the image to my model object and only this will trigger the re-rendering of my templates. So, the .url() should be available at this time. At least, if the insert callback is called after the upload was successful, as I expect.

Anyway, I'm still wondering how on earth I can get a URL for a file that has been uploaded to GridFS as a base64 encoded string, as this is the only thing I get from the mdg:camera API?!

derwaldgeist commented 9 years ago

I now opened the URL that is returned by .url({ brokenIsFine: true }) in the browser. Here one can see an error message:

Error in method "/cfs/files/:value/:value/", Error: TypeError: Cannot read property 'images' of undefined
    at Object.httpGetHandler (packages/cfs:access-point/access-point-handlers.js:61:1)
    at Object.accessPoint.get (packages/cfs:access-point/access-point-server.js:175:1)
    at packages/cfs:http-methods/http.methods.server.api.js:575:1
derwaldgeist commented 9 years ago

After inspecting the database collections, I found out that the file content was never saved to the GridFS collection, if it was uploaded as base64. There is only a file record in cfs.images.filerecord, but no record in cfs_gridfs.images.files. It is quite obvious that this way of uploading a file is broken.

EDIT: I've now also tested uploading a Blob. Same thing. There is a small difference in the database, though. For a blob, "original" contains an entry "updatedAt". For base64 data URLs, this updatedAt is missing, there is only "type" and "size".

derwaldgeist commented 9 years ago

Since there was no feedback so far, I switched over to Slingshot, although that package is not optimal for my use-case. But at least the upload worked there. Still hoping that CollectionFS/GridFS will support this scenario at some time.

AlexFrazer commented 9 years ago

You want to insert as base 64 string? I managed to do this as so:

Meteor.methods({
  'uploadImage': function (base64EncodedImage) {
    let future = new Future();
    let onComplete = future.resolver();
    let fsFile = new FS.File();
    fsFile.attachData(base64EncodedImage, function(error) {
      if (error) resolve(error, null);
      QAImages.insert(fsFile, function (err, fileObj) {
        onComplete(null, fileObj.url());
      });
    });
    return future.wait();
});