Working With Control Files I

by Steven Parker

Published 1999-07-27    Printer-friendly version

My very deep appreciation to Nik Johnson who bailed me out of control file purgatory (but you know what I really meant) when making the transition to ABC. The solution is his; the text of our exchange is at the end of this article.

I've been using control files since... I can't remember when. When I began moving to ABC, I was confident. My experiences with the app converter had been by and large quite good. But the code that accessed and maintained my control files didn't work after conversion.

Consulting Richard Taylor's superb "Making the Transition to ABC" in the online help, I was able to make some adjustments to what the converter had done. Still, it didn't work.

Control files, clearly, had become a different breed of cat in ABC.

What Is A Control File? And Why Should I Use One?

A control file has two unique characteristics. First, a control file has only one record. Second, a control file has no keys (with only one record, a key is sort of pointless).

In many respects, control files duplicate the function of INI files. The important difference is that a control file, unless you use the ASCII or Basic driver (bad move), cannot be read with a text editor, is not so easily corrupted or manipulated as a text file and, should you so wish, can be encrypted. All or part of a standard file, which is what a control file is, can be made read-only.

The purposes for which you use or, indeed, whether you use a control file as opposed to an INI file is, of course, entirely up to you. But if you decide use a control file, how you use it is not.

The Issues

Just as with any file, there two kinds of things you will want to do. First, you'll want to write to the control file, adding and updating records... oops, the record. When adding, of course, you'll want to add only to an empty file. Adding a second record to a control file defeats its purpose. So, adding is a "one time thing."

Second, you'll want access the record, i.e., read the file. Because a control file has only one record, by definition, and no keys, none of the standard template methods of file handling will work as expected. Indeed, they will not work at all.

Sequential processing is not possible (no "sequence," you see). There is no key to prime. Set(key) and Set(key,key) have nothing to operate on. A loop, of course, is pointless. In plain English, the standard templates can do nothing for you except open and close the file.

Similarly, you can't simply add records to the file. A control file may have one and no more than one record. The standard templates will try to add multiple records.

The problem is determining (1) whether or not the file exists at runtime, (2) whether or not there is in fact a record in the file and (3) how to tell a file what you want to do (Add or Put).

(1) is usually only a problem the first time the app is run.

To make matters more interesting, the database driver that you use makes a difference in accessing single record files. TPS files, in particular, do not support the Pointer() function for direct record retrieval while most other non-SQL databases do. Your choice of file systems will make a difference in your handling of control files.

Accessing Control Files

For the moment, let's assume that there is already a record in the control file.

If you want to display the user's company name and address in the header of a report or plug the city, state and postal code on data entry form, the data contained in a record must have been successfully read first. If you haven't first read the record:

CUS:City = CFG:City

isn't likely to give the desired result, is it?

In CW 2.0, or even in DOS, you would done something like the following:

  Open (file)
  Get (file, 1)

Take a moment to look at this code.

First, the file is opened. This does nothing but create a record buffer in memory. Nothing here prepares the file to be read. Ok, it's not "nothing."

Second, normal file handling would follow with a Next() or Set()/Next(). But a control file contains only one record. If the driver fully supports Pointer(), the record can be accessed directly,

  Get(file,1)

The lesson? (1) Open, (2) read. Read = retain for later use.

TPS files don't fully support the Pointer() function (on a TPS file, Pointer() returns a valid pointer which can be used for direct retrieval but "1" is not a valid pointer with TPS files), so:

  Open(Config,42h) !or Share()
  Set(Config)
  Next(Config)

is required for TPS files.

Legacy command such as Open() and Get() can in fact be used in these circumstances even in ABC. Bad form, to be sure. But they will work. You must ensure that the file is closed at the appropriate point, if you Open the file directly.

In ABC, "good form" would be:

 Access:Config.Open
     Set(Config)
     Access:Config.Next()

for TPS files.

Access:file.Open opens the file. Set() prepares the file for reading, as always. And Access:file.Next() reads the first (and in this case only) record, if any.

You will notice there is no ABC analog for the Get() command. Therefore, for file systems fully supporting Pointer(), you can continue using Get():

  Access:Config.Open
  Get(Config,1)

With these file systems, xBase, Clarion, etc., I have had the Set()/Next() strategy fail. Thus, I continue using Get().

Maintaining Control Files

Since there is only one record, a browse is sort of... ah, pointless. Go directly to the form.

The problem is that a form needs to be told what to do. GlobalRequest is typically used to tell the form whether it is being call to add, update or delete a record. Without a legitimate value in GlobalRequest, the form won't do anything.

