How to get file mode in ruby? - ruby

I'm new to ruby file IO. I have a function that takes a File parameter, and I need to make sure that the file is in read-only mode.
def myfunction(file)
raise ArgumentError.new() unless file.kind_of?(File)
#Assert that file is in read-only mode
end
Any help would be appreciated!

If you don't need to raise an error, you can use reopen, I think something like:
file = file.reopen(file.path, "r")
I can't find a way to otherwise verify that there isn't a write stream, but here's a bit of a hack that will work. Although I don't like exception throwing being used in the expected path, you could use close_write:
begin
file.close_write
# you could actually raise an exception here if you want
# since getting here means the file was originally opened for writing
rescue IOError
# This error will be raised if the file was not opened for
# writing, so this is actually the path we want
end

So all you need is 'make sure make sure that the file is in read-only mode', why not just set it as readonly with FileUtils.chmod.
Or if actually you just want to test if it is readonly, use File.writeable?

You can use file.readable?, which returns true or false.
Please check this link.

Related

Dir.chdir(File.dirname(__FILE__)) throws Errno::ENOENT

I got a method where I use Dir.chdir(File.dirname(__FILE__)) inside. I am using it so that I can run the ruby file from anywhere and not get this error: No such file or directory # rb_sysopen - 'filename' (Errno::ENOENT).
Using the method for the first time works fine but using it for the second time throws an error. See method and exact error below.
def meth(string)
Dir.chdir(File.dirname(__FILE__))
hash = JSON.parse(File.read("file.json"))
# do something with hash and string
# return some value
end
meth("first string") # this returns what is expected
meth("second string") # this second usage of the method throws the error
Error sample pinpointing the line where I used Dir.chdir(File.dirname(__FILE__)):
dir/decoder.rb:44:in `chdir': No such file or directory # dir_s_chdir - lib (Errno::ENOENT)
Not sure if OS plays a role here, I am using an m1 BigSur on version 11.2.3.
Why is this happening?
What needs to be done so that the method` can be used as much as needed without running into the error?
Your problem here seems to be that __FILE__ is a relative path like dir/decoder.rb and that path becomes invalid after the first time Dir.chdir is used, because that command changes the working directory of your entire Ruby process. I think the solution would be to do something like this in your decoder.rb file:
DecoderDir = File.realpath(File.dirname(__FILE__))
def meth
Dir.chdir(DecoderDir)
# ...
end
I'm guessing that the first time the Ruby interpreter processes the file, that is early enough that the relative path in __FILE__ still refers to the right place. So, at that time, we generate an absolute path for future use.
By the way, a well-behaved library should not run Dir.chdir because it will affect all use of relative paths throughout your entire Ruby process. I pretty much only run Dir.chdir once and I run it near the beginning of my top-level script. If you're making a reusable library, you might want to do something like this to calculate the absolute path of the file you want to open:
DecoderJson = File.join(DecoderDir, 'file.json')

How to unit test a "disk full" scenario with Ruby RSpec?

