Synchronizing Directories In Code

 What I wanted to do with the implementation was to create a base for all the checks and validation and any child class to provide their own implementation of the algorithm. I decided to leave the IDisposable since later own new check or a base implementation could be added.

/// <summary>

    /// RepositorySynch will contain basic functionality for validation and property holding.

    /// </summary>

    public abstract class RepositorySynch : IDisposable

    {

        // Properties

        public string SourcePath { get; private set; }

        public string DestinationPath { get; private set; }

        public DirectoryInfo SourceDirectory { get; private set; }

        public DirectoryInfo DestinationDirectory { get; private set; }

 

        public RepositorySynch(string SourcePath, string DestinationPath)

        {

            this.SourcePath = SourcePath;

            this.DestinationPath = DestinationPath;

        }

 

        /// <summary>

        /// Validate both source and destination path and create a new instance of the directory info.

        /// </summary>

        protected void ValidatePaths()

        {

            if (Directory.Exists(SourcePath))

                SourceDirectory = new DirectoryInfo(SourcePath);

            else

                throw new DirectoryNotFoundException(string.Format("Unabled to find source: {0}.", SourcePath));

 

            if (Directory.Exists(DestinationPath))

                DestinationDirectory = new DirectoryInfo(DestinationPath);

            else

                throw new DirectoryNotFoundException(string.Format("Unabled to find destination: {0}.", DestinationPath));

        }

 

        public virtual void Synch()

        {

            ValidatePaths();

        }

 

        public void Dispose()

        {

        }

    }

 

 The second code listing illustrates an implementation with a specific logic set and naming convention rules. The idea below was to leverage linq as much as possible to assist in filtering the data and getting down to the files the needed to be copied. 

 

    /// <summary>

    /// InvoiceRepositorySynch will keep two different invoices repositories in synch.

    /// </summary>

    public class InvoiceRepositorySynch : RepositorySynch

    {

        /// <summary>

        /// Specific to this implementation and how the folders are named.

        /// </summary>

        private struct ProcessStruct

        {

            public int Year;

            public int Month;

        }

 

        public InvoiceRepositorySynch(string SourcePath, string DestinationPath)

            : base(SourcePath, DestinationPath)

        {

        }

 

        /// <summary>

        /// Override Synch, still calling the base so we don't have to recall the validation function.

        /// </summary>

        public override void Synch()

        {

            base.Synch();

 

            // Gather Stats - Folder are named as year ex. 2010 and the subfolders are named ex. 12-2010

            // We only want current month and next month.

            int currentYear = DateTime.Now.Year;

            int currentMonth = DateTime.Now.Month;

            int futureMonth = currentMonth + 1;

 

            // Run Current

            Process(currentYear, currentMonth);

   

            // Calculate Next

            if (currentMonth == 12)

            {

                futureMonth = 1;

                currentYear += 1;

            }

 

            // Run Future

            Process(currentYear, futureMonth);

        }

 

        /// <summary>

        /// Synch the folders

        /// </summary>

        /// <param name="year"></param>

        /// <param name="month"></param>

        private void Process(int year, int month)

        {

            try

            {

                ProcessStruct pStruct = new ProcessStruct() { Month = month, Year = year };

 

                // Get the year directory

                DirectoryInfo sourceDir =

                    SourceDirectory.GetDirectories().Where(d => d.Name == pStruct.Year.ToString()).FirstOrDefault();

                DirectoryInfo destinationDir =

                    DestinationDirectory.GetDirectories().Where(d => d.Name == pStruct.Year.ToString()).FirstOrDefault();

 

                if (sourceDir != null && destinationDir != null)

                {

                    // Get the month directory

                    DirectoryInfo sourceMonth =

                        sourceDir.GetDirectories().Where(d => d.Name == ValidName(pStruct)).FirstOrDefault();

                    DirectoryInfo destinationMonth  =

                        destinationDir.GetDirectories().Where(d => d.Name == ValidName(pStruct)).FirstOrDefault();

 

                    if (sourceMonth != null && destinationMonth != null)

                    {

                        // Get the arrays for file info in both directories

                        FileInfo[] sourceFiles = sourceMonth.GetFiles();

                        FileInfo[] destinationFiles = destinationMonth.GetFiles();

 

                        // In this case we only want to perform our last check when the source contains more files

                        if (sourceFiles.Length > destinationFiles.Length)

                        {

                            // Get the filenames array

                            List<string> sourceFileNames = (from f in sourceFiles

                                                            select f.Name).ToList<string>();

                            List<string> destinationFileNames = (from f in destinationFiles

                                                                 select f.Name).ToList<string>();

                            // Get all matches for removal

                            List<string> matches = (from s in sourceFileNames

                                                    join d in destinationFileNames on s equals d

                                                    select s).ToList<string>();

                           

                            // Remove matches to keep a list of all the missing filenames

                            sourceFileNames.RemoveAll(a =>

                                {

                                    return matches.Contains(a);

                                });

 

                            // Copy missing files

                            FileInfo[] filesToCopy = (from f in sourceFiles

                                                      join c in sourceFileNames on f.Name equals c

                                                      select f).ToArray<FileInfo>();

                           

                            foreach (FileInfo f in filesToCopy)

                            {

                                f.CopyTo(destinationMonth.FullName + @"\\" + f.Name);

                            }

                        }

                    }

                }

            }

            catch (Exception ex)

            {

                throw ex;

            }

        }

 

        private string ValidName(ProcessStruct pStruct)

        {

            return string.Format("{0}-{1}", pStruct.Month < 10 ? "0" + pStruct.Month.ToString() : pStruct.Month.ToString(),

                                            pStruct.Year.ToString());

        }

    }

 

 Another way could be to order by the last updated date on the files and only check the files that have change within a certain period of time. I hope this have been helpful and I know that this is just one of many ways to complete this task so please leave comments or suggestions. Thank you in advance.