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:


1using System;
2using System.IO;
3using System.Diagnostics;
4
5namespace BPSoftware
6{
7 class FileSync
8 {
9 private FileSystemWatcher watcher;
10
11 Fields
40
41 Constructors
62
63 Methods
115
116 Events
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

Who says history doesn't repeat itself? I am working on a project that needs to display and access file properties. A quick search through the .NET 2.0 Framework didn't yield anything that popped out. I did however, remember our wonderful friend - the SHELL32 API. Previously, I had posted on using ShellExecuteEX with Delphi (I am a huge Delphi fan; I may not be posting much about it recently, but I did not forget it). Here is that same sample in using C#.


1 public const uint SW_SHOW =0x5;
2 public const uint SEE_MASK_INVOKEIDLIST = 0xC;
3
4 public enum verbs
5 {
6 properties,
7 open,
8 edit,
9 explore,
10 print
11 }

12
13 [StructLayout(LayoutKind.Sequential)]
14 public struct SHELLEXECUTEINFO
15 {
16 public int cbSize;
17 public uint fMask;
18 public IntPtr hwnd;
19 public String lpVerb;
20 public String lpFile;
21 public String lpParameters;
22 public String lpDirectory;
23 public uint nShow;
24 public int hInstApp;
25 public int lpIDList;
26 public String lpClass;
27 public int hkeyClass;
28 public uint dwHotKey;
29 public int hIcon;
30 public int hProcess;
31 }

32
33 [DllImport("shell32.dll")]
34 static extern bool ShellExecuteEx(ref SHELLEXECUTEINFO lpExecInfo);
35
36 public void MyShellExecuteInfo(string filename, verbs verb)
37 {
38 SHELLEXECUTEINFO info = new SHELLEXECUTEINFO();
39 info.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(info);
40 switch (verb)
41 {
42 case verbs.properties:
43 info.lpVerb = "properties";
44 break;
45 case verbs.print:
46 info.lpVerb = "print";
47 break;
48 case verbs.open:
49 info.lpVerb = "open";
50 break;
51 case verbs.explore:
52 info.lpVerb = "explore";
53 break;
54 case verbs.edit:
55 info.lpVerb = "edit";
56 break;
57 }

58
59 info.lpFile = filename;
60 info.nShow = SW_SHOW;
61 info.fMask = SEE_MASK_INVOKEIDLIST;
62 ShellExecuteEx(ref info);
63 }

64