clean /tmp when send_file is over - ruby

I have a Redmine plugin. I create a temporary file in /tmp, then I send it with File.open. I want to delete the temporary file when user has download it. How can I do ?
My code (in a controller):
File.open(filelocation, 'r') do |file|
send_file file, :filename => filename, :type => "application/pdf", :disposition => "attachment"
end
If I remove the file after File.open, it doesn't work.
EDIT
In my controller I do:
def something
temp = Tempfile.new(['PDF_','.pdf'])
# ... some code that modify my pdf ...
begin
File.open(temp.path, 'r') do |file|
send_file file, :filename => temp.path, :type => "application/pdf", :disposition => "attachment"
end
ensure
temp.close
temp.unlink
end
end
My temporary file is remove, but not in the end of my code: the File.open return a damage PDF.

I use send_data instead of send_file, then I delete the file. send_data will block until the data is sent, allowing File.delete request to succeed.
file = temp.path
File.open(file, 'r') do |f|
send_data f.read.force_encoding('BINARY'), :filename => filename, :type => "application/pdf", :disposition => "attachment"
end
File.delete(file)
source: In Ruby on Rails, After send_file method delete the file from server

Call send_file can be offloaded to a web server, therefore it can return asynchronously. Doing anything in tempfile block is dangerous as well as trying to close and unlink the file. When using send_file, the only option is to give up on cleaning the temporary files within the web process.

Consider using the Tempfile class for your job:
Tempfile.create('foo', '/tmp') do |f|
... do something with f ...
end
It's included in standard library and cleanup occur automatically when the block is closed.
Reference:
http://www.ruby-doc.org/stdlib-2.1.1/libdoc/tempfile/rdoc/index.html

Related

How do I send an email with an attachment without first creating a file using ruby-gmail?

