PROGRESS - Validating a user-input file output path - directory

I've written some PROGRESS code that outputs some data to a user defined file. The data itself isn't important, the output process works fine. It's basically
DEFINE VARIABLE filePath.
UPDATE filePath /*User types in something like C:\UserAccount\New.txt */
OUTPUT TO (VALUE) filePath.
Which works fine, a txt file is created in the input directory. My question is:
Does progress have any functionality that would allow me to check if an input
file path is valid? (Specifically, if the user has input a valid directory, and if they have permission to create a file in the directory they've chosen)
Any input or feedback would be appreciated.

FILE-INFO
Using the system handle FILE-INFO gives you a lot of information. It also works on directories.
FILE-INFO:FILE-NAME = "c:\temp\test.p".
DISPLAY
FILE-INFO:FILE-NAME
FILE-INFO:FILE-CREATE-DATE
FILE-INFO:FILE-MOD-DATE
FILE-INFO:FILE-INFO
FILE-INFO:FILE-MOD-TIME
FILE-INFO:FILE-SIZE
FILE-NAME:FILE-TYPE
FILE-INFO:FULL-PATHNAME
WITH FRAME f1 1 COLUMN SIDE-LABELS.
A simple check for existing directory with write rights could be something like:
FUNCTION dirOK RETURNS LOGICAL (INPUT pcDir AS CHARACTER):
FILE-INFO:FILE-NAME = pcDir.
IF INDEX(FILE-INFO:FILE-TYPE, "D") > 0
AND INDEX(FILE-INFO:FILE-TYPE, "W") > 0 THEN
RETURN TRUE.
ELSE
RETURN FALSE.
END FUNCTION.
FILE-NAME:FILE-TYPE will start with a D for directories and a F for plain files. It also includes information about reading and writing rights. Check the help for more info. If the file doesn't exist basically all attributes except FILE-NAME will be empty or unknown (?).
Edit: it seems that FILE-TYPE returns W in some cases even if there's no actual writing rights in that directory so I you might need to handle this through error processing instead
ERROR PROCESSING
OUTPUT TO VALUE("f:\personal\test.txt").
PUT UNFORMATTED "Test" SKIP.
OUTPUT CLOSE.
CATCH eAnyError AS Progress.Lang.ERROR:
/* Here you could check for specifically error no 98 indicating a problem opening the file */
MESSAGE
"Error message and number retrieved from error object..."
eAnyError:GetMessage(1)
eAnyError:GetMessageNum(1) VIEW-AS ALERT-BOX BUTTONS OK.
END CATCH.
FINALLY:
END FINALLY.
SEARCH
When checking for a single file the SEARCH command will work. If the file exists it returns the complete path. It does however not work on directory, only files. If you SEARCH without complete path e g SEARCH("test.p") the command will search through the directories set in the PROPATH environment variable and return the first matching entry with complete path. If there's no match it will return unknown value (?).
Syntax:
IF SEARCH("c:\temp\test.p") = ? THEN
MESSAGE "No such file" VIEW-AS ALERT-BOX ERROR.
ELSE
MESSAGE "OK" VIEW-AS ALERT-BOX INFORMATION.

SYSTEM-DIALOG GET-FILE character-field has an option MUST-EXIST if you want to use a dailogue to get filename/dir from user. Example from manual
DEFINE VARIABLE procname AS CHARACTER NO-UNDO.
DEFINE VARIABLE OKpressed AS LOGICAL INITIAL TRUE.
Main:
REPEAT:
SYSTEM-DIALOG GET-FILE procname
TITLE "Choose Procedure to Run ..."
FILTERS "Source Files (*.p)" "*.p",
"R-code Files (*.r)" "*.r"
MUST-EXIST
USE-FILENAME
UPDATE OKpressed.
IF OKpressed = TRUE THEN
RUN VALUE(procname).
ELSE
LEAVE Main.
END.

Related

Fortran90: Scripting of Standard In not working as expected

Working with Fortran90 in Unix...
I have a programme which needs to read in the input parameters from a file "input-deck.par". This filename is currently hard-coded but I want to run a number of runs using different input-deck files (input-deck01.par, input-deck02.par, input-deck03.par etc.) so I've set-up the code to do a simple "read(*,*) inpfile" to allow the user to input the name of this file directly on run-time with a view to scripting this later.
This works fine interactively. If I execute the programme it asks for the file name, you type it in and the filename is accepted, the file is opened and the programme picks up the parameters from that file.
The issue lies in scripting this. I've tried scripting using the "<" pipe command so:
myprog.e < input-deck01.par
But I get an error saying:
Fortran runtime error: Cannot open file '----------------': No such file or directory
If I print the filename right after the input line, it prints that the filename is '----------------' (I initialise the variable as having 16 characters hence the 16 hyphens I think)
It seems like the "<" pipe is not passing the keyboard input in correctly. I've tried with spaces and quotes around the filename (various combinations) but the errors are the same.
Can anyone help?
(Please be gentle- this is my first post on SO and Fortran is not my primary language....)
Fortran has the ability to read the command line arguments. A simple example is
program foo
implicit none
character(len=80) name
logical available
integer fd
if (command_argument_count() == 1) then
call get_command_argument(1, name)
else
call usage
end if
inquire(file=name, exist=available)
if (.not. available) then
call usage
end if
open(newunit=fd, file=name, status='old')
! Read file
contains
subroutine usage
write(*,'(A)') 'Usage: foo filename'
write(*,'(A)') ' filename --> file containing input info'
stop
end subroutine usage
end program foo
Instead of piping the file into the executable you simply do
% foo input.txt

Progress 4GL: Regarding OUTPUT TO statement

What happens when I don't give directory path? Where the file is exported?
DEFINE VARIABLE cPath AS CHARACTER NO-UNDO.
cPath = STRING(MTIME) + "_user.out".
OUTPUT TO VALUE (cPath).
MESSAGE "In side a file".
OUTPUT CLOSE.
The file is exported to your client's working directory. That is typically the directory where you've been in when you have started the client process (_progres, prowin, prowin32).
You can use FILE-INFO to find out:
DEFINE VARIABLE cPath AS CHARACTER NO-UNDO.
cPath = STRING(MTIME) + "_user.out".
OUTPUT TO VALUE (cPath).
MESSAGE "In side a file".
OUTPUT CLOSE.
file-info:file-name = "./" + cPath.
message cPath file-info:full-pathname.
By the way - if you are hoping that using MTIME() to prefix the file name is going to result in a unique file name then you may be disappointed. Multiple processes running at the same time could have collisions. Or you may have old stale files left over from crashed sessions.

Reading in a binary grid file in Fortran 90

I'm having issues when trying to read in a binary file I've previously written into another program. I have been able to open it and read it to an array with out compilation errors, however, the array is not populated (all 0's). Any suggestions or thoughts would be great. Here is the open/read statement I'm using:
allocate(dummy(imax,jmax))
open(unit=io, file=trim(input), form='binary', access='stream', &
iostat=ioer, status='old', action='READWRITE')
if(ioer/=0) then
print*, 'Cannot open file'
else
print*,'success opening file'
end if
read(unit=io, fmt=*, iostat=ioer) dummy
j=0
k=0
size: do j=1, imax
do k=1, jmax
if(dummy(j,k) > 0.) print*,dummy(j,k)
end do
end do size
Please let me know if you need more info.
Here is how the file is originally written:
out_file = trim(output_dir)//'SEVIRI_FRP_.08deg_'//trim(season)//'.bin'
print*, out_file
print*, i_max,' i_max,',j_max,' j_max'
open (io, file = out_file, access = 'direct', status = 'replace', recl = i_max*j_max*4)
write(io, rec = 1) sev_frp
write(io, rec = 2) count_sev_frp
write(io, rec = 3) sum_sev_frp
check: do n=1, i_max
inna: do m=1, j_max
!if (sev_frp(n,m) > 0) print*, count_sev_frp(n,m)
end do inna
end do check
print*,'n-',n,'m-',m
close(io)
First of all the form takes two possible values as far as I know: "FORMATTED" or "UNFORMATTED".
Second, to read, you should use a open that is symmetric to the open statement that you used to write the file, Unless you know exactely what you are doing. I suggest that for reading, you open with:
open(unit=io, file=trim(input), access='direct', &
iostat=ioer, status='old', action='READ', recl = i_max*j_max*4)
That corresponds to the open statement that you used to save the file.
As innoSPG says, you have a mismatch in the way the file is written and how it is read.
An external file may be connected with one of three access methods: sequential; direct; stream. Further, a connection may be formatted or unformatted.
When the file is opened for writing it uses the direct access method with unformatted records. The records are unformatted because this is the default (in the abscence of the form= specifier).
When you open the file for reading you use the non-standard extension of form="binary" and stream access. There is possibly nothing wrong with this, but it does require care.
However, with the read statements you are using formatted (list-directed) input. This will not be allowed.
The way suggested in the previous answer, of using a similar access method and record length will require a further change to the code. [You'll also need to set the value of the record length somehow.]
Not only will you need to remove the format, to match the unformatted records written, but you'll want to use the rec= specifier to access the records of the file.
Finally, if you are using the iostat= specifier you really should check the resulting value.

unix: can i write to the same file in parallel without missing entries?

I wrote a script that executes commands in parallel. I let them all write an entry to the same log file. It does not matter if the order is wrong or entries are interleaved, but i noticed that some entries are missing. I should probably lock the file before writing, however, is it true that if multiple processes try to write to a file simultaneously, it will result in missing entries?
Yes, if different processes independently open and write to the same file, it may result in overlapping writes and missing data. This happens because each process will get its own file pointer, that advances only by local writes.
Instead of locking, a better option might be to open the log file once in an ancestor of all worker processes, have it inherited across fork(), and used by them for logging. This means that there will be a single shared file pointer, that advances when any of the processes writes a new entry.
In a script you should use ">> file" (double greater than) to append output to that file. The interpreter will open the destination in "append" mode. If your program also wants to append, follow the directives below:
Open a text file in "append" mode ("a+") and give preference to printing only full lines (don't do multiple 'print' followed by a final 'println', but print the entire line with a single 'println').
The fopen documentation states this:
DESCRIPTION
The fopen() function opens the file whose pathname is the
string pointed to by filename, and associates a stream with
it.
The argument mode points to a string beginning with one of
the following sequences:
r or rb Open file for reading.
w or wb Truncate to zero length or create file
for writing.
a or ab Append; open or create file for writing
at end-of-file.
r+ or rb+ or r+b Open file for update (reading and writ-
ing).
w+ or wb+ or w+b Truncate to zero length or create file
for update.
a+ or ab+ or a+b Append; open or create file for update,
writing at end-of-file.
The character b has no effect, but is allowed for ISO C
standard conformance (see standards(5)). Opening a file with
read mode (r as the first character in the mode argument)
fails if the file does not exist or cannot be read.
Opening a file with append mode (a as the first character in
the mode argument) causes all subsequent writes to the file
to be forced to the then current end-of-file, regardless of
intervening calls to fseek(3C). If two separate processes
open the same file for append, each process may write freely
to the file without fear of destroying output being written
by the other. The output from the two processes will be
intermixed in the file in the order in which it is written.
It is because of this intermixing that you want to give preference to
using only 'println' (or its equivalent).

Validate a character as a file path?

What's the best way to determine if a character is a valid file path? So CheckFilePath( "my*file.csv") would return FALSE (on windows * is invalid character), whereas CheckFilePath( "c:\\users\\blabla\\desktop\\myfile.csv" ) would return TRUE.
Note that a file path can be valid but not exist on disk.
This is the code that save is using to perform that function:
....
else file(file, "wb")
on.exit(close(con))
}
else if (inherits(file, "connection"))
con <- file
else stop("bad file argument")
......
Perhaps file.exists() is what you're after? From the help page:
file.exists returns a logical vector indicating whether the files named by its argument exist.
(Here ‘exists’ is in the sense of the system's stat call: a file will be reported as existing only
if you have the permissions needed by stat. Existence can also be checked by file.access, which
might use different permissions and so obtain a different result.
Several other functions to tap into the computers file system are available as well, also referenced on the help page.
No, there's no way to do this (reliably). I don't see an operating system interface in neither Windows nor Linux to test this. You would normally try and create the file and get a fail message, or try and read the file and get a 'does not exist' kind of message.
So you should rely on the operating system to let you know if you can do what you want to do to the file (which will usually be read and/or write).
I can't think of a reason other than a quiz ("Enter a valid fully-qualified Windows file path:") to want to know this.
I would suggest trying checkPathForOutput function offered by the checkmate package. As stated in the linked documentation, the function:
Check[s] if a file path can be safely be used to create a file and write to it.
Example
checkmate::checkPathForOutput(x = tempfile(pattern = "sample_test_file", fileext = ".tmp"))
# [1] TRUE
checkmate::checkPathForOutput(x = "c:\\users\\blabla\\desktop\\myfile.csv")
# [1] TRUE
Invalid path
\0 character should not be used in Linux1 file names:
checkmate::check_path_for_output("my\0file.csv")
# Error: nul character not allowed (line 1)
1 Not tested on Windows, but looking at the code of checkmate::check_path_for_output indicates that function should work correctly on MS Windows system as well.

Resources