CulturalMe / meteor-slingshot

Upload files directly to AWS S3, Google Cloud Storage and others in meteor
MIT License
595 stars 104 forks source link

Cordova File URI #49

Open vertangelx opened 9 years ago

vertangelx commented 9 years ago

Can we pass either DATA_URL or FILE_URI from Cordova's navigator.camera.getPicture to the first parameter of uploader.send?

Right now it seems that we need to pass the file object from an input with type="file"

gsuess commented 9 years ago

Can't you construct a blob from DATA_URL or FILE_URI?

I don't know much about cordova, but there has to be a way to read file contents. And if you can read file contents, then you can create blobs.

gsuess commented 9 years ago

From here I gather:

function onInitFs(fs) {
 fs.root.getFile(fileURL, {}, function(fileEntry) {

    // Get a File object representing the file.
    fileEntry.file(function(file) {
        //file is someting you can send through slingshot.
    }, errorHandler);

  }, errorHandler);
}

window.requestFileSystem(window.TEMPORARY, 1024*1024, onInitFs, errorHandler);
danecando commented 9 years ago

I was curious about this as well and I found out that you can construct the object from the file uri in cordova using the following:

window.resolveLocalFileSystemURL(file.uri, function(fileEntry) {
                fileEntry.file(function(fileObj) {
                    var uploader = new Slingshot.Upload("myFileUploads")
                    uploader.send(fileObj, function (error, downloadUrl) {
                        if (error) console.log(error)
                        console.log(downloadUrl)
                    })
                })

Slingshot accepts the file object fine and it goes ahead with the upload and returns the file url with success but when I view any files uploaded like this from cordova on my s3 bucket they are all 15 bytes large and obviously corrupted somewhere in the process.

If I could get some help with this issue that would be great. I have some code to upload a file to my s3 bucket from cordova with just the uri which works fine but I would prefer to just use the slingshot package for both cordova and web.

danecando commented 9 years ago

Also just noticed testing in the iOS simulator that the object doesn't get a type assigned with is an issue

I'll see if I can figure these out

gsuess commented 9 years ago

@danecando thanks for sharing this. Did you check if fileObj.size matches the size that you are looking for? Also did you try the method I've posted above?

danecando commented 9 years ago

@gsuess I couldn't get the fileEntry using the code you provided.

The file sizes in the object are correct before the upload.

gsuess commented 9 years ago

@danecando did you adjust the file size accordingly in code I've provided? In that sample it was limited to 1MB (1024*1024), which is not very high.

As for your method, 15 bytes sounds like a URI string itself. Did you check the contents of the file that is uploaded to the bucket? Also note that S3 will not overwrite files if you provide the same key as an already existing file. Instead it will silently ignore your upload.

danecando commented 9 years ago

@gsuess this is what the files contain

[object Object] <- perfect 15 bytes :p

gsuess commented 9 years ago

Wonderful.

Check file objects contents with the FileReader:

var reader = new FileReader()

reader.onload = function (event) {
  var contents = event.target.result;
  console.log(contents);
};

reader.readAsText(fileObject);
danecando commented 9 years ago

the onload event doesn't fire

gsuess commented 9 years ago

@danecando did you check the onerror handler then? Looks like we are getting to the bottom of it.

danecando commented 9 years ago
{"type":"error","bubbles":false,"cancelBubble":false,"cancelable":false,"lengthComputable":false,"loaded":0,"total":0,"target":{"_readyState":2,"_error":{"code":5},"_result":null,"_localURL":"cdvfile://localhost/temporary/cdv_photo_027.jpg","_realReader":{"result":null}}}
danecando commented 9 years ago

the first is what the file object looks like and the second is the readastext error

I20150119-11:23:22.806(-5)? (ios) {"name":"cdv_photo_030.jpg","localURL":"cdvfile://localhost/temporary/cdv_photo_030.jpg","type":null,"lastModified":1421684596000,"lastModifiedDate":1421684596000,"size":1396725,"start":0,"end":1396725}
I20150119-11:23:22.808(-5)? (ios) {"type":"error","bubbles":false,"cancelBubble":false,"cancelable":false,"lengthComputable":false,"loaded":0,"total":0,"target":{"_readyState":2,"_error":{"code":5},"_result":null,"_localURL":"cdvfile://localhost/temporary/cdv_photo_030.jpg","_realReader":{"result":null}}}
gsuess commented 9 years ago

So the file comes from the camera? Perhaps should not load images as text...

Use .readAsDataURL() instead.

danecando commented 9 years ago

it looks like readAsDataURL returns successfully with: data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEASABIAAD/......... <- really long string

gsuess commented 9 years ago

I think your platform doesn't support sending files with FormData.

geritol commented 9 years ago

this would be really great to implement!

gsuess commented 9 years ago

@geritol what are you referring to?

geritol commented 9 years ago

to make somehow possible to post pictures from cordova apps.

gsuess commented 9 years ago

@geritol it is possible to post pictures: https://github.com/CulturalMe/meteor-slingshot/issues/24

thomasf1 commented 9 years ago

Hey guys...

Has anyone found a good solution? I´m currently using resolveLocalFileSystemURL, but get the "[object Object]" uploaded as well...

danecando commented 9 years ago

@thomasf1 I'm getting the cordova file with fileEntry.file, turning that into a data uri, and then turning the data uri into a blob

thomasf1 commented 9 years ago

@danecando Thanks! I´ll try that :)

Could you share some of your code? :)