I need to unit test scenarios like the following:
The disk has 1MB free space. I try to copy 2MB of file(s) to the disk.
What's the best way to do this with Ruby RSpec?
For further information, I need to unit test the following file cache method, since it appears to have some issue:
def set first_key, second_key='', files=[]
# If cache exists already, overwrite it.
content_dir = get first_key, second_key
second_key_file = nil
begin
if (content_dir.nil?)
# Check the size of cache, and evict entries if too large
check_cache_size if (rand(100) < check_size_percent)
# Make sure cache dir doesn't exist already
first_cache_dir = File.join(dir, first_key)
if (File.exist?first_cache_dir)
raise "BuildCache directory #{first_cache_dir} should be a directory" unless File.directory?(first_cache_dir)
else
FileUtils.mkpath(first_cache_dir)
end
num_second_dirs = Dir[first_cache_dir + '/*'].length
cache_dir = File.join(first_cache_dir, num_second_dirs.to_s)
# If cache directory already exists, then a directory must have been evicted here, so we pick another name
while File.directory?cache_dir
cache_dir = File.join(first_cache_dir, rand(num_second_dirs).to_s)
end
content_dir = File.join(cache_dir, '/content')
FileUtils.mkpath(content_dir)
# Create 'last_used' file
last_used_filename = File.join(cache_dir, 'last_used')
FileUtils.touch last_used_filename
FileUtils.chmod(permissions, last_used_filename)
# Copy second key
second_key_file = File.open(cache_dir + '/second_key', 'w+')
second_key_file.flock(File::LOCK_EX)
second_key_file.write(second_key)
else
log "overwriting cache #{content_dir}"
FileUtils.touch content_dir + '/../last_used'
second_key_file = File.open(content_dir + '/../second_key', 'r')
second_key_file.flock(File::LOCK_EX)
# Clear any existing files out of cache directory
FileUtils.rm_rf(content_dir + '/.')
end
# Copy files into content_dir
files.each do |filename|
FileUtils.cp(filename, content_dir)
end
FileUtils.chmod(permissions, Dir[content_dir + '/*'])
# Release the lock
second_key_file.close
return content_dir
rescue => e
# Something went wrong, like a full disk or some other error.
# Delete any work so we don't leave cache in corrupted state
unless content_dir.nil?
# Delete parent of content directory
FileUtils.rm_rf(File.expand_path('..', content_dir))
end
log "ERROR: Could not set cache entry. #{e.to_s}"
return 'ERROR: !NOT CACHED!'
end
end
One solution is to stub out methods that write to disk to raise an error. For example, for the specs that test disk space errors, you could try:
before do
allow_any_instance_of(File).to receive(:open) { raise Errno::ENOSPC }
# or maybe # allow(File).to receive(:write) { raise Errno::ENOSPC }
# or # allow(FileUtils).to receive(:cp) { raise Errno::ENOSPC }
# or some combination of these 3...
end
it 'handles an out of disk space error' do
expect{ my_disk_cache.set('key1', 'key2', [...]) }.to # your logic for how BuildCache::DiskCache should handle the error here.
end
There are two problems with this however:
1) Errno::ENOSPC may not be the error you actually see getting raised. That error fits the description in your question, but depending on the peculiarities of your lib and the systems it runs on, you might not really be getting an Errno::ENOSPC error. Maybe you run out of RAM first and are getting Errno::ENOMEM, or maybe you have too many file descriptors open and are getting Errno::EMFILE. Of course if you want to be rigorous you could handle all of these, but this is time consuming and you'll get diminishing returns for handling the more obscure errors.
See this for more information on Errno errors.
2) This solution involves stubbing a specific method on a specific class. (File.open) This isn't ideal because it couples the setup for your test to the implementation in your code. That is to say, if you refactor BuildCache::DiskCache#set to not use File.open, then this test might start failing even though the method might be correct.
That said, File.open is fairly low level. I know that some FileUtils methods use File.open, (Notably, FileUtils.cp) so I would suggest just using that first allow_any_instance_of line. I'd expect that to handle most of your use cases.
Alternatively, there is a tool called fakefs that may be able to help you with this. I am not familiar with it, but it may well have functionality that helps with testing such errors. You may want to look into it.
You could make use of any of the method calls you know are happening inside of the method you need to test, and stub them so they raise an error. E.g. FileUtils.touch is called a number of times, so we could do:
it 'handles file write error gracefully' do
allow(FileUtils).to receive(:touch).and_raise('oh no')
# your expectations
# your test trigger
end

Reopening closed file: Lua

