I am having exactly the same issue as the poster of this question : Ruby (Errno::EACCES) on File.delete. Unlike him, the change provided in the solution for him does not work for me.
Here is my code, it is a compression algorithm where I want to delete the original file :
uncompressed_file = File.new(Rails.root + filepath)
compressed_file = File.new(Rails.root + "#{filepath[0..filepath.size - 1]}.gz", "w+b")
file_writer = Zlib::GzipWriter.new(compressed_file)
buf = ""
File.open(uncompressed_file, "rb") do | uncompressed |
while uncompressed.read(4096, buf)
file_writer << buf
end
file_writer.close
end
begin
files_changed_by_chmod = File.chmod(0777, uncompressed_file)
rescue
puts "Something happened"
end
puts "Number of files changed by CHMOD : " + files_changed_by_chmod.to_s
File.delete(uncompressed_file)
File.rename(Rails.root + "#{filepath[0..filepath.size - 1]}.gz", Rails.root + filepath)
You'll notice there are a couple puts in there to confirm what is happening with the chmod. The output is this :
Number of files changed by CHMOD : 1
and there is no Something happened. Therefore there is no error generated by running the chmod, and chmod indeed modifies one file (presumably, the uncompressed_file.) However, I still get the Errno::EACCESS error on the delete line.
Why can't I delete the files?! It's driving me up the wall. I'm running Windows 8 and ruby 1.9.3.
EDIT: The first answer below solves the issue of not being able to delete the files; however, it invalidates the job my code is trying to do (i.e, when my files are run through the compression algorithm supplied in the solution and then my other algorithms, the file comes back corrupted). Yes, I did also try to emulate the coding style here in my inflation method, but that didn't help. Here is the rest of the code that performs the encryption, decryption, and decompression of my files :
def inflate_attachment(filepath)
compressed_file = File.new(Rails.root + filepath)
File.open(compressed_file, "rb") do | compressed |
File.open(Rails.root + "#{filepath[0..filepath.size - 7]}_FULL.enc", 'w+b') do | decompressed |
gz = Zlib::GzipReader.new(compressed)
result = gz.read
decompressed.write(result)
gz.close
end
end
end
def encrypt_attachment(filepath, cipher)
unencrypted_file = File.new(Rails.root + filepath)
encrypted_file = File.new(Rails.root + "#{filepath[0..filepath.size - 1]}.enc", "w")
buf = ""
File.open(encrypted_file, "wb") do |outf|
File.open(unencrypted_file, "rb") do |inf|
while inf.read(4096, buf)
outf << cipher.update(buf)
end
outf << cipher.final
end
end
end
def decrypt_attachment(filepath, key, iv)
cipher = OpenSSL::Cipher.new(ENCRYPTION_TYPE)
cipher.decrypt
cipher.key = key
cipher.iv = iv
encrypted_file = File.new(Rails.root + filepath)
decrypted_file = File.new(Rails.root + "#{filepath[0..filepath.size - 5]}.dec", "w")
buf = ""
File.open(decrypted_file, "wb") do |outf|
File.open(encrypted_file, "rb") do |inf|
while inf.read(4096, buf)
outf << cipher.update(buf)
end
outf << cipher.final
end
end
end
I think that might have something to do that you haven't properly closed the files. I took the liberty to rewrite your code, without the chmod stuff (which I don't think is necessary)
filename = <your sourcefilename goes here>
filename_gz = filename + ".gz"
filepath = Rails.root + filename
filepath_gz = Rails.root + filename_gz
# gzip the file
buffer = ""
File.open(filepath) do |file|
Zlib::GzipWriter.open(filepath_gz) do |gz|
while file.read(4096, buffer)
gz << buffer
end
end
end
# moves the filepath_gz to filepath (overwriting the original file in the process!)
FileUtils.mv(filepath_gz, filepath)
As you can see I've used File.open(path) and passed a block. This has the effect that the files will be closed automatically when the block exits.
I've also changed the delete/rename code to simply move the gziped file to the original path, which has the same effect.
However, I strongly advice you to keep a backup of your original file.
Related
I'm adapting the RubyZip recursive zipping example (found here) to work with write_buffer instead of open and am coming across a host of issues. I'm doing this because the zip archive I'm producing has word documents in it and I'm getting errors on opening those word documents. Therefore, I'm trying the work-around that RubyZip suggests, which is using write_buffer instead of open (example found here).
The problem is, I'm getting errors because I'm using an absolute path, but I'm not sure how to get around that. I'm getting the error "#//', name must not start with />"
Second, I'm not sure what to do to mitigate the issue with word documents. When I used my original code, which worked and created an actual zip file, any word document in that zip file had the following error upon opening: "Word found unreadable content in Do you want to recover the contents of this document? If you trust the source of this document, click Yes." The unreadable content error is the reason why I went down the road of attempting to use write_buffer.
Any help would be appreciated.
Here is the code that I'm currently using:
require 'zip'
require 'zip/zipfilesystem'
module AdvisoryBoard
class ZipService
def initialize(input_dir, output_file)
#input_dir = input_dir
#output_file = output_file
end
# Zip the input directory.
def write
entries = Dir.entries(#input_dir) - %w[. ..]
path = ""
buffer = Zip::ZipOutputStream.write_buffer do |zipfile|
entries.each do |e|
zipfile_path = path == '' ? e : File.join(path, e)
disk_file_path = File.join(#input_dir, zipfile_path)
#file = nil
#data = nil
if !File.directory?(disk_file_path)
#file = File.open(disk_file_path, "r+b")
#data = #file.read
unless [#output_file, #input_dir].include?(e)
zipfile.put_next_entry(e)
zipfile.write #data
end
#file.close
end
end
zipfile.put_next_entry(#output_file)
zipfile.put_next_entry(#input_dir)
end
File.open(#output_file, "wb") { |f| f.write(buffer.string) }
end
end
end
I was able to get word documents to open without any warnings or corruption! Here's what I ended up doing:
require 'nokogiri'
require 'zip'
require 'zip/zipfilesystem'
class ZipService
# Initialize with the directory to zip and the location of the output archive.
def initialize(input_dir, output_file)
#input_dir = input_dir
#output_file = output_file
end
# Zip the input directory.
def write
entries = Dir.entries(#input_dir) - %w[. ..]
::Zip::File.open(#output_file, ::Zip::File::CREATE) do |zipfile|
write_entries entries, '', zipfile
end
end
private
# A helper method to make the recursion work.
def write_entries(entries, path, zipfile)
entries.each do |e|
zipfile_path = path == '' ? e : File.join(path, e)
disk_file_path = File.join(#input_dir, zipfile_path)
if File.directory? disk_file_path
recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
else
put_into_archive(disk_file_path, zipfile, zipfile_path, e)
end
end
end
def recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
zipfile.mkdir zipfile_path
subdir = Dir.entries(disk_file_path) - %w[. ..]
write_entries subdir, zipfile_path, zipfile
end
def put_into_archive(disk_file_path, zipfile, zipfile_path, entry)
if File.extname(zipfile_path) == ".docx"
Zip::File.open(disk_file_path) do |zip|
doc = zip.read("word/document.xml")
xml = Nokogiri::XML.parse(doc)
zip.get_output_stream("word/document.xml") {|f| f.write(xml.to_s)}
end
zipfile.add(zipfile_path, disk_file_path)
else
zipfile.add(zipfile_path, disk_file_path)
end
end
end
I would like to know how to write a picture twice without writing it once and then copying it.
When a picture is downloaded, it is written in the /tmp and then copied to the wanted path ( I think ) meaning that the following code :
cover_buffer = download_pic(link)
buffer2 = cover_buffer
open(#dir + 'cover.jpg', 'wb') do |pic|
pic << cover_buffer.read()
end
open(#dir + 'cover2.jpg', 'wb') do |pic|
pic << cover_buffer2.read()
end
does not work since the both cover_buffer and buffer2 both point to the same file whitch was moved when writing cover.jpg
Executing that code will correctly write the picture in cover.jpg but cover2.jpg will be an empty file
TESTED SOLUTION
In file two_for_one.rb:
current_dir = File.expand_path(File.dirname(__FILE__))
new_file_1 = File.new(File.join(current_dir, 'image_1.png'), 'w')
new_file_2 = File.new(File.join(current_dir, 'image_2.png'), 'w')
origin_file = File.join(current_dir, 'original_image.png')
begin
File.open(origin_file, "r") do |source|
until source.eof?
chunk = source.read(1024)
new_file_1.write(chunk)
new_file_2.write(chunk)
end
end
ensure
new_file_1.close()
new_file_2.close()
end
Command line:
$ ruby two_for_one.rb
I need to check what method a file object is opened in. E.g. is it r, r+, w, a etc.
thefile = File.open(filename, method)
It must be using the object thefile and not just the filename.
On POSIX platforms, you can call IO#fcntl with F_GETFL to get the file status flags:
require 'fcntl'
def filemode(io)
flags = io.fcntl(Fcntl::F_GETFL)
case flags & Fcntl::O_ACCMODE
when Fcntl::O_RDONLY
'r'
when Fcntl::O_WRONLY
(flags & Fcntl::O_APPEND).zero? ? 'w' : 'a'
when Fcntl::O_RDWR
(flags & Fcntl::O_APPEND).zero? ? 'r+ / w+' : 'a+'
end
end
File.open('test.txt', 'r') { |f| puts filemode(f) } #=> r
File.open('test.txt', 'w') { |f| puts filemode(f) } #=> w
File.open('test.txt', 'a+') { |f| puts filemode(f) } #=> a+
fcntl's return value is a bitwise OR of the individual O_* flags:
Fcntl::O_RDONLY # 0
Fcntl::O_WRONLY # 1
Fcntl::O_RDWR # 2
Fcntl::O_APPEND # 4
Fcntl::O_NONBLOCK # 8
Fcntl::O_ACCMODE can be used to mask the file access modes.
Further information:
http://man7.org/linux/man-pages/man2/fcntl.2.html
http://www.gnu.org/software/libc/manual/html_node/File-Status-Flags.html
http://ruby-doc.org/stdlib/libdoc/fcntl/rdoc/Fcntl.html
I am not going to write the full script for you, but will give you a hint.
Suppose you have an IO opened:
io = File.open("/tmp/foo", "r")
Assuming that io was created successfully, you can tell whether it is opened for writing by attempting write:
begin
io.write("")
rescue IOError => e
puts e.message
end
#=> not opened for writing
Make sure to copy the file before attempting this in order not to lose the file in case the mode was "w" or "w+".
Do along the same line for distinguishing other modes.
I have the following snippet:
buffer = ""
sourceFile = File.read("#{options[:source]}")
destFile = File.open("#{options[:dest]}", "w+")
criteria = ""
if (options[:ora]) then
criteria += "\"SELECT\", \"UPDATE\""
puts criteria
end
sourceFile.each_line do |line|
if (line.start_with?("#{criteria}")) then
buffer << line
buffer << "\n\n"
end
end
File.write("#{options[:dest]}", buffer)
This doesn't work though -- the destination file is empty. However, if I hardcode if (line.start_with?("UPDATE", "SELECT")) then, it works fine. What am I missing?
You are passing the string '"SELECT", "UPDATE"' to function, instead you should pass strings separately(you can use lists and parameter expension if you generate those dynamically):
criteria = ["SELECT", "UPDATE"]
...
line.start_with?(*criteria)
buffer = ""
sourceFile = File.read("#{options[:source]}")
destFile = File.open("#{options[:dest]}", "w+")
criteria = ""
if (options[:ora]) then
criteria += %w(SELECT UPDATE)
puts criteria
end
sourceFile.each_line do |line|
if (line.start_with?(*criteria)) then
buffer << line
buffer << "\n\n"
end
end
File.write("#{options[:dest]}", buffer)
I wrote a script that I decided to refactor so I could add functionality to it as my coworkers think of it. I only saved four lines in the effort, but the main change is I removed both methods and reduced the number of called variables in favor of string interpolation/manipulation. Is there a preference for this? Is it better to declare a new variable just to use once, or is it more DRY to just make minor tweaks to the string when you need to use it? For example here is the original code:
def validate_directory(dir)
puts "Enter the full directory path of the flv files." unless dir
input = dir || gets.chomp
input.gsub!('\\', '/')
input += '/' unless input[-1..-1] == '/'
until File.directory?(input) && Dir.glob("#{input}*.flv") != []
puts "That directory either doesn't exist or contains no .flv files. \nEnter the full directory path of the flv files."
input = $stdin.gets.chomp
input.gsub!('\\', '/')
input += '/' unless input[-1..-1] == '/'
end
dir = input
end
def output(flv, location)
title = flv.dup.gsub!(".flv", ".html")
vid = flv.dup
vid.slice!(0..6)
body = $EMBED.gsub("sample.flv", vid)
htmlOutput = File.open(title, "w")
htmlOutput.write(body)
htmlOutput.close
linkList = File.open("#{location}List of Links.txt", "a")
linkList.write($BASE + vid.gsub(".flv", ".html") + "\n")
linkList.close
puts "Files created successfully."
end
dir = ARGV[0].dup unless ARGV.empty?
folder = validate_directory(dir)
files = folder.clone + "*.flv"
flvs = Dir.glob("#{files}")
File.delete("#{folder}List of Links.txt") if File.exists?("#{folder}List of Links.txt")
flvs.each { |flv| output(flv, folder) }
And the new stuff:
flash_folder = ARGV[0].dup unless ARGV.empty?
if !flash_folder
puts "Enter the full directory path of the flv files."
flash_folder = gets.chomp
end
flash_folder.gsub!('\\', '/')
flash_folder += '/' unless flash_folder[-1..-1] == '/'
until File.directory?(flash_folder) && Dir.glob("#{flash_folder}*.flv") != []
puts "That directory either doesn't exist or contains no .flv files. \nEnter the full directory path of the flv files."
flash_folder = $stdin.gets.chomp
flash_folder.gsub!('\\', '/')
flash_folder += '/' unless flash_folder[-1..-1] == '/'
end
flash_files = Dir.glob("#{flash_folder}*.flv")
File.delete("#{flash_folder}List of Links.txt") if File.exists?("#{flash_folder}List of Links.txt")
flash_files.each do |flv|
html_output = File.open("#{flv.gsub(".flv", ".html")}", "w")
html_output.write("#{embed_code.gsub("sample.flv", flv.slice(7..flv.length))}")
html_output.close
link_list = File.open("#{flash_folder}List of Links.txt", "a")
link_list.write("#{flash_url}#{flv.slice(2..flv.length).gsub(".flv", ".html")}\n")
link_list.close
end
puts "Finished."