Inform 7 Home Page / Documentation
§23.15. Exchanging files with other programs
Provided we declare the files in the right way, it is easy for one project to read a file created by another project.
But if we want more rapid communication, between two projects which are each playing at the same time, we need to be more careful. What if project A tries to read the file at the same moment that project B is writing it?
To avoid this, we have a concept of files being "ready". A file is ready if it exists, and is completely written, and not in use elsewhere. We have already seen:
if the file of Invariants exists...
But now we want a stronger condition:
if ready to read (external file):
This condition is true if the file exists and is marked as being ready to read; that is, it is not in a state where another program is currently writing it. Example:
if ready to read the file of Invariants, ...
A file cannot be ready to read if it does not exist, so this is a stronger condition. If A and B are attempting communication in real time, both running at once, then Project A should check that an external file owned by B is ready before it tries to read it. Files can also be marked as ready or not ready, in effect claiming them, thus:
mark (external file) as ready to read
This phrase marks that we have finished writing to the given file, so that any external program is welcome to read it now. Example:
mark the file of Invariants as ready to read;
mark (external file) as not ready to read
This phrase marks that we are about to start writing to the given file, so that any external program should wait until we're finished if it wants to read the file. Example:
mark the file of Invariants as not ready to read;
Possibilities really begin to open up when project A is our story file, but B is not another story file at all: it is some external program such as a Web service, say. (Of course this is harder to set up, since the player needs to have both A and B running at the same time, but for stories running on an Internet server this can all be made seamless.)
When Inform begins writing a table, or text, to a file, it initially marks the file as not ready: only when the table or text is completely written and the file about to close is the file marked as ready.
In order to write non-story-file programs as B, communicating with story files as A, we need to know the file format used by Inform. An Inform file is currently a Unix text file (with 10 as the line division character), encoded as ASCII Latin-1. (We would like to use Unicode at some point in the future, but the Glk and Glulx layers are still not fully converted to Unicode.) It opens with a single header line in the form:
* //IFID// leafname
The opening character is an asterisk if the file is currently ready, a hyphen if the file is currently not ready. The IFID between the slashes is the IFID number of the project which last wrote to the file. (Marking "ready" or "not ready" does not count as a write for this purpose.) If an external program wrote the file, it should call itself something which will not clash with any story file's IFID. The leafname is the filename text used inside the story file where the file was declared. For instance:
* //4122DDA8-A153-46BC-8F57-42220F9D8795// ice
This example can only work if we have a separate program running in the background while the story file is being played, and as such it will only work if we set things up in a special way. Exactly how to do this will vary from platform to platform.
First, the source text for the Inform end of the communication line:
"Flathead News Network"
The file of RSS Requests is called "rssrequest".
The file of RSS Responses (owned by project "RSS-SCRIPT") is called "rssreply".
To request (RSS feed address - text):
mark the file of RSS Responses as not ready to read;
write the RSS feed address to the file of RSS Requests.
Newsroom is a room. "This is the secret nerve-centre of FNN, the Flathead News Network."
The BBC button is in the Newsroom. Instead of pushing the BBC button: say "Bong!"; request "newsrss.bbc.co.uk/rss/newsonline_uk_edition/front_page/rss.xml".
The NASA button is in the Newsroom. Instead of pushing the NASA button: say "Bang!"; request "www.nasa.gov/rss/breaking_news.rss".
The WHO button is in the Newsroom. Instead of pushing the WHO button: say "Bing!"; request "http://www.doctorwhonews.net".
A screen is in the Newsroom.
Instead of examining the screen:
if ready to read the file of RSS Responses, say "From the screen you read:[line break][text of the file of RSS Responses]";
otherwise say "The screen remains blank for now."
As far as the story file is concerned, then, it sends a request down the communication line by writing the chosen RSS feed to the file named "rssrequest", and expects a reply to come back down the line by being written to the file "rssreply". However, the story file needs to expect that this might take some time. (Maybe forever, if there is no program responding, or if the Internet connection is not working.) The story file marks the "rssreply" file as not ready before it makes a request; if it subsequently finds that the file is now ready, that must mean that the other program has done the honours, and that all is well. In the mean time, "The screen remains blank for now."
Now for the RSS-SCRIPT program. The following provides a crude but workable program suitable for running as a Perl script on a system which provides the standard Internet fetching program "curl": Mac OS X, for instance. (If you have OS X, you can paste the following into a (Unix-format) text file called "rss-script.pl", place it in your home folder, open the Terminal utility, type "perl rss-script.pl", and then hide the Terminal window again.)
for (;;) { # repeat forever:
system("sleep 1"); # wait 1 second
open REQUEST, "rssrequest.glkdata" or next;
# the request file has been detected:
$header_line = <REQUEST>; # the header line
$rss_feed = <REQUEST>; # the actual content - the RSS feed URL
close REQUEST;
if ($header_line =~ m/^\*/) { # if the request file is marked ready
$rss = system("curl $rss_feed >rawrss.txt"); # download the RSS feed
# read the RSS XML into a single Perl string:
open RAWRSS, "rawrss.txt" or next;
$raw = "";
while ($nl = <RAWRSS>) {
$raw = $raw." ".$nl;
}
close RAWRSS;
# look for the title and description of the first item:
if ($raw =~ m/\<item\>\<title.*?\>(.*?)\<\/title\>.*?\<description.*?\>(.*?)\<\/description\>/) {
# write the reply:
open REPLY, ">rssreply.glkdata" or next;
print REPLY "* //RSS-SCRIPT// rssreply\n", $1, "\n", $2, "\n";
close REPLY;
# request safely dealt with, so we can remove it:
system("rm 'rssrequest.glkdata'");
}
}
}