Community Forums Archive

Go Back

Subject:TJ's tricks: Watch Folders
Posted by: _TJ
Date:6/15/2005 4:13:16 PM

The .NET runtime provides a facility called a FileSystemWatcher that can be used to install a callback that gets called whenever files are added / removed / changed in a particular folder.

This can be a very powerful technique for doing batch processing: Just have a script watch a folder and apply a previously defined set of operations on files that show up there.

But there's a catch. It turns out that the FileSystemWatcher class will call back on some random OS Kernel thread unless you supply a Form handle as a synchronization object. Unfortunately, scripts don't have a Form handle available unless the script creates one. So you end up writing quite a bit of code to get a Watcher script working.

First of all, here's the class that I use to wrap and manage a FileSystemWatcher object. This code is in C#


public class Watcher
{
protected System.IO.FileSystemWatcher fsWatch = null;
protected string szDir;
protected string szFilter;
protected long cEvents = 0;
protected WatcherForm form;

public Watcher(string szDirIn, string szFilterIn, WatcherForm formSync)
{
szDir = szDirIn;
szFilter = szFilterIn;
cEvents = 0;
form = formSync;

fsWatch = new System.IO.FileSystemWatcher(szDir, szFilter);

// NOTE: if you don't do this, then your callbacks aren't guranteed to get called on
// the Sound Forge main thread, and if you aren't on the forge main thread, then you can't use
// any of the Sound Forge objects (i.e. IScriptableApp or ISfFileHost will fail all calls.)
//
fsWatch.SynchronizingObject = formSync;
// !!!!

fsWatch.Changed += new System.IO.FileSystemEventHandler(fs_Changed);
fsWatch.Created += new System.IO.FileSystemEventHandler(fs_Changed);
fsWatch.Deleted += new System.IO.FileSystemEventHandler(fs_Changed);
fsWatch.Renamed += new System.IO.RenamedEventHandler(fs_Renamed);
}

public bool Enable { set { fsWatch.EnableRaisingEvents = value; } }

public void Dispose()
{
fsWatch.EnableRaisingEvents = false;
fsWatch.Dispose();
fsWatch = null;
}

private void fs_Changed(object sender, System.IO.FileSystemEventArgs e)
{
string msg = "File " + e.FullPath + " " + e.ChangeType;
++cEvents;
form.WatchChange(msg);
}

private void fs_Renamed(object sender, System.IO.RenamedEventArgs e)
{
string msg = "File " + e.OldFullPath + " " + e.ChangeType + " " + e.FullPath;
++cEvents;
form.WatchChange(msg);
}

public string[] ToStrings()
{
string[] asz = new string[4];
asz[0] = cEvents.ToString();
asz[1] = fsWatch.EnableRaisingEvents ? "Watching" : "Blind";
asz[2] = szFilter;
asz[3] = szDir;
return asz;
}

public override string ToString()
{
return String.Format("{0}\t{1}\t{2}\t{3}", szDir, szFilter, fsWatch.EnableRaisingEvents ? "Watching" : "Blind", cEvents);
}
}


Noice that the constructor of the Watcher class takes 3 arguments.public Watcher(string szDirIn, string szFilterIn, WatcherForm formSync) A folder name, a filter (*.wav for instance, or *.* to watch everything), and an instance of WatcherForm class. I use a WatcherForm rather than a Form class here, because I want to be able to count on having a WatchChange() method on the Form.

So at a minimum, to make use of the watcher class, you need to have a WatcherForm class that derives from Form and adds a WatchChange() method.


public class WatcherForm : System.Windows.Forms.Form
{
/// ...... lots of stuff removed ........

public void WatchChange(string msg)
{
app.OutputText(msg);
UpdateWatchList();
}

private void UpdateWatchList()
{
// more stuff removed here.
}

}


So that's the trick. In the next post, I'll include the full code for my WatchFolders.cs demonstration script. It doesn't do any processing, but it does have the full form plus methods for adding and removing folders to the watched collection.

tj




Message last edited on6/15/2005 4:14:53 PM by_TJ.
Subject:RE: TJ's tricks: Watch Folders
Reply by: _TJ
Date:6/15/2005 4:16:48 PM

Here's the full script.

--------------------- snip here and save as WatchFolders.cs -----------------

using System;
using System.IO;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;
using System.Collections;
using SoundForge;

