v1.2.0.1-beta1
Tail Example

Example of checking a file at regular intervals and reading the last several lines of it when it changes. A.K.A "tailing" a file/log.

See the published documentation for a properly formatted version of this README.
Note
Assets for this example, including the code and sample button shown below, can be found in the project's repository at
https://github.com/mpaperno/DSEP4TP/tree/main/resources/examples/Tail/
1// This is a script to "tail" a file -- watching it for changes and reading the last N lines from the end.
2// Typically used to monitor a log file, or some other text document, for changes.
3// This version is fairly simplistic in that it only checks the monitored file for changes on a schedule,
4// vs. some sort of system-level monitoring a file/directory for modifications.
5// It demonstrates the use of timed recurring events, File object static functions,
6// as well as creating and updating Touch Portal states from inside a script.
7// It is meant to be loaded as a module.
8
9// We save data in the global scope, for simplicity.
10// This module is designed to run in a Private engine instance (so the global scope is not shared).
11var fileName = fileName || "";
12var maxLines = maxLines || 0;
13var statusStateId = statusStateId || "";
14var fLastMod = fLastMod || new Date(0);
15var timerId = timerId === undefined ? -1 : timerId;
16
17// This is the method to invoke from the TP action to start tailing a file.
18// The arguments are the file path/name, the number of lines to read from the end,
19// and how often to check for changes (in milliseconds);
20export function tail(file, lines = 6, interval = 1000)
21{
22 // make sure to stop any existing interval timer
23 cancel();
24 // validate the function arguments.
25 // DSE.INSTANCE_NAME is a constant representing the current instance which created and executed this module.
26 if (!DSE.INSTANCE_NAME || !file || lines < 0 || interval < 1) {
27 throw new TypeError(`Invalid arguments for 'tail(${file}, ${lines}, ${interval})' with instance name ${DSE.INSTANCE_NAME}.`);
28 return;
29 }
30 // Now validate that the file exists using the File utility.
31 if (!File.exists(file)) {
32 throw new URIError("File not found: " + file);
33 return;
34 }
35 // OK we're good to go... save the arguments for later use and set up the interval timer.
36 fileName = file;
37 maxLines = lines;
38 fLastMod = new Date();
39 // This will start a timer which calls our `checkFile()` function (below) every `interval` milliseconds.
40 timerId = setInterval(checkFile, interval);
41
42 // We want to notify the user that the tail is running/active. One way we can do this is to create a new TP State
43 // which will reflect the status of this operation. We use the current instance's state ID and name as the basis for the new
44 // state ID and description, and we save it for future use (in `cancel()` or for next time this function is invoked);
45 const newStatusStateId = DSE.instanceStateId() + "_active";
46 // Only do this once if we don't already have a statusStateId from previous runs, or if has changed.
47 if (statusStateId !== newStatusStateId) {
48 statusStateId = newStatusStateId;
49 // The arguments are: the State ID (must be unique), parent category name (within this plugin's categories, or a new one),
50 // a description/name for the new state (shown in TP UI), and finally the default value (0 for inactive, 1 for active).
51 TP.stateCreate(statusStateId, "Dynamic Values", DSE.INSTANCE_NAME + " Active", "0");
52 }
53 // Now send the state update indicating the tail operation is active.
54 // This state change can be used to trigger an event in TP, eg. visually indicate the tail is active.
55 TP.stateUpdateById(statusStateId, "1");
56
57 // Log what we did.
58 console.info(`Tailing ${lines} lines from ${file} every ${interval}ms for state ${DSE.INSTANCE_NAME} with timerId ${timerId}`);
59
60 // Run the initial file read operation now, unless it's going to be read very soon by the scheduled timer anyway.
61 if (interval > 100)
62 checkFile();
63}
64
65// This function is called periodically by the setInterval() timer we started in tail().
66// This checks the file modification time for changes, reads in any new data and updates
67// the corresponding State in TP.
68function checkFile()
69{
70 // Catch any errors so we can stop the timer and exit "gracefully" if the file couldn't be read.
71 try {
72 // Get the watched file modification time, then compare it to the last saved time.
73 var fmod = File.mtime(fileName);
74 //console.log(fmod, fLastMod, fmod.getTime(), fLastMod.getTime(), fmod.getTime() === fLastMod.getTime());
75 if (fmod.getTime() === fLastMod.getTime())
76 return;
77
78 // File has changed, save the new modification date.
79 fLastMod = fmod;
80
81 // Read in `maxLines` of text from the file, starting at the end (-1) and trimming any trailing newlines.
82 var lines = File.readLines(fileName, maxLines, -1, true);
83 // Rend the results to TP as a state. The TP.stateUpdate() version with one argument will automatically
84 // update the state for the current instance which created and executed this module
85 TP.stateUpdate(lines);
86 //console.log('\n' + lines);
87 }
88 catch (e) {
89 cancel();
90 console.error(e);
91 throw e;
92 }
93}
94
95// Call this function to cancel the tailing operation.
96export function cancel()
97{
98 if (timerId > -1) {
99 // Stop the timer
100 clearInterval(timerId);
101 // Update the state we created earlier to indicate the tail operation has stopped.
102 TP.stateUpdateById(statusStateId, "0");
103 // Log what we did
104 console.info(`Stopped tailing ${fileName} for state ${DSE.INSTANCE_NAME} with timerId ${timerId}`);
105 // We could also now delete the status state we created... this is optional.
106 // But is would be best to schedule that to run a little later so TP can first properly register the state change we just sent.
107 //setTimeout(function (){ TP.stateRemove(statusStateId) }, 1000);
108 }
109 timerId = -1;
110}
The DSE object contains constants and functions related to the plugin environment....
String INSTANCE_NAME
The the current script's Instance Name, as specified in the corresponding Touch Portal action which i...
Definition: DSE.h:161
String instanceStateId()
Gets Touch Portal State ID of the current script's instance. This is what Touch Portal actually uses ...
Definition: DSE.h:337
DynamicScript instance(String name)
Returns the script instance with given name, if any, otherwise returns null.
Date prototype extension methods
The File class provides access to... files. Shocking.
Definition: File.h:58
Date mtime(String &file)
Returns the date and local time when the file was last modified. If the file is not available,...
Definition: File.h:242
bool exists(String &file)
Returns true if the file exists; otherwise returns false.
Definition: File.h:192
String readLines(String &file, int maxLines, int fromLine=0, bool trimTrailingNewlines=true)
Reads up to maxLines lines from file starting at fromLine (default = 0, start of file) and returns th...
Definition: File.h:119
The global TP namespace is used as a container for all Touch Portal API methods, which are invoked wi...
Definition: touchportal.dox:7
void stateCreate(String id, String parentGroup, String desc="", String defaultValue="")
Create a new Touch Portal dynamic State (if it doesn't already exist).
void stateUpdate(String value)
Send a State update to Touch Portal with given value for the current script instance.
void stateUpdateById(String id, String value)
Send a State update to Touch Portal for any arbitrary state/value matching the id,...

Example usage on a button