Click here to Skip to main content
14,216,682 members
Click here to Skip to main content
Tip/Trick
Posted 21 May 2019

Stats

2.4K views
64 downloads
9 bookmarked

File Watching And Backup

,
Rate this:
2.85 (8 votes)
Please Sign up or sign in to vote.
2.85 (8 votes)
21 May 2019     CPOL    
Automation Backup When Detaching File Created, Changed, Deleted

Introduction

At work, I feel like I need to back up my files. Although I also use cloud services, sometimes it make feel uneasy. So I usually put my files into my own portable storage device like SSD memory. The fact that it is required to check out which file is changed is unformfortable, I prefer using it to using those cloud services.  The most uncomfortable thing is that it needs check up the modified files and copy them manually. so, I want to make the program which acts that automatically.

Background

I have a two storage device, D:\ and F:\. The former one is what I really use for my work, and the latter is for backup. This project is designed to watch folders I added into this program and copy files just in case it created, changed, deleted further down the road. When the file is already existed in a destination path, this program will be copy that former file to copy in a backup folder seperately before overwriting it. 

This program is running at a tray, at first.  So after starting this,  just look at your tray bar. You can see the 'MainWindow' for mouse double clicking.

This progam is having another function. That is copying all the file in the folder you added, called 'Sync.'.

You can added the 1-10 folders for watching, those folder's having their own 'Sync.' buttons. You can see that the 'MainWindow'.

For you convenience, I'll attach my project file.

Using the code

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Runtime.InteropServices;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;

using WinForms = System.Windows.Forms;