public class EntryPoint {

public void Begin(IScriptableApp app) {

WatcherForm form = new WatcherForm();
form.ShowDialog(app.Win32Window);
}

public class WatcherForm : System.Windows.Forms.Form
{
protected ArrayList watchList = new ArrayList();
protected ListView lvWatch = null;

public WatcherForm()
{
Size sForm = new Size(520, 250);

this.Text = "Folder Watch Test";
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.StartPosition = FormStartPosition.CenterScreen;
this.ClientSize = sForm;

Point pt = new Point(10,10);
Size sOff = new Size(10,10);

Label lbl = new Label();
lbl.Text = "Watching the following folders:";
lbl.Width = sForm.Width - pt.X - sOff.Width;
lbl.Height = 14;
lbl.Location = pt;
this.Controls.Add(lbl);
pt.Y += lbl.Height;

ListView lv = new ListView();
lv.FullRowSelect = true;
lv.Size = sForm - new Size(20, 70);
lv.Location = pt;
lv.View = View.Details;
lv.Columns.Add("Activity",-2, HorizontalAlignment.Left);
lv.Columns.Add("Status",-2, HorizontalAlignment.Left);
lv.Columns.Add("Filter",-2, HorizontalAlignment.Left);
lv.Columns.Add("Folder",-2, HorizontalAlignment.Left);
this.Controls.Add(lv);

lvWatch = lv;
lv.Items.Clear();

// we position the buttons relative to the bottom and left of the form.
//
pt = (Point)this.ClientSize;
pt -= sOff;

Button btn = new Button();
pt -= btn.Size;
btn.Text = "Quit";
btn.Location = pt;
btn.Click += new EventHandler(OnQuit_Click);
this.Controls.Add(btn);
this.CancelButton = btn;
pt.X -= (btn.Width + 10);

btn = new Button();
btn.Text = "Add...";
btn.Location = pt;
btn.Click += new EventHandler(OnAdd_Click);
this.Controls.Add(btn);
this.AcceptButton = btn;
pt.X -= (btn.Width + 10);

btn = new Button();
btn.Text = "Remove";
btn.Location = pt;
btn.Click += new EventHandler(OnRemove_Click);
this.Controls.Add(btn);
pt.X -= (btn.Width + 10);

btn = new Button();
btn.Text = "Enable";
btn.Location = pt;
btn.Click += new EventHandler(OnEnable_Click);
this.Controls.Add(btn);
pt.X -= (btn.Width + 10);

btn = new Button();
btn.Text = "Disable";
btn.Location = pt;
btn.Click += new EventHandler(OnDisable_Click);
this.Controls.Add(btn);
pt.X -= (btn.Width + 10);
}

private void UpdateWatchList()
{
ListView lv = lvWatch;
lv.Items.Clear();
foreach (Watcher watch in watchList)
{
string [] asz = watch.ToStrings();
ListViewItem lvi = new ListViewItem(asz[0]);
lvi.Tag = watch;
for (int ii = 1; ii < asz.Length; ++ii)
lvi.SubItems.Add(asz[ii]);
lv.Items.Add(lvi);
}
}

public void AddWatch(string szDir, string szFilter)
{
watchList.Add(new Watcher(szDir, szFilter, this));
UpdateWatchList();
}

public void WatchChange(string msg)
{
DPF(msg);
UpdateWatchList();
}

private void OnEnable_Click(object sender, System.EventArgs e)
{
foreach (ListViewItem lvi in lvWatch.SelectedItems)
{
Watcher wi = (Watcher)lvi.Tag;
wi.Enable = true;
}
UpdateWatchList();
}

private void OnDisable_Click(object sender, System.EventArgs e)
{
foreach (ListViewItem lvi in lvWatch.SelectedItems)
{
Watcher wi = (Watcher)lvi.Tag;
wi.Enable = false;
}
UpdateWatchList();
}

private void OnRemove_Click(object sender, System.EventArgs e)
{
foreach (ListViewItem lvi in lvWatch.SelectedItems)
{
Watcher wi = (Watcher)lvi.Tag;
watchList.Remove(wi);
wi.Dispose();
}
UpdateWatchList();
}

private void OnAdd_Click(object sender, System.EventArgs e)
{
string szFilter = "*.*";
string szDir = @"e:\";
szDir = SfHelpers.ChooseDirectory("Choose a Folder to watch.", szDir);
if (szDir != null)
{
this.AddWatch(szDir, szFilter);
}
}

// generic Cancel button click (sets dialog result and dismisses the form)
private void OnQuit_Click(object sender, System.EventArgs e)
{
this.DialogResult = DialogResult.Cancel;
this.Close();
}

private void On_Closing(object sender, CancelEventArgs e)
{
//if (we don't want to close)
// e.Cancel = true;

lvWatch.Items.Clear();

for (int ii = watchList.Count -1; ii >= 0; --ii)
{
Watcher watch = (Watcher)watchList[ii];
watch.Dispose();
}
}
}

public class Watcher
{
protected System.IO.FileSystemWatcher fsWatch = null;
protected string szDir;
protected string szFilter;
protected long cEvents = 0;
protected WatcherForm form;

public Watcher(string szDirIn, string szFilterIn, WatcherForm formSync)
{
szDir = szDirIn;
szFilter = szFilterIn;
cEvents = 0;
form = formSync;

fsWatch = new System.IO.FileSystemWatcher(szDir, szFilter);

// NOTE: if you don't do this, then your callbacks aren't guranteed to get called on
// the Sound Forge main thread, and if you aren't on the forge main thread, then you can't use
// any of the Sound Forge objects (i.e. IScriptableApp or ISfFileHost will fail all calls.)
//
fsWatch.SynchronizingObject = formSync;
// !!!!

fsWatch.Changed += new System.IO.FileSystemEventHandler(fs_Changed);
fsWatch.Created += new System.IO.FileSystemEventHandler(fs_Changed);
fsWatch.Deleted += new System.IO.FileSystemEventHandler(fs_Changed);
fsWatch.Renamed += new System.IO.RenamedEventHandler(fs_Renamed);
}

public bool Enable { set { fsWatch.EnableRaisingEvents = value; } }

public void Dispose()
{
fsWatch.EnableRaisingEvents = false;
fsWatch.Dispose();
fsWatch = null;
}

private void fs_Changed(object sender, System.IO.FileSystemEventArgs e)
{
string msg = "File " + e.FullPath + " " + e.ChangeType;
++cEvents;
form.WatchChange(msg);
}

private void fs_Renamed(object sender, System.IO.RenamedEventArgs e)
{
string msg = "File " + e.OldFullPath + " " + e.ChangeType + " " + e.FullPath;
++cEvents;
form.WatchChange(msg);
}

public string[] ToStrings()
{
string[] asz = new string[4];
asz[0] = cEvents.ToString();
asz[1] = fsWatch.EnableRaisingEvents ? "Watching" : "Blind";
asz[2] = szFilter;
asz[3] = szDir;
return asz;
}

public override string ToString()
{
return String.Format("{0}\t{1}\t{2}\t{3}", szDir, szFilter, fsWatch.EnableRaisingEvents ? "Watching" : "Blind", cEvents);
}
}


public void FromSoundForge(IScriptableApp app) {
ForgeApp = app; //execution begins here
app.SetStatusText(String.Format("Script '{0}' is running.", Script.Name));
Begin(app);
app.SetStatusText(String.Format("Script '{0}' is done.", Script.Name));
}
public static IScriptableApp ForgeApp = null;
public static void DPF(string sz) { ForgeApp.OutputText(sz); }
public static void DPF(string fmt, object o) { ForgeApp.OutputText(String.Format(fmt,o)); }
public static void DPF(string fmt, object o, object o2) { ForgeApp.OutputText(String.Format(fmt,o,o2)); }
public static void DPF(string fmt, object o, object o2, object o3) { ForgeApp.OutputText(String.Format(fmt,o,o2,o3)); }
public static string GETARG(string k, string d) { string val = Script.Args.ValueOf(k); if (val == null || val.Length == 0) val = d; return val; }
public static int GETARG(string k, int d) { string s = Script.Args.ValueOf(k); if (s == null || s.Length == 0) return d; else return Script.Args.AsInt(k); }
public static bool GETARG(string k, bool d) { string s = Script.Args.ValueOf(k); if (s == null || s.Length == 0) return d; else return Script.Args.AsBool(k); }
} //EntryPoint


