2012-03-24 20 views
7

Agregué la solución de use base64 image with Carrierwave en un esfuerzo por cargar una imagen de una clase de Java. Esto es lo que ahora mi clase FileUploader parece - y yo creo que es donde está el problema:Carga de Base64 de Android/Java a RoR Carrierwave

# 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 

El modelo de la imagen:

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 

este momento cuando la llamada del anuncio se hace la ruta del archivo que se guarda en el db es nulo, pero todo lo demás se guarda. Aquí está la clase java/android:

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); 
    } 

} 

¿Alguien tiene alguna idea? Estuve atascado en esto todo el día. Creo que porque no sé mucho sobre Ruby estoy (1) malformando la solicitud; o (2) Implementé la imagen base64 con Carrierwave incorrectamente.

Respuesta

19

¡Finalmente resolvió el problema! Espero que esta respuesta ayude a otros que están tratando de resolver este problema, ya que no hay un buen recurso para ello. Esto fue sorprendente ya que pensé que otros querrían hacer lo mismo. Parece que mis cambios originales en el archivo de inicialización de Carrierwave han sido un callejón sin salida.

De lo que se trataba era de crear ese objeto de imagen cargado en el controlador y volver a inyectarlo en los parámetros.

Para este ejemplo específico, tomamos un archivo base64 (que supongo que tiene, ya que JSON no admite archivos embebidos) y lo guardo como un archivo temporal en el sistema, entonces estamos creando el objeto UploadedFile y finalmente reinyectándolo en los params.

Lo que mis JSON/params parece:

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

Aquí es lo que mi controlador ve ahora:

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 

El único que queda por hacer en este momento es para borrar el archivo temporal, que creo que se puede hacer con tempfile.delete

Espero que esto ayude con su pregunta! Pasé todo el día buscando una solución ayer, y todo lo que he visto es un callejón sin salida. Esto, sin embargo, funciona en mis casos de prueba.

+1

¡Funcionó muy bien! Gracias por esto. Terminé poniendo el procesamiento de parámetros en un 'before_filter: process_attached_file, only:: create' llamada para ayudar a mantener el método de crear más limpio, sin embargo. –

+3

Estoy tratando de hacer lo mismo; ¿Puedes publicar tu código final de Java para tu aplicación de Android? – scientiffic

+0

¿Te importa compartir tu implementación final para la función de carga en el lado de Android? He hecho algo similar y también estoy usando Rails, pero la carga real es increíblemente lenta, suponiendo que esto se deba a que estoy probando en webrick, pero me gustaría saber si redujo la imagen o redujo la resolución. de cualquier manera antes de subir – Riptyde4