AGMSScriptOCron

AGMSScriptOCron is a BeOS / Haiku OS program which runs templated command lines on a schedule.

Quick Introduction

AGMSScriptOCron maintains a library of Commands which can be executed when requested or when a specified time of day arrives.

The Commands are edited using BeOS / Haiku OS scripting messages, usually sent by using the "hey" command line tool. There is also a commercial version of AGMSScriptOCron with a Graphical User Interface, if you find command lines tedious.

A specialist sets up a command line template for each Model of a Command, specifying the full command line arguments and marking out key Fields within those arguments for end-user modification. Some Models come built-in, and you can create your own.

New in version 2 is support for connecting Commands together into a larger serial/parallel operation. A typical use is for processing steps, so you could start off with a Command to download files to a processing directory, then append a Subsidiary Command to do some audio signal levelling, then append another Subsidiary Command to extract MP3 tags into Haiku file attributes, and a final Subsidiary Command to copy the files somewhere. The GUI version also supports data types, which specify the kind of data used for input and output by a Model. It also uses data types to let you pick from only the valid Model possibilities when inserting new Subsidiary Commands.

For example, a Model that pops up an alert box with an end-user defined message would have a template something like alert --info "Message Text", and a Field named Message Text with some default text that the end-user can change to be their message.

The end-user creates their Commands by copying the Model Command and filling in the Field values with their own text, for example with AGMSScriptOCron running in the background, try this:

hey application/scriptocron get model
hey application/scriptocron create command with "name=My Alert" and model=alert
hey application/scriptocron set value of field "Message Text" of command "My Alert" to "Time to put on the tea kettle."
hey application/scriptocron set trigger of command "My Alert" to "* * * * 0,15,30,45"
hey application/scriptocron set LogFileEnable of command "My Alert" to true
hey application/scriptocron do edit of command "my alert"

Most of those do the obvious thing. The first line lists the available Models (and corresponding data types, if any), the middle ones create a Command that displays a message every quarter of an hour, the last line "do edit" saves the changes (as compared to "delete edit" which reverts to the previously saved version). Double quotes are needed around elements with spaces.

Commands are triggered either by a cron style time specification or by BeOS/Haiku scripting operations. For example, if the end-user doesn't want to wait for the next quarter hour to happen, they can manually run it like this:

hey application/scriptocron do run of command "my alert"

The alert box will pop up with the user's message. Coincidentally, a log file will be written to "settings/AGMSScriptOCron/Logs/My Alert" with time stamped output from running the Command.

A commercial program named Fetchit! with a graphical user interface built on top of AGMSScriptOCron is available from Tune Tracker Systems. The GUI covers most end user operations (though you still need to use "hey" for advanced things like creating new Fields). It lists of all your Commands, with colour coded state and progress bars for each one. Clicking on one lets you edit the values of the Fields of the Command, set the trigger time, view logs, and so on. See http://tunetrackersystems.com/fetchit.html for details and screen-shots.

Longer Example

Here's a longer example, showing how to create your own novel Commands. In this one, we'll make a Command to rename a file.