namespace FileWNB
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        [DllImport("kernel32.dll")]
        public static extern int GetPrivateProfileString(          //ini read
            String section,
            String key,
            String def,
            StringBuilder retVal,
            int size,
            String filepath);

        [DllImport("kernel32.dll")]
        public static extern int WritePrivateProfileString(        //ini write
            String section,
            String key,
            String val,
            String filepath);

        private const String INI_FILE_NAME = "FileWNB.ini";
        private static ObservableCollection<WatchDirectory> _DirectoryList = new ObservableCollection<WatchDirectory>();

        public static ObservableCollection<WatchDirectory> DirectoryList
        {
            get { return MainWindow._DirectoryList; }
            set { MainWindow._DirectoryList = value; }
        }

        private WinForms.NotifyIcon mNofifyIcon;
        private bool mExtFlag;

        public MainWindow()
        {
            InitializeComponent();

            this.DataContext = this;
            mNofifyIcon = null;
            mExtFlag = false;
        }

        private void BackupDestFile(String strDesinationPath, WatcherChangeTypes changeType)
        {
            // create backup information -> {backupdisk}\\WNB_BACKUP\\{backup time}\\{backup time}.ini
            // backup former file overwriting -> {backupdisk}\\WNB_BACKUP\\{backup time}\\{filename}

            // backup time
            String strBackupDirName = DateTime.Now.ToString("yyyyMMddHHmmssfff");

            // backup path
            String strBackupDir = String.Format(
                "{0}WNB_BACKUP\\{1}\\",
                Directory.GetDirectoryRoot(WatchDirectory.BackupPath),
                strBackupDirName);

            if (!Directory.Exists(strBackupDir))
                Directory.CreateDirectory(strBackupDir);

            String strBackupPath = String.Format(
                "{0}{1}", strBackupDir, Path.GetFileName(strDesinationPath));
            
            // file backup
            File.Copy(strDesinationPath, strBackupPath, true);

            String strBackupInfoPath = String.Format(
                "{0}{1}.ini", strBackupDir, strBackupDirName);

            // write backup information into ini file
            WritePrivateProfileString("BACKUP", "CHANGE_TYPE", changeType.ToString(), strBackupInfoPath);
            WritePrivateProfileString("BACKUP", "FILE_NAME", strDesinationPath, strBackupInfoPath);
        }

        #region event handler
        private void OnCommonHander(String strChagedFilePath, WatcherChangeTypes changedType)
        {
            // this function is also called automatically just when OnCreate and OnDelete event (you will find it when debugging it),
            // but passing argment (e.FullPath) is only having a directory path, not file path. 
            // when this is called with no file, ignored
            DirectoryInfo dirInfo = new DirectoryInfo(strChagedFilePath);
            if (dirInfo != null && 
                dirInfo.Attributes == FileAttributes.Directory) return;

            String strRootDir = Directory.GetDirectoryRoot(strChagedFilePath);

            try
            {
                OnShowMsg(String.Format("[{0}]! File: FullPath:'{1}'", changedType, strChagedFilePath));

                // find destination path
                String strDestinationRoot = Directory.GetDirectoryRoot(WatchDirectory.BackupPath);
                String strDestinationPath = strChagedFilePath.Replace(strRootDir, strDestinationRoot);

                if (!File.Exists(strDestinationPath))
                {
                    String strDestinationDir = Path.GetDirectoryName(strDestinationPath);
                    if (!Directory.Exists(strDestinationDir))
                        Directory.CreateDirectory(strDestinationDir);

                    // no backup
                    if (changedType != WatcherChangeTypes.Deleted)
                        File.Copy(strChagedFilePath, strDestinationPath, true);
                }
                else
                {
                    if (changedType != WatcherChangeTypes.Deleted)
                    {
                        FileInfo fsSource = new FileInfo(strChagedFilePath);
                        if(fsSource.Exists)
                        {
                            long nSourceFileSize = fsSource.Length;
                            DateTime dtSourceLast = fsSource.LastWriteTime;

                            FileInfo fsDest = new FileInfo(strDestinationPath);
                            long nDestFileSize = fsDest.Length;
                            DateTime dtDestLast = fsDest.LastWriteTime;

                            if (dtDestLast < dtSourceLast)
                            {
                                // backup first
                                BackupDestFile(strDestinationPath, changedType);

                                File.Copy(strChagedFilePath, strDestinationPath, true);
                            }
                        }
                        else
                        {
                            OnShowMsg(String.Format("[{0}] error! File is not existed: '{1}'", changedType, strChagedFilePath));
                        }
                    }
                    else
                    {
                        // backup first
                        BackupDestFile(strDestinationPath, WatcherChangeTypes.Deleted);

                        // delete
                        File.Delete(strDestinationPath);
                    }
                }
            }
            catch (Exception ex)
            {
                OnShowMsg(String.Format("[{0}] error! File: '{1}', '{2}'", changedType, strChagedFilePath, ex.ToString()));
            }  
        }

        private void OnChanged(object source, FileSystemEventArgs e)
        {
            OnCommonHander(e.FullPath, e.ChangeType);
        }

        private void OnCreated(object source, FileSystemEventArgs e)
        {
            OnCommonHander(e.FullPath, e.ChangeType);
        }

        private void OnDeleted(object source, FileSystemEventArgs e)
        {
            OnCommonHander(e.FullPath, e.ChangeType);
        }

        private void OnRenamed(object source, RenamedEventArgs e)
        {
            // delete
            OnCommonHander(e.OldFullPath, WatcherChangeTypes.Deleted);

            // create
            OnCommonHander(e.FullPath, WatcherChangeTypes.Created);
        }
        #endregion

        private void OnShowMsg(String strMsg)
        {
            try
            {
                txtMsg.Dispatcher.Invoke((Action)delegate
                {
                    txtMsg.Text += (strMsg + "\r\n");
                });

                scMsg.Dispatcher.Invoke((Action)delegate
                {
                    scMsg.ScrollToEnd();
                });
            }
            catch (Exception) { }
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            #region read ini file
            String strINIPath = String.Format("{0}\\{1}", AppDomain.CurrentDomain.BaseDirectory, INI_FILE_NAME);
            StringBuilder sb = new StringBuilder(1024);

            List<WatchData> tmpRegistPath = new List<WatchData>();
            for(int i = 1; i <= 10; i++)
            {
                sb = new StringBuilder(1024);
                GetPrivateProfileString("CONFIG", "WATCH_PATH_" + i.ToString() , "", sb, 1024, strINIPath);
                String strWatchPath = sb.ToString().Trim();

                if (!String.IsNullOrEmpty(strWatchPath))
                {
                    WatchData newData = new WatchData();
                    newData.OnShowMsgHander += OnShowMsg;
                    newData.WatchPath = strWatchPath;

                    if (!tmpRegistPath.Contains(newData))
                        tmpRegistPath.Add(newData);
                }
                
            }
            dgRegisteredPath.ItemsSource = tmpRegistPath;

            if (tmpRegistPath.Count > 0)
                Task.Factory.StartNew(() => threadAction(tmpRegistPath));

            sb = new StringBuilder(1024);
            GetPrivateProfileString("CONFIG", "BACKUP_PATH", "", sb, 1024, strINIPath);
            WatchDirectory.BackupPath = sb.ToString().Trim();
            txtBackupPath.Text = WatchDirectory.BackupPath;
            txtWatchPath.Text = "";
            #endregion

            #region for tray
            WinForms.ContextMenu mnuMain = new WinForms.ContextMenu();            
            WinForms.MenuItem mnuExitProgram = new WinForms.MenuItem();
            mnuExitProgram.Text = "Exit";
            mnuExitProgram.Click += delegate(object notiSender, EventArgs notiE) 
            {
                mExtFlag = true;
                this.Close(); 
            };
            mnuMain.MenuItems.Add(mnuExitProgram);

            mNofifyIcon = new WinForms.NotifyIcon();

            // you must add icon in the resources
            mNofifyIcon.Icon = Properties.Resources.TrayIcon;
            mNofifyIcon.Visible = true;
            mNofifyIcon.DoubleClick += delegate(object notiSender, EventArgs notiE)
            {
                this.Show();
                this.WindowState = WindowState.Maximized;
            };
            mNofifyIcon.ContextMenu = mnuMain;
            mNofifyIcon.Text = "File Watcher and Backup";
            #endregion
        }

        protected override void OnStateChanged(EventArgs e)
        {
            if(WindowState.Minimized.Equals(WindowState))
            {
                this.Hide();
            }
            base.OnStateChanged(e);
        }

        private void threadAction(List<WatchData> aryRegistPath)
        {
            try
            {
                foreach (WatchData wd in aryRegistPath)
                {
                    OnShowMsg(String.Format("starting add '{0}' dir", wd.WatchPath));

                    #region put all child directories in the watchpath to a list

                    // the way one the below is easy to count of all directories
                    // but, it's also easy to gernerate some exceptions (access denied, authorization, and so on)
                    // int nCount = Directory.EnumerateDirectories(strWatchPath, "*.*", SearchOption.AllDirectories).Count();

                    AddDirectory(wd.WatchPath);
                    #endregion
                }

                OnShowMsg("complete adding dirs. starting mornitoring");
            }
            catch (Exception ex)
            {
                OnShowMsg(ex.ToString());
            }
        }

        private void AddDirectory(String strBaseDir)
        {
            try
            {
                WatchDirectory newWatch = new WatchDirectory(strBaseDir);

                // one directory has one watcher
                if (!_DirectoryList.Contains(newWatch))
                {
                    FileSystemWatcher fw = new FileSystemWatcher();

                    fw.Path = strBaseDir;

                    // Watch for changes in LastAccess and LastWrite times, and
                    // the renaming of files or directories.
                    fw.NotifyFilter = NotifyFilters.LastAccess
                                         | NotifyFilters.LastWrite
                                         | NotifyFilters.FileName
                                         | NotifyFilters.DirectoryName;

                    // Only watch text files.
                    //watcher.Filter = "*.txt";

                    fw.Filter = "*.*";

                    // Add event handlers.
                    fw.Changed += OnChanged;
                    fw.Created += OnCreated;
                    fw.Deleted += OnDeleted;
                    fw.Renamed += OnRenamed;

                    // Begin watching.
                    fw.EnableRaisingEvents = true;

                    newWatch.Add(fw);

                    _DirectoryList.Add(newWatch);
                }

                #region go recursive call to get child directorie's count

                List<String> lstDirs = new List<String>(Directory.EnumerateDirectories(strBaseDir));

                // As I have tested, the codes below take 5 minutes for 50000 directories, just in my case (2.39Ghz cpu)
                //foreach (String strDir in lstDirs)
                //{
                //    AddDirectory(strDir);
                //}

                // in this case, It takes only 30 seconds
                Parallel.For(0, lstDirs.Count, (i) =>
                {
                    String strDir = lstDirs[i];

                    // get parent and root directory
                    String strParentDir = Directory.GetParent(strDir).FullName;
                    String strRootDir = Directory.GetDirectoryRoot(strDir);

                    if (!strParentDir.Equals(strRootDir))
                        AddDirectory(strDir);
                    else
                    {
                        // if this directory is a child of root directory and having a hidden attribute,
                        // I recognized it as a system folder

                        FileInfo fs = new FileInfo(strDir);
                        if((fs.Attributes & FileAttributes.Hidden) == 0)
                            AddDirectory(strDir);
                        else
                            OnShowMsg(String.Format("'{0}' is hidden", strDir));
                    }
                });

                #endregion
            }
            catch (Exception e)
            {
                OnShowMsg(String.Format("'{0}' error : {1}", strBaseDir, e.ToString()));
            }
        }

        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            if (mExtFlag)
            {
                foreach (WatchData selectedWatchData in dgRegisteredPath.Items)
                    selectedWatchData.StopSync();
            }
            else
            {
                this.WindowState = WindowState.Minimized;
                e.Cancel = true;
            }
        }

        private void btnSave_Click(object sender, RoutedEventArgs e)
        {
            #region save ini file
            String strINIPath = String.Format("{0}\\{1}", AppDomain.CurrentDomain.BaseDirectory, INI_FILE_NAME);

            int i = 1;
            foreach (WatchData wd in dgRegisteredPath.Items)
            {
                WritePrivateProfileString("CONFIG", "WATCH_PATH_" + i, wd.WatchPath, strINIPath);
                i++;
            }

            for(;i <= 10; i++)
                WritePrivateProfileString("CONFIG", "WATCH_PATH_" + i, "", strINIPath);

            WritePrivateProfileString("CONFIG", "BACKUP_PATH", txtBackupPath.Text.Trim(), strINIPath);
            #endregion
        }

        private void btnAdd_Click(object sender, RoutedEventArgs e)
        {
            String strWatchPath = txtWatchPath.Text.Trim();

            if (!String.IsNullOrEmpty(strWatchPath))
            {
                List<WatchData> tmpRegistPath = dgRegisteredPath.ItemsSource as List<WatchData>;

                WatchData newData = new WatchData();
                newData.OnShowMsgHander += OnShowMsg;
                newData.WatchPath = strWatchPath;

                if (tmpRegistPath.Contains(newData))
                    OnShowMsg(String.Format("add error : '{1}' is already existed. You have to remove that item", newData.WatchPath));
                else if (tmpRegistPath.Count >= 10)
                    OnShowMsg(String.Format("add error : too many. You have to remove one more item", newData.WatchPath));
                else
                {
                    tmpRegistPath.Add(newData);

                    // rebinding
                    dgRegisteredPath.ItemsSource = null;
                    dgRegisteredPath.ItemsSource = tmpRegistPath;
                }
            }
        }

        private void EventSetter_OnHandler(object sender, MouseButtonEventArgs e)
        {
            WatchData selectedWatchData = dgRegisteredPath.SelectedItem as WatchData;
            if(selectedWatchData != null)
            {
                String strHearder = ((DataGridCell)sender).Column.Header.ToString();
                if ("Sync.".Equals(strHearder, StringComparison.OrdinalIgnoreCase))
                {
                    String strErr;
                    if(!selectedWatchData.RunSync(out strErr))
                        OnShowMsg(String.Format("sync error : {0}", strErr));
                    else
                        OnShowMsg(String.Format("'{0}' sync start", selectedWatchData.WatchPath));
                }
                else
                {
                    List<WatchData> tmpRegistPath = new List<WatchData>();

                    foreach (WatchData wd in dgRegisteredPath.ItemsSource)
                    {
                        if (!String.IsNullOrEmpty(selectedWatchData.WatchPath) &&
                            selectedWatchData.WatchPath.Equals(wd.WatchPath)) continue;

                        tmpRegistPath.Add(wd);
                    }

                    // rebinding
                    dgRegisteredPath.ItemsSource = null;
                    dgRegisteredPath.ItemsSource = tmpRegistPath;

                    e.Handled = true;
                }
            }
        }
    }

    public class WatchDirectory
    {
        private static String _BackupPath = "";
        public static String BackupPath
        { 
            get
            {
                if (!_BackupPath.EndsWith("\\")) _BackupPath += "\\";
                return _BackupPath;
            }
            set
            {
                if (!value.EndsWith("\\")) _BackupPath = value + "\\";
                else
                    _BackupPath = value;
            } 
        }

        private String mPath;
        private FileSystemWatcher mWatcher;
        
        private WatchDirectory() { }

        public WatchDirectory(String path) : this()
        {
            mPath = path;
            mWatcher = null;
        }

        public override bool Equals(object obj)
        {
            WatchDirectory wd = obj as WatchDirectory;
            if (wd != null)
                return mPath.Equals(wd.mPath);
            else
                return false;
        }

        public void Add(FileSystemWatcher wc)
        {
            mWatcher = wc;
        }
    }

    public class WatchData
    {
        public delegate void OnShowMsgEvent(String strMsg);
        public OnShowMsgEvent OnShowMsgHander;

        public String WatchPath { get; set; }
        private Thread mSyncTheard;

        public WatchData()
        {
            mSyncTheard = null;
            OnShowMsgHander = null;
        }

        public bool RunSync(out String strErr)
        {
            bool isOK = false;
            strErr = "";

            if(mSyncTheard != null)
            {
                strErr = String.Format("The processing for sync at '{0}'. is already running", WatchPath);
            }
            else
            {
                mSyncTheard = new Thread(() =>
                {
                    String strDestinationPath = "";
                    try
                    {
                        String strRootDir = Directory.GetDirectoryRoot(WatchPath);

                        // find destination path
                        String strDestinationRoot = Directory.GetDirectoryRoot(WatchDirectory.BackupPath);
                        strDestinationPath = WatchPath.Replace(strRootDir, strDestinationRoot);

                        String strErrMsg;
                        if (!Copy(WatchPath, strDestinationPath, out strErrMsg)) OnShowMsgHander(strErrMsg);
                    }
                    catch(ThreadAbortException)
                    {
                        Thread.ResetAbort();
                    }
                    catch(Exception e)
                    {
                        OnShowMsgHander(
                            String.Format("Running Sync error : '{0}', '{1}'", strDestinationPath, e.ToString()));
                    }

                    mSyncTheard = null;
                    OnShowMsgHander(
                        String.Format("Running Sync compelete : '{0}'", strDestinationPath));
                    
                });
                mSyncTheard.Start();

                isOK = true;
            }

            return isOK;
        }

        public void StopSync()
        {
            if (mSyncTheard != null) mSyncTheard.Abort();
        }

        private bool Copy(string sourceDir, string targetDir, out String strErr)
        {
            bool isOK = false;
            strErr = "";

            String strDestinationPath = targetDir;
            try
            {
                if (!Directory.Exists(targetDir))
                    Directory.CreateDirectory(targetDir);

                foreach (var file in Directory.GetFiles(sourceDir))
                {
                    FileInfo fsSource = new FileInfo(file);

                    String strDest = Path.Combine(targetDir, Path.GetFileName(file));                    
                    if (File.Exists(strDest))
                    {
                        long nSourceFileSize = fsSource.Length;
                        DateTime dtSourceLast = fsSource.LastWriteTime;

                        FileInfo fsDest = new FileInfo(strDest);
                        long nDestFileSize = fsDest.Length;
                        DateTime dtDestLast = fsDest.LastWriteTime;

                        // compare source and target 
                        if (nSourceFileSize != nDestFileSize && dtSourceLast != dtDestLast)
                            File.Copy(file, strDest, true);
                    }
                    else
                        File.Copy(file, strDest, true);
                }

                // recursive folder
                foreach (var directory in Directory.GetDirectories(sourceDir))
                {
                    strDestinationPath = System.IO.Path.Combine(targetDir, System.IO.Path.GetFileName(directory));
                    isOK = Copy(directory, strDestinationPath, out strErr);
                    if (!isOK) break;
                }
            }
            catch(Exception e)
            {
                strErr = String.Format("Running Sync error : '{0}', '{1}'", strDestinationPath,  e.ToString());
            }

            return isOK;
        }

        public override bool Equals(object obj)
        {
            if (!String.IsNullOrEmpty(WatchPath))
                return WatchPath.Equals(obj);
            else
                return false;
        }
    }
}

 

Remember that this program must run as administrator.

History

5/21/2019 : First version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Bloody Chicken
Software Developer (Senior) Wiseneosco
Korea (Republic of) Korea (Republic of)
You know I can't speak English well but I'm learning. If there anything wrong in my article. Understand me. Smile | :) I'm so thankful if you could correct this.
Anyway, I'm a software programmer in Korea. I have been doing development about 10 years.
I majored in Computer Science and Engineering. I'm using c# mainly. Bye!

Comments and Discussions

 
QuestionSample Project? Pin
abmv21-May-19 2:17
professionalabmv21-May-19 2:17 
AnswerRe: Sample Project? Pin
mattCertent21-May-19 4:15
membermattCertent21-May-19 4:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.