------------------- end script ---------------------------------

Subject:RE: TJ's tricks: Watch Folders
Reply by: ghova
Date:6/15/2005 4:25:59 PM

Absolutely amazing. This had me stumped for a few days, which was why I went back to CLI-triggering.

I appreciate it, TJ! I'll implement it as soon as I can tomorrow morning, and tell you how it goes.

~Gil

Subject:RE: TJ's tricks: Watch Folders
Reply by: clmn
Date:12/15/2005 5:20:51 PM

Hey,

Im working with TJ's code for watch folders and I got it working. But now I need it to do some executing on the file that is dropped into the folder that's being watched when its added to the watched folder. I need it to normalize the file when it is detected in the change event in watch folders. I cant figure out were to put the code for this. I tried putting the code to normalize inside the change event but when I do that it doenst execute that code or any of the code after the normalization code. The only time I could get the normalize code to work is when it was inside of the
public void Begin(IScriptableApp app) Event.

Hopefully someone can help me get this figured out.

Thanks

Message last edited on12/15/2005 5:22:26 PM byclmn.
Subject:RE: TJ's tricks: Watch Folders
Reply by: _TJ
Date:12/15/2005 7:30:33 PM

what sort of error are you gettting? Could you post your code?
tj

Subject:RE: TJ's tricks: Watch Folders
Reply by: clmn
Date:12/16/2005 6:07:35 AM

I do not get any errors the code just doesnt execute the file open and normalize code and the lines after that code in the change event. Heres what I have.


using System;
using System.IO;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;
using System.Collections;
using SoundForge;
using System.Diagnostics;
using System.Xml;
public class EntryPoint {

public void Begin(IScriptableApp app) {


WatcherForm form = new WatcherForm();
form.ShowDialog();
}

public class WatcherForm : System.Windows.Forms.Form
{
protected ArrayList watchList = new ArrayList();
protected ListView lvWatch = null;

public WatcherForm()
{
Size sForm = new Size(520, 250);

this.Text = "Folder Watch Test";
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.StartPosition = FormStartPosition.CenterScreen;
this.ClientSize = sForm;

Point pt = new Point(10,10);
Size sOff = new Size(10,10);

Label lbl = new Label();
lbl.Text = "Watching the following folders:";
lbl.Width = sForm.Width - pt.X - sOff.Width;
lbl.Height = 14;
lbl.Location = pt;
this.Controls.Add(lbl);
pt.Y += lbl.Height;

ListView lv = new ListView();
lv.FullRowSelect = true;
lv.Size = sForm - new Size(20, 70);
lv.Location = pt;
lv.View = View.Details;
lv.Columns.Add("Activity",-2, HorizontalAlignment.Left);
lv.Columns.Add("Status",-2, HorizontalAlignment.Left);
lv.Columns.Add("Filter",-2, HorizontalAlignment.Left);
lv.Columns.Add("Folder",-2, HorizontalAlignment.Left);

this.Controls.Add(lv);

lvWatch = lv;
lv.Items.Clear();

// we position the buttons relative to the bottom and left of the form.
//
pt = (Point)this.ClientSize;
pt -= sOff;

Button btn = new Button();
pt -= btn.Size;
btn.Text = "Quit";
btn.Location = pt;
btn.Click += new EventHandler(OnQuit_Click);
this.Controls.Add(btn);
this.CancelButton = btn;
pt.X -= (btn.Width + 10);

btn = new Button();
btn.Text = "Add...";
btn.Location = pt;
btn.Click += new EventHandler(OnAdd_Click);
this.Controls.Add(btn);
this.AcceptButton = btn;
pt.X -= (btn.Width + 10);

btn = new Button();
btn.Text = "Remove";
btn.Location = pt;
btn.Click += new EventHandler(OnRemove_Click);
this.Controls.Add(btn);
pt.X -= (btn.Width + 10);

btn = new Button();
btn.Text = "Enable";
btn.Location = pt;
btn.Click += new EventHandler(OnEnable_Click);
this.Controls.Add(btn);
pt.X -= (btn.Width + 10);

btn = new Button();
btn.Text = "Disable";
btn.Location = pt;
btn.Click += new EventHandler(OnDisable_Click);
this.Controls.Add(btn);
pt.X -= (btn.Width + 10);
this.Load += new EventHandler(WatcherForm_Load);




}

private void WatcherForm_Load(object sender, EventArgs e)
{
string szWatchDir = "";
string szCopyDir = "";
string szProcessDir = "";


XmlTextReader reader = new XmlTextReader(@"C:\SoundForgePaths.xml");

while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
if (reader.LocalName.Equals("WatchFolder"))
{
szWatchDir = reader.ReadString();
}

if (reader.LocalName.Equals("Original_SubFolder"))
{

szCopyDir = reader.ReadString();
}
if (reader.LocalName.Equals("Processed_SubFolder"))
{

szProcessDir = reader.ReadString();
}
}
}





string szFilter = "*.*";

if (szWatchDir != null)
{
this.AddWatch(szWatchDir, szFilter);
}


foreach (ListViewItem lvi in lvWatch.Items)
{
Watcher wi = (Watcher)lvi.Tag;
wi.Enable = true;
}
UpdateWatchList();
this.Close();


}

