How to get a filename from an IO object in ruby - ruby

In ruby...
I have an IO object created by an external process, which I need to get the file name from.
However I only seem to be able to get the File descriptor (3), which is not very useful to me.
Is there a way to get the filename from this object or even to get a File Object?
I am getting the IO object from notifier. So this may be a way of getting the file path as well?

There is a similar question on how to get a the filename in C, I will present here the answer to this question in a ruby way.
Getting the filename in Linux
Suppose io is your IO Object. The following code gives you the filename.
File.readlink("/proc/self/fd/#{io.fileno}")
This does not work for example if the file was removed after the io object was created for it. With this solution you have the filename, but not an File object.
Getting a File object which does not know the filename
The method IO#for_fd can create an IO and it's subclasses for any given integer filedescriptor. Your get your File object for your fd by doing:
File.for_fd(io.fileno)
Unfortunely this File object does not know the filename.
File.for_fd(io.fileno).path # => nil
I scanned through the ruby-1.9.2 sources. There seems to be no way in pure ruby to manipulate the path after the file object was created.
Getting a File object which does know the filename
An extension to ruby can be created in C which first calls File#for_fd and afterwards manipulates the Files internal data structures. This sourcecode does work for ruby-1.9.2, for other versions of ruby it may has to be adjustet.
#include "ruby.h"
#include "ruby/io.h"
VALUE file_fd_filename(VALUE self, VALUE fd, VALUE filename) {
VALUE file= rb_funcall3(self, rb_intern("for_fd"), 1, &fd);
rb_io_t *fptr= RFILE(rb_io_taint_check(file))->fptr;
fptr->pathv= rb_str_dup(filename);
return file;
}
void Init_filename() {
rb_define_singleton_method(rb_cFile, "for_fd_with_filename", file_fd_filename, 2);
}
Now you can do after compiling:
require "./filename"
f= File.for_fd_with_filename(io.fileno, File.readlink("/proc/self/fd/#{io.fileno}"))
f.path # => the filename
The readlink could also be put into the File#for_fd_with_filename definiton. This examples is just to show how it works.

If you are sure that the IO object represents a File you could try something like this
path = io.path if io.respond_to?(:path)
See documentation for File#path

Related

How can I use SaveData to write an ASCII file in ParaView 3.98.1?

I am writing an automation script for an old project and I need some help with pvpython from Paraview 3.98.1. The function SaveData() in this version does not exist. I found its implementation here and moved it to my code. How can I save a file as ASCII? Calling it like SaveData(filename, proxy=px, FileType='Ascii') saves my files as binaries (awkward behavior).
I need this version because some of my codes in the scripting pipeline handle very specific vtk files. Using the SaveData() function of the latest versions ended up creating different metadata in my final files, and when I process them it ends up destroying my results. It is easier at the moment to use an older version of Paraview than to modify all my codes.
Edit:
The website is not working now, but it was yesterday. Maybe it is an internal problem? Anyway, the code is attached below.
# -----------------------------------------------------------------------------
def SetProperties(proxy=None, **params):
"""Sets one or more properties of the given pipeline object. If an argument
is not provided, the active source is used. Pass a list of property_name=value
pairs to this function to set property values. For example::
SetProperties(Center=[1, 2, 3], Radius=3.5)
"""
if not proxy:
proxy = active_objects.source
properties = proxy.ListProperties()
for param in params.keys():
pyproxy = servermanager._getPyProxy(proxy)
pyproxy.__setattr__(param, params[param])
# -----------------------------------------------------------------------------
def CreateWriter(filename, proxy=None, **extraArgs):
"""Creates a writer that can write the data produced by the source proxy in
the given file format (identified by the extension). If no source is
provided, then the active source is used. This doesn't actually write the
data, it simply creates the writer and returns it."""
if not filename:
raise RuntimeError ("filename must be specified")
session = servermanager.ActiveConnection.Session
writer_factory = servermanager.vtkSMProxyManager.GetProxyManager().GetWriterFactory()
if writer_factory.GetNumberOfRegisteredPrototypes() == 0:
writer_factory.UpdateAvailableWriters()
if not proxy:
proxy = GetActiveSource()
if not proxy:
raise RuntimeError ("Could not locate source to write")
writer_proxy = writer_factory.CreateWriter(filename, proxy.SMProxy, proxy.Port)
writer_proxy.UnRegister(None)
pyproxy = servermanager._getPyProxy(writer_proxy)
if pyproxy and extraArgs:
SetProperties(pyproxy, **extraArgs)
return pyproxy
# -----------------------------------------------------------------------------
def SaveData(filename, proxy=None, **extraArgs):
"""Save data produced by 'proxy' in a file. If no proxy is specified the
active source is used. Properties to configure the writer can be passed in
as keyword arguments. Example usage::
SaveData("sample.pvtp", source0)
SaveData("sample.csv", FieldAssociation="Points")
"""
writer = CreateWriter(filename, proxy, **extraArgs)
if not writer:
raise RuntimeError ("Could not create writer for specified file or data type")
writer.UpdateVTKObjects()
writer.UpdatePipeline()
del writer
# -----------------------------------------------------------------------------
The question is answered here (also my post). I used SaveData() to save a binary file with the proxy I need and then used DataSetWriter() to change my FileType to ASCII. It is not a beautiful solution since SaveData() is supposed to do that, but it does the job.

