Let's Synchronize Some Files
Recently, I posted about the new addition to my backup strategy; a NAS. My intended use of this device is to have a hot backup of my files. The system I was using before was not capable of storing the amount of data I need backed up, nor was it easy to manage.
I modified the set of xCopy batch files that I used for my backups to write the files to the NAS.
This system worked fine, but I needed more. I manage a lot of data and have a lot of files. I wouldn't want to lose any in the event of a system failure. The one problem I see with using this process is the lack of a real time solution.
I was in search of a solution that would offer me real-time synchronization between my files and the NAS. I didn't have to search very far. The .NET Framework includes the FileSystemWatcher Class.
The FileSystemWatcher Class "Listens to the file system change notifications and raises events when a directory, or file in a directory, changes." (quoted directly from MSDN) After reading through this class I quickly whipped up a solution. I developed my own FileSync class that allowed me to easily synchronize files between my remote and local storage. I created a Windows Service (threaded) built around my class (the screen shot in this post is from a sample app I used to test my class) so that I could have the FileSync class running and keeping my files in sync without me needing to do anything. Here is the unit I created:
1
using System;
2
using System.IO;
3
using System.Diagnostics;
4
5
namespace BPSoftware
6
...{
7
class FileSync
8
...{
9
private FileSystemWatcher watcher;
10
11
Fields#region Fields
12
private string destpath;
13
public string DestPath
14
...{
15
get ...{ return destpath; }
16
set ...{ destpath = value; }
17
}
18
19
private string sourcepath;
20
public string SourcePath
21
...{
22
get ...{ return sourcepath; }
23
set ...{ sourcepath = value; }
24
}
25
26
private string filter;
27
public string Filter
28
...{
29
get ...{ return filter; }
30
set ...{ filter = value; }
31
}
32
33
private bool watchsubfolders;
34
public bool WatchSubFolders
35
...{
36
get ...{ return watchsubfolders; }
37
set ...{ watchsubfolders = value; }
38
}
39
#endregion
40
41
Constructors#region Constructors
42
public FileSync()
43
...{
44
this.watcher = new FileSystemWatcher();
45
this.watcher.InternalBufferSize = 0x20000;
46
this.watcher.NotifyFilter = NotifyFilters.FileNameNotifyFilters.LastWrite;
47
this.watcher.IncludeSubdirectories = false;
48
this.watcher.Changed += new FileSystemEventHandler(this.OnChanged);
49
this.watcher.Created += new FileSystemEventHandler(this.OnCreated);
50
this.watcher.Deleted += new FileSystemEventHandler(this.OnDeleted);
51
this.watcher.Renamed += new RenamedEventHandler(this.OnRenamed);
52
}
53
54
public FileSync(string source, string destination, string filefilter, bool watchsubs): this()
55
...{
56
this.SourcePath = source;
57
this.DestPath = destination;
58
this.Filter = filefilter;
59
this.WatchSubFolders = watchsubs;
60
}
61
#endregion
62
63
Methods#region Methods
64
private string GetDestinationFile(string infile)
65
...{
66
string fileName = Path.GetFileName(infile);
67
return (this.destpath + fileName);
68
}
69
70
private void LogEvent(string message, int id)
71
...{
72
EventLog.WriteEntry("FileSync", message, EventLogEntryType.Error, id);
73
}
74
75
public void Start()
76
...{
77
try
78
...{
79
if (this.watcher != null)
80
...{
81
this.watcher.Path = this.SourcePath;
82
this.watcher.Filter = this.Filter;
83
this.watcher.IncludeSubdirectories = this.WatchSubFolders;
84
this.watcher.Filter = this.Filter;
85
this.watcher.EnableRaisingEvents = true;
86
}
87
}
88
catch (Exception exception)
89
...{
90
this.LogEvent(exception.Message, 0x01);
91
}
92
}
93
94
public bool Stop()
95
...{
96
try
97
...{
98
if (this.watcher != null)
99
...{
100
this.watcher.EnableRaisingEvents = false;
101
this.watcher.Changed -= new FileSystemEventHandler(this.OnChanged);
102
this.watcher.Created -= new FileSystemEventHandler(this.OnCreated);
103
this.watcher.Deleted -= new FileSystemEventHandler(this.OnDeleted);
104
this.watcher.Renamed -= new RenamedEventHandler(this.OnRenamed);
105
}
106
}
107
catch (Exception exception)
108
...{
109
this.LogEvent(exception.Message, 0x02);
110
return false;
111
}
112
return true;
113
}
114
#endregion
115
116
Events#region Events
117
private void OnChanged(object source, FileSystemEventArgs e)
118
...{
119
try
120
...{
121
File.Copy(e.FullPath, this.GetDestinationFile(e.FullPath), true);
122
}
123
catch (Exception exception)
124
...{
125
this.LogEvent(exception.Message, 0x03);
126
}
127
}
128
129
private void OnCreated(object source, FileSystemEventArgs e)
130
...{
131
try
132
...{
133
File.Copy(e.FullPath, this.GetDestinationFile(e.FullPath));
134
}
135
catch (Exception exception)
136
...{
137
this.LogEvent(exception.Message, 0x04);
138
}
139
}
140
141
private void OnDeleted(object source, FileSystemEventArgs e)
142
...{
143
try
144
...{
145
File.Delete(this.GetDestinationFile(e.FullPath));
146
}
147
catch (Exception exception)
148
...{
149
this.LogEvent(exception.Message, 0x05);
150
}
151
}
152
153
private void OnRenamed(object source, RenamedEventArgs e)
154
...{
155
try
156
...{
157
File.Move(this.GetDestinationFile(e.OldFullPath), this.GetDestinationFile(e.FullPath));
158
}
159
catch (Exception exception)
160
...{
161
this.LogEvent(exception.Message, 0x06);
162
}
163
}
164
#endregion
165
}
166
}
167
DSOFile and Summary Properties
Yesterday I posted about using ShellExecuteEx to display the document properties dialog for a file or folder. Displaying the properties dialog is nice, however I also need to access the OLE document properties (Document Summary Properties), the information that is stored on the Summary and Custom tabs of the properties dialog. I did quite a bit of searching, reading and testing in an attempt to find a simple solution.
I did get to learn a lot about the Document Summary Properties and it looked like my only option was to head down the road using the IPropertyStorage Interface. As I was testing some code I came across the best little ActiveX component that can quickly access the property information. The Dsofile.dll is an ActiveX component is available from Microsoft. They even include a sample application (although the samples are in VB6 and VB7 they are useable). This component simplifies (I like things simple) setting and getting the document summary properties. The KB article and download mention that this is meant for Office files, however I have found that it works fine on other files as well (the exception being that the Office documents seem to be the only types of files with Custom Properties). The other great thing about this component is that "You have a royalty-free right to use, to modify, to reproduce, and to distribute the Dsofile.dll sample file component and the C++ source code files in any way you find useful." That quote is taken directly from the DSOfile.dll Article ID: 224351 that is listed on Microsoft's Support site. My thoughts, why reinvent the wheel when you really don't need to. I highly recommend using this component if you need to access the extended properties. As usual, I whipped up a sample application testing this components functionality. The meager sample code is listed here (image of application screen is also included in this post):
namespace FileProperties1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void btnSelectFile_Click(object sender, EventArgs e)
{
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
textBox1.Text = openFileDialog1.FileName;
toolTip1.SetToolTip(textBox1, textBox1.Text);
lstSummary.Items.Clear();
OleDocumentPropertiesClass odpc = new OleDocumentPropertiesClass();
odpc.Open(
@openFileDialog1.FileName,
false,
dsoFileOpenOptions.dsoOptionOpenReadOnlyIfNoWriteAccess
);
// Summary "Standard" Properties
ListViewItem lvTitle = new ListViewItem();
lvTitle.Group = lstSummary.Groups[0];
lvTitle.Text = "Title";
lvTitle.SubItems.Add(odpc.SummaryProperties.Title);
lstSummary.Items.Add(lvTitle);
ListViewItem lvSubject = new ListViewItem();
lvSubject.Group = lstSummary.Groups[0];
lvSubject.Text = "Subject";
lvSubject.SubItems.Add(odpc.SummaryProperties.Subject);
lstSummary.Items.Add(lvSubject);
ListViewItem lvCategory = new ListViewItem();
lvCategory.Group = lstSummary.Groups[0];
lvCategory.Text = "Category";
lvCategory.SubItems.Add(odpc.SummaryProperties.Category);
lstSummary.Items.Add(lvCategory);
ListViewItem lvKeyword = new ListViewItem();
lvKeyword.Group = lstSummary.Groups[0];
lvKeyword.Text = "Keywords";
lvKeyword.SubItems.Add(odpc.SummaryProperties.Keywords);
lstSummary.Items.Add(lvKeyword);
ListViewItem lvComments = new ListViewItem();
lvComments.Group = lstSummary.Groups[0];
lvComments.Text = "Comments";
lvComments.SubItems.Add(odpc.SummaryProperties.Comments);
lstSummary.Items.Add(lvComments);
ListViewItem lvAuthor = new ListViewItem();
lvAuthor.Group = lstSummary.Groups[1];
lvAuthor.Text = "Author";
lvAuthor.SubItems.Add(odpc.SummaryProperties.Author);
lstSummary.Items.Add(lvAuthor);
ListViewItem lvRevision = new ListViewItem();
lvRevision.Group = lstSummary.Groups[1];
lvRevision.Text = "Revision Number";
lvRevision.SubItems.Add(odpc.SummaryProperties.RevisionNumber);
lstSummary.Items.Add(lvRevision);
// Custom Properties
foreach (DSOFile.CustomProperty cp in odpc.CustomProperties)
{
ListViewItem lvc = new ListViewItem();
lvc.Group = lstSummary.Groups[2];
lvc.Text=cp.Name;
lvc.SubItems.Add(cp.get_Value().ToString());
lstSummary.Items.Add(lvc);
}
odpc.Close(false);
}
}
}
}
ShellExecuteEX and ShellExecuteInfo Revisited