I'm using the ruby-gmail gem to send mails.
What I'd like to do is send a ruby hash as a json file.
Example:
require 'gmail'
require 'json'
hash = {'foo' => 'bar}
Gmail.new(<EMAIL>, <PASSWORD>) do |gmail|
gmail.deliver do
to <RECIPIENT>
subject <SUBJECT>
text_part do
body <EMAIL MESSAGE>
end
add_file hash.to_json
end
end
When I try this it simply sends the mail without the attachment.
What could I try next?
Edit:
I want to do this without first creating a file.
You'd need to create the file before you can send it in your add_file method:
hash = {'foo' => 'bar'}
File.open("filename.json","w") do |f|
f.write(hash.to_json)
end
then:
Gmail.new(<EMAIL>, <PASSWORD>) do |gmail|
gmail.deliver do
to <RECIPIENT>
subject <SUBJECT>
text_part do
body <EMAIL MESSAGE>
end
add_file 'path/to/filename.json`
end
end
If you use the "gmail" gem (https://rubygems.org/gems/gmail), then you can add a file with name and content parameters rather than having to actually create a local file. For example:
Gmail.connect(<EMAIL>, <PASSWORD>) do |gmail|
gmail.deliver do
to(<RECIPIENT>)
subject(<SUBJECT>)
text_part do
body(<TEXT_BODY>)
end
html_part do
content_type('text/html; charset=UTF-8')
body(<HTML_BODY>)
end
add_file({filename: <FILE_NAME>, content: <FILE_CONTENT>})
end
end

Manually upload and save files in Carrierwave

I have a directory of existing files which I need to migrate into my rails app as part of a legacy migration. Essentially I need to upload these files manually and save a new record for them in the database. I haven't quite found the proper way to do this. Currently I have the following in a rake task:
#attachments.each do |attachment|
begin
new_attachment = Attachment.new
#attachment_file_path = "/home/username/Attachments/" + attachment.Filename
file = File.open(#attachment_file_path)
new_attachment[:file] = new_attachment.file.store!(file)
# Map old record fields to new
new_attachment.attributes = {
:project_id => attachment.ProjectID,
:name => attachment.Description,
:user_id => attachment.UserId,
:created_at => attachment.CreatedDate,
:updated_at => attachment.LastModifiedDate
}
new_attachment.save!
puts "Attachment added successfully "
rescue => error
puts "Error migrating Attachment: #{error}"
end
end
attachment.rb
class Attachment < ActiveRecord::Base
mount_uploader :file, FileUploader
end
uploader:
class FileUploader < CarrierWave::Uploader::Base
include CarrierWave::RMagick
include CarrierWave::MimeTypes
process :set_content_type
storage :fog
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
def extension_white_list
%w(jpg jpeg gif png pdf doc docx txt)
end
version :thumb do
process resize_to_fit: [152, nil]
end
def default_url
ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
end
protected
def image?(new_file)
if new_file.content_type == nil
return false
else
new_file.content_type.include? 'image'
end
end
end
This does not work currently. The file never gets uploaded, and occasionally I get the following error:
Failed to manipulate with rmagick, maybe it is not an image? Original Error: no decode delegate for this image format
In this instance, the file is a '.doc' file.
What is the correct way to open a local file and upload it manually via Carrierwave?
Any help is appreciated.
Try this
#attachments.each do |attachment|
begin
options = {
:project_id => attachment.ProjectID,
:name => attachment.Description,
:user_id => attachment.UserId,
:created_at => attachment.CreatedDate,
:updated_at => attachment.LastModifiedDate,
:file => File.new(File.join("/home/username/Attachments/",attachment.Filename))
}
new_attachment = Attachment.new(options)
new_attachment.save!
puts "Attachment added successfully "
rescue => error
puts "Error migrating Attachment: #{error}"
end
end
Perhaps that would do for you as carrierwave would internally call store! for you
Question?
Failed to manipulate with rmagick, maybe it is not an image? Original Error: no decode delegate for this image format
Not sure what are you trying to over here because you have define an image? method which is not specified in condition also is that something that you want the content_type to be only present for image file
if no perhaps only the process call would work
process :set_content_type
if yes then perhaps you have to do something like this
process :set_content_type , :if => :image?
def image?(new_file)
%w(jpg jpeg gif).include?(new_file.extension)
end
Hope this help
EDIT based upon the comment
try this just used the condition same logic
version :thumb ,:if => image? do
// your code
end

Can't read an uploaded file with Ruby's Framework Padrino

I have this form for uploading files:
-# coding: utf-8
- content_for(:body_classes, "body3")
.content
- form_tag url(:images, :create), :method => :post, :multipart => true do
= file_field_tag :file
= submit_tag "Upload"
And this Controller to handle it:
Fbapp.controllers :images do
get :new do
render 'images/new'
end
post :create do
require 'net/ftp'
file = params[:file]
ftp = Net::FTP.new('xxx.xxx.xxx.xxx')
ftp.passive = true
ftp.login('user','pass')
ftp.storbinary("STOR " + "original_filename", StringIO.new(file.read), Net::FTP::DEFAULT_BLOCKSIZE)
ftp.quit
end
end
And every time I try to upload a file I get "Internal Server Error". And my log has this:
NoMethodError - undefined method `read' for #<Hash:0x00000003697780>:
I'm trying this on Heroku by the way. I can't figure out what's the problem... It seems to work for a lot of people but me.
You should use:
file = params[:file][:tempfile]
and I suggest to retrieve the filename
name = params[:file][:filename]

Ruby Rack - mounting a simple web server that reads index.html as default

I'm trying to get some information from this tutorial: http://m.onkey.org/2008/11/18/ruby-on-rack-2-rack-builder
basically I want to have a file config.ru that tell rack to read the current directory so I can access all the files just like a simple apache server and also read the default root with the index.html file...is there any way to do it?
my current config.ru looks like this:
run Rack::Directory.new('')
#this would read the directory but it doesn't set the root to index.html
map '/' do
file = File.read('index.html')
run Proc.new {|env| [200, {'Content-Type' => 'text/html'}, file] }
end
#using this reads the index.html mapped as the root but ignores the other files in the directory
So I don't know how to proceed from here...
I've also tried this following the tutorials example but thin doesn't starts properly.
builder = Rack::Builder.new do
run Rack::Directory.new('')
map '/' do
file = File.read('index.html')
run Proc.new {|env| [200, {'Content-Type' => 'text/html'}, file] }
end
end
Rack::Handler::Thin.run builder, :port => 3000
Thanks in advance
I think that you are missing the the rackup command. Here is how it is used:
rackup config.ru
This is going to run your rack app on port 9292 using webrick. You can read "rackup --help" for more info how you can change these defaults.
About the app that you want to create. Here is how I think it should look like:
# This is the root of our app
#root = File.expand_path(File.dirname(__FILE__))
run Proc.new { |env|
# Extract the requested path from the request
path = Rack::Utils.unescape(env['PATH_INFO'])
index_file = #root + "#{path}/index.html"
if File.exists?(index_file)
# Return the index
[200, {'Content-Type' => 'text/html'}, File.read(index_file)]
# NOTE: using Ruby >= 1.9, third argument needs to respond to :each
# [200, {'Content-Type' => 'text/html'}, [File.read(index_file)]]
else
# Pass the request to the directory app
Rack::Directory.new(#root).call(env)
end
}
I ended up on this page looking for a one liner...
If all you want is to serve the current directory for a few one-off tasks, this is all you need:
ruby -run -e httpd . -p 5000
Details on how it works: http://www.benjaminoakes.com/2013/09/13/ruby-simple-http-server-minimalist-rake/
You can do this using Rack::Static
map "/foo" do
use Rack::Static,
:urls => [""], :root => File.expand_path('bar'), :index => 'index.html'
run lambda {|*|}
end
For me, using Ruby 2.0 and Rack 1.5.2, sinm solution worked for serving the index page (both as default page for root and loaded explicitly), but for other files I obtained errors similar to the following:
Rack::Lint::LintError: Status must be >=100 seen as integer
I combined sinm solution with this SO answer and the snippet found on Heroku documentation to obtain the desired behavior (assuming that the entire site is contained in a folder called public):
use Rack::Static,
:urls => ["/images", "/js", "/css"],
:root => "public",
:index => 'index.html'
run Rack::File.new("public")
My example for doing the exact same below:
module Rack
class DirectoryIndex
def initialize(app)
#app = app
end
def call(env)
index_path = ::File.join($documentRoot, Rack::Request.new(env).path.split('/'), 'index.html')
if ::File.exists?(index_path)
return [200, {"Content-Type" => "text/html"}, [::File.read(index_path)]]
else
#app.call(env)
end
end
end
end
require 'rack_directory_index.rb'
$documentRoot = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'build'))
Capybara.app = Rack::Builder.new do |builder|
puts "Creating static rack server serving #{$documentRoot}"
use Rack::DirectoryIndex
run Rack::Directory.new($documentRoot)
end
Capybara.configure do |config|
config.run_server = true
end
The solution is mostly a copy and paste from different answers but it works fine. You can find it as a gist here aswell, good luck

Ruby: Paperclip, S3, and Deep-cloning

What I have is a Theme model, which contains many Assets. Assets are using Paperclip and storing their file content in my Amazon AWS-S3 system. I'm also using deep_clone because my customers have the ability to copy built in Themes and then modify them to their hearts content. All the deep_clone stuff is working great, but when I deep_clone the assets, the old file contents don't get added to my S3 buckets. The record gets saved to the database, but since the file-contents don't get saved with the new ID the file.url property points to a dead file.
I've tried calling paperclip's save and create method manually but I can't figure out how to get paperclip to "push" the file back to the bucket since it now has a new ID, etc....
require 'open-uri'
class Asset < ActiveRecord::Base
belongs_to :theme
attr_accessor :old_id
has_attached_file :file,
:storage => "s3",
:s3_credentials => YAML.load_file("#{RAILS_ROOT}/config/aws.yml")[RAILS_ENV],
:bucket => "flavorpulse-" + RAILS_ENV,
:path => ":class/:id/:style.:extension"
validates_attachment_presence :file
validates_attachment_size :file, :less_than => 5.megabytes
before_save :delete_assets_in_same_theme_with_same_name
after_create :copy_from_cloned_asset
private
def delete_assets_in_same_theme_with_same_name
Asset.destroy_all({:theme_id => self.theme_id, :file_file_name => self.file_file_name})
end
def copy_from_cloned_asset
if (!old_id.blank?)
if (old_id > 0)
old_asset = Asset.find(old_id)
if (!old_asset.blank?)
self.file = do_download_remote_image(old_asset.file.url)
self.file.save
end
end
end
end
def do_download_remote_image (image_url)
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
Any ideas on how I can get paperclip to push the file? I also wouldn't be opposed to doing this using Amazon's aws-s3 gem but I couldn't seem to get that to work either.
According to this former question/answer, it should be possible with this simple line of code:
self.file = old_asset.file

Resources