After loading a url by open-uri, how to handle the generated Tempfile object?

I wanna figure out how to download images from internet then store them locally.
Here's what I did:
require 'open-uri' # => true
file = open "https://s3-ap-southeast-1.amazonaws.com/xxx/Snip20180323_40.png"
# => #<Tempfile:/var/folders/k0/.../T/open-uri20180524-60756-1r44uix>
Then I was confused about this Tempfile object. I found I can get the original url by:
file.base_uri
# => #<URI::HTTPS https://s3-ap-southeast-1.amazonaws.com/xxx/Snip20180323_40.png>
But I failed in finding a method that can directly get the original file name Snip20180323_40.png.
Is there a method that can directly get the original file name from a Tempfile object?
What purpose are Tempfile objects mainly used for? Are they different from normal file objects such as: file_object = File.open('how_old.rb') # => #<File:how_old.rb>?
Can I convert a Tempfile object to a File object?
How can I write this Tempfile as the same name file in a local directory, for example /users/user_name/images/Snip20180323_40.png?
The original filename is only really available in the URL. Just take uri.path.split("/").last.
Tempfiles are effective Files, with the distinction that when it is garbage collected, the underlying file is deleted.
You can copy the underlying file with FileUtils.copy, or you can open the Tempfile, read it, and write it into a new File handle of your choosing.
Something like this should work:
def download_url_to(url, base_path)
uri = URI(url)
filename = uri.path.split("/").last
new_file = File.join(base_path, filename)
response = uri.open
open(new_file, "wb") {|fp| fp.puts response.read }
return new_file
end
It's worth noting that if the file is less than 10kb, you'll get a StringIO object rather than a Tempfile object. The above solution handles both cases. This also just accepts whatever the last part of the path parameter is - it's going to be up to you to sanitize it, as well as the contents of the file itself; you don't want to permit clients to download arbitrary files to your system, in most cases. For example, you may want to be extra sure that the filename doesn't include paths like ..\\..\\.."which may be used to write files to non-intended locations.

Can't use io.open in home directory - Lua

I'm writing a Mac OS program, and I have the following lines:
os.execute("cd ~/testdir")
configfile = io.open("configfile.cfg", "w")
configfile:write("hello")
configfile:close()
The problem is, it only creates the configfile in the scripts current directory instead of the folder I have just cd' into. I realised this is because I'm using a console command to change directory, then direct Lua code to write the file. To combat this I changed the code to this:
configfile = io.open("~/testdir/configfile.cfg", "w")
However I get the following result:
lua: ifontinst.lua:22: attempt to index global 'configfile' (a nil value)
stack traceback:
ifontinst.lua:22: in main chunk
My question is, what's the correct way to use IO.Open to create a file in a folder I have just created in the users home directory?
I appreciate I'm making a rookie mistake here, so I apologise if you waste your time on me.
You have problems with ~ symbol. In your os.execute("cd ~/testdir") is the shell who interprets the symbol and replaces it by your home path. However, in io.open("~/testdir/configfile.cfg", "w") is Lua who receives the string and Lua doesn't interprets this symbol, so your program tries to open a file in the incorrect folder. One simple solution is to call os.getenv("HOME") and concatenate the path string with your file path:
configfile = io.open(os.getenv("HOME").."/testdir/configfile.cfg", "w")
In order to improve error messages I suggests you to wrap io.open() using assert() function:
configfile = assert( io.open(os.getenv("HOME").."/testdir/configfile.cfg", "w") )

IO.readlines method in ruby behaving wierdly?

It sounds stupid but it has given me 3 hr of head banging...!!
I have created a class method in which I am extracting a file base name (placed in the Root folder). The issue is IO.readlines method is not accepting the file with a base name returned from the fetching. It returns error:
./lib/fileCheck.rb:36:in `readlines': No such file or directory - (Errno::ENOENT)
But it works as soon as I manually enter the file base name in readlines. Here is the class method:
class FileCheck
def self.read_file
file = File.basename(Dir[File.join(File.expand_path('../.'), "*.txt")].to_s)
file = IO.readlines(file)
return file
end
end
No result, but as soon as I place the file name manually, it works perfectly.
def self.read_file
#file = File.basename(Dir[File.join(File.expand_path('../.'), "*.txt")].to_s)
file = IO.readlines('sample.txt')
return file
end
I check with irb and the statement
File.basename(Dir[File.join(File.expand_path('../.'), "*.txt")].to_s)
is returning a file base name of class String.
Any suggestions?????
it does not matter if you use expand_path or join. you still have a mayor issue in your code:
File.basename(Dir[File.join(File.expand_path('../.'), "*.txt")].to_s)
the Dir[] also known as Dir.glob returns an array! make sure to pick one of the elements instead of calling to_s. you will run into problems when there is more than one file.

How to specify the path of the public directory in a ruby on rails app?

I want to parse .csv file which is in public folder, I've tried /../'s, #{RAILS_ROOT}/public but with no success (No such file or directory error). I dunno exactly how to use Rails.public_path (Rails.public_path/filename.csv doesn't work) please help
You have access to the Rails.root path, use it to get a path
Rails.root.join("public", "filename.csv")
You'll possibly have to call to_s on it depending on how you want to use the result (as a Path object or as a string).
In Rails 4, Rails.public_path, like Rails.root, returns a stdlib Pathname object, so you can also use join with it:
Rails.public_path.join('filename.csv')

Resources