FileWatcher/code/src/FileGroupTab.cs

793 lines
28 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;
}
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(1000, "Script Running", "Running '" + ScriptPath + "'", ToolTipIcon.Info);
//scriptWorker.RunWorkerAsync(m_UpdatedFiles);
// run script in separate thread
Func<List<string>, bool> updated = pyScope.GetVariable<Func<List<string>, bool>>("process_updated_files");
try
{
var updateFiles = m_UpdatedFiles;
m_UpdatedFiles.Clear();
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
{
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();
}
}
}