There will be two input Fields, one with the full path name to the file, and the other with the new name. We'll use the "mv" command line tool to do it, though it won't work across different disk volumes ("copyattr -d" would be needed, since regular "cp" doesn't copy BFS attributes). First we'll change the current directory to the location of the file (it starts out somewhere useless), then do the move.

Here are the command lines you would type in:

date > /boot/home/Junk.txt
date > /boot/home/Debris.txt
hey application/scriptocron create command with "name=Rename a File"
hey application/scriptocron let command "Rename a File" do create field with name=Path
hey application/scriptocron let command "Rename a File" do create field with "name=New Name"
hey application/scriptocron set value of field path of command "Rename a File" to "/boot/home/Junk.txt"
hey application/scriptocron set value of field "new name" of command "Rename a File" to "Trashy.txt"
hey application/scriptocron set template of command "Rename a File" to "cd \"PathAsDir\" ; pwd ; mv -v \"PathAsName\" \"New Name\""
hey application/scriptocron get template of command "Rename a File"
hey application/scriptocron get CommandLine of command "Rename a File"
hey application/scriptocron do run of command "rename a file"
hey application/scriptocron do edit of command "rename a file"
hey application/scriptocron do run of command "rename a file"
hey application/scriptocron get log of command "rename a file"
hey application/scriptocron do run of command "rename a file"
hey application/scriptocron get log of command "rename a file"
hey application/scriptocron let command "rename a file" do create edit
hey application/scriptocron set value of field path of command "Rename a File" to "/boot/home/Debris.txt"
hey application/scriptocron do edit of command "rename a file"
hey application/scriptocron do run of command "rename a file"
hey application/scriptocron get log of command "rename a file"

Here is what it looks like when doing it for real:

Sat Feb 17 11:39:23 1036 /boot/home>cd /tmp
Sat Feb 17 11:39:29 1037 /tmp>date > /boot/home/Junk.txt
Sat Feb 17 11:39:38 1038 /tmp>date > /boot/home/Debris.txt
Sat Feb 17 11:39:46 1039 /tmp>hey application/scriptocron create command with "name=Rename a File"
Reply BMessage(B_REPLY):

Sat Feb 17 11:40:25 1040 /tmp>hey application/scriptocron let command "Rename a File" do create field with name=Path
Reply BMessage(B_REPLY):

Sat Feb 17 11:40:37 1041 /tmp>hey application/scriptocron let command "Rename a File" do create field with "name=New Name"
Reply BMessage(B_REPLY):

Sat Feb 17 11:40:44 1042 /tmp>hey application/scriptocron set value of field path of command "Rename a File" to "/boot/home/Junk.txt"
Reply BMessage(B_REPLY):

Sat Feb 17 11:40:52 1043 /tmp>hey application/scriptocron set value of field "new name" of command "Rename a File" to "Trashy.txt"
Reply BMessage(B_REPLY):

Sat Feb 17 11:41:02 1044 /tmp>hey application/scriptocron set template of command "Rename a File" to "cd \"PathAsDir\" ; pwd ; mv -v \"PathAsName\" \"New Name\""
Reply BMessage(B_REPLY):

Sat Feb 17 11:56:08 1045 /tmp>hey application/scriptocron get template of command "Rename a File"
Reply BMessage(B_REPLY):
   "result" (B_STRING_TYPE) : "cd "PathAsDir" ; pwd ; mv -v "PathAsName" "New Name""

Sat Feb 17 11:56:18 1046 /tmp>hey application/scriptocron get CommandLine of command "Rename a File"
Reply BMessage(B_REPLY):
   "result" (B_STRING_TYPE) : "cd "/boot/home/" ; pwd ; mv -v "Junk.txt" "Trashy.txt""

Sat Feb 17 11:56:33 1047 /tmp>hey application/scriptocron do run of command "rename a file"
Reply BMessage(B_REPLY):
   "error" (B_INT32_TYPE) : -2147483634 (0x8000000E)
   "message" (B_STRING_TYPE) : "Can't run Command named "Rename a File" because it is Editing instead of Ready to Run"

Sat Feb 17 11:56:52 1048 /tmp>hey application/scriptocron do edit of command "rename a file"
Reply BMessage(B_REPLY):

Sat Feb 17 11:57:01 1049 /tmp>hey application/scriptocron do run of command "rename a file"
Reply BMessage(B_REPLY):

Sat Feb 17 11:57:08 1050 /tmp>hey application/scriptocron get log of command "rename a file"
Reply BMessage(B_REPLY):
   "result" (B_STRING_TYPE) : "
================================================================================

Command "Rename a File" started on Sat Feb 17 11:57:08 2018.
cd "/boot/home/" ; pwd ; mv -v "Junk.txt" "Trashy.txt"
/boot/home
Junk.txt -> Trashy.txt
Command "Rename a File" finished on Sat Feb 17 11:57:09 2018.
It provided an exit code of 0 (by convention zero means OK).
"

Sat Feb 17 11:57:15 1051 /tmp>hey application/scriptocron do run of command "rename a file"
Reply BMessage(B_REPLY):

Sat Feb 17 11:57:30 1052 /tmp>hey application/scriptocron get log of command "rename a file"
Reply BMessage(B_REPLY):
   "result" (B_STRING_TYPE) : "
================================================================================

Command "Rename a File" started on Sat Feb 17 11:57:30 2018.
cd "/boot/home/" ; pwd ; mv -v "Junk.txt" "Trashy.txt"
/boot/home
/bin/mv: Junk.txt: No such file or directory
Command "Rename a File" finished on Sat Feb 17 11:57:31 2018.
It provided an exit code of 1 (by convention zero means OK).
"

Sat Feb 17 11:58:35 1056 /tmp>hey application/scriptocron let command "rename a file" do create edit
Reply BMessage(B_REPLY):

Sat Feb 17 11:58:47 1057 /tmp>hey application/scriptocron set value of field path of command "Rename a File" to "/boot/home/Debris.txt"
Reply BMessage(B_REPLY):

Sat Feb 17 11:59:35 1058 /tmp>hey application/scriptocron do edit of command "rename a file"
Reply BMessage(B_REPLY):

Sat Feb 17 11:59:44 1059 /tmp>hey application/scriptocron do run of command "rename a file"
Reply BMessage(B_REPLY):

Sat Feb 17 11:59:51 1060 /tmp>hey application/scriptocron get log of command "rename a file"
Reply BMessage(B_REPLY):
   "result" (B_STRING_TYPE) : "
================================================================================

Command "Rename a File" started on Sat Feb 17 11:59:51 2018.
cd "/boot/home/" ; pwd ; mv -v "Debris.txt" "Trashy.txt"
/boot/home
Debris.txt -> Trashy.txt
Command "Rename a File" finished on Sat Feb 17 11:59:52 2018.
It provided an exit code of 0 (by convention zero means OK).
"

Sat Feb 17 11:59:58 1061 /tmp>

Subsidiary Commands

New to version 2 are Subsidiary Commands, which are connected together to form a larger Command tree under an "ancestor" Command. The main purpose is to chain together pre-made processing steps in different ways to do more complex file manipulation work.

Previously you could name some other Command in the Trigger string and it would run your Command when the named one finished successfully. Subsidiary Commands are similar, but now are more directly subservient to the initial Ancestor Command: sharing the Ancestor's log file and work directories, and the Ancestor waits for the Subsidiaries to finish their work before it ends.

A typical use would be to start by creating the Ancestor Command from a Model that downloads files to a temporary directory. Then you would create Subsidiary Commands from Models of various useful processing steps, and connect them to make a chain of operations (the first Subsidiary connects to the Ancestor, the second Subsidiary connects to the first one, etc). The chain's steps can do things like copying MP3 metadata into BeOS/Haiku file attributes, renaming the files, doing audio level equalization, and finally copying the processed files to a destination folder. Once created from a Model, each Subsidiary can be customized, such as specifying a renaming scheme to use, or audio level to set, or directory to copy files to. So, that gives you stock processing steps, combined in the way you want, with custom parameters for each step.

The new Subsidiary system supports both parallel and serial operations by organizing Commands into a tree structure. Each Command runs all its child Commands in parallel, and waits for them all to finish before it finishes and returns a combination exit code. That's the whole scheme. If you want to do Commands in parallel, make them all have the same parent Command in their Trigger name. For serial, arrange them as a tall linear skinny tree, with each Command the child of the previous Command.

AGMScriptOCron also makes sure that Subsidiary Commands follow the ancestor around. If you rename the Ancestor Command, it will automatically rename all the Subsidiary Commands and update their Trigger text to use the appropriate new names (generated from the Ancestor name, a sequence number, and the name of the Model used to make the Subsidiary Command). If you delete the Ancestor, all the attached Subsidiary Commands get deleted. If you turn on edit mode, you have to do it for the Ancestor, which will then turn it on for all the Subsidiary Commands in the tree. If you try to make a sneaky circular reference in the tree, it will be detected and removed.

Auto-Upgrading Commands

Since a bit of work now goes into making Models (for the comercial version), version 2 has an update mechanism that upgrades Commands if their Model was changed. When you make a new Model of your own, a UserData field with the name and date of the Model creation is saved inside the Model. This is inherited by any Commands created from that Model. When AGMSScriptOCron starts up, after loading all the models (built in ones, ones in add-on files, and yours, in that order, earlier takes priority), it loads the Commands from the settings file and upgrades them. If it can find a Model for a Command where the date is different (newer or older), the Command is changed to match the Model: UserData and Template are copied from the Model to the Command and UserData for its Fields, except for things the end user may have changed (Description UserData and some Fetchit GUI state stuff aren't upgraded). If you are running it from the command line, you'll see a list of the things upgraded during startup.

List of Scripting Operations

To see what scripting operations are currently available, run AGMSScriptOCron from the command line. The ones described here are as of version 2.90. You can also use hey application/scriptocron getsuites to get the usage help text for the top level scripting operations. For other levels, you'll have to create a Command and then do hey application/scriptocron getsuites of command [0]. Similarly, hey application/scriptocron getsuites of field [0] of command [0] will tell you what Fields can do.

Application Scripting

Quit
Stop the program. Useful if it's running as a server.
Get Serial
Returns the change serial number for the Application overall. It gets increased by some small amount (may wrap around to negative values eventually) when this object or any of its children have been changed. Useful for knowing when to update a GUI displaying this object.
Count Command
Counts the number of Commands currently in the list of commands.
Create Command
Create a new Command. The Command's default name is "Unnamed". You can set a name for the new Command by adding a string named "name" to the BMessage used for scripting. If you want the new Command to be a copy of a Model Command, add a string named "model" with the name of the Model to use. If no Model is specified, you get an empty Command (no Fields, no command line, etc). The new Command starts in Edit mode, so it doesn't accidentally start running. The Hey command would look like: hey AGMSScriptOCron create Command with name=NewCommandNameHere and model=ModelNameHere
N/A Command
Specify a particular Command sub-object for further scripting. It's better to refer to Commands by name than by index, since the array is kept in alphabetical order and adding or removing Commands will affect the indices of several other Commands. The Hey command syntax would be: "hey AGMSScriptOCron get something of Command NameOfCommandHere" For indexing, it would be: "hey AGMSScriptOCron get something of Command [0]" (note the space between Command and [0]). If those don't work, try "hey AGMSScriptOCron let Command [0] do get something" which goes through a different code path to find the target object.
Get Model
Gets a list of the names of the Models, returned as an array of strings, in alphabetical order. A Model is a saved Command that can be copied to make a new Command. There are built-in ones for common tasks and ones which the user can create from existing Commands. Also returns the data types of the input each model needs in an array of strings named "Datatype of Input", with an empty string for Models that don't have any inputs. There's a corresponding array of "Datatype of Output" strings. Those are used for Models that can be turned into Subsidiary Commands that can be chained together by matching data types of outputs from one subsidiary Command to the input of the next one. This operation also returns the change serial number for the last time the model data was changed in a separate "serial" field.
Count Model
Gets the serial number for the last change of the Model data. Lets you determine if your cached Models need to be updated. If you want to know the number of Models, get the list of names and count them up.
Create Model
Creates a Model from a Command. An extra string called "name" specifies the name of the Command to be copied into the new Model (which will have the same name). Fails if the Command doesn't exist or the name is already in use by another Model.
Delete Model
Deletes the Model specified by the name specifier. Try: "hey AGMSScriptOCron delete Model NameOfModelHere"
Get UserTempDir
Returns the user specified temporary files directory. Commands will put their temporary files inside it, inside a AGMSScriptOCron folder which has subfolders for each Command's use. If empty, the system default temporary directory will be used. Use the "Paths" scripting property on a Command to get the actual path used. Also returns the serial number at the time it was last changed, in key "serial".
Set UserTempDir NewValue
Change the temporary files directory, where Commands temporarily put their downloaded files and other temporary things. Usually this will be /tmp or something similar, varying depending on the operating system. You can reset it back to the default one by specifying an empty string. Trailing slashes aren't needed in your path, but it should start with a slash. Usually you only need to change this if the default is on a disk without enough free space, or you don't like the way that the default /tmp directory gets automatically cleaned out when you reboot.
Delete UserTempDir
Put the temporary files directory back to the system default one.

Command Scripting

Get Name
Gets the name of this Command.
Set Name NewValue
Changes the name of this Command. This will also affect the order of the Commands array (which is always in alphabetical order), so the index to this Command and others may change. Fails if the new name is already in use or isn't suitable as a directory name (no slashes, not all periods, not empty). You can change the name at any time, no need to switch into edit mode. Will also rename the Work directory and Log file to match the new name, though if that fails you'll get an error but the Command rename still takes place.
Get Serial
Returns the change serial number for this Command, increased when this object or any of its children change in any way (including new log text, settings modifications, etc).
Delete Command
Delete this Command. Fails if the command is not in edit mode (running or idle). Hey syntax is a bit odd, use "hey AGMSScriptOCron let Command [3] do delete". A bit of a hack has been put in so you can use the more obvious "delete Command [3]" syntax, also "hey AGMSScriptOCron delete Command of Command [3]" does work.
Create Duplicate
Make a copy of this Command. The new name of the command is "Unnamed" unless you specify it with a string called "name". The new name must not exist, otherwise this operation will fail. Can make a copy of a Command in any state; the new one will always start in the Edit state.
Create Edit
Start editing this Command. This creates a backup copy of the Command so that it can be restored to its original state if you cancel the editing. Can only start editing if the Command isn't running or otherwise in use. Changes the Command state to being edited, which will prevent other uses of the Command (like running it) while it is being edited. Only works on non-subsidiary Commands; all subsidiary child Commands will also switch to edit mode when their ancestor Command changes. Use "hey AGMSScriptOCron let Command [3] do create edit"
Delete Edit
Cancel the editing of this Command. Restores it to its original state, unless you've somehow created another command with the old name. In that rare case, you'll get an error and the Command and its backup copy are deleted rather than being restored.
Execute Edit
Finish editing the Command. Gets rid of the backup copy of the original state of the Command, and changes the Command state to being ready to run. The Hey command awkwardly uses "do" to mean execute, so it would have a double do like: hey AGMSScriptOCron let Command [0] do do Edit
Get Edit
Returns true if the Command is being edited, false otherwise.
Execute Run
Start running the Command. Fails if it is being edited or is already running. The Hey command awkwardly uses "do" to mean execute, so it would have a double do like: hey AGMSScriptOCron let Command [0] do do Run
Get Run
Returns true if the Command is running or waiting for child Commands to finish running, false otherwise. You can tell the difference by looking for the extra boolean "waiting", which will be true if the Command is waiting for child subsidiary Commands to finish.
Get Template
Get the Template for building the command line used by this Command.
Set Template NewValue
Set the Template for building the command line used by this Command. Various magic words in the Template will be replaced with specific values. The most basic magic word is a Field name, which will get replaced with the Field's Value. Another is a Field name with "AsDir" appended, which uses the Value up to and including the last slash (/) character, nothing if no slash. Similarly, "AsName" is all the text after the last slash. "AsUserData(name)" gets replaced by the UserData under the named key attached to the Field, empty string if no UserData by that name or if it isn't a string. "AsDatedString" triggers date expansion if the Field Value starts with a plus sign, like AsDatedURL but without the conversion to and from a URL. "AsDatedURL" decodes the Field's value as a URL, and if the name portion starts with a plus sign, passes the name to the system "date" command as a format argument, with an optional UserData at key "DateOffset" string being used as a date offset, resulting in a CLI command that looks like 'date "--date=1 day ago 2 minutes ago" "+Sermon%Y-%m%d.mp3" | tr -d "\n"'. Date expansion is also done to the directory path if it starts with a plus sign. Then the date expanded text is converted back to URL encoded format. "AsNCFTP" treats the Field's value as a URL and convertes it into arguments (separate parts for host name, directory, etc) for the ncftpput command. Arguments are escaped and put in quotes for you. You can also append "AsSafe" to convert problematical characters (currently !$"\`) to their escaped equivalents. You can specify multiple postfixes in any order, but they will get processed in the AsUserData, AsDir, AsName, AsDatedString, AsDatedURL, AsNCFTP, AsSafe order. "SOCName" is replaced by the name of the ancestor Command. "SOCMyName" is replaced by the name of the current Command, even if it is a subsidiary one (SOCName is the one users should normally see). "SOCWorkDir" is replaced by the absolute path to the working directory for this command, ending without a slash, typically /boot/home/config/settings/AGMSScriptOCron/Work/CommandName, with the Command name sanitised (slashes changed to dashes and a few other things). Subsidiary Commands use their ancestor's name so they all work from the same directory. The work directory is used for things like a file containing the list of URLs to download or a file listing the server's contents as of the last run. It is persistent between boots. You need to create the directory if needed; we just provide the name. "SOCLogFile" is replaced by the absolute path to the log file for this Command and its subsidiary Commands. "SOCTempDir" is for /tmp/AGMSScriptOCron/CommandName and is used for temporary files which get erased at the next system bootup.
Get CommandLine
Get the current command line for this Command. Will substitute Field names (and other related magic words) in the Template with their current Values to make the command line.
Get Trigger
Get the Trigger string used by this Command.
Set Trigger NewValue
Set the Trigger string used by this Command. A Trigger string can be several things, which control when this Command is started. If it's an empty string, the Command can only be manually started. If it's "@Launch" then the Command starts up when AGMSScriptOCron starts running (usually soon after the computer boots up, if it's configured that way). If it's a Cron style string then the Command is started whenever the current time (local time zone) matches. Finally, if it's the name of some other Command then this Command is started when that other Command finishes successfully (successful means with an exit code of zero). I might as well explain Cron style time strings here. Technically they're a modified version of the Unix crontab format (look it up online if you wish to read more). It's a way of specifying dates and times, formatted as five fields separated by white space in this order (opposite of crontab order): DayOfWeek Month DayOfMonth Hour Minute. Each field can contain a single value, a bunch of values separated by commas, a range of values using a hyphen, or be an asterisk to mean all possible values. All fields must match to activate a time trigger. So to specify the top of the hour at 2am on January 6th (years aren't mentioned, so it would happen every year at that time), it would be "* 1 6 2 0". To specify 11:30pm on work days it would be "Mon-Fri * * 23 30". For something happening every quarter hour from noon to just before 5pm (16:45 would be the last time) it would be "* * * 12-16 0,15,30,45". The sneakiest may be for things like the first Friday of the month "Fri * 1-7 0 0" which activates at midnight if the day is a Friday and in the first 7 days of the month (one of them must be a Friday?). Note that the standard Unix crontab interpretation does it differently when combining day of week and day of month. The valid range for minutes is 0 to 59, hours 0-23, days 1-31, months 1-12 (January to December), day of week 0-6 (Sunday, Monday, ..., Saturday).
Get Children
Gets the list of child Commands to this Command, and other relatives. The "result" field is an array of strings naming the children, can be none. Parent / child relationships are determined by trigger strings listing the name of the parent Command rather than a start time. You can add an optional boolean of "subsidiary", set it to TRUE to restrict the list to just subsidiary child Commands. Setting it to FALSE gives you a list of just the regular Command children. Don't include the boolean if you want all children. This operation also returns the names of some relatives. The name of the ancestor Command is in a field named "ancestor", which only exists for Subsidiary Commands. If the Command has a parent (an actually existing Command named by the Trigger string) it will be named in a "parent" field, otherwise the field will be missing.
Get Disable
Gets the disable status of this Command. Result field is TRUE if it is disabled.
Set Disable NewValue
Set the disable status of this Command. Add a boolean "data" field set to TRUE to disable the Command. FALSE will enable it. An enabled Command will run as usual. A disabled Command won't even try to run at scheduled times, ignoring the Trigger string. However, if it is a Subsidiary Command (is marked as Subsidiary and has the name of its parent Command specified in its Trigger) and that parent finishes running successfully, this Command will partly run. A partial run will add a log entry saying that this Command was skipped because it was disabled, and any child Commands (ones with this Command listed as their trigger parent) will then be run. This is needed so that you can disable individual renaming steps (each one is a child Command of the previous step) without affecting later ones.
Get Subsidiary
Gets the Subsidiary status of this Command. Result field is TRUE if it is marked as a Subsidiary.
Set Subsidiary NewValue
Sets the Subsidiary status of this Command. Set a boolean "data" BMessage field to TRUE to mark it as a Subsidiary, FALSE as a regular Command. A Subsidiary Command usually has a parent Command listed in its Trigger string (ones without shouldn't happen, will be deleted when detected). It will use its nearest non-subsidiary ancestor Command's log file for log messages. The parents all the way up to that ancestor will wait for subsidiary children to finish running before they stop running. Subsidiary Commands also follow their ancestor's edit state, and get deleted when the ancestor is deleted. When the ancestor is renamed, Subsidiary Commands all get renamed as the ancestor's name plus a sequence number plus their original Model name (if it exists). Models include their Subsidiary child Commands.
Get Log
Get the accumulated log output (currently Standard Output and Standard Error combined) from the execution of this Command. The accumulated data (which is stored in memory) is also cleared at this point. The serial number is also changed whenever new log data arrives. If you are running a particularly verbose command, it's best to periodically get the log output data to avoid missing anything since the in-memory buffer only holds 2000000 bytes.
Get LogFileEnable
Returns a boolean that is TRUE if the output from this Command is being logged to a file. FALSE if it's not being filed away.
Set LogFileEnable NewValue
Turns on or off file logging of this Command's output. Takes effect only when the Command starts running, not during. The boolean "data" field needs to be set to TRUE to enable file logging, false to turn it off. Regular in-memory logging proceeds irregardless. Logged text will be appended to the file; it's up to you to delete it if it gets too big. The log file will have the same name as the Command (minus slashes) and be stored in the Logs directory in the program's subdirectory in the user settings directory. Typically it will be named something like /boot/home/config/settings/AGMSScriptOCron/Logs/CommandName. If you don't want it there, replace the file with a symbolic link to a different file in a different place.
Get Paths
Returns strings for the various stock paths for this Command. The one at key "SOCTempDir" is the temporary directory path for this particular Command (may get erased during reboots, usually inside /tmp, a sanitised version of the Command name will be somewhere in the path). "SOCWorkDir" is the directory for the Command's more persistent data, usually somewhere under the AGMSScriptOCron settings directory. There's also "SOCLogFile" for the path to the log file, though the file might not exist. Returned strings don't end in a slash.
Create Field
Create a new Field inside the Command. The Command needs to be in Edit mode. The Field's default name is "Unnamed". You can set a name for the new Field by adding a string named "name" to the BMessage used for scripting. The Hey command would look like: hey AGMSScriptOCron let Command [0] do create Field with name=NewFieldNameHere
Count Field
Counts the number of Fields in the Command.
N/A Field
Specify a particular Field sub-object of a Command for further scripting. It's better to refer to Fields by name than by index, since the array is kept in alphabetical order and adding or removing Fields will affect the indices of several other Fields.

Field Scripting

Get Name
Gets the name of this Field.
Set Name NewValue
Changes the name of this Field. This will also affect the order of the Fields array (which is always in alphabetical order), so the index to this Field and others may change. Fails if the new name is already in use or the parent Command isn't in edit mode.
Get Serial
Returns the change serial number for this Field, increased when this object changes in any way (usually due to user editing).
Delete Field
Delete this Field. Fails if the parent command is not in edit mode.
Get Value
Gets the Value of the Field.
Set Value NewValue
Sets the Value of the Field. This is the user provided string that will be substituted in the command line everywhere the Field name is present.

UserData Scripting

Both Commands and Fields have UserData capability. This lets you store arbitrary data identified by a key word, and retrieve it later using the same key word.

Set UserData NewValue
UserData is used for storing key and value pairs of user specified data into scriptable objects, currently Commands and Fields. It's often used by a GUI on top of this program to store things such as the data type related UI to pop up to edit a particular Field. The key (passed in the name specifier of the scripting operation) is a non-empty UTF-8 string, with case insensitive matching. The value can be any of the BMessage field data types (including a BMessage) and can also be an array of them. This scripting operation changes the value stored in the UserData entry for the specified name or at the specified index to the contents of your "data" argument(s). If you don't specify any data, then the contents become NULL and when you get it next time, the "result" field will be missing. If the entry doesn't exist and you used the name specifier, it will be created (thus the lack of a B_CREATE_PROPERTY operation). For example: hey AGMSScriptOCron set UserData "DisplayType" of field "A-URL" of Command "Download New Files" to "URL"
Delete UserData
Deletes the UserData entry for the specified name or at the specified index.
Count UserData
Counts the number of UserData entries currently stored. Coincidentally and not surprisingly, valid index specifiers are from zero up to this number minus one.
Get UserData
Gets the value stored in the UserData entry for the specified name or at the specified index number (zero based, be wary of the shuffling which happens when items are added or removed - they're kept in alphabetical order). Returned in the usual "result" output, which will be missing if the stored value is NULL.

Here are some standard UserData keys/values:

Datatype of Input/string. Names the kind of data this Command uses as an input.

Datatype of Output/string. Names the kind of data this Command outputs. Particular data types are: "BaseURL" for a text file named SOCWorkDir/BaseURL containing the URL to the remote directory (ends with a slash) of files to be downloaded, without any end of line characters. "DownloadURLList" for a text file named SOCWorkDir/URLsToDownload containing a list of URLS (one per line) to be downloaded. "ProcessDir" for all the files in directory SOCTempDir/Processing. They'll get batch processed in some way, perhaps renaming or adjusting audio levels.

Description/string. User notes, can be multiple paragraphs.

DisplayType/[CheckBox | JustNewOps | LocalDir | LocalFile | LocalPath | MultipleChoice | MultipleChoiceWithText | String | URL | URLDirOnly | Renamer] Specifies how to display a Field in the GUI version of the program. If not specified, you get the usual String display. A DisplayType of "CheckBox" shows a checkbox which sets the Field's value to "0" or "1". "JustNewOps" is a specially made user interface for the "Fetch Just New Files" Command Model which has buttons to reset the list of downloaded files in various ways (its Field is just a dummy). "MultipleChoice" uses a multiple choice pop-up menu, with UserData["Choices"] specifying the choices separated by vertical bar characters, and UserData["Labels"] contains wordier text describing each choice (defaults to the corresponding Choices value if an empty string or if not specified). "MultipleChoiceWithText" is similar but has an additional text box where the user can type in new choices. The "LocalDir" one uses a file requester to select a directory, the resulting path is absolute if it starts with a slash, or relative to the current Station directory. Often the path ends with a slash for directories, though the user can edit it. The "LocalFile" similarly uses a file requester to select a file, relative to the Station directory. "LocalPath" lets the user choose either a file or a directory. The "URL" DisplayType breaks apart the URL (only FTP and HTTP(S) ones) into all the parts and lets the user edit each part individually before reassembling the result into a new URL (with encoding of odd characters too). "URLDirOnly" is similar except that it doesn't ask for a file name and only works for FTP. "Renamer" prompts for information needed to rename a file and builds a SED (stream editor) command string to rename an original file name into a new name.

Documentation/string. Documentation for the Command (could be for Fields too, but it isn't currently displayed). Typically will list the inputs and outputs for subsidiary Commands. Can be multiple paragraphs.

GroupBox/string. Starts a multiple Field grouping box, with the string as the title of the box. The box contains this Field and the following ones (in alphabetical order) up to the last Field in the Command or stops when a Field with another GroupBox definition is encountered. A string of "NoBox" turns off the grouping box for that Field and following ones. If the string starts with "+" then the box is visible in both basic and advanced modes (and the + isn't shown to the user), otherwise the box is only visible in basic mode.

GUITitles/string. Changes the title of various GUI elements used in displaying a Field like the LocalDir one from the default of "Save files to", "Change", "Show", "Choose save directory for" and "Use" to be whatever the strings are. The different elements separated by "|" characters and the order of the elements is Field DisplayType specific. We haven't gotten around to implementing it for all field types.

HelpText/string. One line (max about 120 characters) of help text about a Field or a Command, which will be displayed in the status bar of the GUI version of the program when the mouse is over that Field or Command.

Original Model/string+string. If present then the user has not edited the template (they may have changed fields etc). The original Model's name is given by the string. A second string stores the version of the Model; the datestamp when the Model was saved. Later on when doing software updates, we can replace the Template and other properties of the Command with the more recent Template from the Model of the same name. When the user changes the Template of a Command, this entry is deleted.

VisibleModes/string. A single character that specifies when the related Command (subsidiary or not) or Field is visible to the user in the FetchIt user interface. ' ' (space) hides it, 'A' makes it show only in Advanced mode, 'B' makes it appear in Basic mode, 'C' shows up in both modes. If not specified, it shows up in all modes.

Viewing the Settings

It's somewhat awkward to poke and prod with scripting messsages to see what's going on. You can get a quick view of what you have by looking at the settings file. It's a flattened BMessage, which you can examine with the right tools.

Message

The easiest way is to use Haiku's "message" command:

Sat Feb 17 16:52:26 177 /boot/home>message /boot/home/config/settings/AGMSScriptOCron/AGMSScriptOCron\ Settings
BMessage('ASOC') {
        UserSpecifiedTemporaryDirectory = string("", 1 bytes)
        CommandList = BMessage('ARCV') {
                class = string("Command", 8 bytes)
                _name = string("My Alert", 9 bytes)
                UserData = BMessage('DATA') {
                        key = string("Description", 12 bytes)
                        result = string("Pops up an alert box with your message.  Set the Trigger string to be the name of some other command and this one will run when that one has finished successfully.", 164 bytes)
                }
                Disabled = bool(false)
                LogFileEnabled = bool(true)
                Subsidiary = bool(false)
                Template = string("alert --info "Message Text"", 28 bytes)
                Trigger = string("* * * * 0,15,30,45", 19 bytes)
                FieldList = BMessage('ARCV') {
                        class = string("Field", 6 bytes)
                        _name = string("Message Text", 13 bytes)
                        Value = string("Time to put on the tea kettle.", 31 bytes)
                }
        }
}

ViewIt

If you use ViewIt, with the "Dig Files" checkbox turned on, you can drag and drop the "AGMSScriptOCron Settings" file into it (and then select all with the keyboard and drag and drop the text into a text editor). The results look like this:

File: /boot/home/config/settings/AGMSScriptOCron/AGMSScriptOCron Settings
 BMessage file:
 > What='ASOC'
 > B_MESSAGE_TYPE        "CommandList"
 >  | What=B_ARCHIVED_OBJECT
 >  | B_STRING_TYPE         "class"                  "Command"
 >  | B_STRING_TYPE         "_name"                  "My Alert"
 >  | B_MESSAGE_TYPE        "UserData" #1
 >  |  | What=B_SIMPLE_DATA
 >  |  | B_STRING_TYPE         "key"                    "Description"
 >  |  | B_STRING_TYPE         "result"                 "Pops up an alert box with your message.  Set the Trigger string to be the name of some other command and this one will run when that one has finished successfully."
 >  | B_MESSAGE_TYPE        "UserData" #2
 >  |  | What=B_SIMPLE_DATA
 >  |  | B_STRING_TYPE         "key"                    "Original Model"
 >  |  | B_STRING_TYPE         "result"                 "Alert"
 >  | B_BOOL_TYPE           "LogFileEnabled"         1
 >  | B_STRING_TYPE         "Template"               "alert --info "Message Text""
 >  | B_STRING_TYPE         "Trigger"                "* * * * 0,15,30,45"
 >  | B_MESSAGE_TYPE        "FieldList"
 >  |  | What=B_ARCHIVED_OBJECT
 >  |  | B_STRING_TYPE         "class"                  "Field"
 >  |  | B_STRING_TYPE         "_name"                  "Message Text"
 >  |  | B_STRING_TYPE         "Value"                  "Time to put on the tea kettle."

QuickRes

QuickRes can also show you flattened BMessages. Start a new resource file in QuickRes, drag and drop the "AGMSScriptOCron Settings" file into it, then edit the resulting resource entry line changing its type from "RAWT" to "MSGG" (you need to click a few times on the RAWT to make it editable). Use the menu to Save As, and in the file requester "File Format" menu, pick "Source (.rdef)" and save it somewhere. Open the resulting file in a text editor to see something like this:

/*
** /boot/var/tmp/x
**
** Automatically generated by BResourceParser on
** Wednesday, December 30, 2015 at 16:21:50.
**
*/

#include "x.h"

resource(1, "AGMSScriptOCron Settings") message('ASOC') {
	"CommandList" = archive(, 'ARCV') Command {
		"_name" = "My Alert",
		"UserData" = message('DATA') {
			"key" = "Description",
			"result" = #'CSTR' array {
					"Pops up an alert box with your message.  Set the Trigger string "
					"to be the name of some other command and this one will run when "
					"that one has finished successfully."
				}
		},
		"UserData" = message('DATA') {
			"key" = "Original Model",
			"result" = "Alert"
		},
		"LogFileEnabled" = true,
		"Template" = "alert --info \"Message Text\"",
		"Trigger" = "* * * * 0,15,30,45",
		"FieldList" = archive(, 'ARCV') Field {
			"_name" = "Message Text",
			"Value" = "Time to put on the tea kettle."
		}
	}
};

Other Possibilities

You could hack up Haiku's "rc" or the similar "beres" and "deres" tools included in the QuickRes package to convert between .rdef text files and flattened BMessages rather than resource files.

Contact Info

If you have questions, comments, suggestions send them to me. I'm AGMS on BeShare, agmsmith@ncf.ca by e-mail. I've got a web site too where you can find more of my BeOS / Haiku software. If it has moved due to the passage of time, try searching for "Alexander G. M. Smith". My PGP key is known as 2A2CFFBB, with key fingerprint = FB3D 3722 F680 8D23 3F2F 5B5A 653F D057 2A2C FFBB.

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

Hope you find version 2 of AGMSScriptOCron useful!
-----BEGIN PGP SIGNATURE-----

iQIzBAEBCAAdFiEE+z03IvaAjSM/L1taZT/QVyos/7sFAlqJnz0ACgkQZT/QVyos
/7sLzQ/+LhLg4pYmkJ9zQ9OcCzq9OfDrXuVITyboyldp/CaCJVwuCtQKY4F+hC8T
pQshqg6ZjDkD+It34xflzFJgzyq364roGXYX/RGSnSV0Jb4SW/shYbPXWFLjAvQo
LE3ez302XUaBs3y1L2sL/7ikNjL0GJ8TVUdh+OF8rhD/RmMQbQJcMRBbRM10vWNX
TpKpdglKL4lPPeOtZPJgNkpzcRWCnRbD3tkkkiOvo3FPKbQTjFHyVQrk+dznk2pM
Zzo8TpHz0pxr3LXSWGwihbQTw8UGeaMApn3lo2jxg2DZ3OeH3a0QeZnEpQQba+Bv
13H4TEpcHQM98VmQVFOzqe7++v5rK3wLKbILsB4BmNS/ZH8+VHY/ulMm96Q6Ncc/
39FvkX1BYdW+s6335lIJGUe+G8ki6bF9cpLF0vwq5KNh/ymTjZH2MdAxUyVu170v
RkaBo6Pf+t0MbI1eyqIRvZH+UFzJkacynhA+XA2ZhuqExqBonMsNHQb1tO7pE4Ep
mTAZ8WLjrSzQ5uw4UhM78/XhPuHioQ5vZzJ+KCF6Bdx0a+W0jTB3OyLVq/FvNYcC
Ot8I9yA2sviqKrh69rZcVf+vkE5L1vaxovGty5zhqr2bnSvapQsAn6+MwcxJYUJr
266HomjU6xUwhDIoNwLLb3fshPGjKkWaw+dXRaDASImBnQu/Wns=
=Zi7Q
-----END PGP SIGNATURE-----

Copyright © 2018 by Alexander G. M. Smith.