danecando commented 9 years ago

@thomasf1 something like this: https://gist.github.com/danecando/92ef9240c6ab715f35c4

thomasf1 commented 9 years ago

@danecando Thanks, that helped me a lot :)... Got it almost working, some filename stuff remaining...

thomasf1 commented 9 years ago

@danecando Funny thing: It worked perfectly on iOS (it was our last issue, submitted to the app store), but doesn´t seem to work on Android - there the uploaded File on S3 is empty... Did you have similar issues?

danecando commented 9 years ago

@thomasf1 it works fine for me on my galaxy s4. I haven't really tested on any other android devices. I'm also sending mine through another function that resizes the image and spits out a data URI

thomasf1 commented 9 years ago

@danecando Ah, in what cases was the build-in "targetWidth:" not quite working/satisfactory?

lorensr commented 9 years ago

When using mdg:camera https://github.com/meteor/mobile-packages/tree/master/packages/mdg:camera you get back a DATA_URL, which can be converted to a blob with

function dataURItoBlob(dataURI) {
    var binary = atob(dataURI.split(',')[1]);
    var array = [];
    for(var i = 0; i < binary.length; i++) {
        array.push(binary.charCodeAt(i));
    }
    return new Blob([new Uint8Array(array)], {type: 'image/jpeg'});
}
kevohagan commented 9 years ago

@danecando this doesnt work for me with video file :/ im getting this problem : http://stackoverflow.com/questions/26733070/cordova-capture-video-and-retrieve-base64-data

basically returns " data:video/mp4;base64," but there isn't any encoded data after the filetype.

any thoughts guys ? @gsuess @danecando @lorensr @thomasf1

Thanks

kevohagan commented 9 years ago

For future reference this is what i did , i had to recreate the file object from media-capture plugin with resolveLocalFileSystem


      captureSuccess = (mediaFiles) ->

        path = mediaFiles[0].fullPath
        window.resolveLocalFileSystemURL path, (fileEntry)->

          fileEntry.file((data)->

            reader = new FileReader()

            reader.onloadend = (e)->
              console.log e.target.result
              blob = b64toBlobAlt(e.target.result, 'video/mp4')
              console.log blob

              if blob
                uploader = new Slingshot.Upload('myVideoUpload')
                uploader.send blob, (error, downloadUrl) ->
                  if error
                    console.error 'Error uploading', uploader.xhr.response
                  else
                    console.log downloadUrl
                    Meteor.users.update Meteor.userId(), $set: 'profile.videoUrl': downloadUrl
                  return

            reader.readAsDataURL(data)

          )

        return
PranayShah commented 9 years ago

Same issue with me too. The file argument of the callback in FileEntry.file ( ) is accepted by uploader.send but the file uploaded is exactly of 15 bytes, corrupted and the checksums don't match

thomasf1 commented 9 years ago

@kevohagan Sorry, we didn´t try/use Videos...

elGusto commented 9 years ago

