Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Base64 upload from Android/Java to RoR Carrierwave

I added the solution from use base64 image with Carrierwave in an effort to upload an image from a java class. This is now what my FileUploader class looks like -- and I believe to be where the problem is:

# encoding: utf-8

class FileUploader < CarrierWave::Uploader::Base

  # Include RMagick or MiniMagick support:
    include CarrierWave::RMagick
  # include CarrierWave::MiniMagick

  # Choose what kind of storage to use for this uploader:
  storage :file
  # storage :fog

  #START FROM BASE64 POST LINKED ABOVE
  class FilelessIO < StringIO
    attr_accessor :original_filename
    attr_accessor :content_type
  end

  before :cache, :convert_base64

  def convert_base64(file)
    if file.respond_to?(:original_filename) &&
        file.original_filename.match(/^base64:/)
      fname = file.original_filename.gsub(/^base64:/, '')
      ctype = file.content_type
      decoded = Base64.decode64(file.read)
      file.file.tempfile.close!
      decoded = FilelessIO.new(decoded)
      decoded.original_filename = fname
      decoded.content_type = ctype
      file.__send__ :file=, decoded
    end
    file
  end
#END FROM POST LINKED ABOVE


  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{model.user_id}"
  end

  # Provide a default URL as a default if there hasn't been a file uploaded:
  # def default_url
  #   "/images/fallback/" + [version_name, "default.png"].compact.join('_')
  # end

  # Process files as they are uploaded:
  # process :scale => [200, 300]
  #
  # def scale(width, height)
  #   # do something
  # end

  # Create different versions of your uploaded files:
    version :thumb do
      process :resize_to_fit  => [200, 300]
    end

    version :web do
      process :resize_to_fit  => [1000, 1000]
    end

  # Add a white list of extensions which are allowed to be uploaded.
  # For images you might use something like this:
   def extension_white_list
     %w(jpg jpeg gif png)
   end

  # Override the filename of the uploaded files:
  # Avoid using model.id or version_name here, see uploader/store.rb for details.
   def filename
     if original_filename
     Time.new.to_i.to_s+"_"+original_filename
     end
   end

end

The picture model:

class Picture < ActiveRecord::Base

  belongs_to :user
  belongs_to :folders

  attr_accessible :user_id, :picture_name, :picture_description,
    :folder_id, :picture_path, :file_save

  mount_uploader :picture_path, FileUploader

   before_save :update_pictures_attributes

  def update_pictures_attributes
      self.file_size = picture_path.file.size
  end

end

Right now when the Post call is made the file path that is saved in the db is nil -- but everything else is saved. Here is the java/android class:

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.client.*;
import org.apache.http.client.entity.*;
import org.apache.http.client.methods.*;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.*;
import org.apache.http.message.*;
import org.apache.commons.io.FileUtils;
import org.json.*;
import android.util.Base64;
import android.util.Log;

public class Uploader {

    private String url;
    private String fileName;

    public Uploader(String url, String fileName){
        this.url = url;
        this.fileName = fileName;
    }

    public Boolean upload() throws JSONException, ClientProtocolException, IOException {
        Boolean success = true;
        JSONObject jsonObject = constructPictureJson();
            DefaultHttpClient httpClient = new DefaultHttpClient();

            ResponseHandler <String> responseHandler = new BasicResponseHandler();
            HttpPost postMethod = new HttpPost(url);
            postMethod.setEntity(new StringEntity(jsonObject.toString()));
            postMethod.setHeader("Accept", "application/json");
            postMethod.setHeader("Content-type", "application/json");
            postMethod.setHeader("Data-type", "json");
            try{
            httpClient.execute(postMethod, responseHandler);
            } catch (org.apache.http.client.HttpResponseException error){
                Log.d("Uploader Class Error", "Error code: "+error.getStatusCode());
                Log.d("Uploader Class Error", "Error message: "+error.getMessage());
                success = false;
            }
            //Log.d("server resposne", response);
            return success;
    }

    public JSONObject constructPictureJson() throws JSONException, IOException{
        String userId = "1"; 
        String folderId = "1";
        String[] file = fileName.split("/");
        JSONObject pictureData = new JSONObject();
        pictureData.put("user_id", userId);
        pictureData.put("folder_id", folderId); 
        pictureData.put("picture_name", "picture name");
        pictureData.put("picture_description", "1"); 
        pictureData.put("content_type", "jpg");
        pictureData.put("original_filename", "base64:"+file[file.length-1]);
        pictureData.put("filename", file[file.length-1]);
        pictureData.put("picture_path", encodePicture(fileName));

        return pictureData;
    }

    public String encodePicture(String fileName) throws IOException{
        File picture = new File(fileName);
        return Base64.encodeToString(FileUtils.readFileToByteArray(picture), Base64.DEFAULT);
    }

}

Does anyone have any ideas? I've been stuck on this all day. I think because I don't know much about Ruby I am either (1) malforming the request; or (2) I implemented the base64 image with Carrierwave incorrectly.

like image 304
TomJ Avatar asked Mar 24 '12 19:03

TomJ


1 Answers

Finally solved the problem! I hope this answer helps out others who are trying to solve this problem as there is no good resource for it. This was surprising as I figured others would have wanted to do the same. My original changes to the Carrierwave initialize file appear to have been a dead end.

What it came down to was creating that uploaded image object in the controller and then injecting it back into the params.

For this specific example, we are taking a base64 file (which I assume you have, as JSON doesn't support embeded files) and saving it as a temp file in the system then we are creating that UploadedFile object and finally reinjecting it into the params.

What my json/params looks like:

picture {:user_id => "1", :folder_id => 1, etc., :picture_path {:file => "base64 awesomeness", :original_filename => "my file name", :filename => "my file name"}}

Here is what my controller looks like now:

40        # POST /pictures
41    # POST /pictures.json
42    def create
43  
44      #check if file is within picture_path
45      if params[:picture][:picture_path]["file"]
46           picture_path_params = params[:picture][:picture_path]
47           #create a new tempfile named fileupload
48           tempfile = Tempfile.new("fileupload")
49           tempfile.binmode
50           #get the file and decode it with base64 then write it to the tempfile
51           tempfile.write(Base64.decode64(picture_path_params["file"]))
52     
53           #create a new uploaded file
54           uploaded_file = ActionDispatch::Http::UploadedFile.new(:tempfile => tempfile, :filename => picture_path_params["filename"], :original_filename => picture_path_params["original_filename"]) 
55     
56           #replace picture_path with the new uploaded file
57           params[:picture][:picture_path] =  uploaded_file
58     
59      end
60  
61      @picture = Picture.new(params[:picture])
62  
63      respond_to do |format|
64        if @picture.save
65          format.html { redirect_to @picture, notice: 'Picture was successfully created.' }
66          format.json { render json: @picture, status: :created, location: @picture }
67        else
68          format.html { render action: "new" }
69          format.json { render json: @picture.errors, status: :unprocessable_entity }
70        end
71      end
72    end

The only thing left to do at this point is to delete the tempfile, which I believe can be done with tempfile.delete

I hope this helps with your question! I spent all day looking for a solution yesterday, and everything I have seen is a dead end. This, however, works on my test cases.

like image 68
TomJ Avatar answered Oct 20 '22 15:10

TomJ