How do I get a temporary File object (of correct content-type, without writing to disk) directly from a ZipEntry (RubyZip, Paperclip, Rails 3)?
Asked Answered
D

2

6

I'm currently trying to attach image files to a model directly from a zip file (i.e. without first saving them on a disk). It seems like there should be a clearer way of converting a ZipEntry to a Tempfile or File that can be stored in memory to be passed to another method or object that knows what to do with it.

Here's my code:

def extract (file = nil)
  Zip::ZipFile.open(file) { |zip_file|
    zip_file.each { |image|
      photo = self.photos.build
      # photo.image = image # this doesn't work
      # photo.image = File.open image # also doesn't work
      # photo.image = File.new image.filename
      photo.save
    }
  }
end

But the problem is that photo.image is an attachment (via paperclip) to the model, and assigning something as an attachment requires that something to be a File object. However, I cannot for the life of me figure out how to convert a ZipEntry to a File. The only way I've seen of opening or creating a File is to use a string to its path - meaning I have to extract the file to a location. Really, that just seems silly. Why can't I just extract the ZipEntry file to the output stream and convert it to a File there?

So the ultimate question: Can I extract a ZipEntry from a Zip file and turn it directly into a File object (or attach it directly as a Paperclip object)? Or am I stuck actually storing it on the hard drive before I can attach it, even though that version will be deleted in the end?

UPDATE Thanks to blueberry fields, I think I'm a little closer to my solution. Here's the line of code that I added, and it gives me the Tempfile/File that I need:

photo.image = zip_file.get_output_stream image

However, my Photo object won't accept the file that's getting passed, since it's not an image/jpeg. In fact, checking the content_type of the file shows application/x-empty. I think this may be because getting the output stream seems to append a timestamp to the end of the file, so that it ends up looking like imagename.jpg20110203-20203-hukq0n. Edit: Also, the tempfile that it creates doesn't contain any data and is of size 0. So it's looking like this might not be the answer.

So, next question: does anyone know how to get this to give me an image/jpeg file?

UPDATE:

I've been playing around with this some more. It seems output stream is not the way to go, but rather an input stream (which is which has always kind of confused me). Using get_input_stream on the ZipEntry, I get the binary data in the file. I think now I just need to figure out how to get this into a Paperclip attachment (as a File object). I've tried pushing the ZipInputStream directly to the attachment, but of course, that doesn't work. I really find it hard to believe that no one has tried to cast an extracted ZipEntry as a File. Is there some reason that this would be considered bad programming practice? It seems to me like skipping the disk write for a temp file would be perfectly acceptable and supported in something like Zip archive management.

Anyway, the question still stands:

Is there a way of converting an Input Stream to a File object (or Tempfile)? Preferably without having to write to a disk.

Dysentery answered 4/2, 2011 at 0:28 Comment(1)
File objects just encapsulate a filename. They don't have a content-type of any description.Plenary
B
6

Try this

Zip::ZipFile.open(params[:avatar].path) do |zipfile|
  zipfile.each do |entry|
    filename = entry.name
    basename = File.basename(filename)

    tempfile = Tempfile.new(basename)
    tempfile.binmode
    tempfile.write entry.get_input_stream.read

    user = User.new
    user.avatar = {
      :tempfile => tempfile,
      :filename => filename
    }
    user.save

  end
end
Blissful answered 5/8, 2011 at 11:15 Comment(2)
This works, but the syntax for the tempfile creation was incorrect. I've edited that to use a different syntax.Newburg
In this example, you're setting user.avatar to a hash. Is User.avatar a Paperclip::Attachment? If so, how can you pass in a hash? I've tried it, and it doesn't work for me. Paperclip:Attachment has a set of "io adapters" for different types of objects (Such as StringIO and Tempfile), but I looked through the io_adapters, and can't see one which would handle a hash.Hijoung
E
0

Check out the get_input_stream and get_output_stream messages on ZipFile.

Errancy answered 4/2, 2011 at 0:39 Comment(2)
I tried this out. Using zip_file.get_output_stream(image) did result in a File. However, the model seems to interpret that file as 'application/x-empty', which causes it to fail. It also seems to be reading the filename as 'image_name.jpg20110203-20203-1lyjn3i', which I assume is the standard timestamp that paperclip gives its images for version tracking. Do you know of a way to make sure that it is read as a jpeg?Dysentery
Actually, I just checked it out. The output stream is opened as a Tempfile. That timestamp is added when that Tempfile is created. I just checked content_type of the Tempfile that is extracted, and it comes out as application/x-empty.Dysentery

© 2022 - 2024 — McMap. All rights reserved.