send_file for a tempfile in Sinatra - ruby

I'm trying to use Sinatra's built-in send_file command but it doesn't seem to be working for tempfiles.
I basically do the following to zip an album of mp3s:
get '/example' do
songs = ...
file_name = "zip_test.zip"
t = Tempfile.new(['temp_zip', '.zip'])
# t = File.new("testfile.zip", "w")
Zip::ZipOutputStream.open(t.path) do |z|
songs.each do |song|
name = song.name
name += ".mp3" unless name.end_with?(".mp3")
z.put_next_entry(name)
z.print(open(song.url) {|f| f.read })
p song.name + ' added to file'
end
end
p t.path
p t.size
send_file t.path, :type => 'application/zip',
:disposition => 'attachment',
:filename => file_name,
:stream => false
t.close
t.unlink
end
When I use t = File.new(...) things work as expected, but I don't want to use File as it will have concurrency problems.
When I use t = Tempfile.new(...), I get:
!! Unexpected error while processing request: The file identified by body.to_path does not exist`
Edit: It looks like part of the problem is that I'm sending multiple files. If I just send one song, the Tempfile system works as well.

My guess is that you have a typo in one of your song-names, or maybe a slash in one of the last parts of song.url? I adopted your code and if all the songs exist, sending the zip as a tempfile works perfectly fine.

Related

rubyzip: open zip, modify it temporary, send to client

i want to temporary modify a zip file and send the changed file to the client.
right now i create a file stream and send it:
require 'zip'
zip_stream = Zip::OutputStream.write_buffer do |zip|
zip.put_next_entry 'new_folder/file'
zip.print "some text"
end
zip_stream.rewind
send_data zip_stream.read, type: 'application/zip', disposition: 'attachment', filename: 'thing.zip'
i dont get how i can open a existing zip in the filesystem and put additional file in it and send it without saving it do the disk.
can you give me a hint?
in the end i did it like this:
require 'zip'
zip_stream = Zip::OutputStream.write_buffer do |new_zip|
existing_zip = Zip::File.open('existing.zip')
existing_zip.entries.each do |e|
new_zip.put_next_entry(e.name)
new_zip.write e.get_input_stream.read
end
new_zip.put_next_entry 'new_file'
new_zip.print "text"
end
Check this https://github.com/rubyzip/rubyzip
require 'rubygems'
require 'zip'
folder = "Users/me/Desktop/stuff_to_zip"
input_filenames = ['image.jpg', 'description.txt', 'stats.csv']
zipfile_name = "/Users/me/Desktop/archive.zip"
Zip::File.open(zipfile_name, Zip::File::CREATE) do |zipfile|
input_filenames.each do |filename|
# Two arguments:
# - The name of the file as it will appear in the archive
# - The original file, including the path to find it
zipfile.add(filename, File.join(folder, filename))
end
zipfile.get_output_stream("myFile") { |f| f.write "myFile contains just this" }
end

What SHOULD I be seeing to know this code has done what it was supposed to?

I am doing the EventManager tutorial from Jumpstart Labs. Originally I could not get it my .rb file to read a .erb file, and I think I may have solved that, but I am not sure as I do not know what I SHOULD be seeing if all is running correctly and unfortunately the tutorial doesn't tell you. Here is my original question
Now after a simple change, I am no longer getting the error - but I am also not getting any indication that the code is working as expected. The tutorial says that this code should be creating a new directory and storing a copy of each 'thank you' letter into a file called 'output' in that new directory. But when I run it all I see is the => EventManager initialized from the terminal, which tells me that my .rb is being read and (I think) that the .erb is finally being read...but I do not see any new directories/files in the file structure, nor any indication that anything was created - so I can't tell if it is actually doing anything.
I am kind of expecting to see some kind of message telling me the directory has been created, perhaps with a file path or something.
I have never done anything like this and I'm not sure what I should be seeing...can anyone tell me how I would know that this code is preforming as expected? And if it is not, why?
require "csv"
require "sunlight/congress"
require "erb"
Sunlight::Congress.api_key = "e179a6973728c4dd3fb1204283aaccb5"
def save_thank_you_letters(id, form_letter)
Dir.mkdir("output") unless Dir.exists? ("output")
filename = "output/thanks_#{id}.html"
File.open(filename, 'w') do |file|
file.puts form_letter
end
end
def legislators_by_zipcode(zipcode)
legislators = Sunlight::Congress::Legislator.by_zipcode(zipcode)
end
def clean_zipcode(zipcode)
zipcode.to_s.rjust(5,"0")[0..4]
end
puts "EventManager initialized."
contents = CSV.open "event_attendees.csv", headers: true, header_converters: :symbol
template_letter = File.read( "event_manager/form_letter.erb")
erb_template = ERB.new template_letter
contents.each do |row|
id = row[0]
name = row[:first_name]
zipcode = clean_zipcode(row[:zipcode])
legislators = legislators_by_zipcode(zipcode)
form_letter = erb_template.result(binding)
save_thank_you_letters(id, form_letter)
end
I've (ever so slightly) modified your save_thank_you_letters method to spit out some helpful information as it writes files:
def save_thank_you_letters(id, form_letter)
Dir.mkdir("output") unless Dir.exists? ("output")
filename = "output/thanks_#{id}.html"
File.open(filename, 'w') do |file|
file.puts form_letter
puts "Wrote ID: #{id} to #{filename}"
end
end
The line puts "Wrote ID: #{id} to #{filename}" will print the ID and file path of the message it has written. You can place additional puts "Your text here..." throughout your Ruby logic to print more information to the console as you see fit.
Side note: in general, it's a super bad idea to post your personal API keys to any public forums. If this key is private/unique to you, delete it and request a new one. Anyone can now impersonate your account at Sunlight Labs.

Taking json data and converting it to a CSV file

Okay... so new to Ruby here but loving it so far. My problem is I cannot get the data to go into the CSV files.
#!/usr/bin/env ruby
require 'date'
require_relative 'amf'
require 'json'
require 'csv'
amf = Amf.new
#This makes it go out 3 days
apps = amf.post( 'Appointments.getBetweenDates',
{ 'startDate' => Date.today, 'endDate' => Date.today + 4 }
)
apps.each do |app|
cor_md_params = { 'appId' => app['appID'], 'relId' => 7 }
cor_md = amf.post( 'Clinicians.getByAppIdAndRelId', cor_md_params ).first
#this is where it breaks ----->
CSV.open("ile.csv", "wb") do |csv|
csv << ["column1", "column2", "etc.", "etc.."]
csv << ([
# if added puts ([ I can display the info and then make a csv...
app['patFirstName'],
app['patMiddleName'],
app['patLastName'],
app['patBirthdate'],
app['patHin'],
app['patPhone'],
app['patCellPhone'],
app['patBusinessPhone'],
app['appTime'],
app['appID'],
app['patPostalCode'],
app['patProvince'],
app['locName'],
# note that this is not exactly accurate for follow-ups,
# where you have to replace the "1" with the actual value
# in weeks, days, months, etc
#app[ 'bookName' ], => not sure this is needed
cor_md['id'],
cor_md['providerCode'],
cor_md['firstName'],
cor_md['lastName']
].join(', '))
end
end
Now, if I remove the attempt to make the ile.cvs file and just output it with a puts, all the data shows. But I don't want to have to go into the terminal and create a csv file... I would rather just run the .rb program and have it created. Also, hopefully I am making the columns correctly as well...
The thought occurred to me that I could just add another puts above the output.
Or, better, insert a row into the array before I output it...
Really not sure what is best practice here and standards.
This is what I have done and attempted. How can I get it to cleanly output to a CSV file since my attempts are not working
Also, to clarify where it breaks, it does add the column names just not the JSON info that is parsed. I could also be completely doing this the wrong way or a way that isn't possible. I just do not know.
What kind of error do you get? Is it this one:
<<': undefined methodmap' for "something":String (NoMethodError)
I think, you should remove the .join(', ')
The << method of CSV accepts an array, but not a String
http://ruby-doc.org/stdlib-1.9.2/libdoc/csv/rdoc/CSV.html#method-i-3C-3C
So instead of:
cor_md['lastName']
].join(', '))
rather:
cor_md['lastName']
])
The problem with the loop (why it writes only 1 row of data)
In the body of your loop, you always reopen the file, and always rewrite what you added before. What you want to do, is probably this:
CSV.open("ile3.csv", "wb") do |csv|
csv << ["column1", "column2", "etc.", "etc.."]
apps.each do |app|
cor_md_params = { 'appId' => app['appID'], 'relId' => 7 }
cor_md = amf.post( 'Clinicians.getByAppIdAndRelId', cor_md_params ).first
#csv << your long array
end
end

Get file name and extension in Ruby

I'm working on a program to download a video from YouTube, convert it to MP3 and create a directory structure for the files.
My code is:
FileUtils.cd("#{$musicdir}/#{$folder}") do
YoutubeDlhelperLibs::Downloader.get($url)
if File.exists?('*.mp4')
puts 'Remove unneeded tempfile'
Dir['*.mp4'].each do |waste|
File.delete(waste)
end
else
puts 'Temporary file already deleted'
end
Dir['*.m4a'].each do |rip|
rip.to_s
rip.split
puts 'Inside the function'
puts rip
end
end
The first one goes to the already created music folder. Inside that I'm executing get. After that I have two files in the directory: "xyz.mp4" and "xyz.m4a".
I would like to fetch the filename without the extension so I can handle both files differently.
I'm using an array, but an array for just one match sounds crazy for me.
Has anyone another idea?
You can use the following functions for your purpose:
path = "/path/to/xyz.mp4"
File.basename(path) # => "xyz.mp4"
File.extname(path) # => ".mp4"
File.basename(path, ".mp4") # => "xyz"
File.basename(path, ".*") # => "xyz"
File.dirname(path) # => "/path/to"

How do I avoid EOFError with Ruby script?

I have a Ruby script (1.9.2p290) where I am trying to call a number of URLs, and then append information from those URLs into a file. The issue is that I keep getting an end of file error - EOFError. An example of what I'm trying to do is:
require "open-uri"
proxy_uri = URI.parse("http://IP:PORT")
somefile = File.open("outputlist.txt", 'a')
(1..100).each do |num|
page = open('SOMEURL' + num, :proxy => proxy_uri).read
pattern = "<img"
tags = page.scan(pattern)
output << tags.length
end
somefile.puts output
somefile.close
I don't know why I keep getting this end of file error, or how I can avoid getting the error. I think it might have something to do with the URL that I'm calling (based on some dialogue here: What is an EOFError in Ruby file I/O?), but I'm not sure why that would affect the I/O or cause an end of file error.
Any thoughts on what I might be doing wrong here or how I can get this to work?
Thanks in advance!
The way you are writing your file isn't idiomatic Ruby. This should work better:
(1..100).each do |num|
page = open('SOMEURL' + num, :proxy => proxy_uri).read
pattern = "<img"
tags = page.scan(pattern)
output << tags.length
end
File.open("outputlist.txt", 'a') do |fo|
fo.puts output
end
I suspect that the file is being closed because it's been opened, then not written-to while 100 pages are processed. If that takes a while I can see why they'd close it to avoid apps using up all the file handles. Writing it the Ruby-way automatically closes the file immediately after the write, avoiding holding handles open artificially.
As a secondary thing, rather than use a simple pattern match to try to locate image tags, use a real HTML parser. There will be little difference in processing speed, but potentially more accuracy.
Replace:
page = open('SOMEURL' + num, :proxy => proxy_uri).read
pattern = "<img"
tags = page.scan(pattern)
output << tags.length
with:
require 'nokogiri'
doc = Nokogiri::HTML(open('SOMEURL' + num, :proxy => proxy_uri))
output << doc.search('img').size

Resources