private void UpdateWatchList()
{
ListView lv = lvWatch;
lv.Items.Clear();
foreach (Watcher watch in watchList)
{
string [] asz = watch.ToStrings();
ListViewItem lvi = new ListViewItem(asz[0]);
lvi.Tag = watch;
for (int ii = 1; ii < asz.Length; ++ii)
lvi.SubItems.Add(asz[ii]);
lv.Items.Add(lvi);
}
}

public void AddWatch(string szWatchDir, string szFilter)
{
watchList.Add(new Watcher(szWatchDir, szFilter, this));
UpdateWatchList();
}

public void WatchChange(string msg)
{
DPF(msg);
UpdateWatchList();
}

private void OnEnable_Click(object sender, System.EventArgs e)
{
foreach (ListViewItem lvi in lvWatch.SelectedItems)
{
Watcher wi = (Watcher)lvi.Tag;
wi.Enable = true;
}
UpdateWatchList();
}

private void OnDisable_Click(object sender, System.EventArgs e)
{
foreach (ListViewItem lvi in lvWatch.SelectedItems)
{
Watcher wi = (Watcher)lvi.Tag;
wi.Enable = false;
}
UpdateWatchList();
}

private void OnRemove_Click(object sender, System.EventArgs e)
{
foreach (ListViewItem lvi in lvWatch.SelectedItems)
{
Watcher wi = (Watcher)lvi.Tag;
watchList.Remove(wi);
wi.Dispose();
}
UpdateWatchList();
}

private void OnAdd_Click(object sender, System.EventArgs e)
{
string szFilter = ".wav";
string szWatchDir = @"e:\";
szWatchDir = SfHelpers.ChooseDirectory("Choose a Folder to watch.", szWatchDir);
if (szWatchDir != null)
{
this.AddWatch(szWatchDir, szFilter);
}
}

// generic Cancel button click (sets dialog result and dismisses the form)
private void OnQuit_Click(object sender, System.EventArgs e)
{
this.DialogResult = DialogResult.Cancel;
this.Close();
}

private void On_Closing(object sender, CancelEventArgs e)
{
//if (we don't want to close)
// e.Cancel = true;

lvWatch.Items.Clear();

for (int ii = watchList.Count -1; ii >= 0; --ii)
{
Watcher watch = (Watcher)watchList[ii];
watch.Dispose();
}
}
}

public class Watcher
{
protected System.IO.FileSystemWatcher fsWatch = null;
protected string szWatchDir;
protected string szCopyDir;
protected string szProcessDir;
protected string szFilter;
protected long cEvents = 0;
protected WatcherForm form;



public Watcher(string szDirIn, string szFilterIn, WatcherForm formSync)
{

XmlTextReader reader = new XmlTextReader(@"C:\SoundForgePaths.xml");

while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
if (reader.LocalName.Equals("Original_SubFolder"))
{

szCopyDir = reader.ReadString();
}
if (reader.LocalName.Equals("Processed_SubFolder"))
{

szProcessDir = reader.ReadString();
}
}
}



szWatchDir = szDirIn;
szFilter = szFilterIn;
cEvents = 0;
form = formSync;

fsWatch = new System.IO.FileSystemWatcher(szWatchDir, szFilter);

// NOTE: if you don't do this, then your callbacks aren't guranteed to get called on
// the Sound Forge main thread, and if you aren't on the forge main thread, then you can't use
// any of the Sound Forge objects (i.e. IScriptableApp or ISfFileHost will fail all calls.)
//
fsWatch.SynchronizingObject = formSync;
// !!!!

fsWatch.Changed += new System.IO.FileSystemEventHandler(fs_Changed);
fsWatch.Created += new System.IO.FileSystemEventHandler(fs_Changed);
fsWatch.Deleted += new System.IO.FileSystemEventHandler(fs_Changed);
fsWatch.Renamed += new System.IO.RenamedEventHandler(fs_Renamed);
}

public bool Enable { set { fsWatch.EnableRaisingEvents = value; } }

public void Dispose()
{
MessageBox.Show("Dispose");
fsWatch.EnableRaisingEvents = false;
fsWatch.Dispose();
fsWatch = null;
}

private void fs_Changed(object sender, System.IO.FileSystemEventArgs e)
{
string msg = "File " + e.FullPath + " " + e.ChangeType;



string[] files = System.IO.Directory.GetFiles(szWatchDir);

for (int i = 0; i < files.Length; i++)
{
//Original File is copied to Sub Folder
File.Copy(files.ToString() ,files.ToString().Replace(szWatchDir,szCopyDir));
//Execute effects here********

//The problem area to normalize file************************************
//This code does not execute at all as does the code after this block

Do("File.Open");
Do("Process.Normalize", false); //MODIFY HERE: use true to use the last preset rather than being asked for one.
Do("File.Save");

//end problem area******************************************************
MessageBox.Show("Executed");



//Changed file is moved to Processed Folder
File.Move(files.ToString(), files.ToString().Replace(szWatchDir,szProcessDir));
File.Delete(files.ToString());

++cEvents;
form.WatchChange(msg);

}
}








