5

Este problema me ha estado molestando durante muchas horas y parece que no puedo encontrar una solución.Carga de varios archivos directamente a Amazon S3 utilizando Rails 3.2 y AJAX (soluciones de carga que no sean de memoria flash)

Tengo una aplicación Rails 3.2 que permite a los usuarios subir archivos a una cuenta de Amazon S3 utilizando carrierwave_direct, fog y carrierwave (dependencia para carrierwave_direct). El uso de carrierwave_direct permite al usuario omitir la carga del archivo en el servidor al enviarlo directamente a Amazon S3 (guarda el procesamiento del servidor y los tiempos de espera como Heroku para archivos de gran tamaño).

Funciona bien si todo lo que hace es seleccionar 1 archivo, cárguelo en Amazon y desee un redireccionamiento a la URL que proporcione Amazon. Lo hace al enviar el formulario a Amazon S3 y Amazon responde a una URL proporcionada (especifique esta URL en su formulario) con algunos parámetros en la URL, que luego se almacenan como un puntero al archivo en Amazon en su modelo.

Así que el ciclo de vida es: seleccione 1 archivo, POST en Amazon, Amazon responde con una URL que lo envía a otra página, y luego puede guardar un registro con un puntero al archivo de Amazon.

Lo que he estado intentando averiguar es cómo puedo permitir que se seleccionen y carguen varios archivos y actualizar el progreso de la carga? Intento hacer esto con javascript puro (usando la API de archivo proporcionada por los navegadores modernos), así que no quiero ninguna herramienta de terceros. Además, en la búsqueda de aprender esto en profundidad, evito cualquier plugin e intento escribir el código yo mismo.

La funcionalidad que estoy tratando de obtener es:

  1. usuario ve formulario con campo de archivo (o arrastrar/soltar)
  2. El usuario selecciona varios archivos (o bien haga clic en el campo de archivos o arrastrar/soltar)
  3. el uso de Javascript (no hay servidores aún), construir una cola de archivos seleccionados para subir (solo nombre de archivo y el tamaño, el uso de la API de navegador de archivos)
  4. usuario hace clic en un "Subir comenzar" botón
  5. iterar sobre cada archivo en la cola un d PUBLIQUE el archivo a Amazon S3; Amazon responderá a cada POST individual con una URL y esa URL debe manejarse a través de Javascript, no como una solicitud estándar; la URL proporcionada por Amazon creará un registro que almacena el puntero al archivo de Amazon; una vez que se ha creado el registro, el código pasa al siguiente archivo en la cola hasta que finaliza.

En este punto, podría hacerlo sin una barra de progreso individual; Estaría feliz de obtener múltiples archivos enviados a Amazon S3 sin actualizaciones de página.

No soy del agrado de ninguna de las gemas. De hecho, tengo miedo de tener que escribir desde cero lo que quiero hacer si realmente quiero que se haga de una manera específica. El objetivo es cargar archivos múltiples en una cuenta de Amazon S3 a través de AJAX. Estaría extasiado incluso con conceptos generales de cómo abordar el problema. He pasado muchas horas buscando en Google esto y simplemente no he encontrado ninguna solución que haga lo que quiero. Cualquier ayuda sería muy apreciada.

EDITAR 2014-03-02

Raj preguntó cómo he implementado mi carga múltiple. Ha pasado tanto tiempo que no recuerdo todo el "por qué" detrás de lo que hice (probablemente el código fue malo de todos modos, ya que era la primera vez), pero esto es lo que estaba pasando.

El modelo que estaba cargando era un Testimonio, que tiene una imagen asociada almacenada en Amazon S3.Permitió al usuario seleccionar múltiples imágenes (creo que en realidad eran archivos PDF que convertí en imágenes) y arrastrarlas/soltarlas en la pantalla. Mientras subía, mostré un modo que le daba al usuario comentarios sobre cuánto tiempo tomaría.

No pretendo recordar lo que estaba haciendo en un montón de esto, pero si ayuda a sentir libre de usarlo.

# Gemfile 
# For client-side multiple uploads 
gem "jquery-fileupload-rails" 

# For file uploads and Amazon S3 storage 
gem "rmagick" 
gem "carrierwave" 
gem "fog" 

Aquí está la vista:

# app/views/testimonials/new.html.erb 
<div id="main" class="padded"> 
    <div class="center"> 
    <div id="dropzone"> 
     Click or Drop Files here to Upload 
    </div> 

    <%= form_for @testimonial do |f| %> 
     <div class="field"> 
     <%= file_field_tag :image, multiple: true, name: "testimonial[image]", id: "testimonial_image" %> 
     </div> 
    <% end %> 
    </div> 
</div> 

