How to call a Win32API function from Ruby? - ruby

I want to lock a file as MS Office applications do from a Ruby program so that deletions won't be allowed because "the file is opened on another program".
The Ruby standard library does not seem to be able to do that - I just tried flock() - so I'm trying to invoke the LockFileEx function.
fd = File.open("locked.file", File::RDWR|File::CREAT, 0644)
fd.write "this file to be locked"
import_array = %w(p i i i i i)
wapi = Win32API.new('kernel32', 'LockFileEx', import_array, "i")
wapi.call(fd, 1, 0, 0, 0, 0)
The wapi.call fails with a TypeError exception "Can't convert File into String".
What should I use as first item in import_array to represent the file handle ?
How do I pack the file descriptor into a String ? Where do I get the file descriptor structure ?
I'm using Ruby 1.9.3.

The Ruby file locking mechanism is co-operative and relies on all parties knowing the convention of the Ruby lock file. Microsoft Office will not co-operate.
Instead I suggest that you get the file system to enforce the lock. Simply open the file with an exclusive lock using standard Ruby file handling mechanisms.

First, you have to map the Ruby file descriptor to a C runtime file descriptor. I'm not familiar enough with Ruby source to know how to do that; it may be an identity transform.
Second, you have to map the C runtime descriptor to a Win32 file handle. For this, you need _get_osfhandle.
Third, you need to fix your call to LockFileEx so that you actually pass a valid OVERLAPPED structure; NULL will NOT work.

Related

Opening file with write throws "No implicit conversion of String into Integer"

