Rubyzip problems on Windows - ruby-on-rails

I'm using Rubyzip to unzip files that are uploaded by the user. The file contains a bunch of images, which are unpacked and put into a folder. This works fine on a mac, but on windows it won't unpack the zip file. Here's the model I use:
require "zip/zip"
class Photo < ActiveRecord::Base
validates_presence_of :image_file_name, :message => "Er is geen bestand bijgevoegd!"
belongs_to :album
has_attached_file :image, :styles => {
:original => ["1024x1024>", :jpg],
:medium => "300x250#",
:thumb => "150x100#"
}, :url => "/uploads/photos/:id/:style.:extension"
def zip?
image.content_type == "application/zip"
end
def save_photo
if zip?
extract_zip
true
else
self.save
end
end
def extract_zip
Zip::ZipFile.foreach(image.queued_for_write[:original].path) do |entry|
next if entry.name =~ /__MACOSX/ or entry.name =~ /\.DS_Store/ or !entry.file?
filename = entry.name
basename = File.basename(filename)
tempfile = Tempfile.new(basename)
tempfile.binmode
tempfile.write(entry.get_input_stream.read)
photo = Photo.create(:image => tempfile, :album_id => album_id)
end
end
end
Because it works fine on a mac I think it's the way Windows zips a file. maybe something to do with the header structure or something? Any help is very much appreciated!

Related

Carrierwave getting image width and height and storing it in an hstore field

