FileWatcher/code/src/FileGroupTab.cs

802 lines
29 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Data;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using IronPython.Hosting;
using IronPython.Runtime;
using Microsoft.Scripting;
using Microsoft.Scripting.Hosting;
using Microsoft.Win32;
namespace FileWatcher
{
public partial class FileGroupTab : UserControl
{
/// <summary>
/// Used for catching output from the Python runtime
/// </summary>
private EventRaisingStreamWriter m_LogStream = null;
private EventRaisingStreamWriter m_ErrorStream = null;
private MemoryStream m_MemoryStream = new MemoryStream();
private delegate void SetTextCallback(object sender, MyEvtArgs<string> e);
private delegate void SetStringCallback(string e);
// TODO: Push the updated files to a multiprocessing queue that I create inside of the scope of the script
private ScriptEngine pyEngine = null;
private ScriptRuntime pyRuntime = null;
private ScriptScope pyScope = null;
BindingList<string> m_FolderList = new BindingList<string>();
List<FileSystemWatcher> m_FileWatchers = new List<FileSystemWatcher>();
List<string> m_UpdatedFiles = new List<string>();
TimerPlus m_Timer = new TimerPlus();
System.Timers.Timer m_TickCounter = new System.Timers.Timer();
private FileGroupSettings m_Settings = null;
private static Regex regexSpaces = new Regex(@"^([a-zA-Z0-9_]*\*?[a-zA-Z0-9_]*(\.(\*|[a-zA-Z0-9]+))\s?)+$");
private static Regex regexMS = new Regex(@"^\d+ms$");
private static Regex regexS = new Regex(@"^\d+s$");
private static Regex regexM = new Regex(@"^\d+m$");
private static Regex regexH = new Regex(@"^\d+h$");
private static Regex regexTime = new Regex(@"^(\d+(ms|s|m|h)\s?)+$");
private static Regex regexAbsolutePath = new Regex(@"^[a-zA-Z]\:\\$");
private static Regex regexNetworkPath = new Regex(@"^\\\\$");
public FileGroupTab()
{
InitializeComponent();
m_Timer.Enabled = false;
m_Timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Tick);
m_TickCounter.Enabled = false;
m_TickCounter.Elapsed += new System.Timers.ElapsedEventHandler(timer_TickCountdown);
if (pyEngine == null)
{
Dictionary<string, object> options = new Dictionary<string, object>();
options["Debug"] = true;
pyEngine = Python.CreateEngine(options);
pyScope = pyEngine.CreateScope();
m_LogStream = new EventRaisingStreamWriter(m_MemoryStream);
m_LogStream.StringWritten += new EventHandler<MyEvtArgs<string>>(script_StringWritten);
pyEngine.Runtime.IO.SetOutput(m_MemoryStream, m_LogStream);
m_ErrorStream = new EventRaisingStreamWriter(m_MemoryStream);
m_ErrorStream.StringWritten += new EventHandler<MyEvtArgs<string>>(script_ErrorStringWritten);
pyEngine.Runtime.IO.SetErrorOutput(m_MemoryStream, m_ErrorStream);
}
foldersListBox.DataSource = m_FolderList;
m_FolderList.ListChanged += new ListChangedEventHandler(foldersListBox_ListChanged);
toolStripTextBox1_TextChanged(this, null);
}
public List<string> GetPaths()
{
List<string> paths = new List<string>();
foreach (string str in foldersListBox.Items)
{
paths.Add(str);
}
return paths;
}
public bool AreExtenionsValid()
{
return regexSpaces.Matches(FileExtensionsToolBox.Text).Count != 0;
}
public string ExtensionString
{
get { return FileExtensionsToolBox.Text; }
set { FileExtensionsToolBox.Text = value; }
}
public string[] Extensions
{
get { return FileExtensionsToolBox.Text.Split(' '); }
set { FileExtensionsToolBox.Text = string.Join(" ", value); }
}
public string ScriptPath
{
get { return scriptPathTextBox.Text; }
set { scriptPathTextBox.Text = value; }
}
public string GetScriptAbsolutePath()
{
bool match1 = regexAbsolutePath.Matches(ScriptPath).Count > 0;
bool match2 = regexNetworkPath.Matches(ScriptPath).Count > 0;
if (match1 == false && match2 == false)
{
// Path is not an absolute path, make it one
return Path.GetFullPath(Path.Combine(new FileInfo(Application.ExecutablePath).DirectoryName, ScriptPath));
} return ScriptPath;
}
public string Delay
{
get { return toolStripTextBox1.Text; }
set { toolStripTextBox1.Text = value; }
}
public FileGroupSettings Settings
{
get
{
if (null == m_Settings || m_Settings.IsDisposed)
{
m_Settings = new FileGroupSettings();
//m_Settings.Text = "";
} return m_Settings;
}
}
public List<string> Directories
{
get { return m_FolderList.Cast<String>().ToList(); }
set
{
m_FolderList.Clear();
foldersListBox.DataSource = null;
foldersListBox.SelectionMode = SelectionMode.None;
foreach (string i in value)
m_FolderList.Add(i);
foldersListBox.DataSource = m_FolderList;
foldersListBox.SelectionMode = SelectionMode.MultiExtended;
}
}
static public string GetPythonDir()
{
// if you want better debugging information, return the iron python path
String ironPythonDir = "C:\\Program Files (x86)\\IronPython 2.7";
if (Directory.Exists(ironPythonDir))
{
return ironPythonDir;
}
// Use the Libs folder in the local path (and let there be an error if it's not there)
return new FileInfo(Application.ExecutablePath).DirectoryName;
// Iron Python doesn't seem properly compatible with Python, don't use the Python libs.
/*string[] paths = Environment.GetEnvironmentVariable("PATH").Split(';');
foreach (string s in paths)
{
if (s.ToLower().IndexOf("python") != -1 && File.Exists(Path.Combine(s, "python.exe")))
{
return s;
}
}
return null;*/
}
private bool CompileSourceAndExecute()
{
string code = "";
string pythonDir = GetPythonDir();
if (pythonDir == null)
{
scriptWarning.Visible = true;
scriptWarning.ToolTipText = "Could not find your Python path, try adding it to your system PATH and try again.";
Logger.LogError("Could not find your Python path, try adding it to your system PATH and try again.");
return false;
}
string pythonLibs = Path.Combine(pythonDir, "Lib");
if (Directory.Exists(pythonLibs))
{
code += "import sys" + Environment.NewLine + "if '" + pythonLibs + "' not in sys.path: sys.path.append('" + pythonLibs + "')" + Environment.NewLine;
}
else
{
scriptWarning.Visible = true;
scriptWarning.ToolTipText = "Could not find your Python Lib path in '" + pythonLibs + "'. Good luck.";
Logger.LogError("Could not find your Python Lib path in '" + pythonLibs + "'. Good luck.");
return false;
}
try
{
// TODO: backup list of unprocessed files and move it to the new scope
// WARNING: for some reason the dependencies that are loaded from the
// previous script do not get reloaded, even though they should be in
// the scope that we're disposing and not in the new scope. Will have
// to consider destroying the Python Engine and recreating that as well.
pyScope = pyEngine.CreateScope();
pyEngine.Execute(code.Replace("\\", "\\\\"), pyScope);
pyEngine.ExecuteFile(GetScriptAbsolutePath(),pyScope);
//ScriptSource source = pyEngine.CreateScriptSourceFromString(code);
// object result = source.Execute(pyScope);
}
catch (Exception e)
{
if (e is Microsoft.Scripting.SyntaxErrorException)
{
scriptWarning.Visible = true;
scriptWarning.ToolTipText = "There is a syntax error: '" + e.Message + "' on line " + ((Microsoft.Scripting.SyntaxErrorException)e).Line;
Logger.LogError("There is a syntax error in '" + Path.GetFileName(ScriptPath) + "': " + e.Message + "' on line " + ((Microsoft.Scripting.SyntaxErrorException)e).Line);
return false;
}
else if (e is System.Reflection.TargetInvocationException)
{
scriptWarning.Visible = true;
scriptWarning.ToolTipText = "There is an invokation error: '" + e.Message + "'" + Environment.NewLine + e.StackTrace;
Logger.LogError("There is an invokation error in '" + Path.GetFileName(ScriptPath) + "': " + e.Message + "'" + Environment.NewLine + e.StackTrace);
return false;
}
else if (e is System.ArgumentException)
{
scriptWarning.Visible = true;
scriptWarning.ToolTipText = "There is an argument error: '" + e.Message + "'" + Environment.NewLine + e.StackTrace;
Logger.LogError("There is an argument error in '" + Path.GetFileName(ScriptPath) + "': " + e.Message + "'" + Environment.NewLine + e.StackTrace);
return false;
}
else if (e is IronPython.Runtime.Exceptions.ImportException)
{
scriptWarning.Visible = true;
scriptWarning.ToolTipText = "There is an import error: '" + e.Message + "'" + Environment.NewLine + e.StackTrace;
Logger.LogError("There is an import error in '" + Path.GetFileName(ScriptPath) + "': " + e.Message + "'" + Environment.NewLine + e.StackTrace);
return false;
}
else
{
scriptWarning.Visible = true;
scriptWarning.ToolTipText = "There is an unknown error: '" + e.Message + "'" + Environment.NewLine + e.StackTrace;
Logger.LogError("There is an unknown error in '" + Path.GetFileName(ScriptPath) + "': " + e.Message + "'" + Environment.NewLine + e.StackTrace);
return false;
}
}
try
{
Func<List<string>, bool> updated = pyScope.GetVariable<Func<List<string>, bool>>("process_updated_files");
}
catch (System.MissingMemberException)
{
scriptWarning.Visible = true;
scriptWarning.ToolTipText = "The function 'process_updated_files' is missing, it accepts a list of files.";
Logger.LogError("'" + Path.GetFileName(ScriptPath) + "' is missing the function 'process_updated_files', which accepts a list of files. ");
return false;
}
Logger.Log("Loaded and compiled '" + Path.GetFileName(ScriptPath) + "'");
SetInterval();
return true;
}
private void toolStripButton5_Click(object sender, EventArgs e)
{
}
private void toolStripButton7_Click(object sender, EventArgs e)
{
}
public void AddWatchPath(string path )
{
foldersListBox.DataSource = null;
foldersListBox.SelectionMode = SelectionMode.None;
m_FolderList.Add(path);
foldersListBox.DataSource = m_FolderList;
foldersListBox.SelectionMode = SelectionMode.MultiExtended;
}
public void RemoveWatchPath(string path)
{
foldersListBox.DataSource = null;
foldersListBox.SelectionMode = SelectionMode.None;
m_FolderList.Remove(path);
foldersListBox.DataSource = m_FolderList;
foldersListBox.SelectionMode = SelectionMode.MultiExtended;
}
private void toolStripButton5_Click_1(object sender, EventArgs e)
{
//Form1.s_FolderDialogue.InitialDirectory = Environment.CurrentDirectory;
if (Form1.s_FolderDialogue.ShowDialog())
{
AddWatchPath(Form1.s_FolderDialogue.FileName);
Form1.Instance.AutoSave();
}
}
private void toolStripButton6_Click(object sender, EventArgs e)
{
while (foldersListBox.SelectedItems.Count > 0)
{
RemoveWatchPath((string)foldersListBox.SelectedItems[0]);
}
Form1.Instance.AutoSave();
}
private void toolStripButton7_Click_1(object sender, EventArgs e)
{
Process myProcess = new Process();
myProcess.StartInfo.FileName = Preferences.Instance.GetDefaultEditor(); //not the full application path
if (String.IsNullOrEmpty(myProcess.StartInfo.FileName))
{
MessageBox.Show("Your selected default and backup text editors cannot be found, please open up preferences ( Ctrl + P ) and select an editor that can be found on the local computer.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
// TODO: check if directory is relative or not
myProcess.StartInfo.Arguments = GetScriptAbsolutePath();
myProcess.Start();
}
private void FileExtensionsToolBox_TextChanged(object sender, EventArgs e)
{
bool isMatch = regexSpaces.IsMatch(FileExtensionsToolBox.Text);
extensionWarning.Visible = isMatch == false;
UpdateFileWatchers();
}
private void openFileDialog1_FileOk(object sender, CancelEventArgs e)
{
}
private void toolStripButton1_Click(object sender, EventArgs e)
{
// TODO: check if directory is relative or not
openFileDialog1.InitialDirectory = Path.GetDirectoryName(GetScriptAbsolutePath());
openFileDialog1.FileName = Path.GetFileName(ScriptPath);
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
scriptPathTextBox.Text = Utils.MakeRelativePath(System.IO.Path.GetDirectoryName(System.Windows.Forms.Application.ExecutablePath), openFileDialog1.FileName);
LoadScript();
}
}
private void toolStripButton2_Click(object sender, EventArgs e)
{
Settings.ShowDialog(this);
}
public void SetDelayCountDownText(string str)
{
if (toolStrip.InvokeRequired)
{
SetStringCallback d = new SetStringCallback(SetDelayCountDownText);
this.Invoke(d, new object[] { str });
}
else
{
delayCountDownLabel.Text = str;
}
}
private void script_StringWritten(object sender, MyEvtArgs<string> e)
{
if (richTextBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(script_StringWritten);
this.Invoke(d, new object[] { sender, e });
}
else
{
richTextBox1.AppendText(e.Value, Color.Black);
richTextBox1.SelectionStart = richTextBox1.Text.Length;
richTextBox1.ScrollToCaret();
}
}
private void script_ErrorStringWritten(object sender, MyEvtArgs<string> e)
{
if (richTextBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(script_ErrorStringWritten);
this.Invoke(d, new object[] { sender, e });
}
else
{
richTextBox1.AppendText(e.Value, Color.Maroon);
richTextBox1.SelectionStart = richTextBox1.Text.Length;
richTextBox1.ScrollToCaret();
}
}
private void fileSystemWatcher1_Changed(object sender, FileSystemEventArgs e)
{
try
{
// DO NOT check to see if equal to directory
if ((File.GetAttributes(e.FullPath) & FileAttributes.Directory) == FileAttributes.Directory)
{
return;
}
}
catch (FileNotFoundException)
{
return;
}
catch (DirectoryNotFoundException)
{
return;
}
if (m_Timer.Enabled == false || Settings.restartDelayCB.Checked)
{
m_Timer.Stop();
m_TickCounter.Stop();
m_Timer.Enabled = false;
m_TickCounter.Enabled = false;
SetInterval();
m_Timer.Enabled = true;
m_TickCounter.Enabled = true;
m_Timer.Start();
m_TickCounter.Start();
}
if (Settings.stopScriptCB.Checked)
{
scriptWorker.CancelAsync();
}
// TODO: check to see if the path is relative or absolute
string fullPath = Path.Combine(new FileInfo(Application.ExecutablePath).DirectoryName, e.FullPath);
if (false == m_UpdatedFiles.Contains(fullPath))
{
m_UpdatedFiles.Add(fullPath);
}
}
private void scriptPathTextBox_TextChanged(object sender, EventArgs e)
{
LoadScript();
}
void UpdateFileWatchers()
{
if (false == AreExtenionsValid())
{
return;
}
foreach (FileSystemWatcher fsw in m_FileWatchers)
{
fsw.Dispose();
}
m_FileWatchers.Clear();
foreach (string s in Directories)
{
string path = Path.Combine(new FileInfo(Application.ExecutablePath).DirectoryName, s);
foreach (string ext in Extensions)
{
if (false == Directory.Exists(path)) continue;
FileSystemWatcher fileWatcher = new FileSystemWatcher();
// TODO: check to see if the path is relative
fileWatcher.Path = path;
fileWatcher.Filter = ext;
fileWatcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
fileWatcher.Created += fileSystemWatcher1_Changed;
fileWatcher.Changed += fileSystemWatcher1_Changed;
fileWatcher.Renamed += fileSystemWatcher1_Changed;
fileWatcher.Deleted += fileSystemWatcher1_Changed;
// TODO: handle deleted files differently
fileWatcher.EnableRaisingEvents = true;
fileWatcher.IncludeSubdirectories = true;
m_FileWatchers.Add(fileWatcher);
}
}
}
void foldersListBox_ListChanged(object sender, ListChangedEventArgs e)
{
switch (e.ListChangedType)
{
case ListChangedType.ItemMoved:
case ListChangedType.ItemAdded:
case ListChangedType.ItemChanged:
case ListChangedType.ItemDeleted:
UpdateFileWatchers();
break;
}
}
private void timer_Tick(object sender, EventArgs e)
{
SetDelayCountDownText("--ms");
m_Timer.Stop();
m_Timer.Enabled = false;
m_TickCounter.Stop();
m_TickCounter.Enabled = false;
//Form1.Instance.notifyIcon1.ShowBalloonTip(500, "Script Running", "Running '" + ScriptPath + "'", ToolTipIcon.Info);
//scriptWorker.RunWorkerAsync(m_UpdatedFiles);
try
{
var updateFiles = m_UpdatedFiles.ToList();
m_UpdatedFiles.Clear();
// run script in separate thread
Func<List<string>, bool> updated = pyScope.GetVariable<Func<List<string>, bool>>("process_updated_files");
updated(updateFiles);
Logger.Log("Just finished running '" + ScriptPath + "'");
Form1.Instance.notifyIcon1.ShowBalloonTip(500, "Script Ran", "Finished running '" + ScriptPath + "'", ToolTipIcon.Info);
}
catch (System.Exception ex)
{
Logger.LogError("'" + ScriptPath + "' finished running with errors");
Form1.Instance.notifyIcon1.ShowBalloonTip(500, "Script Error", "'" + ScriptPath + "' finished running with errors", ToolTipIcon.Error);
if (ex is IronPython.Runtime.Exceptions.ImportException)
{
Logger.Log("Import exception in '" + ScriptPath + "':" + ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + ex.TargetSite);
}
else if (ex is System.MissingMemberException)
{
Logger.Log("Missing member exception in '" + ScriptPath + "':" + ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + ex.TargetSite);
}
else if (ex is System.IO.DirectoryNotFoundException)
{
Logger.Log("Directory not found exception in '" + ScriptPath + "':" + ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + ex.TargetSite);
}
else if (ex is System.ArgumentException)
{
Logger.Log("Directory not found exception in '" + ScriptPath + "':" + ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + ex.TargetSite);
}
else if (ex is System.IO.IOException)
{
Logger.Log("IOException in '" + ScriptPath + "':" + ex.Message + Environment.NewLine + ex.StackTrace + Environment.NewLine + ex.TargetSite);
Logger.Log("Your script had an IO exception, you should alter your script to catch individual file errors so you can try to process them all even when an exception happens. You may also want to alter your file IO in case you can get around this error.");
}
else
{
Logger.Log("Error running '" + ScriptPath + "':" + ex.Message + Environment.NewLine + ex.StackTrace);
}
}
SetInterval();
}
private void timer_TickCountdown(object sender, EventArgs e)
{
string time_elapsed = "";
const double one_hour_to_ms = 3600000;
const double one_minute_to_ms = 60000;
const double one_second_to_ms = 1000.0;
double ms = m_Timer.TimeRemaining.TotalMilliseconds;
int hours = 0;
while (ms > one_hour_to_ms)
{
ms -= one_hour_to_ms;
++hours;
}
int minutes = 0;
while (ms > one_minute_to_ms)
{
ms -= one_minute_to_ms;
++minutes;
}
int seconds = 0;
while (ms > one_second_to_ms)
{
ms -= one_second_to_ms;
++seconds;
}
// round up
if (ms > 0)
{
++seconds;
}
if (hours > 0)
{
time_elapsed += hours + "h";
}
if (minutes > 0)
{
time_elapsed += (time_elapsed.Length > 0 ? " " : "") + minutes + "m";
}
if (seconds > 0)
{
time_elapsed += (time_elapsed.Length > 0 ? " " : "") + seconds + "s";
}
SetDelayCountDownText(time_elapsed);
}
bool SetInterval()
{
// disable timer before setting interval
if (m_Timer.Enabled) return false;
bool matches = regexTime.IsMatch(toolStripTextBox1.Text);
toolStripLabel2.Visible = matches == false;
toolStripLabel2.ToolTipText = "Invalid delay";
if (matches)
{
int interval = 0;
string[] values = toolStripTextBox1.Text.Split(' ');
foreach (string val in values)
{
if (val.Length == 0) continue;
if (regexMS.IsMatch(val))
{
interval += Convert.ToInt32(Regex.Match(val, @"\d+").Value);
}
else if (regexS.IsMatch(val))
{
interval += Convert.ToInt32(Regex.Match(val, @"\d+").Value) * 1000;
}
else if (regexM.IsMatch(val))
{
interval += Convert.ToInt32(Regex.Match(val, @"\d+").Value) * 1000 * 60;
}
else if (regexH.IsMatch(val))
{
interval += Convert.ToInt32(Regex.Match(val, @"\d+").Value) * 1000 * 60 * 24;
}
else
{
throw new Exception("Unexpected format");
}
}
if (interval != 0)
{
m_Timer.Interval = interval;
m_TickCounter.Interval = 1000;
return true;
}
toolStripLabel2.Visible = true;
toolStripLabel2.ToolTipText = "Cannot have a value of 0";
}
return false;
}
private void toolStripTextBox1_TextChanged(object sender, EventArgs e)
{
SetInterval();
}
public void ToggleScriptUpdate(bool enabled)
{
if (enabled)
{
scriptMonitorButton.Image = Properties.Resources.drop_box;
scriptMonitorButton.Enabled = true;
scriptMonitorButton.ToolTipText = "Script updated, press to reload it";
}
else
{
scriptMonitorButton.Image = Properties.Resources.block;
scriptMonitorButton.Enabled = false;
scriptMonitorButton.ToolTipText = "Script Monitor (when script is updated check here to reload script)";
}
}
public bool LoadScript()
{
ToggleScriptUpdate(false);
string absolutePath = GetScriptAbsolutePath();
if ( false == File.Exists(absolutePath))
{
scriptWatcher1.EnableRaisingEvents = false;
scriptWarning.Visible = true;
scriptWarning.ToolTipText = "The source file '" + absolutePath + "' could not be found.";
Logger.Log("The source file '" + absolutePath + "' could not be found.");
return false;
}
scriptWatcher1.Path = new FileInfo(absolutePath).DirectoryName;
scriptWatcher1.Filter = Path.GetFileName(absolutePath);
scriptWatcher1.Changed += scriptWatcher1_Changed;
scriptWatcher1.EnableRaisingEvents = true;
if (false == CompileSourceAndExecute())
{
return false;
}
scriptWarning.Visible = false;
scriptWarning.ToolTipText = "";
return true;
}
private void scriptWatcher1_Changed(object sender, FileSystemEventArgs e)
{
// script updated
ToggleScriptUpdate(true);
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
}
private void scriptMonitorButton_Click(object sender, EventArgs e)
{
LoadScript();
}
}
}