AGMSScriptOCron is a BeOS / Haiku OS program which runs templated command lines on a schedule.
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.
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>
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.
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.
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.
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.
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.
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.
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) } } }
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 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." } } };
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.
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.