Hi Mark,
Yes, this can be done with the FileWatcher and FileFind objects in PixInsight. I have adapted one of our test scripts with comments to describe how this can be done safely and efficiently:
/*
* FileWatcher / FileFind demo script.
*/
#include <pjsr/TextAlign.jsh>
#include <pjsr/Sizer.jsh>
function FileWatcherDemoDialog( dirPath )
{
this.__base__ = Dialog;
this.__base__();
//
this.watchedDirectory = dirPath;
this.busy = false; // flag to prevent reentrant FileWatcher events
this.dirty = true; // flag to signal a pending FileWatcher update event
//
/*
* We use a periodic timer and a 'dirty' flag to ensure that our dialog is
* always responsive to accumulated FileWatcher events. This is necessary
* because FileWatcher events are asynchronous. Multiple FileWatcher events
* may happen while we are updating GUI elements (which are relatively
* expensive operations). See also the comments in updateDirectory().
*/
this.fileWatcher = new FileWatcher( [dirPath] );
this.fileWatcher.dialog = this; // necessary because FileWatcher is not a Control object
this.fileWatcher.onDirectoryChanged = function( /*dirPath*/ )
{
this.dialog.dirty = true;
};
this.updateTimer = new Timer;
this.updateTimer.interval = 0.5; // timing interval in seconds
this.updateTimer.periodic = true; // periodic or single shot timer
this.updateTimer.dialog = this; // necessary because Timer is not a Control object
this.updateTimer.onTimeout = function()
{
if ( this.dialog.dirty )
this.dialog.updateDirectory();
};
//
this.directory_Tree = new TreeBox( this );
this.directory_Tree.alternateRowColor = true;
this.directory_Tree.headerVisible = false;
this.directory_Tree.numberOfColumns = 3; // name, size, lastModified
this.directory_Tree.rootDecoration = true;
this.directory_Tree.uniformRowHeight = true;
this.directory_Tree.minWidth = 600;
this.directory_Tree.minHeight = 400;
//
/*
* Returns a readable textual representation of a file size in bytes with
* automatic units conversion.
*/
this.fileSizeAsString = function( bytes, precision )
{
const kb = 1024;
const mb = 1024 * kb;
const gb = 1024 * mb;
const tb = 1024 * gb;
if ( bytes >= tb )
return format( "%.*g TiB", precision, bytes/tb );
if ( bytes >= gb )
return format( "%.*g GiB", precision, bytes/gb );
if ( bytes >= mb )
return format( "%.*g MiB", precision, bytes/mb );
if ( bytes >= kb )
return format( "%.*g KiB", precision, bytes/kb );
return format( "%lld B", bytes );
};
/*
* Recursive routine to explore a directory tree. The parent TreeBox is
* populated with the contents of the specified dirPath directory.
*/
this.searchFiles = function( parent, dirPath )
{
var files = [];
var directories = [];
var find = new FileFind;
if ( find.begin( dirPath + "/*" ) )
do
{
if ( find.name != "." && find.name != ".." )
{
var item = { name:find.name, size:find.size, lastModified:find.lastModified };
if ( find.isDirectory )
directories.push( item );
else
files.push( item );
}
}
while ( find.next() );
var dirNode = new TreeBoxNode( parent );
dirNode.setText( 0, File.extractNameAndSuffix( dirPath ) );
dirNode.expanded = true;
for ( var i = 0; i < directories.length; ++i )
this.searchFiles( dirNode, dirPath + '/' + directories[i].name );
for ( var i = 0; i < files.length; ++i )
{
var fileNode = new TreeBoxNode( dirNode );
fileNode.setText( 0, File.extractNameAndSuffix( files[i].name ) );
fileNode.setText( 1, this.fileSizeAsString( files[i].size, 3 ) );
fileNode.setAlignment( 1, TextAlign_Right );
fileNode.setText( 2, files[i].lastModified.toLocaleDateString() + ' ' + files[i].lastModified.toLocaleTimeString() );
}
};
/*
* Regenerate the directory tree.
*/
this.updateDirectory = function()
{
/*
* ### NB: We prevent reentrant events with the 'busy' property.
* This is necessary because FileWatcher events are asynchronous, so they
* may happen while we are regenerating our TreeBox.
*/
if ( !this.busy )
{
this.busy = true;
this.dirty = false;
this.directory_Tree.clear();
this.searchFiles( this.directory_Tree, this.watchedDirectory );
this.directory_Tree.adjustColumnWidthToContents( 0 );
this.directory_Tree.adjustColumnWidthToContents( 1 );
this.busy = false;
}
};
//
this.ok_Button = new PushButton( this );
this.ok_Button.text = "OK";
this.ok_Button.onClick = function()
{
this.dialog.ok();
};
this.buttons_Sizer = new HorizontalSizer;
this.buttons_Sizer.addStretch();
this.buttons_Sizer.add( this.ok_Button );
this.buttons_Sizer.addStretch();
//
this.sizer = new VerticalSizer;
this.sizer.margin = 8;
this.sizer.spacing = 8;
this.sizer.add( this.directory_Tree );
this.sizer.add( this.buttons_Sizer );
this.windowTitle = "FileWatcher Demo";
this.adjustToContents();
//
/*
* We start watching the filesystem as soon as our dialog is shown.
*/
this.onShow = function()
{
this.updateTimer.start();
};
/*
* The safest way to stop FileWatcher and Timer events is to cancel them
* when our dialog is hidden.
*/
this.onHide = function()
{
this.updateTimer.stop();
this.fileWatcher.clear();
};
//
this.updateDirectory();
}
FileWatcherDemoDialog.prototype = new Dialog;
(new FileWatcherDemoDialog( File.systemTempDirectory )).execute();
The scripts watches the system temporary storage directory by default (/tmp on UNIX/Linux, C:\Users\<current-user>\<something>\Temp on Windows). You can change File.systemTempDirectory to other directory of your choice if you wish. Just run the script, place the script's dialog on a convenient location on the screen, and copy/delete some files on the watched directory with your system's file explorer utility.
FileWatcher events are a bit difficult to handle because they are asynchronous. Please read the code comments to learn how to work around this problem efficiently with a periodic timer and 'dirty' flags. Let me know if this is what you were looking for.
Edit: Fixed a minor error in the code example (line 133: clear the dirty flag *before* regenerating the tree box).