I have a file called backup.lua, which the program should write to every so often in order to backup its status, in case of a failure.
The problem is that the program writes the backup.lua file completely fine first-time round, but any other times it refuses to write to the file.
I tried removing the file while the program was still open but Windows told me that the file was in use by 'CrysisWarsDedicatedServer.exe', which is the program. I have told the host Lua function to close the backup.lua file, so why isn't it letting me modify the file at will after it has been closed?
I can't find anything on the internet (Google actually tried to correct my search) and the secondary programmer on the project doesn't know either.
So I'm wondering if any of you folks know what we are doing wrong here?
Host function code:
function ServerBackup(todo)
local write, read;
if todo=="write" then
write = true;
else
read = true;
end
if (write) then
local source = io.open(Root().."Mods/Infinity/System/Read/backup.lua", "w");
System.Log(TeamInstantAction:GetTeamScore(2).." for 2, and for 1: "..TeamInstantAction:GetTeamScore(1))
System.LogAlways("[System] Backing up serverdata to file 'backup.lua'");
source:write("--[[ The server is dependent on this file; editing it will lead to serious problems.If there is a problem with this file, please re-write it by accessing the backup system ingame.--]]");
source:write("Backup = {};Backup.Time = '"..os.date("%H:%M").."';Backup.Date = '"..os.date("%d/%m/%Y").."';");
source:write(XFormat("TeamInstantAction:SetTeamScore(2, %d);TeamInstantAction:SetTeamScore(1, %d);TeamInstantAction:UpdateScores();",TeamInstantAction:GetTeamScore(2), TeamInstantAction:GetTeamScore(1) ));
source:close();
for i,player in pairs(g_gameRules.game:GetPlayers() or {}) do
if (IsModerator(player)) then
CMPlayer(player, "[!backup] Completed server backup.");
end
end
end
--local source = io.open(Root().."Mods/Infinity/System/Read/backup.lua", "r"); Can the file be open here and by the Lua scriptloader too?
if (read) then
System.LogAlways("[System] Restoring serverdata from file 'backup.lua'");
--source:close();
Backup = {};
Script.LoadScript(Root().."Mods/Infinity/System/Read/backup.lua");
if not Backup or #Backup < 1 then
System.LogAlways("[System] Error restoring serverdata from file 'backup.lua'");
end
end
end
Thanks all :).
Edit:
Although the file is now written to the disk fine, the system fails to read the dumped file.
So, now the problem is that the "LoadScript" function isn't doing what you expect:
Because I'm psychic, i have divined that you're writing a Crysis plugin, and are attempting to use it's LoadScript API call.
(Please don't assume everyone here would guess this, or be bothered to look for it. It's vital information that must form part of your questions)
The script you're writing attempts to set Backup - but your script, as written - does not separate lines with newline characters. As the first line is a comment, the entire script will be ignored.
Basicallty the script you've written looks like this, which is all treated as a comment.
--[[ comment ]]--Backup="Hello!"
You need to write a "\n" after the comment (and, I'd recommend in other places too) to make it like this. In fact, you don't really need block comments at all.
-- comment
Backup="Hello!"

Sinatra File deleter

I am using sinatra,ruby and MongoDB to export a CSV file from the MongoDB. I am able to create the CSV file and export it.
I delete the file after exporting it.
But it gets deleted only after I exit sinatra.
Can anybody explain why is this?
Suppose a file abc****.csv is created.
I am deleting this file using
file_path = '/home/Test_app';
file = Tempfile.new([##uname,'.csv'],file_path);
file_name = file.path();
puts file_name # gives /home/Test_app/xyz****.csv
send_file(file_name, :disposition => 'attachment', :filename =>File.basename(file_name));
File.delete(file_name);
File.unlink(file_name);
But it gets deleted only after I exit sinatra server. Can anyone explain please?
Your never call file.close, meaning the file will be kept open and therefore not deleted until your application exits.
Try following the suggestion given in the Tempfile documentation:
file = Tempfile.new('foo')
begin
...do something with file...
ensure
file.close
file.unlink # deletes the temp file
end
This will make sure the file is properly closed and deleted even in the event of exceptions being raised in the code between begin and ensure.
Perhaps this is a large file; since the HTTP connection does not close until the streaming is complete, the code after send_file is not getting executed. That might be a valid reason. Have you checked if the entire file is being downloaded to the client? If that is not the case, try it out with a smaller file. I'm assuming you've implemented (but haven't written it here) the code for the data getting written into the file_name from MongoDB.

How to create a file in Ruby

I'm trying to create a new file and things don't seem to be working as I expect them too. Here's what I've tried:
File.new "out.txt"
File.open "out.txt"
File.new "out.txt","w"
File.open "out.txt","w"
According to everything I've read online all of those should work but every single one of them gives me this:
ERRNO::ENOENT: No such file or directory - out.txt
This happens from IRB as well as a Ruby script. What am I missing?
Use:
File.open("out.txt", [your-option-string]) {|f| f.write("write your stuff here") }
where your options are:
r - Read only. The file must exist.
w - Create an empty file for writing.
a - Append to a file.The file is created if it does not exist.
r+ - Open a file for update both reading and writing. The file must exist.
w+ - Create an empty file for both reading and writing.
a+ - Open a file for reading and appending. The file is created if it does not exist.
In your case, 'w' is preferable.
OR you could have:
out_file = File.new("out.txt", "w")
#...
out_file.puts("write your stuff here")
#...
out_file.close
Try
File.open("out.txt", "w") do |f|
f.write(data_you_want_to_write)
end
without using the
File.new "out.txt"
Try using "w+" as the write mode instead of just "w":
File.open("out.txt", "w+") { |file| file.write("boo!") }
OK, now I feel stupid. The first two definitely do not work but the second two do. Not sure how I convinced my self that I had tried them. Sorry for wasting everyone's time.
In case this helps anyone else, this can occur when you are trying to make a new file in a directory that does not exist.
If the objective is just to create a file, the most direct way I see is:
FileUtils.touch "foobar.txt"
The directory doesn't exist. Make sure it exists as open won't create those dirs for you.
I ran into this myself a while back.
File.new and File.open default to read mode ('r') as a safety mechanism, to avoid possibly overwriting a file. We have to explicitly tell Ruby to use write mode ('w' is the most common way) if we're going to output to the file.
If the text to be output is a string, rather than write:
File.open('foo.txt', 'w') { |fo| fo.puts "bar" }
or worse:
fo = File.open('foo.txt', 'w')
fo.puts "bar"
fo.close
Use the more succinct write:
File.write('foo.txt', 'bar')
write has modes allowed so we can use 'w', 'a', 'r+' if necessary.
open with a block is useful if you have to compute the output in an iterative loop and want to leave the file open as you do so. write is useful if you are going to output the content in one blast then close the file.
See the documentation for more information.
data = 'data you want inside the file'.
You can use File.write('name of file here', data)
You can also use constants instead of strings to specify the mode you want. The benefit is if you make a typo in a constant name, your program will raise an runtime exception.
The constants are File::RDONLY or File::WRONLY or File::CREAT. You can also combine them if you like.
Full description of file open modes on ruby-doc.org

Resources