Specifically, if called to add a new record, the form needs an empty buffer. If called to change or delete, the form needs a buffer with the correct record.

So, GlobalRequest needs to be set; what's the big deal? The big deal is that you need to know whether or not the file has a record before you set GlobalRequest. If there is no record, GlobalRequest must be InsertRecord and if there is a record GlobalRequest must be ChangeRecord (you don't ever want to delete a control record, do you?).

As it turns out, this is easily determined. If Relate:file.Open returns a non-zero value, there was an error opening the file (e.g., the file does not exist and is not set up for create-if-not-found). If Access:file.Next() returns a value, there is no record in the file. So the following code can be used to set up the call to a form:

Listing 1. Setting up the call to the control file form.
IF NOT Relate:Config.Open()   
  SET(Config)
  IF Access:Config.Next()  
    GlobalRequest = InsertRecord
  ELSE
    GlobalRequest = ChangeRecord
  END
  SetupForm
  Relate:Config.Close
Else
  !action if file create not allowed
END

This ensures that GlobalRequest is always set properly.

Trying to determine the appropriate value for GlobalRequest from inside the Form procedure is a bit more difficult. It is more difficult because GlobalRequest is read before the file is opened (see Figure 1), early in the INIT method.

Figure 1. The Init method embed points.

control_fig1.gif (11875 bytes)

Of course, the code in Listing 1 will work perfectly well before "Snap-shot GlobalRequest" (without the procedure call, of course) and not wreck havoc on the standard template code (so long as you close the file first).

To ensure that the configuration file is properly opened and read when the program is started, I use something like:

Listing 2. Opening the configuration file.
Loop
  Access:Config.Open
  Set(Config)
  If Access:Config.Next()
    SetupForm
  Else
    Break
  End
End

in the main procedure's INIT method. Notice that this code loops until there is no problem opening and reading the file. Combined with the code in Listing 1, the form always knows what is required of it.

Summary

Config files aren't especially difficult. But they do prove just how spoiled we are. Everywhere else, Clarion sets up the file buffer, accesses files and sets up forms. When there's only one record in the file, you're on your own as far as these go.

On the other hand, if you can manipulate a configuration file, you know how to access a file.

In next week's issue of Clarion Magazine, Nik Johnson takes control files a step further with a class and template. 

Listing 3. Nik and Steve's correspondence.
> I have a single record (configuration) file and, based on what 
> I did in CW2003, I used:
>
>  Access:Config.Open
>  Set(Config)
>  Access:Config.Next()
>
> to get that record, but it doesn't seem to be returning the 
> correct >field values.Could it be that the error is in my file 
> access in the update form:
>
>  Access:Config.Next()
>  If ErrorCode()
>    If ErrorCode() = 35 then ThisWindow.Request = InsertRecord.
>  Else
>    ThisWindow.Request = ChangeRecord
>  End
>  ThisWindow.OriginalRequest = ThisWindow.Request
>
> because I noticed (finally) that I had 15 or 16 records in the 
> file. Ok, where'd I foul up? (Please...)
>
> TIA
>
> Steve Parker

(Nik Johnson's reply:)

I think you have to be careful about timing here. What I would 
do is have a source procedure which sits between the menu (or 
whatever triggers the configuration file update process) and 
the update form:

LinkUpdate PROCEDURE

  CODE
  IF NOT Relate:Config.Open
    SET(Config)
    IF Access:Config.Next()
      GlobalRequest = InsertRecord
    ELSE
      GlobalRequest = ChangeRecord
    END
  ConfigForm()
  Relate:Config.Close
  END
  RETURN

This makes sure that the file is open, the record is current 
or the buffer cleared) and GlobalRequest is set -before- you 
ever get to the update form. In that way you don't have to 
worry about little things like the fact that the update 
window will internalize GlobalRequest before -it- opens the file. 
The update form at this point should see no difference between 
this call for a singular record and a request from a browse of 
many records. Objects are like people. They function best when 
given directions in a familiar context.

-Nik

Steve Parker started his professional life as a Philosopher but now tries to imitate a Clarion developer. He has been attempting to subdue Clarion since version 2007 (DOS, that is). He reports that, so far, Clarion is winning. Steve has been writing about Clarion since 1993.

Printer-friendly version

Reader Comments

To add a comment to this article you must log in.

 
 

Search

 

Advanced Search
Topical Index

Related Articles

Subscribe to
ClarionMag

One year: $189

(includes all back issues since '99)

Renewals from $139

Two years: $289

Renewals from $239

More Info

Subscribe Now!

ClarionMag Blog

RSS Feeds

Updates via Email

Enter your Email


Powered by FeedBlitz

Quick Links