ChoicesDoc ========== 0. Overview -------- This document details how you should read and write choices in your applications. Correctly implemented, choices should be able to be used on Pre-RPC and RPC systems, as well as persisting across an upgrade. 1. Implementation ----------------- The Choices: path and Choices$Write variables are one of the worst used parts of the current boot system. Not because nobody uses it, but because very few of those people who do use it, use it correctly. Let's start with some fundamentals : Choices$Path This is a path variable for reading the configuration files for applications. The general concensus is that there is one directory or file for each application, named the same thing as the application, without the leading !. You may not write to files in with the path Choices:*. Choices$Write This is a directory variable for modifying the configuration files of applications. You may not read from files in the path .*. So, we have one variable to read from and one to write to. Why ? Very simple really. This way it is possible to have 'global' defaults which are set for all users of a system, and personal defaults used by each user. Ideally, Choices$Path is set to point first to the user's Choices directory, followed by the global one, and Choices$Write is set to the user's Choices directory. Ok, now a simple example : We have a program !DoNowt, which wishes to store its configuration details - a few switches - as a binary file. When it starts, !DoNowt will do the equivalent of : i%=OPENIN("Choices:DoNowt") :REM Open the file IF i%=0 THEN REM The file does not exist - use defaults choices=default_value ELSE REM The file exists - use those in the file choices=BGET#i% CLOSE#i% ENDIF To read its configuration. But of course that isn't backward compatible with non-RPC boot structures. So, how do we get around that ? Like this : SYS "XOS_ReadVarVal","Choices$Write",-1,-1,0,0 TO,,read IF read<0 THENfile$="Choices:DoNowt" ELSEfile$=".Choices" i%=OPENIN(file$) etc... Which will allow it to write to its own directory if it runs on a non-RPC Boot structured machine. But what about writing ? That is similarly easy, and we can just steal the same code pretty much : SYS "XOS_ReadVarVal","Choices$Write",-1,-1,0,0 TO,,read IF read<0 THENfile$=".DoNowt" ELSEfile$=".Choices" o%=OPENOUT(file$) IF o%=0 THEN REM The file couldn't be opened - directory/disc full ? ERROR 0,"Could not write choices file, dying horribly" ELSE REM The file was opened - write the data BPUT#o%,choices CLOSE#o% ENDIF Which is vaguely entertaining. Obviously the error should be reported differently, and should at worst simply do nothing as if the options had been written correctly. Now consider that someone has upgraded their machine from non-RPC style boot sequence to one that is. This presents a problem. We have to auto-upgrade. But we can do that whilst reading the files : SYS "XOS_ReadVarVal","Choices$Write",-1,-1,0,0 TO,,read IF read<0 THEN REM The variable exists - check for auto-upgrade file$="Choices:DoNowt" SYS "XOS_File",5,".Choices" TO ;flags IF (flags AND 1)=0 THEN REM The file exists, so we should move it to the new structure SYS "XOS_FSControl",26,".Choices",".DoNowt", (1<<7)+(1<<1) TO ;flags IF (flags AND 1) THENERROR 0,"Could not auto-upgrade the choices file" ENDIF ELSE file$=".Choices" ENDIF i%=OPENIN(file$) etc... This will move the file from the application directory to the users choices directory. This will be in the Choices: path and will therefore be read next. Notice that we don't copy to Choices: as this may have multiple entries. This technique requires that your application is distributed without a choices file. If you wish to include a default choices file you should give this another name within the application and if the users choices file did not exist read that : i%=OPENIN(file$) IF i%=0 THENi%=OPENIN(".Defaults") IF i%=0 THEN REM The defaults choices file does not exist either - generate defaults choices=default_value ELSE REM The file exists - use those in the file choices=BGET#i% CLOSE#i% ENDIF The 'generate defaults' section can easily be omited if you feel that the user should never touch the inside of your application. So, what do we have ? Well, we have an application which will load its options from the Choices system if a RPC boot sequence is installed, or its own directory if not. It will write back to the correct place for the user configuration. It is NOT up to the application to provide multi-user support except in exceptional situations - this is the point of Choices. Ok, so lets see the code we have so far. When reading a file : SYS "XOS_ReadVarVal","Choices$Write",-1,-1,0,0 TO,,read IF read<0 THEN REM The variable exists - check for auto-upgrade file$="Choices:DoNowt" SYS "XOS_File",5,".Choices" TO ;flags IF (flags AND 1)=0 THEN REM The file exists, so we should move it to the new structure SYS "XOS_FSControl",26,".Choices",".DoNowt", (1<<7)+(1<<1) TO ;flags IF (flags AND 1) THENERROR 0,"Could not auto-upgrade the choices file" ENDIF ELSE file$=".Choices" ENDIF i%=OPENIN(file$) IF i%=0 THENi%=OPENIN(".Defaults") IF i%=0 THEN REM The defaults choices file does not exist either - generate defaults choices=default_value ELSE REM The file exists - use those in the file choices=BGET#i% CLOSE#i% ENDIF When writing a file : SYS "XOS_ReadVarVal","Choices$Write",-1,-1,0,0 TO,,read IF read<0 THENfile$=".DoNowt" ELSEfile$=".Choices" o%=OPENOUT(file$) IF o%=0 THEN REM The file couldn't be opened - directory/disc full ? ERROR 0,"Could not write choices file, dying horribly" ELSE REM The file was opened - write the data BPUT#o%,choices CLOSE#o% ENDIF This is all well and good, but it doesn't cope with the times when we need to store many files in the Choices structure - say the choices from different areas of the program. This means we have to think a little more. The best way to sort this out is to have a function that returns the filename we should use for a particular file. DEFFNchoicesfile(app$,name$,write) LOCAL read,file$,flags SYS "XOS_ReadVarVal","Choices$Write",-1,-1,0,0 TO,,read IF read<0 THEN REM The variable exists IF write THEN REM We're writing to the file file$="."+app$+"."+name$ REM Ensure the direcotry exists SYS "XOS_File",8,"."+app$,,,0 ELSE REM We're reading the file file$="Choices:"+app$+"."+name$ ENDIF REM Check for an auto-upgrade SYS "XOS_File",5,"<"+app$+"$Dir>.Choices."+name$ TO ;flags IF (flags AND 1)=0 THEN REM The file exists, so we should move it to the new structure... IF write THEN REM If we're writing the file, then the one we would copy is out of date REM so we delete it SYS "XOS_File",6,"<"+apps$+"$Dir>.Choices."+name$ ELSE REM We are reading, upgrade the files - need to create the directory first SYS "XOS_File",8,"."+app$,,,0 REM Now we move the file SYS "XOS_FSControl",26,"<"+apps$+"$Dir>.Choices."+name$, "."+app$+"."+name$, (1<<7)+(1<<1) TO ;flags IF (flags AND 1) THEN REM Could not auto-upgrade the choices file - delete it! ERROR 0,"Could not auto-upgrade the choices file" ENDIF ENDIF ENDIF ELSE file$="<"+app$+"$Dir>.Choices."+name$ ENDIF =file$ Notice that this deals with almost every eventuality. If there is an error during the auto-upgrade then an error will be generated. I'm sure you can think of a suitable fallback method in this situation. The easiest thing to do is just delete the original. Remember that the function above only finds the name of the file containing our options. However, it does simplify our code a little. For reading : file$=FNchoicesfile("DoNowt","Choices",FALSE):REM Note: FALSE i%=OPENIN(file$) IF i%=0 THEN REM We just want to know the defaults choices=default_value ELSE REM The file exists - use those in the file choices=BGET#i% CLOSE#i% ENDIF This removes the option of the defaults file. This can easily be added as it was above. For writing : file$=FNchoicesfile("DoNowt","Choices",TRUE):REM Note: TRUE o%=OPENOUT(file$) IF o%=0 THEN REM The file couldn't be opened - directory/disc full ? ERROR 0,"Could not write choices file, dying horribly" ELSE REM The file was opened - write the data BPUT#o%,choices CLOSE#o% ENDIF If I've written that all correctly then you should be able to write an application very easily which will not only work on RISC OS 3.1 through to RISC OS 3.7 in both networked and stand-alone environments, but also will handle the user upgrading their machine completely transparently, and running from a ReadOnly media such as CD-ROM or on a network mounted filestore. 2. 'Grouping' applications -------------------------- In the example above I have dealt with a single application storing its options. You may feel that it would be reasonable to store files for all your applications in one place. At least one author is currently doing this, and it reduces the problem of the 77 file per directory limit. 'New' FileCore will be available in Phoebe, but will not be available for older computers. Choices:.. may seem a little longwinded, but it helps to reduce the number of objects in the root of Choices:. In this case I recommend that you use your initial and surname, eg JFletcher. A better alternative for some programs may be to use a category directory. Acorn themselves have started the ball rolling on this with a 'WWW' directory for all Web related resources. Again, this will only work if people use it. To my knowledge only three category directories are in use at present : IRC : All IRC related applications should store their details here. Many clients currently do not, but it should not be difficult to auto-upgrade from their own directory to the IRC directory. WWW : All Web related applications should store their details here. !ZapUser : Zap modules and resources for the user live here. It is not necessary to create a new category if the need is specialised and there are not likely to be more applications making use of it. If you feel there is a need for a new category you may wish to contact Acorn or other developers to confirm or deny any need. 3. Registration of names ------------------------ When you register an application name (and have had it confirmed!), you also have an allocation in Choices: of the same name. Bare this in mind when creating your application. 4. Contents of files -------------------- Now the only question that remains is what to store in your configuration files. Obviously the configuration details, but I would suggest that you store them in textual format in colon delimited field format (ie like MessageTrans files). Why would you want to do this ? Well, it makes it easier on you to see what is going on in the configuration system, and for the user to modify if they need to. It is, of course, your decision as to how you store your configuration details :-) 5. Robustness ------------- As a word of warning, no application should ever crash because of a poorly formatted configuration file. Loading an entire file into a structure (in C) is likely to cause problems if the file is corrupt in any way. Reading textual strings in BASIC should be checked for overly long strings. Non-existant tags (in tagged files) should be given default values. I'd recommend that you simply ignore any 'broken' configuration file (possibly telling the user) and use the default choices. 6. When to write choices ------------------------ In general, you should not write choices unless the user asks you to. This is not practical in some situations, particularly where the choices require a reload to take effect or where there are so few that it is not sensible to include a 'Save' option. In particular you should not write choices when your application quits. Doing so may cause invalid choices to be written, or (in the case of defaulted choices) overwrite the choices. The latter case should be avoided, as the user may have been able to 'fix' the problem (or have caused it in the first place!). 7. Comments ----------- Comments about this document should be directed to the author, . You can, of course, ignore any part of this document. 8. History ---------- Version 0.00 : 04 Jun 1998 Released to various people via IRC and email for comments. Sent to Dave Walker for confirmation that I'm not talking rubbish :-) Version 1.00 : 29 Jun 1998 Suggestions, comments and complaints added to the document. Reformated into sections.