<div id="mask"></div> 
<div id="modal"> 
    <h1> 
    Uploading <span id="global-upload-count">0</span> Files... 
    </h1> 
    <div id="global-progress"> 
    <div id="global-progress-bar" style="width: 0%"> 
     <div id="global-progress-percentage">0%</div> 
    </div> 
    </div> 
    <div id="global-processing"> 
    <span class="spinner"></span> Processing...<span id="global-processing-count">0</span> sec 
    </div> 
</div> 

<script id="template-upload" type="text/x-tmpl"> 
    <div class="upload"> 
    {%=o.name%} ({%=o.readable_size%}) 
    <div class="float-right percentage"></div> 
    <div class="progress"><div class="bar" style="width: 0%"></div></div> 
    </div> 
</script> 

Y la JS:

number_to_human_size = (bytes) -> 
    sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'] 
    i = parseInt(Math.floor(Math.log(bytes)/Math.log(1024))) 
    return Math.round(bytes/Math.pow(1024, i), 2) + ' ' + sizes[i] 

dropzone_hover = (e) -> 
    e.preventDefault() 
    $(this).addClass("dropzone-hover") 

dropzone_leave = (e) -> 
    e.preventDefault() 
    $(this).removeClass("dropzone-hover") 

jQuery -> 
    global_count = 0 
    seconds_to_process = 0 
    processing_factor = 5 # seconds to convert/process each uploaded file 

    $("#testimonial_image").hide() 

    dropzone = $("#dropzone") 

    dropzone.bind "click", (e) -> 
    $("#testimonial_image").click() 

    dropzone.bind("dragover", dropzone_hover) 
    dropzone.bind("dragleave", dropzone_leave) 
    dropzone.bind("drop", dropzone_leave) 

    $("#new_testimonial").data("global-count", "0") 

    $("#new_testimonial").fileupload 
    dropZone: $("#dropzone") 
    maxFileSize: 5000000 # 5 MB 
    dataType: "script" 

    add: (e, data) -> 
     file = data.files[0] 
     file.readable_size = number_to_human_size(file.size) 
     data.context = $(tmpl("template-upload", file).trim()) 
     $("#new_testimonial").append(data.context) 
     data.submit() 
     global_count += 1 

    progress: (e, data) -> 
     if data.context 
     progress = parseInt(data.loaded/data.total * 100, 10) 
     data.context.find(".bar").css("width", progress + "%") 
     data.context.find(".percentage").text(progress + "%") 

    submit: (e, data) -> 
     $("#mask").show() 
     $("#modal").center().show() 

    progressall: (e, data) -> 
     $("#global-upload-count").text(global_count) 
     global_progress = parseInt(data.loaded/data.total * 100, 10) 
     $("#global-progress-bar").css("width", global_progress + "%") 
     $("#global-progress-percentage").text(global_progress + "%") 

     if global_progress >= 100 
     seconds_to_process = global_count * processing_factor 
     $("#global-processing-count").text(seconds_to_process) 

     $("#global-processing").show() 

     timer = setInterval(-> 
      seconds_to_process = seconds_to_process - 1 
      $("#global-processing-count").text(seconds_to_process) 

      if seconds_to_process == 0 
      clearInterval(timer) 
      global_count = 0 
      seconds_to_process = 0 
      $("#modal, #mask").hide(0) 
     , 1000) 

El modelo Testimonio:

class Testimonial < ActiveRecord::Base 
    mount_uploader :image, ImageUploader 

    def display_name 
    if name.blank? 
     return "Testimonial #{self.id}" 
    else 
     return name 
    end 
    end 
end 
+2

Utilice esta: http://blueimp.github.com/jQuery-File-Upload/ – apneadiving

+0

@apneadiving que funcionó exactamente como yo quería. La página de complementos tuvo algunos ejemplos muy útiles que me orientaron en la dirección correcta y he implementado exactamente lo que deseo. Si responde a la pregunta en lugar de un comentario, con mucho gusto aceptaré su respuesta. Muchas gracias, me ayudaste a hacer un gran esfuerzo. –

+0

Agradable de leer :) – apneadiving

Respuesta

6

como se aconseja en comentario, utilice jQuery Subir: http://blueimp.github.com/jQuery-File-Upload/

+7

Si bien esto puede teóricamente responder a la pregunta, [sería preferible] (http://meta.stackexchange.com/q/8259) para incluir las partes esenciales de la respuesta aquí, y proporcionar el enlace de referencia. –

+1

@IlmariKaronen Publiqué al solicitar asker. Pero no dude en votar negativamente si le gusta – apneadiving

+0

Alguien marcó su respuesta como "no es una respuesta". No estoy del todo de acuerdo, pero * es * bastante limítrofe. Dicho eso, la pregunta tampoco es la más clara jamás. –

0

He comenzado a escribir una biblioteca básica para esta funcionalidad. Tengo una versión que funciona en github, y estoy escribiendo una serie de publicaciones en blogs para detallar cómo lograr esto.

código 'Trabajo' se puede encontrar en: https://github.com/joeandrews/s3multipartupload.

Cuestiones relacionadas