DOCUMENT #672
=======================================================================
NETWORKING WITH R:BASE: VARIABLE DIRECTORIES
=======================================================================
PRODUCT: R:BASE VERSION : 3.1 or Higher
=======================================================================
AREA : PROGRAMMING IN R:BASE CATEGORY: MULTI-USER
=======================================================================
From Michael Peters
Michael works for Janus s/w Projekte, Simon-Meister-Str.42,
5000 Koeln 60, Germany
(which is the German Microrim representative) as an application
developer. From the US, the easiest way to reach him is via
Compuserve (CIS-ID: 100041,247). Otherwise, the telephone no.
is (Germany)221-7393241.
When designing R:BASE applications for networks, you need to meet the
special requirements of multi-user data access but also need to
integrate the application into the customer's working environment,
that is, into the customer's network server directory structure.
In large networks where many users access all kinds of data, the
customer's system manager or network administrator must design the
server directory structure with great care. During the design phase,
the developer should ascertain from the system manager critical
requirements for the application.
Don't just throw databases and programs together
Usually, as a developer, we are sometimes asked to place the database
files into their server directory, with the application programs
stored in another directory. In single user environments throwing
all the stuff into one directory usually doesn't cause any problems,
here it might!
The system managers are responsible for data security. They are not
interested in a confusing heap of database files, APX programs,
temporary menus, printer files (.PRD), auxiliary DOS batch files and
the like, but want all of this clearly divided and defined. With the
database itself stored in a directory of its own, data backups are
much easier to handle. Then, there is another directory just for the
application programs and all kinds of auxiliary files. To keep the
more adventurous network users from tampering with these files, the
system manager may place a read-only restriction on this directory
(and we should thank him for that...).
On the other hand, the read-only flag can also be a severe restriction
to our application. With the read-only flag on the programs
directory, we can no longer use dynamically created menus, screen
snaps, or any other fancy stuff we might want to include to enhance
functionality, because we can no longer write temporary files to the
hard disk - unless we write them into yet another directory (one
without a writing restriction). Ok, so let's create a third directory
just for all the temporary stuff!
Handling Temporary Files in Network Applications
================================================
Unfortunately, one extra directory for temporary files is not enough.
Imagine one user writing something into this new directory, say a
dynamically created menu called TEMP.MNU. While this user works with
the file, it is locked by the operating system. If another user
happens to work with the same command block at the same time, his
program will not be able to create the file, and might react in an
unpredictable way. Similar problems might occur when using SNAP.
Imagine coming back from a WHILE loop and having DISPLAY show the
previous menu screen, just to see a different menu screen snapped by
somebody else!
There is a simple solution to this 'traffic problem': Put the
directory for the temporary files on local drives instead of a
network drive. Given that the workstations run under DOS and can
manage no more than one session at a time, the local drive is the
logical place for our third directory. Local drive access is fast,
and temporary files usually don't need much disk space.
Another reason is that there are more user-specific things going on
on the local drives anyway. When working with R:BASE in a network
environment, at least one 'private' file is needed for each user:
RBASE.CFG, containing the user's unique network ID. It looks like a
good idea to keep all the 'private' stuff together. So why don't we
simply create one private directory on each workstation, initially
containing nothing but the .CFG file. Later, when the application
runs, it can simply place all the temporary files for our session
there, without causing any problems.
This private directory is also the logical place for the batch file
that starts our application. (Remember that the directory containing
RBASE.CFG should be part of the DOS search path. You should place it
at the beginning of the path to avoid confusion with any RBASE.CFGs
that might be wandering about somewhere else in local or server
directories.)
If diskless workstations are used, the 'temporary stuff' directories
have to reside on the server, but there is no difference in
principle: one private directory for each user is necessary to avoid
file access collisions.
If we take all of this into account, our working environment will go
through a major change. Instead of a single directory which contains
everything that belongs to the application, we will have three
directories:
1. one server directory exclusively for the database
(e.g. F:\RBASE\DBFILES)
2. another server directory for all the application programs
(e.g. F:\RBASE\RBPROGS)
3. a multitude of local, private directories (e.g. C:\RBTEMP).
Managing Three Directories in Program Code
==========================================
Before starting to code on this basis, let's take a quick look at what
has to be done. Actually, there is only one difference to traditional
R:BASE programming: Everywhere in your code, the proper directory
names have to precede the file names. That's all !
Simply replace expressions like 'RUN This IN That.APX' by 'RUN This
IN F:\RBASE\RBPROGS\That.APX'. Write 'OUTPUT C:\RBTEMP\TEMP.MNU'
instead of 'OUTPUT TEMP.MNU', and don't forget to replace the simple
'CONNECT Test' by 'CONNECT F:\RBASE\DBFILES\Test'.
If an existing application has to be adapted to the new situation, no
problem. Use a good word processor with macro programming, and the
job can be done in a couple of hours, even less.
Directories Might Be Subject to Change
======================================
The only thing we need to know before starting to change the code is:
What will the real directory names be at the customer's site? Let's
talk to the system operator again to ask him. He'll probably like the
new directory structure we present to him. It fits his needs.
But he might frown at us when we ask for the real directory names that
we want to code into our application. Maybe he just doesn't know yet
where to put all the stuff. Even if he knows, he doesn't want the
directory names to be fixed for all time, without any chance to
change them if a new hardware situation should arise. We begin to
realize that changes to the directory structure or to file locations
on the server might be necessary from time to time, even long after
we have finished to work for our customer. We cannot hard-code
directory names into our application. They can be subject to change
anytime, and with hard-coded names, the program code would also have
to be changed every time. On the other hand, the directory names have
to be introduced somewhere in the code! So, how do we accomplish
making changes to the code without changing the code ?
To meet this requirement, two steps have to be taken.
Two Steps to Flexibility
========================
Step 1: Our application has to become flexible - that means, it must
not contain literal directory names anywhere! File names in the code
have to be preceded by drive and directory name as explained, but
instead of hard-coding the literal directory name ('RUN This IN
F:\RBASE\RBPROGS\That.APX'), we should use expressions like 'RUN This
IN &vPrg', with the variable vPrg containing the file name preceded
by the currently valid program directory name. To connect the
database, we need a similar variable with the current database
directory and the database name, and for the temporary files, we need
a third variable containing the name of the private directory and the
temporary file name.
Step 2: We have to make these variables directly accessible from the
'outside world'. The currently valid directory names have to be
supplied somehow by the system operator, but how? We wouldn't want
him to change anything in the code. Ideally, he should keep (and, if
necessary, change) the three names somewhere entirely outside of the
code. The code should 'know' where the names are kept and read them
into variables during the start program.
There are a number of ways to realize this.
Where to Keep Directory Names
=============================
At the first glance, the database itself may look like the logical
place to keep the current directory names. We could place three rows
in a special table which could be called 'SysDir'. These rows would
contain the three directory names, and the first thing for the
application to do would be to read them into global variables like
vPrg. If necessary, changing the rows would be a simple task for the
operator. But there is one problem: To evaluate the SysDir entries,
the application has to connect to the database first, so the database
directory name must be known before connecting. Under these
circumstances, keeping the database directory name in the database
itself would not make sense.
There is a much better and even simpler approach: Put the three
directory names (as we have shown, only three directories will be
necessary) into DOS system variables using the SET command, and
evaluate these variables in your application using the R:BASE ENVVAL
function. To apply changes to DOS variables, the system operator
simply has to modify one single batch file containing the three SET
commands. This batch file, residing somewhere on the server, can then
be called from the workstations' AUTOEXEC.BAT or from the batch file
that starts our application. If you want the directory for the
temporary files to be specific for each workstation, place the SET
commands directly into the workstations' AUTOEXEC instead (or into
the local batch file that starts the application), or keep them in
user specific batch files on the server which can be called from the
workstations.
An Example Startup Batch File
=============================
As explained above, we need three directories and a system variable
for each of them. To declare system variables, we need the SET
command. For each directory, we have to issue one SET. The SET
commands can be issued from the DOS prompt, but, for the sake of
simplicity, we will include them in the batch file that starts our
application.
This batch file - let's call it TESTAPP.BAT - should be located in the
private directory. It will begin with the three following lines (be
sure there are no blanks around the '=' equals symbol):
SET RBTEMP=C:\RBTEMP
SET RBDB=F:\RBASE\DBFILES
SET RBPROGS=F:\RBASE\RBPROGS
Besides this, the batch file contains the application start command.
The R:BASE program directory must be part of the DOS search path, of
course, and the program that comes starts the application - called
STARTAPP - should also be in the private directory.
RBASE -R STARTAPP
(Before starting the application for the first time, you might want to
check if the three new DOS system variables have been set correctly.
Issue the three SET commands without starting R:BASE, then issue SET
without parameters to check the environment that contains the
variables. If the variables turn out to be crippled, you have to
enlarge the DOS environment size.)
Example Directories
===================
C:\RBTEMP is our local private directory. In our example, it
is also be our working directory. \RBTEMP contains the RBASE.CFG
file, the batch file TESTAPP.BAT that is used to launch our
application, and the startup program for the application itself,
STARTAPP. This startup program contains lines of code that evaluates
the current value of the three new system variables. \RBTEMP is
also used as the wastedump for temporary files we might have to
create during runtime.
F:\RBASE\DBFILES is our example database directory. It is located
somewhere on the server and contains nothing but the three RBF files.
F:\RBASE\RBPROGS, also residing on the server, contains everything
else, that is, all .APX files, .PRD files and all sorts of auxiliary
stuff. This directory, as explained above, might be write-protected.
Evaluating System Variables in the Startup Program
==================================================
The startup program, STARTAPP, has to make sure that the three
system variables are set. If they aren't, the application cannot run.
If they are, the values of the DOS variables are loaded into global
R:BASE variables. These variables have to be kept alive during the
application, so don't use a CLEAR ALL VAR anywhere, or if you do,
specify the three variables in the EXCEPT list.
SET ERROR VAR vError
SET V vErr INTEGER = 0
SET V vRBTemp TEXT = (ENVVAL('RBTemp'))
SET V vErr = (.vErr + .vError)
SET V vRBProgs TEXT = (ENVVAL('RBProgs'))
SET V vErr = (.vErr + .vError)
SET V vRBDB TEXT = (ENVVAL('RBDB'))
SET V vErr = (.vErr + .vError)
IF vErr <> 0 THEN
CLS
CLS FROM 1 TO 2 RED
WRITE ' System variables are not set correctly!' AT 1 1 +
WHITE ON RED
WRITE ' Please contact your system manager.' AT 2 1 +
WHITE ON RED
PAUSE 1
EXIT
ENDIF
If all went well, we now have three global variables containing the
directory names. Before starting the application itself, there are
some preparations to do.
Change into the Working Directory
=================================
We recommend choosing the private directory on the local drive as
your working directory. If you do start your application from there,
you don't have to change directories first, because you already are
in the correct directory. Just to make sure, however, you should
include some code that does change into RBTEMP first. Then you will
have a reliable starting point under all circumstances.
Consider that the directory to change to could be on another drive.
Another difficulty is that the CHDIR command doesn't work when it
finds a trailing backslash in the name of the target directory,
unless the target is a root directory.
SET V vLen INTEGER = (SLEN(.vRBTemp))
SET V vColon INTEGER = (SLOC(.vRBTemp,':'))
IF vColon <> 0 THEN
--change drive if drive name found:
SET V vDrive TEXT = (SGET(.vRBTemp,.vColon,1))
&vDrive
IF vError <> 0 THEN
CLS FROM 1 TO 1 RED
WRITE 'Error changing drive to',.vDrive AT 1 1 WHITE ON RED
PAUSE 1
EXIT
ENDIF
ENDIF
SET V vLastChr TEXT = (SGET(.vRBTemp,1,.vLen))
IF vLastChr = '\' AND vLen > 3 THEN
--remove trailing backslash
--if not root dir
SET V vLen = (.vLen - 1)
SET V vRBTemp = (SGET(.vRBTemp,.vLen,1))
ENDIF
SET V vRBTemp = (LUC(.vRBTemp))
--change directory
CD &vRBTemp
IF vError <> 0 THEN
CLS FROM 1 TO 1 RED
WRITE 'Error changing directory to', .vRBTemp AT 1 1 +
WHITE ON RED
PAUSE 1
EXIT
ENDIF
Preparing the Directory Variables
=================================
After a successful change directory, we add a backslash to the working
directory variable. This will make it easier to add filenames later
in the program. The same applies to the two other variables.
IF vLen > 3 THEN
SET V vRBTemp = (.vRBTemp + '\')
ENDIF
SET V vRBProgs = (LUC(.vRBProgs))
SET V vLen INTEGER = (SLEN(.vRBProgs))
SET V vLastChr TEXT = (SGET(.vRBProgs,1,.vLen))
IF vLastChr <> '\' THEN
SET V vRBProgs = (.vRBProgs + '\')
ENDIF
The name of the database itself must also be kept in a variable!
Let's assume our database is called TEST. When working from another
directory, a simple CONNECT TEST won't be sufficient anymore, of
course. We create another global variable called vDB for the database
name, preceded by the database directory name, and connect with this.
SET V vRBDB = (LUC(.vRBDB))
SET V vLen INTEGER = (SLEN(.vRBDB))
SET V vLastChr TEXT = (SGET(.vRBDB,1,.vLen))
IF vLastChr = '\' THEN
SET V vLen = (.vLen - 1)
SET V vRBDB = (SGET(.vRBDB,.vLen,1))
ENDIF
SET V vDB TEXT = (.vRBDB + '\TEST')
If the database cannot be opened for some unknown reason, the SQLCODE
is usually -7 regardless of the situation. Should this happen, show
the 'Unable to connect database' message issued by R:BASE, and leave.
SET MESSAGE ON
SET ERROR MESSAGE ON
CONNECT &vDB
IF SQLCode = -7 THEN
PAUSE 1
EXIT
ENDIF
SET MESSAGE ON
SET ERROR MESSAGE ON
(The database name itself, if you don't want it to appear anywhere in
the code, could be taken from another DOS variable; however,
usually this won't be necessary.)
Working Within Variable Directories
===================================
By having your variables ready, you can now apply some necessary changes
to the code. It may seem like a difficult task to juggle with three
directories in a program, but the rules are quite simple, even
mechanical, as we have seen. (If you have to translate a major-sized
single-directory-application into a variable-directory-application,
you may try to devise some word processor macros to do most changes.)
Here are some guidelines:
1. Don't use literal filenames in any RUN, QUIT TO, or ZIP. Instead, use
these commands with variables, that is, filenames preceded by their
directories. For example, use
SET VAR vAPX = (.vRBProgs + 'TestMain.APX')
RUN Thisthat IN &vAPX
instead of the traditional
RUN ThisThat IN TestMain.APX
anywhere in your program.
2. Although it is recommended to create temporary files in your
(private) working directory (which should be your current directory)
you should add the RBTemp directory variable to the temp file names
just to make sure. For example, use
SET VAR vTempMnu = (.vRBTemp + 'TEMP.MNU')
OUTPUT &vTempMnu
instead of the traditional
OUTPUT TEMP.MNU
or use
SET VAR vSnap = (.vRBTemp + 'Screen.Tmp')
SNAP &vSnap
instead of
SNAP Screen.Tmp
3. Remember for EEPs in your forms.
RUN EEP1 IN EEPS.APX
by
RUN EEP1 IN &vEEPS
in all your EEP calls. The variable vEEPs has to be declared either in
the form itself, or in every program using the form that calls the
EEP.
4. UDF's are also program files. Everything described here applies to
UDF's as well. So, replace UDF calls like
SET VAR vFExists = (UDF('FILE.EXE', 'Testfile'))
by
SET VAR vUDFFile = (.vRBProgs + 'FILE.EXE')
SET VAR vFExists = (UDF(.vUDFFile, 'Testfile'))
Actually, the filename 'Testfile' that is looked for in this specific
example would also have to preceded by a directory name! We really
have to be careful.
5. Programs containing the R:BASE BACKUP/RESTORE commands have to be
given special treatment. RESTORE always restores into the current
directory, so the current directory has to be changed from RBTEMP to
RBDB, and back after the restoration. We have already seen how to do
that in principle.
6. Check if there are any CLEAR ALL VARs in your program. If so,
include the directory and database variables in the EXCEPT list.
Conclusion
==========
Writing network applications doesn't have to be more difficult than
writing small, single-user programs. Most concurrency regulations are
already solved for us. R:BASE takes care of them and we usually don't
even notice. However, a small set of special network rules and
guidelines for database and program design should be taken into
account. One recommendation is: Keep your application directory
structure flexible. As we have seen, this can be achieved quite
simply, thanks to R:BASE flexibility.