/*
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE":
* I wrote this file. As long as you retain this notice you can do
* whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return - Sebastian Roll
* ----------------------------------------------------------------------------
*/
using System;
using System.ComponentModel;
using System.Windows.Forms;
using System.IO;
using System.Collections;
namespace FileSystemWatch
{
/// <summary>
/// Main window with a dataGridView, contextMenu and notifyIcon (systemTray).
/// dataGridView shows file activities for desired directories.
/// .Net Framework class System.IO.FileSystemWatcher is used to keep track of each directory.
/// (one FSW for one directory)
/// File-Events change, create, delete and rename are tracked.
/// But file is only shown to user if it passes the file extension filter.
/// (e.g. if it is not a *.txt file -> do not show)
/// (e.g. if file is within "directories to exclude" -> do not show)
/// On fileActivity, a balloon notification may appear in systemTray with infoText set to fileName.
/// All activites can be logged to file.
/// A file, shown in dataGridView, can be opened via contextMenu (with associatet application or viewed in explorer).
/// </summary>
public partial class MainWindow : Form
{
// two arrays with strings. a string is e.g. "C:\myDirectory"
PathContainer pathsToWatch = new PathContainer();
PathContainer pathsToExclude = new PathContainer();
// array with directory watchers (System.IO.FileSystemWatcher)
// each FSW watches exactly one directory, as contained in "pathsToWatch"
ArrayList watchers = new ArrayList();
// option window with filter config and other usefull app-options
OptionsWindow options = new OptionsWindow();
// counting incomming FSW events
UInt32 totalIncommingNotificationEvents = 0;
// ctor
public MainWindow()
{
InitializeComponent();
}
// on show window: load options from file,
// load directories to watch
// load directorie to exclude from watch
// setup FileSystemWatcher objects on each directory to watch
private void MainWindow_Load(object sender, EventArgs e)
{
trace("MainWindow_Load()");
options.load("FSWConfig.txt");
pathsToWatch.load("FSWPathsInclude.txt");
pathsToExclude.load("FSWPathsExclude.txt");
rebuildFileSystemWatchersList(pathsToWatch);
updateStatusBarText();
// set main window position to last used position,
// but first check if last position is present in configFile
if (!options.MainWindowLocation.IsEmpty)
{
Location = options.MainWindowLocation;
}
if (!options.MainWindowSize.IsEmpty)
{
Size = options.MainWindowSize;
}
}
// FSW object sent an event: some file changed in certain directory
private void fileSystemWatcher1_Changed(object sender, System.IO.FileSystemEventArgs e)
{
// do nothing if file is our own logfile
if (isNotificationOnOwnLogFile(e.FullPath))
{
return;
}
// count and log event
totalIncommingNotificationEvents++;
trace("Changed()", e.FullPath);
// user wants to be notified on file change?
if (options.NotifyOnFileChange)
{
// yes, add to dataGridView
addRow(e.FullPath, "Changed");
}
else
{
// no, just log to file
trace("Changed() " + e.FullPath, " filtered by options.NotifyOnFileChange");
}
// show some text in window's status bar
updateStatusBarText();
}
// FSW object sent an event: some file was created in certain directory
private void fileSystemWatcher_Created(object sender, FileSystemEventArgs e)
{
// do nothing if file is our own logfile
if (isNotificationOnOwnLogFile(e.FullPath))
{
return;
}
// count and log event
totalIncommingNotificationEvents++;
trace("Created()", e.FullPath);
// user wants to be notified on file create?
if (options.NotifyOnFileCreate)
{
// yes, add to dataGridView
addRow(e.FullPath, "Created");
}
else
{
// no, just log to file
trace("Created() " + e.FullPath, " filtered by options.NotifyOnFileCreate");
}
// show some text in window's status bar
updateStatusBarText();
}
// FSW object sent an event: some file was deleted in certain directory
private void fileSystemWatcher_Deleted(object sender, FileSystemEventArgs e)
{
// do nothing if file is our own logfile
if (isNotificationOnOwnLogFile(e.FullPath))
{
return;
}
// count and log event
totalIncommingNotificationEvents++;
trace("Deleted()", e.FullPath);
// user wants to be notified on file delete?
if (options.NotifyOnFileDelete)
{
// yes, add to dataGridView
addRow(e.FullPath, "Deleted");
}
else
{
// no, just log to file
trace("Deleted() " + e.FullPath, " filtered by options.NotifyOnFileDelete");
}
// show some text in window's status bar
updateStatusBarText();
}
// FSW object sent an event: some file was renamed in certain directory
private void fileSystemWatcher_Renamed(object sender, RenamedEventArgs e)
{
// do nothing if file is our own logfile
if (isNotificationOnOwnLogFile(e.FullPath))
{
return;
}
// count and log event
totalIncommingNotificationEvents++;
trace("Renamed()", e.FullPath);
// user wants to be notified on file rename?
if (options.NotifyOnFileRename)
{
// yes
// check if user does not want to see any MS Office AutoSafe fileOperations
if (options.MsOfficeDiscardAutoSave)
{
String fileExt = Path.GetExtension(e.OldFullPath);
if (fileExt == "")
{
// it is MS Office AutoSafe: just log to file and return
trace("Renamed() " + e.FullPath, " filtered by options.MsOfficeDiscardAutoSave");
return;
}
}
// add to dataGridView
addRow(e.FullPath, "Renamed from " + e.OldFullPath);
}
else
{
// no, just log to file
trace("Renamed() " + e.FullPath, " filtered by options.NotifyOnFileRename");
}
// show some text in window's status bar
updateStatusBarText();
}
// FSW object sent an event: some file opration throws error
// :) never occurred until now
private void fileSystemWatcher_Error(object sender, ErrorEventArgs e)
{
trace("fileSystemWatcher_Error() " + e.GetException().Message);
MessageBox.Show(e.GetException().Message, "FSW Error");
}
// appends a line to dataGridView with given Strings
// checks if line can be added due to filter-settings in option-window
private void addRow(String fileName, String reason)
{
// user want to filter events by file-extension?
if (options.UseFileFilter)
{
// yes, check if this file-extension is within filter-list
if (!fileExtensionFilterPassed(fileName))
{
// cannot find this extension in extension-list from options window
// do not add line to dataGridView, just log to file
trace("addRow() " + fileName, " filtered by fileExtension");
return;
}
}
// user has set exclude-directory for given file?
String dir = Path.GetDirectoryName(fileName);
if (pathsToExclude.containsIgnoreCase(dir))
{
// yes, do not add line to dataGridView, just log to file
trace("addRow() " + fileName, " filtered by pathsToExclude");
return;
}
// user does not want to see directory-rename actions?
if (!options.NotifyOnFolderChange)
{
// yes, check if current event points to a directory
// (current event is e.g. C:/myDir (has no extension, e.g. *.txt))
if (Directory.Exists(fileName))
{
// yes, do not add line to dataGridView, just log to file
trace("addRow() " + fileName, " filtered by options.NotifyOnFolderChange");
return;
}
}
// user does not want to see MS-Office TempFile actions?
// e.g. MS Word always creates a copy of a file to open (e.g. ~$WRD876EF.tmp)
if (options.MsOfficeDiscardTempFiles)
{
// yes, check for temp-file
String fileWoExt = Path.GetFileNameWithoutExtension(fileName);
if (fileWoExt.StartsWith("~"))
{
// it is a temp-file, do not add line to dataGridView, just log to file
trace("addRow() " + fileName, " filtered by options.MsOfficeDiscardTempFiles");
return;
}
}
// user wants to see a balloon tip on systemTray?
if (options.ShowBaloonTips)
{
// yes, setup and fire up a balloon tip. show tip 1000 ms.
String ballooTitle = "FSW " + reason + ":";
notifyIcon1.ShowBalloonTip(1000, ballooTitle, fileName, ToolTipIcon.Info);
}
// add given Strings + self created timeStamp to dataGridView
int i = dataGridView1.Rows.Add();
String timeStamp = DateTime.Now.ToString("yyyy'-'MM'-'dd HH':'mm':'ss");
dataGridView1.Rows[i].Cells["MyColumnTimeStamp"].Value = timeStamp;
dataGridView1.Rows[i].Cells["MyColumnName"].Value = fileName;
dataGridView1.Rows[i].Cells["MyColumnReason"].Value = reason;
// user wants to log events to file?
if (options.LogToFile)
{
// yes, log to file
addLineToLogfile(timeStamp, fileName, reason);
}
// user wants always to see very last event?
if (options.AutoScrollDataGrid)
{
// yes, bring shown area to added line
int scrollTo = dataGridView1.Rows.Count - 1;
dataGridView1.FirstDisplayedScrollingRowIndex = scrollTo;
}
}
// appends given strings to a self-made log file.
// log file name is created on the fly on first call.
String logFileName = "";
private void addLineToLogfile(string timeStamp, string fileName, string reason)
{
// first call?
if (logFileName.Length == 0)
{
// yes, create unique file name
logFileName = "FSWLogfile_" + timeStamp.Replace(' ', '_').Replace(':', '_') + ".txt";
try
{
using (StreamWriter sw = new StreamWriter(logFileName))
{
// write info to very first line
sw.WriteLine("FileSystemWatcher Logfile " + timeStamp);
}
}
catch (Exception exp)
{
MessageBox.Show(exp.Message, "FSW Error");
}
}
// append given strings to logfile
try
{
using (StreamWriter sw = new StreamWriter(logFileName, true))
{
sw.WriteLine(timeStamp + ";" + fileName + ";" + reason);
}
}
catch (Exception exp)
{
MessageBox.Show(exp.Message, "FSW Error");
}
}
// append given strings to log file
private void trace(string timeStamp, string info, string reason)
{
// user want to see debug trace info?
if (options.DebugToFile)
{
addLineToLogfile(timeStamp, info, reason);
}
}
// append given strings to log file (overload 1)
private void trace(string info, string reason)
{
// user want to see debug trace info?
if (options.DebugToFile)
{
String timeStamp = DateTime.Now.ToString("yyyy'-'MM'-'dd HH':'mm':'ss");
addLineToLogfile(timeStamp, info, reason);
}
}
// append given strings to log file (overload 2)
private void trace(string info)
{
// user want to see debug trace info?
if (options.DebugToFile)
{
String timeStamp = DateTime.Now.ToString("yyyy'-'MM'-'dd HH':'mm':'ss");
addLineToLogfile(timeStamp, info, "DEBUG");
}
}
// returns true if an FSW event belongs to our own created logfile
// in general we do not respond to those events, because it will
// result in recursive "logToFile->event->logToFile->event->..."
private bool isNotificationOnOwnLogFile(String fileName)
{
if (options.LogToFile || options.DebugToFile)
if (logFileName.Length > 0)
if (fileName.Contains(logFileName))
return true;
return false;
}
// returns true if any filter-string matches given string
// filter-strings are taken from Options-Window-TextEditBox
// this is used to discard useless events from user
// (e.g. user does only want to see *.doc files)
// string compare is 'ignore case'
private bool fileExtensionFilterPassed(String s)
{
bool result = false;
// extract extension from given fileName
String extension = Path.GetExtension(s);
// get Options-Window-TextEditBox
String filter = options.FileFilterList;
// those chars are useless for comparison
char[] trimChars = { ' ', '.' };
extension = extension.Trim(trimChars);
// is given string valid?
if (extension == "")
{
// no, just log to file
trace("fileExtensionFilterPassed() " + s + " filtered by <has no extension>");
return false;
}
// make both strings to lower chars
extension = extension.ToLower();
filter = filter.ToLower();
// return true if any filter-extension matches given extension
result = filter.Contains(extension);
return result;
}
// user touched main window' size
private void MainWindow_Resize(object sender, EventArgs e)
{
// minimize to system tray if minimize button was pressed
if (WindowState == FormWindowState.Minimized)
{
Hide();
}
// store current size -> to be safed to configFile on application exit
// (do not store position if minimized or maximized)
if (WindowState == FormWindowState.Normal)
{
options.MainWindowSize = Size;
}
// user wants always to see very last event?
if (options.AutoScrollDataGrid)
{
// yes, bring dataGridView's shown area to dataGridView's last line
int scrollTo = dataGridView1.Rows.Count - 1;
if (scrollTo > 0)
{
try
{
// throws exception if there is no room to display a row
// e.g. window's overall size is very small
dataGridView1.FirstDisplayedScrollingRowIndex = scrollTo;
}
catch (Exception exp)
{
Console.WriteLine(exp.Message);
}
}
}
}
// user touched main window's position
private void MainWindow_Move(object sender, EventArgs e)
{
// store current position -> to be safed to configFile on application exit
// (do not store position if minimized or maximized)
if (WindowState == FormWindowState.Normal)
{
options.MainWindowLocation = Location;
}
}
// user clicked one row on dataGridView
// 1. put row to "selected" state
// 2. enable context menu items
private void dataGridView1_MouseDown(object sender, MouseEventArgs e)
{
// get a "where has user clicked" object
DataGridView.HitTestInfo Hti;
// setup the object
Hti = dataGridView1.HitTest(e.X, e.Y);
// user clicked a cell?
if (Hti.Type == DataGridViewHitTestType.Cell)
{
// yes, enable context menu items and select clicked row (row color turns blue)
contextMenuStrip1.Items["openToolStripMenuItem"].Enabled = true;
contextMenuStrip1.Items["openContainingFolderToolStripMenuItem"].Enabled = true;
dataGridView1.ClearSelection();
((DataGridViewRow)dataGridView1.Rows[Hti.RowIndex]).Selected = true;
}
else
{
// no, disable context menu items
contextMenuStrip1.Items["openToolStripMenuItem"].Enabled = false;
contextMenuStrip1.Items["openContainingFolderToolStripMenuItem"].Enabled = false;
}
}
// user rightClicked and selected an item from context menu
// -> user wants to open a filed isplayed in that cell
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
// get name of file, user want to open
String s = dataGridView1.SelectedRows[0].Cells["MyColumnName"].Value.ToString();
// start process
startProcess(s, null);
}
// user rightClicked and selected an item from context menu
// -> user wants to open file's containing folder
private void openContainingFolderToolStripMenuItem_Click(object sender, EventArgs e)
{
// get folder to show as an Explorer-Process
String s = dataGridView1.SelectedRows[0].Cells["MyColumnName"].Value.ToString();
s = System.IO.Path.GetDirectoryName(s);
// start Explorer-Process with file's parent folder as argument
startProcess("explorer.exe", "/e," + s);
}
// user doubleClicked a cell
// -> user wants to open the file displayed in that cell
private void dataGridView1_CellMouseDoubleClick(object sender, DataGridViewCellMouseEventArgs e)
{
// check if left mouse button was used
if (e.Button == MouseButtons.Left)
{
// yes, get name of file, user want to open
int rowIndex = e.RowIndex;
// check range of index (could be header, i.e. -1)
if (rowIndex >= 0 && rowIndex < dataGridView1.Rows.Count)
{
String s = dataGridView1.Rows[rowIndex].Cells["MyColumnName"].Value.ToString();
startProcess(s, null);
}
}
}
private void startProcess(String name, String arguments)
{
// get a process object
System.Diagnostics.Process p = new System.Diagnostics.Process();
try
{
// setup process' name and args
p.StartInfo.FileName = name;
p.StartInfo.Arguments = arguments;
// start object
p.Start();
}
catch (Exception exp)
{
trace("startProcess() " + exp.Message);
MessageBox.Show(exp.Message, "FSW Error");
}
}
// user rightClicked and selected an item from context menu
// user wants to edit include/exclude directories
private void editPathsToolStripMenuItem_Click(object sender, EventArgs e)
{
// get a directory-edit window object
EditDirectoriesWindow edw = new EditDirectoriesWindow(this, pathsToWatch, pathsToExclude);
// show object. On close we will get informed to rebuild our FileSystemWatchers-list
edw.ShowDialog();
}
// appends FSW array with a new created FSW
private void addFileSystemWatcher(String pathToWatch)
{
// get a FSW object
FileSystemWatcher fsw = new FileSystemWatcher();
try
{
// setup object
fsw.BeginInit();
fsw.Path = pathToWatch;
// continue setup object
fsw.IncludeSubdirectories = true;
fsw.Filter = "*.*"; // we do our own filtering
fsw.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.Size | NotifyFilters.FileName;
fsw.Renamed += new RenamedEventHandler(fileSystemWatcher_Renamed);
fsw.Changed += new FileSystemEventHandler(fileSystemWatcher1_Changed);
fsw.Created += new FileSystemEventHandler(fileSystemWatcher_Created);
fsw.Deleted += new FileSystemEventHandler(fileSystemWatcher_Deleted);
fsw.Error += new ErrorEventHandler(fileSystemWatcher_Error);
fsw.EnableRaisingEvents = true;
fsw.SynchronizingObject = this; // this thread creates the FSW -> this thread must handle FSW events
fsw.EndInit();
}
catch (Exception e)
{
trace("addFileSystemWatcher() Cannot apply path: " + pathToWatch + " " + e.Message, "FSW Error");
MessageBox.Show("Cannot apply path: " + pathToWatch + "\n" + e.Message, "FSW Error");
fsw.Dispose();
return;
}
// append object to array
watchers.Add(fsw);
// log to file
trace("addFileSystemWatcher() added path: " + pathToWatch);
}
// searches inside FSW array for given string
// if found, associated FSW is removed from array
private void removeFileSystemWatcher(String pathToWatch)
{
// iterate all FSW's
foreach (FileSystemWatcher f in watchers)
{
String s = f.Path.ToString();
// is given string same as FSW's string?
if (s.CompareTo(pathsToWatch) == 0)
{
// yes, remove FSW
f.Dispose();
watchers.Remove(f);
trace("removeFileSystemWatcher() removed path: " + s);
}
}
}
// clear FSW array
private void removeAllFileSytemWatchers()
{
trace("removeAllFileSytemWatchers()");
foreach (FileSystemWatcher f in watchers)
{
f.Dispose();
}
watchers.Clear();
}
// reset and setup FSW array with given array of strings
public void rebuildFileSystemWatchersList(PathContainer newPaths)
{
trace("rebuildFileSystemWatchersList()");
// clear FSW-array
removeAllFileSytemWatchers();
// overtake given strings to our private member variable
pathsToWatch = newPaths;
// iterate strings
foreach (String s in pathsToWatch.paths)
{
// append new FSW to FSW-array
addFileSystemWatcher(s);
}
// show some text in window's status bar
updateStatusBarText();
}
// build a string to be shown in lower left window area
private void updateStatusBarText()
{
// setup info text
int watcherCount = watchers.Count;
int notifyCount = dataGridView1.Rows.Count;
String msg = "Paths Watching: " + watcherCount;
msg += " | Total Notifications: " + totalIncommingNotificationEvents;
msg += " | Shown: " + notifyCount;
msg += " | Filtered: " + (totalIncommingNotificationEvents - notifyCount);
// show info text
toolStripStatusLabel1.Text = msg;
}
// user rightClicked and selected an item from context menu (main window)
// user wants to close application (this)
private void exitToolStripMenuItem1_Click(object sender, EventArgs e)
{
// clean up, save options and exit
cleanUpAndSafeConfig();
Application.Exit();
}
// user rightClicked and selected an item from context menu (main window)
// user wants to show option-window
private void optionsToolStripMenuItem1_Click(object sender, EventArgs e)
{
options.ShowDialog();
}
// user rightClicked and selected an item from context menu (main window)
// user wants to show help-window
private void helpToolStripMenuItem_Click(object sender, EventArgs e)
{
string msg = "";
msg += "This program monitors files and folders on your computer or network." + Environment.NewLine;
msg += "Executed in background, it supervises file and folder changes (create, delete, change)." + Environment.NewLine;
msg += "Right click in main window, then select 'Edit Paths...' to configure directories you want to watch." + Environment.NewLine;
MessageBox.Show(msg, "FSW Help", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
// user rightClicked and selected an item from context menu (systemTrayIcon)
// user wants to show option-window
private void optionsToolStripMenuItem_Click(object sender, EventArgs e)
{
options.ShowDialog();
}
// user rightClicked and selected an item from context menu (systemTrayIcon)
// user wants to show main window (this)
private void showToolStripMenuItem_Click(object sender, EventArgs e)
{
notifyIcon1_DoubleClick(null, null);
}
// user rightClicked and selected an item from context menu (systemTrayIcon)
// user wants to close application (this)
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
// clean up, save options and exit
cleanUpAndSafeConfig();
Application.Exit();
}
// user wants to close main window
// can be canceled, if user enabled "close to systemTray"
private void MainWindow_FormClosing(object sender, FormClosingEventArgs e)
{
// event comes from user?
if (e.CloseReason == CloseReason.UserClosing)
{
// user do not want to exit but minimize window?
if (options.MinimizeToTrayOnCloseWindow)
{
// yes, minimize and discard event
e.Cancel = true;
WindowState = FormWindowState.Minimized;
Hide();
}
else
{
// no, clean up and save options
cleanUpAndSafeConfig();
}
}
}
// user double clicked systemTray icon
// user wants to see main window (this)
private void notifyIcon1_DoubleClick(object sender, EventArgs e)
{
// bring window to normal state and set fokus
Show();
WindowState = FormWindowState.Normal;
Activate();
}
// user single clicked systemTray icon
// user wants to show/hide main window (this)
private void notifyIcon1_MouseClick(object sender, MouseEventArgs e)
{
// left mouse button used?
if (e.Button == MouseButtons.Left)
{
// yes, check if this window is currently minimized
if (WindowState == FormWindowState.Minimized)
{
// yes, bring window to normal state and set fokus
Show();
WindowState = FormWindowState.Normal;
Activate();
}
else
{
// no, minimize window
Hide();
WindowState = FormWindowState.Minimized;
}
}
}
// safe config file
// remove watchers from system
// free objects
private void cleanUpAndSafeConfig()
{
trace("cleanUpAndSafeConfig()");
options.safe("FSWConfig.txt");
notifyIcon1.Visible = false;
notifyIcon1.Dispose();
removeAllFileSytemWatchers();
notifyIcon1.Dispose();
}
// user pressed a key
// check if we should open focused element
// check if we should minimize window
private void dataGridView1_KeyDown(object sender, KeyEventArgs e)
{
// check if user wants to open file currently focused in dataGridView
if (e.KeyCode == Keys.Return)
{
// check if any row is selected at all
if (null != ((DataGridView)sender).CurrentRow)
{
// get name of file, user want to open
int rowIndex = ((DataGridView)sender).CurrentRow.Index;
// check range of index (could be header, i.e. -1)
if (rowIndex >= 0 && rowIndex < dataGridView1.Rows.Count)
{
String s = dataGridView1.Rows[rowIndex].Cells["MyColumnName"].Value.ToString();
startProcess(s, null);
// indicate our handling to caller
e.Handled = true;
}
}
}
// check if user wants to minimize application window
else if (e.KeyCode == Keys.Escape)
{
WindowState = FormWindowState.Minimized;
MainWindow_Resize(sender, (EventArgs)e);
}
}
// user deleted row(s)
private void dataGridView1_UserDeletedRow(object sender, DataGridViewRowEventArgs e)
{
// show some text in window's status bar
updateStatusBarText();
}
}
}