My FileHandler model can allows all types of files
class FileHandler < ActiveRecord::Base
serialize :properties, ActiveRecord::Coders::Hstore
mount_uploader :file_path, FileUploader
//I'm already setting some of the file attributes here
def update_file_attributes
if file_path.present? && file_path_changed?
self.file_name = file_path.file.filename
self.file_type = file_path.file.content_type
self.file_size = file_path.file.size
end
end
//I want to get height and width here
#Hstore
%w[ImageHeight ImageWidth].each do |key|
attr_accessible key
define_method(key) do
properties && properties[key]
end
define_method("#{key}=") do |value|
self.properties = (properties || {}).merge(key => value)
end
end
And my fileUploader class
class FileUploader < CarrierWave::Uploader::Base
# Include RMagick or MiniMagick support:
include CarrierWave::RMagick
version :big, :if => :image? do
process :resize_to_limit => [760, nil]
end
version :thumb_big, :if => :image? do
process :resize_to_limit => [200, 200]
end
version :thumb, :if => :image? do
process :resize_to_limit => [160, 160]
end
version :tiny, :if => :image? do
process :resize_to_limit => [40, 40]
end
protected
def image?(new_file)
new_file.content_type.include? 'image'
end
end
My question is, how do i get the height and width property of the original image and store it in the hstore field?
Any help would be greatly appreciated.
Try this
class FileUploader < CarrierWave::Uploader::Base
# Include RMagick or MiniMagick support:
include CarrierWave::RMagick
process :store_geometry, :if => :image?
#......
#......
#......
def store_geometry
if image?(#file)
img = ::Magick::Image::read(#file.file).first
if model
model.ImageWidth = img.columns
model.ImageHeight = img.rows
end
end
end
end
#Hstore
%w[ImageHeight ImageWidth].each do |key|
attr_accessible key
define_method(key) do
properties && properties[key]
end
define_method("#{key}=") do |value|
self.properties = (properties || {}).merge(key => value)
end
end
Assumptions
I'm assuming there's a reason you have the image method that checks if the file is an image, that must mean you're uploading other file formats as well. Well, i've put it to good use here, it calls process_geometry method only if the file is an image.
Hope it helps.
Well you can get the dimension of Image by using Rmagick as far as I know
All I remember is that you can do is this
img = Magick::Image::read("image_file").first
img.columns => width of image
img.rows => height of image
Perhaps then you can set them in HSTORE
Hope this help
Thanks
As an addition to Tommy's comment. This is what I did to save size of every particular image version not just a size of original image. You have to add "geometry" field to your model so you'll have something like: "phone=146x220&tablet=292x440"
class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::RMagick
storage :file
version :thumb do
process :resize_to_fill => [80, 80]
end
version :phone do
process :resize_to_limit => [290, 220]
process :store_geometry => :phone
process :quality => 75
end
version :tablet do
process :resize_to_limit => [580, 440]
process :store_geometry => :tablet
process :quality => 35
end
process
def extension_white_list
%w(jpg jpeg gif png)
end
def store_geometry(version)
manipulate! do |img|
model.geometry ||= ""
model.geometry << "&" unless model.geometry.empty?
model.geometry << "#{version}=#{img.columns}x#{img.rows}"
img
end
end
end

Ruby on Rails: REST API + file upload + paperclip

I'm trying to upload an attachment using REST API on my server through a PUT request. I can do this by putting the binary file in the request body but I'd also like to save this file as an attachment to a model which uses paperclip to save attachments.
Here's my current involved class definitions:
class Cl < ActiveRecord::Base
after_update :save_tses
validates_associated :tses
has_many :tses
...truncated...
def save_tses
tses.each do |ts|
ts.save(false)
end
end
end
class Ts < ActiveRecord::Base
has_attached_file :tsa, :styles => { :thumb => {:geometry => "100x141>", :format => :jpg} },
:path => ":rails_root/public/system/:attachment/:id/:style/:friendly_filename",
:url => "/system/:attachment/:id/:style/:friendly_filename"
belongs_to :cl
def friendly_filename
"#{self.tsa_file_name.gsub( /[^a-zA-Z0-9_\.]/, '_')}"
end
end
I can save the attachments just fine using the file upload on the html page. I'd like to do this on a controller that receives the file as binary data through a PUT request.
Any suggestions?
Also you can you use -
https://github.com/jwagener/httmultiparty
Got it,
# controller.rb
def add_ts
# params[:id]
# params[:tsa]
#cl = Cl.find(params[:id])
ts = #cl.tses.build(:name => "#{#cl.name}_#{Time.now.to_i}")
ts.tsa = params[:tsa]
if ts.save
render :json => {:status => "OK"}
else
render :json => {:status => "ERROR"}
end
end
# Test
curl -F "tsa=#file.pdf" "http://host/cl/474/add_ts"
=> {"status":"OK"}

Paperclip wrong attachment url on validation errors

I have a preview of attached image in my update form. The problem appears when user gets validation errors on attachment field. In this case image thumbnail url becomes as if an image was uploaded without any errors (it shows name of file that was not saved at server).
Here is how I get image url in my view:
<%= image_tag(#product.photo.url(:medium)) %>.
Controller:
def update
#product = Product.find(params[:id])
#product.update_attributes(params[:product]) ? redirect_to('/admin') : render(:new)
end
def edit
#product = Product.find(params[:id])
render :new
end
Model:
class Product < ActiveRecord::Base
<...>
##image_sizes = {:big => '500x500>', :medium => '200x200>', :thumb=> '100x100>'}
has_attached_file :photo, :styles => ##image_sizes, :whiny => false
validates_attachment_presence :photo
validates_attachment_content_type :photo, :content_type => ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'], :message => I18n.t(:invalid_image_type)
validates_attachment_size :photo, :less_than => 1.megabytes, :message => I18n.t(:invalid_image_size, :max => '1 Mb')
after_post_process :save_image_dimensions
<...>
end
UPD: The simpliest solution is to add #photo_file_url = #product.photo.url(:medium) in controller's update action before #product.update_attributes and <% #photo_file_url ||= #product.photo.url(:medium) %> in the view.
I actually had the same issue and here is what I had done (which seems a bit of a hack) but works and will show the default or previous image on update failure
after_validation :logo_reverted?
def logo_reverted?
unless self.errors[:logo_file_size].blank? or self.errors[:logo_content_type].blank?
self.logo.instance_write(:file_name, self.logo_file_name_was)
self.logo.instance_write(:file_size, self.logo_file_size_was)
self.logo.instance_write(:content_type, self.logo_content_type_was)
end
end
In my app I had similar issues, so I used:
<%= image_tag(#product.photo.url(:medium)) if #photo.valid? %>
That way nothing shows up if the Photo isn't valid (ie successfully created), but when you edit existing records the image is shown.
Hope that helps.

Uploading to S3 on Heroku with Paperclip (delayed_job question)

I'm trying to upload to a portfolio app I've built, specifically trying to find where to hook delayed_job into the process. It all works otherwise. Right now it returns undefined method 'call' for #<Class:0xae68750> on app/controllers/portfolio_items_controller.rb:18:in 'create' so here's my model and that portion of the controller... anyone see anything that could be going wrong? The hook I'm using now I got from this blog: http://madeofcode.com/posts/42-paperclip-s3-delayed-job-in-rails
/app/controllers/portfolio_items_controller.rb
def create
#portfolio_item = PortfolioItem.new(params[:portfolio_item])
if #portfolio_item.save
flash[:notice] = "Portfolio item created. As soon as files are uploaded Portfolio item will be made live."
redirect_to #portfolio_item
else
render :action => 'new'
end
end
/app/models/asset.rb
class Asset < ActiveRecord::Base
attr_accessible :image, :image_file_name, :image_content_type, :image_file_size, :portfolio_item_id, :order
belongs_to :portfolio_item
has_attached_file :image,
:styles => {
:thumb => "20x20#",
:small => "100x100",
:large => "600x600>"
},
:storage => :s3,
:s3_credentials => {
:access_key_id => ENV["S3_KEY"],
:secret_access_key => ENV["S3_SECRET"]
},
:bucket => ENV["S3_BUCKET"],
:path => "portfolio/:attachment/:id/:style/:basename.:extension"
before_source_post_process do |image|
if source_changed?
processing = true
false
end
end
after_save do |image|
if image.source_changed?
Delayed::Job.enqueue ImageJob.new(image.id)
end
end
def regenerate_styles!
self.source.reprocess!
self.processing = false
self.save(false)
end
def source_changed?
self.source_file_size_changed? ||
self.source_file_name_changed? ||
self.source_content_type_changed? ||
self.source_update_at_changed?
end
end
class ImageJob < Struct.new(:image_id)
def perform
Image.find(self.image_id).regenerate_styles!
end
end
Edit: thanks to kind people, it's not the missing .new anymore. But now it's that the before_source_post_process is not defined? And I can't find that method in anywhere but that blog post and this SO question. Is there something more appropriate?
The before_source_post_process won't work for you. It only works for:
has_attached_file :source
In your case it should be
before_image_post_process
Similarly, the source_changed? method should be:
def source_changed?
self.image_file_size_changed? ||
self.image_file_name_changed? ||
self.image_content_type_changed? ||
self.image_update_at_changed?
end
I think this:
#portfolio_item = PortfolioItem.(params[:portfolio_item])
should most likely be this:
#portfolio_item = PortfolioItem.new(params[:portfolio_item])

Saving files using Paperclip without upload

I had a quick question. Is it possible to save a file without actually uploading it through a form?
For example, let's say I'm looking at attachments from emails, and I want to save them using a paperclip. How do I do this? Do I manually have to call a save_file(or something similar) somewhere?
Any help would be much appreciated!
I have a rake task that loads images (client logos) from a directory directly onto parperclip. You can probably adapt it to your needs.
This is my simplified Client model:
class Client < ActiveRecord::Base
LOGO_STYLES = {
:original => ['1024x768>', :jpg],
:medium => ['256x192#', :jpg],
:small => ['128x96#', :jpg]
}
has_attached_file :logo,
:styles => Client::LOGO_STYLES,
:url => "/clients/logo/:id.jpg?style=:style"
attr_protected :logo_file_name, :logo_content_type, :logo_size
Then on my rake task I do this:
# the logos are in a folder with path logos_dir
Dir.glob(File.join(logos_dir,'*')).each do |logo_path|
if File.basename(logo_path)[0]!= '.' and !File.directory? logo_path
client_code = File.basename(logo_path, '.*') #filename without extension
client = Client.find_by_code(client_code) #you could use the ids, too
raise "could not find client for client_code #{client_code}" if client.nil?
File.open(logo_path) do |f|
client.logo = f # just assign the logo attribute to a file
client.save
end #file gets closed automatically here
end
end
Regards!
The file saved in Paperclip doesn't have to be uploaded directly through a form.
I'm using Paperclip in a project to save files from URLs from webcrawler results. I'm not sure how you'd get email attachments (are they on the local file system of the server? Is your app an email app like GMail?) but as long as you can get a file stream (via something like open(URI.parse(crawl_result)) in my case...) you can attach that file to your model field that's marked has_attached_file.
This blog post about Easy Upload via URL with Paperclip helped me figure this out.
Since it now appears the original blog post is no longer available - here's the gist of it pulled from wayback machine:
This example shows a Photo model that has an Image attachment.
The technique we're using requires adding a *_remote_url (string) column for your attachment, which is used to store the original URL. So, in this case, we need to add a column named image_remote_url the photos table.
# db/migrate/20081210200032_add_image_remote_url_to_photos.rb
class AddImageRemoteUrlToPhotos < ActiveRecord::Migration
def self.up
add_column :photos, :image_remote_url, :string
end
def self.down
remove_column :photos, :image_remote_url
end
end
Nothing special is required for the controller...
# app/controllers/photos_controller.rb
class PhotosController < ApplicationController
def create
#photo = Photo.new(params[:photo])
if #photo.save
redirect_to photos_path
else
render :action => 'new'
end
end
end
In the form, we add a text_field called :image_url, so people can upload a file or provide a URL...
# app/views/photos/new.html.erb
<%= error_messages_for :photo %>
<% form_for :photo, :html => { :multipart => true } do |f| %>
Upload a photo: <%= f.file_field :image %><br>
...or provide a URL: <%= f.text_field :image_url %><br>
<%= f.submit 'Submit' %>
<% end %>
The meaty stuff is in the Photo model. We need to require open-uri, add an attr_accessor :image_url, and do the normal has_attached_file stuff. Then, we add a before_validation callback to download the file in the image_url attribute (if provided) and save the original URL as image_remote_url. Finally, we do a validates_presence_of :image_remote_url, which allows us to rescue from the many exceptions that can be raised when attempting to download the file.
# app/models/photo.rb
require 'open-uri'
class Photo < ActiveRecord::Base
attr_accessor :image_url
has_attached_file :image # etc...
before_validation :download_remote_image, :if => :image_url_provided?
validates_presence_of :image_remote_url, :if => :image_url_provided?, :message => 'is invalid or inaccessible'
private
def image_url_provided?
!self.image_url.blank?
end
def download_remote_image
self.image = do_download_remote_image
self.image_remote_url = image_url
end
def do_download_remote_image
io = open(URI.parse(image_url))
def io.original_filename; base_uri.path.split('/').last; end
io.original_filename.blank? ? nil : io
rescue # catch url errors with validations instead of exceptions (Errno::ENOENT, OpenURI::HTTPError, etc...)
end
end
Everything will work as normal, including the creation of thumbnails, etc. Plus, since we're doing all of the hard stuff in the model, "uploading" a file via URL works from within script/console as well:
$ script/console
Loading development environment (Rails 2.2.2)
>> Photo.new(:image_url => 'http://www.google.com/intl/en_ALL/images/logo.gif')
=> #<Photo image_file_name: "logo.gif", image_remote_url: "http://www.google.com/intl/en_ALL/images/logo.gif">

Resources