It's been quite a while time since I last wrote code in Ruby (Ruby 2 was new and wow it's 3 already), so I feel like an idiot.
I have a text file containing only the word:
hello
My ruby file contains the following code:
content = File.read("test_file_str.txt","w")
puts content
When I run it, I get:
`read': no implicit conversion of String into Integer (TypeError)
I've never had this happen before, but it has been quite a while since I wrote code, so clearly PEBKAC.
However, when I run this without ,"w" all is seemingly well. What am I doing wrong?
ruby 3.0.3p157 (2021-11-24 revision 3fb7d2cadc) [x64-mingw32]
As per the docs, the second argument for File.read is the length of bytes to be read from the given file which is meant to be an integer.
Opens the file, optionally seeks to the given offset, then returns length bytes (defaulting to the rest of the file). read ensures the file is closed before returning.
So, in your case the error happens because you're passing an argument which must be an integer. It doesn't state this per-se in the docs for File.read, but it does it for File#read:
Reads length bytes from the I/O stream.
length must be a non-negative integer or nil.
If you want to specify the mode, you can use the mode option for that:
File.read("filename", mode: "r") # "r" or any other
# or
File.new("filename", mode: "r").read(1)
Open Files for Reading Don't Accept Write Mode
In general, it doesn't make sense to open a filehandle for reading in write mode. So, you need to refactor your method to something like:
content = File.read("test_file_str.txt")
or perhaps:
content = File.new("test_file_str.txt", "r+").read
depending on exactly what you're trying to do.
See Also: File Permissions in IO#new
The documentation for File in Ruby 3.0.3 points you to IO#new for the available mode permissions. You might take a look there if you don't see exactly the options you're looking for.

Opening filehandles for use with TabularMSA in skbio

Hey there skbio team.
So I need to allow either DNA or RNA MSAs. When I do the following, if I leave out the alignment_fh.close() skbio reads the 'non header' line in the except block making me think I need to close the file first so it will start at the beginning, but if I add alignment_fh.close() I cannot get it to read the file. I've tried opening it via a variety of methods, but I believe TabularMSA.read() should allow files OR file handles. Thoughts? Thank you!
try:
aln = skbio.TabularMSA.read(alignment_fh, constructor=skbio.RNA)
except:
alignment_fh.close()
aln = skbio.TabularMSA.read(alignment_fh, constructor=skbio.DNA)
I've tried opening it via a variety of methods, but I believe TabularMSA.read() should allow files OR file handles.
You're correct: scikit-bio generally supports reading and writing files using open file handles or file paths.
The issue you're running into is that your first TabularMSA.read() call reads the entire contents of the open file handle, so that when the second TabularMSA.read() call is hit within the except block, the file pointer is already at the end of the open file handle -- this is why you're getting an error message hinting that the file is empty.
This behavior is intentional; when scikit-bio is given an open file handle, it will read from or write to the file but won't attempt to manage the handle's file pointer (that type of management is up to the caller of the code).
Now, when asking scikit-bio to read a file path (i.e. a string containing the path to a file on disk or accessible at some URI), scikit-bio will handle opening and closing the file handle for you, so that's often the easier way to go.
You can use file paths or file handles to accomplish your goal. In the following examples, suppose aln_filepath is a str pointing to your alignment file on disk (e.g. "/path/to/my/alignment.fasta").
With file paths: You can simply pass the file path to both TabularMSA.read() calls; no open() or close() calls are necessary on your part.
try:
aln = skbio.TabularMSA.read(aln_filepath, constructor=skbio.RNA)
except ValueError:
aln = skbio.TabularMSA.read(aln_filepath, constructor=skbio.DNA)
With file handles: You'll need to open a file handle and reset the file pointer within your except block before reading a second time.
with open(aln_filepath, 'r') as aln_filehandle:
try:
aln = skbio.TabularMSA.read(aln_filehandle, constructor=skbio.RNA)
except ValueError:
aln_filehandle.seek(0) # reset file pointer to beginning of file
aln = skbio.TabularMSA.read(aln_filehandle, constructor=skbio.DNA)
Note: In both examples, I've used except ValueError instead of a "catch-all" except statement. I recommend catching specific error types (e.g. ValueError) instead of any exception because the code could be failing in different ways than what you're expecting. For example, with a "catch-all" except statement, users won't be able to interrupt your program with Ctrl-C because KeyboardInterrupt will be caught and ignored.

Get the file type of a file using the Windows API

I am trying to identify when a file is PNG or JPG to apply it as a wallpaper. I am using the SHGetFileInfo to get the type name with the .szTypeName variable, but I just realized that it changes if the OS is in another language.
This is my code:
SHFILEINFOW fileInfo;
UINT sizeFile = sizeof(fileInfo);
UINT Flags = SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES;
// Getting file info to find out if it has JPG or PNG format
SHGetFileInfoW(argv[1], 0, &fileInfo, sizeFile, Flags);
This is how I am validating:
if (wcscmp(fileInfo.szTypeName, L"JPG File") == 0)
{
//Code here
}
When the OS is in spanish, the value changes to "Archivo JPG" so I would have to validate against all language, and does not make sense.
Any idea what other function I can use?
This API is meant to be used to produce a user-facing string representation for known file types1). It is not meant to be used to implement code logic.
More importantly, it doesn't try to parse the file contents. It works off of the file extension alone. If you rename an Excel workbook MySpreadsheet.xlsx to MySpreadsheet.png, it will happily report, that this is a "PNG File".
The solution to your problem is simple: You don't have to do anything, other than filtering on the file extension. Use PathFindExtension (or PathCchFindExtension for Windows 8 and above) to get the file extension from a fully qualified path name.
This can fail, in case the user appended the wrong file extension. Arguably, this isn't something your application should fix, though.
As an aside, you pass SHGFI_USEFILEATTRIBUTES to SHGetFileInfoW but decided to not pass any file attributes (second argument) to the call. This is a bug. See What does SHGFI_USEFILEATTRIBUTES mean? for details.
1) It is the moral equivalent of SHGFI_DISPLAYNAME. The only thing you can do with display names is display them.

Lua: Get function source

I'm working on a system for serialization/deserialization, and I'm trying to get some really fancy stuff going with functions. My goal is to serialize objects containing functions in a human-readable and reversible manner (the serialized files will need processing after the loadfile() step). I need a way to get the actual source of a function, and it looks like I can't always do that with debug.getinfo().
I know that debug.getinfo() will give be the file and line where it was defined (or the source of the function, depending on its origin). Is there a way I can read the function text from that file? I'd be willing to use some kind of parser utilities to do so. Maybe there are Lua packages for parsing Lua code?
Perhaps there's a way I can get loadfile() or require() to automatically retain function source somewhere?
Yes, I know you can get all sorts of information out of debug.getinfo, but it was unable to deal with functions loaded through stdin...
uberblah#glade-m:~$ lua
Lua 5.1.5 Copyright (C) 1994-2012 Lua.org, PUC-Rio
> a = function() print("hello, world!") end
> require("serpent")
> s = require("serpent")
> =s.block(debug.getinfo(a))
{
currentline = -1,
func = loadstring("LuaQ\000\000\000\000\000\000\000\000\000=stdin\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000A#\000\000#\000\000�\000\000\000\000\000\000\000\000\000\000\000print\000\000\000\000\000\000\000\000hello, world!\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",'#serialized') --[[function: 0x2068a30]],
lastlinedefined = 1,
linedefined = 1,
namewhat = "",
nups = 0,
short_src = "stdin",
source = "=stdin",
what = "Lua"
} --[[table: 0x206cf80]]
> f = io.open("stdin", "r")
> =f
nil
SOLUTION FOR SOURCE FROM STDIN...
1) Capture all STDIN, write it to a file
2) Load the information from that file, instead of from stdin
3) debug will track the function line numbers in that file
Using debug.getinfo you can get source, linedefined, and lastlinedefined. Unless you format your code really weirdly you should be able to extract the complete code of your function from this info. There is no need to parse code, just to get the correct set of lines.
You want to disasseble the bytecode into Lua statements and expressions. Try http://chunkspy.luaforge.net/. Or maybe http://luadec.luaforge.net/ but I haven't used them so can't give much more info. Luac (Lua compiler) also has -l switch which produces assembly listing that could potentially be parsed. Then there is lbci (bhttp://www.tecgraf.puc-rio.br/~lhf/ftp/lua/#lbci). You might find Getting the AST of a function useful. Finally, I suggest you do a search for "lua decompiler".

How to explicitly close datasets in GDAL ruby binding?

I am using GDAL 1.7.1 from ruby1.9 to generate GeoTIFF files. In the tutorial they recommend to use GDALClose() to close the datasets and flush any remaining content to the filesystem. The same happens in the destructor for the dataset. The problem is that the ruby bindings rely on this destructor mechanism to close the dataset, and I need the result of the file already in the process that generates it. Since ruby is garbage collected, it seems I can not reliably close my files, without exiting the ruby process. For now I patched my version of GDAL to support the GDALClose method, but this doesn't seem to be a good long term solution.
require 'gdal/gdal'
[...]
# open the driver for geotiff format
driver = Gdal::Gdal.get_driver_by_name('GTiff')
# create a new file
target_map = driver.create(output_path,
xsize,
ysize, 3,
Gdal::Gdalconst::GDT_UINT16, ["PHOTOMETRIC=RGB"])
# write band data
3.times do |i|
band = target_map.band(i + 1)
target_map.write_band(i + 1, mapped_data)
end
# now I would like to use the file in output_path, but at this point
# large parts of the data still resides in memory it seems until
# target_map is destroyed
file = File.open( output_path, "r" )
[...]
Is there something in either ruby or swig to force the destructor call, that I may have overlooked?
Normally what is done with the GDAL bindings in Python is to set the objects to None. So in Ruby, this would be nil:
band = nil
target_map = nil
It's a funny way to save/flush/close the data, but it is how it is done.

Resources