private void fs_Renamed(object sender, System.IO.RenamedEventArgs e)
{
MessageBox.Show("Renamed");
string msg = "File " + e.OldFullPath + " " + e.ChangeType + " " + e.FullPath;
++cEvents;
form.WatchChange(msg);
}

public string[] ToStrings()
{
string[] asz = new string[4];
asz[0] = cEvents.ToString();
asz[1] = fsWatch.EnableRaisingEvents ? "Watching" : "Blind";
asz[2] = szFilter;
asz[3] = szWatchDir;
return asz;
}

public override string ToString()
{
return String.Format("{0}\t{1}\t{2}\t{3}", szWatchDir, szFilter, fsWatch.EnableRaisingEvents ? "Watching" : "Blind", cEvents);
}
}
public static void Do(string cmd) { ForgeApp.DoMenuAndWait(cmd, false); }
public static void Do(string cmd, bool fUseLastPreset) { ForgeApp.DoMenuAndWait(cmd, fUseLastPreset); }

public void FromSoundForge(IScriptableApp app) {
ForgeApp = app; //execution begins here
app.SetStatusText(String.Format("Script '{0}' is running.", Script.Name));
Begin(app);
app.SetStatusText(String.Format("Script '{0}' is done.", Script.Name));
}



public static IScriptableApp ForgeApp = null;
public static void DPF(string sz)
{
ForgeApp.OutputText(sz);




}


public static void DPF(string fmt, object o) { ForgeApp.OutputText(String.Format(fmt,o)); }
public static void DPF(string fmt, object o, object o2) { ForgeApp.OutputText(String.Format(fmt,o,o2)); }
public static void DPF(string fmt, object o, object o2, object o3) { ForgeApp.OutputText(String.Format(fmt,o,o2,o3)); }
public static string GETARG(string k, string d) { string val = Script.Args.ValueOf(k); if (val == null || val.Length == 0) val = d; return val; }
public static int GETARG(string k, int d) { string s = Script.Args.ValueOf(k); if (s == null || s.Length == 0) return d; else return Script.Args.AsInt(k); }
public static bool GETARG(string k, bool d) { string s = Script.Args.ValueOf(k); if (s == null || s.Length == 0) return d; else return Script.Args.AsBool(k); }

}


//EntryPoint


Another Question:
How would I alter the code below so that it will normalize the file that was found in the Watch Folders changed event instead of opening the file manually like this code below does?

Do("File.Open");
Do("Process.Normalize", false); //MODIFY HERE: use true to use the last preset rather than being asked for one.
Do("File.Save");



Thanks

Message last edited on12/16/2005 7:43:33 AM byclmn.
Subject:RE: TJ's tricks: Watch Folders
Reply by: clmn
Date:12/16/2005 8:53:03 AM

Ok I got the Do("File.Open"); and the rest of the normilize code to execute. The problem was I did this.close() to close my form after it starts watching the selected folder. Now I have two questions left

1. How would I alter the code below so that it will normalize the file that was found in the Watch Folders changed event instead of selecting the file manually like this code below does?

Do("File.Open");
Do("Process.Normalize", false); //MODIFY HERE: use true to use the last preset rather than being asked for one.
Do("File.Save");

UPDATE Part 1.
I just tried using the code below:
ForgeApp.OpenFile(e.FullPath, false, true);
Do("Process.Normalize", true);
Do("File.Save");
Do("File.Close");
And it works successfully except that after it executes that on the .wav file it tries to do the same to the .sfk file that shows up and gives me an error stating "Warning: An error occured during the current operation. The file is an unsupported format." ?




2. How could I hide the form for watch folders and still have the code from my first question execute since it did not work when I used 'this.close();' in my form load. The purpose behind this is my manager wants this program to run on a server and run without displaying any forms. I had it set up before where in form load it would select the files we wanted to watch then it would enable them and close the form. Then it would execute correctly and call the changed event when it was suppose to except it would not execute the sound forge code to normalize a file like I described earlier.

Message last edited on12/16/2005 12:09:16 PM byclmn.
Subject:RE: TJ's tricks: Watch Folders
Reply by: _TJ
Date:12/16/2005 12:40:26 PM

ok. First of all, remember that if your SCRIPT changes the content of the watch folder, that will also generate a callback. So you may want turn off all of the nofications other than the Create notification. That's this block of code

fsWatch.Changed += new System.IO.FileSystemEventHandler(fs_Changed);
fsWatch.Created += new System.IO.FileSystemEventHandler(fs_Changed);
fsWatch.Deleted += new System.IO.FileSystemEventHandler(fs_Changed);
fsWatch.Renamed += new System.IO.RenamedEventHandler(fs_Renamed);

You should probably just comment out all but the created line, but you could also send the other notifications to some place other than fs_Changed

// fsWatch.Changed += new System.IO.FileSystemEventHandler(fs_Changed);
fsWatch.Created += new System.IO.FileSystemEventHandler(fs_Changed);
// fsWatch.Deleted += new System.IO.FileSystemEventHandler(fs_Changed);
// fsWatch.Renamed += new System.IO.RenamedEventHandler(fs_Renamed);


Alright, now to the script stuff. You already figured out that you need to
use ForgeApp.OpenFile(), but you have also noticed that when you open the file, Sound Forge creates an .sfk file, we need to ignore that .sfk file.

So we just put an If statement around the processing