@thomasf1 How did you resolve your file naming issues if they are (resolved that is) ?

nikhildaga commented 9 years ago

@kevohagan : Were you able to upload videos successfully? I am not with these code.


  var captureSuccess = function(mediaFiles) {
    var b64toBlobAlt = function(dataURI, contentType) {
      var ab, byteString, i, ia;
      byteString = atob(dataURI.split(',')[1]);
      ab = new ArrayBuffer(byteString.length);
      ia = new Uint8Array(ab);
      i = 0;
      while (i < byteString.length) {
        ia[i] = byteString.charCodeAt(i);
        i++;
      }
      return new Blob([ab], {
        type: contentType
      });
    };
    var path = mediaFiles[0].fullPath;
    window.resolveLocalFileSystemURL(path, function(fileEntry) {
      fileEntry.file(function(data) {
        var reader = new FileReader();
        reader.onloadend = function(e) {
          var blob = b64toBlobAlt(e.target.result, 'video/mp4');
          if (blob) {
            var uploader = new Slingshot.Upload("myFileUploads");
            alert(uploader);
            uploader.send(blob, function(error, downloadUrl) {
              if (error) {
                alert('Error uploading', uploader.xhr.response);
              } else {
                alert(downloadUrl);
              }
            });
          }
        };
        reader.readAsDataURL(data);
      });
    });
  };
}
derwaldgeist commented 8 years ago

I'm stuck at this, too. Tried the Blob conversion (using both code I found somewhere else and the @lorensr version), but I always get the following error message:

processMessage failed: Stack: Error: Match error: Expected particular constructor
    at checkSubtree (http://meteor.local/packages/check.js?ac81167b8513b85b926c167bba423981b0c4cf9c:287:11)
    at check (http://meteor.local/packages/check.js?ac81167b8513b85b926c167bba423981b0c4cf9c:67:5)
    at _.extend.send (http://meteor.local/packages/edgee_slingshot.js?83a247b1bf011050d107d9d4e4ac2b3c0ae4ab34:110:7)

Files uploaded using a file-input field are working fine with my code. It's the data URI's I'm struggling with (BTW: had similar problems with CollectionFS/GridFS, so I hoped it would work better with slingshot). Would anybody who already solved this problem please be so kind and post a complete working sample? That would be great! PS: Like @elGusto I'm wondering how I can set a filename for my S3 bucket if I'm uploading the file as a data URI. I guess that's a case for the 'key' field of the directive, but maybe there's an easier way to do this?

EDIT: My problem is solved. The root cause was Meteor loading a very, very old version of Slingshot (0.0.3) instead of the latest one. Slingshot uploads now work as a charm.

bmanturner commented 8 years ago

Does anyone have an example that works on the iOS simulator?

costinelmarin commented 8 years ago

None of the example work for me. The code below works for me on METEOR@1.3-cordova-beta.2 hope will help someone i don't try on iOS simulator just on actual device.

function videoCaptureSuccess(mediaFiles) {

  var file = '/local-filesystem' + mediaFiles[0].fullPath;

  getBlobURL(file, "video/quicktime", function(url, blob){
    uploader.send(blob, function (error, downloadUrl) {
      alert(downloadUrl);
    });
  });
}

function getBlobURL(url, mime, callback) {
  var xhr = new XMLHttpRequest();
  xhr.open("get", url);
  xhr.responseType = "arraybuffer";

  xhr.addEventListener("load", function() {
    var arrayBufferView = new Uint8Array( this.response );
    var blob = new Blob( [ arrayBufferView ], { type: mime } );
    var url = window.URL.createObjectURL(blob);

    callback(url, blob);
  });
  xhr.send();
}
jamalx31 commented 8 years ago

+1

cluxter commented 8 years ago

Same problem here: the file stored on AWS S3 is 15 bytes long and contains:

[object Object]

I still don't understand why exactly. It would be cool to patch Slingshot instead of using a hack. After all the purpose of Slingshot is to avoid these sort of pains.

mordka commented 7 years ago

While working on Ionic 2 app I encountered same issue with FIleReader events not firing. Turned out to be an issue with zone.js and after long hours I got an workaround here: https://github.com/driftyco/ionic-native/issues/505#issuecomment-243434491