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.