private void fs_Changed(object sender, System.IO.FileSystemEventArgs e)
{
string msg = "File " + e.FullPath + " " + e.ChangeType;
++cEvents;
form.WatchChange(msg);

string strExtension = Path.GetExtension(e.FullPath);

if ((null != strExtension) && (strExtension.ToLower() == ".wav"))
{
ForgeApp.OpenFile(e.FullPath, false, true);
Do("Process.Normalize", true);
Do("File.Save");
Do("File.Close");
File.Move(e.FullPath, e.FullPath.Replace(szWatchDir,szProcessDir));
}

}


You could also go back to your original code and File.Move() the file into a different folder before you open it, that would cause Sound Forge to create the .sfk in the processing folder rather than in the watch folder.

tj








Subject:RE: TJ's tricks: Watch Folders
Reply by: _TJ
Date:12/16/2005 12:55:17 PM

2. How could I hide the form for watch folders and still have the code from my first question execute since it did not work when I used 'this.close();' in my form load.

The form is neccessary for the Watcher to work. As soon as the form closes, the watcher will also shut down.

Now, the form doesn't have to be VISIBLE, you could get away with setting form.visible = false. But the form still has to be running or else the script the watch notifications have nowhere to go. (also the script exits when the form closes).


public void Begin(IScriptableApp app) {

WatcherForm form = new WatcherForm();
form.ShowDialog(app.Win32Window);
}


The script will only run until form.ShowDialog() returns. form.Close() destroys the form's window and also causes form.ShowDialog to exit, so then the script exits, and after that it can't execute any code anymore*

tj

*ok, that's an oversimplification - a script can execute code after it exits under certain conditions - it won't in this script - but it is possible to write a scrip that does. But if you write a script that tries to leave stuff behind to execute after it exits, things are likely to work incorrectly since what the script does and what the user does will be happening at the same time.

Subject:RE: TJ's tricks: Watch Folders
Reply by: clmn
Date:12/16/2005 1:10:56 PM

Thanks alot TJ everything worked for me. Another question for you though:
How would I go about hiding the watch folders form?

The purpose behind this is my manager wants this program to run on a server and run without displaying any forms. I had it set up before where in form load it would select the folders we wanted to watch then it would enable them and close the form. But using this.close caused the soundforge code not to execute. If it isnt possible to run this script without displaying the form do you think the notifyIcon would be the route to go ?

Thanks again.

Subject:RE: TJ's tricks: Watch Folders
Reply by: _TJ
Date:12/16/2005 1:35:03 PM

The form is necessary - the script really can't be made to work without it.

So I have to ask - WHY does your manager want the script to run without showing any forms? Is he trying to run as a service? (because that really isn't ever going to work with this version of Sound Forge).

You could certainly make the form SMALLER,

Also, I don't understand what you mean by "nofityIcon".

Subject:RE: TJ's tricks: Watch Folders
Reply by: clmn
Date:12/16/2005 1:46:04 PM

OK good, Thats kinda what I was thinking to when I tried to make it work without a form earlier before I gave up.

Notify Icon as in the .NET component that runs as an icon at the bottom right corner of the screen and allows you to hide the forum and still run the program and then view the form when you want to by clicking on the icon.


Thanks for all your help TJ.

Subject:RE: TJ's tricks: Watch Folders
Reply by: _TJ
Date:12/16/2005 2:47:07 PM

Ok, I get the Notify Icon. That would be fine (i think). but I'm not sure it would do what you want because the Sound Forge main window would still be showing...

On the other hand, with a minor change to the script, you should be able to minimize Sound Forge while the script is running.

just change

public void Begin(IScriptableApp app) {

WatcherForm form = new WatcherForm();
form.ShowDialog(app.Win32Window);
}


to

public void Begin(IScriptableApp app) {

WatcherForm form = new WatcherForm();
form.Show();
app.ModalPump(form.Handle, true);
}


And the minimize button of Sound Forge will work even while the form is showing.

tj




Subject:RE: TJ's tricks: Watch Folders
Reply by: clmn
Date:12/16/2005 4:58:58 PM

Their we go, thats what I need.

Thanks alot.

Subject:RE: TJ's tricks: Watch Folders
Reply by: clmn
Date:12/19/2005 7:50:05 AM

Im not sure if this is a problem with my .wav files or what but sometimes when I drop a .wav file in the watch folder with the sound forge window open to watch it execute the .wav file that is opened says Peaks Not Available in its window then in the bottom right of the sound forge window it says Savings (filename).wav..0% and it freezes like this. Usuall it happens when I put more then one .wav file into the folder at the same time so I guess you may have to only do one file at a time. Also I tried your code to enable the minimize button for sound forge but when I ran that the sound forge code didnt execute and some times the change event wasnt even triggered when a new file was moved to the watch folder.

Here is my current code Im working with:

using System;
using System.IO;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;
using System.Collections;
using SoundForge;
using System.Diagnostics;
using System.Xml;
public class EntryPoint {

public void Begin(IScriptableApp app) {


WatcherForm form = new WatcherForm();
form.ShowDialog();
}


public class WatcherForm : System.Windows.Forms.Form
{
protected ArrayList watchList = new ArrayList();
protected ListView lvWatch = null;

public WatcherForm()
{
Size sForm = new Size(520, 250);

this.Text = "Folder Watch Test";
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.StartPosition = FormStartPosition.CenterScreen;
this.ClientSize = sForm;

Point pt = new Point(10,10);
Size sOff = new Size(10,10);

Label lbl = new Label();
lbl.Text = "Watching the following folders:";
lbl.Width = sForm.Width - pt.X - sOff.Width;
lbl.Height = 14;
lbl.Location = pt;
this.Controls.Add(lbl);
pt.Y += lbl.Height;

ListView lv = new ListView();
lv.FullRowSelect = true;
lv.Size = sForm - new Size(20, 70);
lv.Location = pt;
lv.View = View.Details;
lv.Columns.Add("Activity",-2, HorizontalAlignment.Left);
lv.Columns.Add("Status",-2, HorizontalAlignment.Left);
lv.Columns.Add("Filter",-2, HorizontalAlignment.Left);
lv.Columns.Add("Folder",-2, HorizontalAlignment.Left);

this.Controls.Add(lv);

lvWatch = lv;
lv.Items.Clear();

// we position the buttons relative to the bottom and left of the form.
//
pt = (Point)this.ClientSize;
pt -= sOff;

Button btn = new Button();
pt -= btn.Size;
btn.Text = "Quit";
btn.Location = pt;
btn.Click += new EventHandler(OnQuit_Click);
this.Controls.Add(btn);
this.CancelButton = btn;
pt.X -= (btn.Width + 10);

btn = new Button();
btn.Text = "Add...";
btn.Location = pt;
btn.Click += new EventHandler(OnAdd_Click);
this.Controls.Add(btn);
this.AcceptButton = btn;
pt.X -= (btn.Width + 10);

btn = new Button();
btn.Text = "Remove";
btn.Location = pt;
btn.Click += new EventHandler(OnRemove_Click);
this.Controls.Add(btn);
pt.X -= (btn.Width + 10);

btn = new Button();
btn.Text = "Enable";
btn.Location = pt;
btn.Click += new EventHandler(OnEnable_Click);
this.Controls.Add(btn);
pt.X -= (btn.Width + 10);

btn = new Button();
btn.Text = "Disable";
btn.Location = pt;
btn.Click += new EventHandler(OnDisable_Click);
this.Controls.Add(btn);
pt.X -= (btn.Width + 10);
this.Load += new EventHandler(WatcherForm_Load);




}

private void WatcherForm_Load(object sender, EventArgs e)
{
string szWatchDir = "";
string szCopyDir = "";
string szProcessDir = "";


XmlTextReader reader = new XmlTextReader(@"C:\SoundForgePaths.xml");

while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
if (reader.LocalName.Equals("WatchFolder"))
{
szWatchDir = reader.ReadString();
}

if (reader.LocalName.Equals("Original_SubFolder"))
{

szCopyDir = reader.ReadString();
}
if (reader.LocalName.Equals("Processed_SubFolder"))
{

szProcessDir = reader.ReadString();
}
}
}





string szFilter = "*.*";

if (szWatchDir != null)
{
this.AddWatch(szWatchDir, szFilter);
}


foreach (ListViewItem lvi in lvWatch.Items)
{
Watcher wi = (Watcher)lvi.Tag;
wi.Enable = true;
}
UpdateWatchList();
// this.Close();


}

private void UpdateWatchList()
{
ListView lv = lvWatch;
lv.Items.Clear();
foreach (Watcher watch in watchList)
{
string [] asz = watch.ToStrings();
ListViewItem lvi = new ListViewItem(asz[0]);
lvi.Tag = watch;
for (int ii = 1; ii < asz.Length; ++ii)
lvi.SubItems.Add(asz[ii]);
lv.Items.Add(lvi);
}
}

public void AddWatch(string szWatchDir, string szFilter)
{
watchList.Add(new Watcher(szWatchDir, szFilter, this));
UpdateWatchList();
}

public void WatchChange(string msg)
{
DPF(msg);
UpdateWatchList();
}

private void OnEnable_Click(object sender, System.EventArgs e)
{
foreach (ListViewItem lvi in lvWatch.SelectedItems)
{
Watcher wi = (Watcher)lvi.Tag;
wi.Enable = true;
}
UpdateWatchList();
}

private void OnDisable_Click(object sender, System.EventArgs e)
{
foreach (ListViewItem lvi in lvWatch.SelectedItems)
{
Watcher wi = (Watcher)lvi.Tag;
wi.Enable = false;
}
UpdateWatchList();
}

private void OnRemove_Click(object sender, System.EventArgs e)
{
foreach (ListViewItem lvi in lvWatch.SelectedItems)
{
Watcher wi = (Watcher)lvi.Tag;
watchList.Remove(wi);
wi.Dispose();
}
UpdateWatchList();
}

private void OnAdd_Click(object sender, System.EventArgs e)
{
string szFilter = "*.wav";
string szWatchDir = @"e:\";
szWatchDir = SfHelpers.ChooseDirectory("Choose a Folder to watch.", szWatchDir);
if (szWatchDir != null)
{
this.AddWatch(szWatchDir, szFilter);
}
}

// generic Cancel button click (sets dialog result and dismisses the form)
private void OnQuit_Click(object sender, System.EventArgs e)
{
this.DialogResult = DialogResult.Cancel;
this.Close();
}

private void On_Closing(object sender, CancelEventArgs e)
{
//if (we don't want to close)
// e.Cancel = true;

lvWatch.Items.Clear();

for (int ii = watchList.Count -1; ii >= 0; --ii)
{
Watcher watch = (Watcher)watchList[ii];
watch.Dispose();
}
}
}

public class Watcher
{
protected System.IO.FileSystemWatcher fsWatch = null;
protected string szWatchDir;
protected string szCopyDir;
protected string szProcessDir;
protected string szFilter;
protected long cEvents = 0;
protected WatcherForm form;



public Watcher(string szDirIn, string szFilterIn, WatcherForm formSync)
{

XmlTextReader reader = new XmlTextReader(@"C:\SoundForgePaths.xml");

while (reader.Read())
{
if (reader.NodeType == XmlNodeType.Element)
{
if (reader.LocalName.Equals("Original_SubFolder"))
{

szCopyDir = reader.ReadString();
}
if (reader.LocalName.Equals("Processed_SubFolder"))
{

szProcessDir = reader.ReadString();
}
}
}



szWatchDir = szDirIn;
szFilter = szFilterIn;
cEvents = 0;
form = formSync;

fsWatch = new System.IO.FileSystemWatcher(szWatchDir, szFilter);

// NOTE: if you don't do this, then your callbacks aren't guranteed to get called on
// the Sound Forge main thread, and if you aren't on the forge main thread, then you can't use
// any of the Sound Forge objects (i.e. IScriptableApp or ISfFileHost will fail all calls.)
//
fsWatch.SynchronizingObject = formSync;
// !!!!

// fsWatch.Changed += new System.IO.FileSystemEventHandler(fs_Changed);
fsWatch.Created += new System.IO.FileSystemEventHandler(fs_Changed); //Executes when file is created
// fsWatch.Deleted += new System.IO.FileSystemEventHandler(fs_Changed); //Executes when file in watch folder is deleted
// fsWatch.Renamed += new System.IO.RenamedEventHandler(fs_Renamed);
}

public bool Enable { set { fsWatch.EnableRaisingEvents = value; } }

public void Dispose()
{

fsWatch.EnableRaisingEvents = false;
fsWatch.Dispose();
fsWatch = null;
}

private void fs_Changed(object sender, System.IO.FileSystemEventArgs e)
{

try
{
string msg = "File " + e.FullPath + " " + e.ChangeType;

string strExtension = Path.GetExtension(e.FullPath);
if ((null != strExtension) && (strExtension.ToLower() == ".wav"))
{
File.Copy(e.FullPath, e.FullPath.Replace(szWatchDir, szCopyDir),true);
//Execute effects here********
File.Move(e.FullPath, e.FullPath.Replace(szWatchDir, szProcessDir));
ForgeApp.OpenFile(e.FullPath.Replace(szWatchDir,szProcessDir), false, true);
Do("Process.Normalize", true); //MODIFY HERE: use true to use the last preset rather than being asked for one.
Do("File.Save");
Do("File.Close");
}


++cEvents;
form.WatchChange(msg);

}
catch { }
}








private void fs_Renamed(object sender, System.IO.RenamedEventArgs e)
{
MessageBox.Show("Renamed");
string msg = "File " + e.OldFullPath + " " + e.ChangeType + " " + e.FullPath;
++cEvents;
form.WatchChange(msg);
}

public string[] ToStrings()
{
string[] asz = new string[4];
asz[0] = cEvents.ToString();
asz[1] = fsWatch.EnableRaisingEvents ? "Watching" : "Blind";
asz[2] = szFilter;
asz[3] = szWatchDir;
return asz;
}

public override string ToString()
{
return String.Format("{0}\t{1}\t{2}\t{3}", szWatchDir, szFilter, fsWatch.EnableRaisingEvents ? "Watching" : "Blind", cEvents);
}
}
public static void Do(string cmd) { ForgeApp.DoMenuAndWait(cmd, false); }
public static void Do(string cmd, bool fUseLastPreset) { ForgeApp.DoMenuAndWait(cmd, fUseLastPreset); }

public void FromSoundForge(IScriptableApp app) {
ForgeApp = app; //execution begins here
app.SetStatusText(String.Format("Script '{0}' is running.", Script.Name));
Begin(app);
app.SetStatusText(String.Format("Script '{0}' is done.", Script.Name));
}



public static IScriptableApp ForgeApp = null;
public static void DPF(string sz)
{
ForgeApp.OutputText(sz);




}


public static void DPF(string fmt, object o) { ForgeApp.OutputText(String.Format(fmt,o)); }
public static void DPF(string fmt, object o, object o2) { ForgeApp.OutputText(String.Format(fmt,o,o2)); }
public static void DPF(string fmt, object o, object o2, object o3) { ForgeApp.OutputText(String.Format(fmt,o,o2,o3)); }
public static string GETARG(string k, string d) { string val = Script.Args.ValueOf(k); if (val == null || val.Length == 0) val = d; return val; }
public static int GETARG(string k, int d) { string s = Script.Args.ValueOf(k); if (s == null || s.Length == 0) return d; else return Script.Args.AsInt(k); }
public static bool GETARG(string k, bool d) { string s = Script.Args.ValueOf(k); if (s == null || s.Length == 0) return d; else return Script.Args.AsBool(k); }

}


//EntryPoint



Message last edited on12/19/2005 9:55:40 AM byclmn.
Subject:RE: TJ's tricks: Watch Folders
Reply by: _TJ
Date:12/19/2005 12:49:56 PM

Most likely the problem is that Windows or Sound Forge doesn't like that you are spending so much time inside the fs_Changed() callback. It may refuse to deliver a new fs_Changed() callback until you exit the previous one, and thus you may be missing callbacks.

This is just a guess, since I don't really know what the code looks like that delivers the fs_Changed() callbacks (it's part of the .NET runtime).

One possible solution to this, would be to just use fs_Changed() as a trigger, and have it post a message to the form and then handle that posted message by scanning the watch folder and processing all of the files you find there. That way if you miss a callback, you will still process that file.

The way you do this is to use a .NET method called BeginInvoke, I'll try to play with this a bit and then post some sample code.

Here's an article on the basic technique
http://weblogs.asp.net/justin_rogers/archive/2004/02/20/76915.aspx

tj

Subject:RE: TJ's tricks: Watch Folders
Reply by: clmn
Date:12/29/2005 6:26:37 AM

Is there a version of sound forge that would make it possible to run this script as a service ?

Subject:RE: TJ's tricks: Watch Folders
Reply by: _TJ
Date:12/29/2005 9:16:05 PM

Is there a version of sound forge that would make it possible to run this script as a service ?
No. The only version of Sound Forge is the one that you are using.

Subject:RE: TJ's tricks: Watch Folders
Reply by: clmn
Date:12/30/2005 6:02:31 AM

Thanks

Go Back