/// <summary>
        /// Discover and fill in the project info
        /// </summary>
        public static void FillProjectInfo()
        {
            DateTime StartTime = DateTime.Now;

            List <DirectoryInfo> DirectoriesToSearch = GetNonForeignProjectBaseDirs().Select(x => new DirectoryInfo(x.FullName)).ToList();

            Log.TraceVerbose("\tFound {0} directories to search", DirectoriesToSearch.Count);

            foreach (DirectoryInfo DirToSearch in DirectoriesToSearch)
            {
                if (DirToSearch.Exists)
                {
                    foreach (DirectoryInfo SubDir in DirToSearch.EnumerateDirectories())
                    {
                        foreach (FileInfo UProjFile in SubDir.EnumerateFiles("*.uproject", SearchOption.TopDirectoryOnly))
                        {
                            AddProject(new FileReference(UProjFile));
                        }
                    }
                }
                else
                {
                    Log.TraceVerbose("ProjectInfo: Skipping directory {0} from .uprojectdirs file as it doesn't exist.", DirToSearch);
                }
            }

            DateTime StopTime = DateTime.Now;

            if (UnrealBuildTool.bPrintPerformanceInfo)
            {
                TimeSpan TotalProjectInfoTime = StopTime - StartTime;
                Log.TraceInformation("FillProjectInfo took {0} milliseconds", TotalProjectInfoTime.TotalMilliseconds);
            }
        }
Example #2
0
        // 재귀 단계 조정
        //private static void ProcessDir( string SourceDir, int ReCursionLevel )
        //{
        //    //if( ReCursionLevel <= HowDeepToScan )
        //    if(  ReCursionLevel <= HowDeepToScan )
        //    {
        //        // Process the list of files found in the directory.
        //        fileEntries = Directory.GetFiles( SourceDir );

        //        foreach( string fileName in fileEntries )
        //        {
        //            using( StreamWriter fs = new StreamWriter( @"G:\Baidu\allfile.txt", true ) )
        //            {
        //                fs.WriteLine( fileName );
        //            }
        //            // do something with fileName
        //            //Console.WriteLine( fileName );
        //        }

        //        // Recurse into subdirectories of this directory.
        //        SubDirectoryEntries = Directory.GetDirectories( SourceDir );
        //        foreach( string SubDirectory in SubDirectoryEntries )
        //            // Do not iterate through reparse points
        //            if( ( File.GetAttributes( SubDirectory ) &
        //                 FileAttributes.ReparsePoint ) !=
        //                     FileAttributes.ReparsePoint )

        //                ProcessDir( SubDirectory, ReCursionLevel + 1 );
        //    }
        //}

        // 일반적으로 재귀 방식을 쓰지만 복잡하거나 중첩 규모가 크면 스택 오버 플로우 발생 가능성
        private static void TraverseTree(string SourceDir)
        {
            Stack <string> dirs = new Stack <string>(20);

            if (!Directory.Exists(SourceDir))
            {
                throw new DirectoryNotFoundException();
            }

            // 스택에 소스 경로 넣기( Push )
            dirs.Push(SourceDir);

            while (dirs.Count > 0)
            {
                string   CurrentDir = dirs.Pop();
                string[] SubDirs    = null;

                try
                {
                    SubDirs = Directory.GetDirectories(CurrentDir);
                    foreach (var SubDir in SubDirs)
                    {
                        DeleteSubDirs.Push(SubDir.ToString());
                        //Console.WriteLine(SubDir.ToString() );
                    }
                }
                catch (UnauthorizedAccessException e)
                {
                    Debug.WriteLine(e.Message);
                }

                string[] files = null;
                try
                {
                    files = Directory.GetFiles(CurrentDir);
                }
                catch (UnauthorizedAccessException e)
                {
                    Debug.WriteLine(e.Message);
                }

                foreach (string file in files)
                {
                    try
                    {
                        FileInfo fi = new FileInfo(file);
                        //Console.WriteLine( "{0}: {1}, {2}", fi.Name, fi.Length, fi.CreationTime );
                    }
                    catch (FileNotFoundException e)
                    {
                        Debug.WriteLine(e.Message);
                    }
                }

                foreach (string SubDir in SubDirs)
                {
                    dirs.Push(SubDir);
                }
            }
        }
 /// <inheritdoc/>
 public override int GetHashCode()
 {
     unchecked
     {
         // NOTE: Exclude Path from comparison to allow easy testing with randomized TemporaryFiles
         int hashCode = (SubDir != null ? SubDir.GetHashCode() : 0);
         hashCode = (hashCode * 397) ^ (Destination != null ? Destination.GetHashCode() : 0);
         hashCode = (hashCode * 397) ^ (MimeType != null ? MimeType.GetHashCode() : 0);
         hashCode = (hashCode * 397) ^ StartOffset.GetHashCode();
         hashCode = (hashCode * 397) ^ (OriginalSource != null ? OriginalSource.GetHashCode() : 0);
         return(hashCode);
     }
 }
    /// <summary>
    /// Finds files to stage under a given base directory.
    /// </summary>
    /// <param name="BaseDir">The directory to search under</param>
    /// <param name="Pattern">Pattern for files to match</param>
    /// <param name="ExcludePatterns">Patterns to exclude from staging</param>
    /// <param name="Option">Options for the search</param>
    /// <param name="Files">List to receive the enumerated files</param>
    private void FindFilesToStageInternal(DirectoryReference BaseDir, string Pattern, StageFilesSearch Option, List <FileReference> Files)
    {
        // Enumerate all the files in this directory
        Files.AddRange(DirectoryReference.EnumerateFiles(BaseDir, Pattern));

        // Recurse through subdirectories if necessary
        if (Option == StageFilesSearch.AllDirectories)
        {
            foreach (DirectoryReference SubDir in DirectoryReference.EnumerateDirectories(BaseDir))
            {
                string Name = SubDir.GetDirectoryName();
                if (!RestrictedFolderNames.Contains(Name))
                {
                    FindFilesToStageInternal(SubDir, Pattern, Option, Files);
                }
            }
        }
    }
Example #5
0
            /// <summary>
            /// Removes any directory at the specified path which has a file  matching the provided name
            /// older than the specified number of days. Used by code that writes a .token file to temp
            /// folders
            /// </summary>
            /// <param name="InPath"></param>
            /// <param name="FileName"></param>
            /// <param name="Days"></param>
            public static void CleanupMarkedDirectories(string InPath, int Days)
            {
                DirectoryInfo Di = new DirectoryInfo(InPath);

                if (Di.Exists == false)
                {
                    return;
                }

                foreach (DirectoryInfo SubDir in Di.GetDirectories())
                {
                    bool HasFile =
                        SubDir.GetFiles().Where(F => {
                        int DaysOld = (DateTime.Now - F.LastWriteTime).Days;

                        if (DaysOld >= Days)
                        {
                            // use the old and new tokennames
                            return(string.Equals(F.Name, "gauntlet.tempdir", StringComparison.OrdinalIgnoreCase) ||
                                   string.Equals(F.Name, "gauntlet.token", StringComparison.OrdinalIgnoreCase));
                        }

                        return(false);
                    }).Count() > 0;

                    if (HasFile)
                    {
                        Log.Info("Removing old directory {0}", SubDir.Name);
                        try
                        {
                            SubDir.Delete(true);
                        }
                        catch (Exception Ex)
                        {
                            Log.Warning("Failed to remove old directory {0}. {1}", SubDir.FullName, Ex.Message);
                        }
                    }
                    else
                    {
                        CleanupMarkedDirectories(SubDir.FullName, Days);
                    }
                }
            }
Example #6
0
        // 일반적으로 재귀 방식을 쓰지만 복잡하거나 중첩 규모가 크면 스택 오버 플로우 발생 가능성
        private void TraverseTree(string[] SourceDirs)
        {
            Stack <string> dirs = new Stack <string>(30);

            // 스택에 소스 경로 넣기( Push )
            foreach (var SourceDir in SourceDirs)
            {
                dirs.Push(SourceDir);
            }

            while (dirs.Count > 0)
            {
                string   CurrentDir = dirs.Pop();
                string[] SubDirs    = null;
                string[] files      = null;

                try
                {
                    SubDirs = Directory.GetDirectories(CurrentDir);
                    foreach (var SubDir in SubDirs)
                    {
                        // 폴더 일반 속성으로 변경
                        File.SetAttributes(SubDir, FileAttributes.Normal);

                        // 스택에 폴더 넣기( 후입선출 - LIFO )
                        DeleteSubDirs.Push(SubDir.ToString());
                    }
                    files = Directory.GetFiles(CurrentDir);
                    ListView_AddFile(files);
                }
                catch (UnauthorizedAccessException e)
                {
                    Debug.WriteLine(e.Message);
                }

                foreach (string SubDir in SubDirs)
                {
                    dirs.Push(SubDir);
                }
            }
        }
Example #7
0
        internal void CopyDirectoryTree(string FromDir,
                                        string FromDirBaseString,
                                        string ToDirBaseString)
        {
            try
            {
                if (!Directory.Exists(ToDirBaseString))
                {
                    Directory.CreateDirectory(ToDirBaseString);
                    ShowStatus(ToDirBaseString);
                }

                string [] SubDirEntries = Directory.GetDirectories(FromDir);
                foreach (string SubDir in SubDirEntries)
                {
                    if (!MForm.CheckEvents())
                    {
                        return;
                    }

                    string NewDir = SubDir.Replace(
                        FromDirBaseString, ToDirBaseString);

                    if (!Directory.Exists(NewDir))
                    {
                        Directory.CreateDirectory(NewDir);
                    }

                    ShowStatus(NewDir);

                    // Call itself recursively.
                    CopyDirectoryTree(SubDir, FromDirBaseString,
                                      ToDirBaseString);
                }
            }
            catch (Exception Except)
            {
                ShowStatus("Exception in SearchOneDirectory():");
                ShowStatus(Except.Message);
            }
        }
Example #8
0
 /// <summary>
 /// Searches a directory tree for build products to be cleaned.
 /// </summary>
 /// <param name="BaseDir">The directory to search</param>
 /// <param name="NamePrefixes">Target or application names that may appear at the start of the build product name (eg. "UE4Editor", "ShooterGameEditor")</param>
 /// <param name="NameSuffixes">Suffixes which may appear at the end of the build product name</param>
 /// <param name="FilesToClean">List to receive a list of files to be cleaned</param>
 /// <param name="DirectoriesToClean">List to receive a list of directories to be cleaned</param>
 public void FindBuildProductsToClean(DirectoryReference BaseDir, string[] NamePrefixes, string[] NameSuffixes, List <FileReference> FilesToClean, List <DirectoryReference> DirectoriesToClean)
 {
     foreach (FileReference File in DirectoryReference.EnumerateFiles(BaseDir))
     {
         string FileName = File.GetFileName();
         if (IsDefaultBuildProduct(FileName, NamePrefixes, NameSuffixes) || IsBuildProduct(FileName, NamePrefixes, NameSuffixes))
         {
             FilesToClean.Add(File);
         }
     }
     foreach (DirectoryReference SubDir in DirectoryReference.EnumerateDirectories(BaseDir))
     {
         string SubDirName = SubDir.GetDirectoryName();
         if (IsBuildProduct(SubDirName, NamePrefixes, NameSuffixes))
         {
             DirectoriesToClean.Add(SubDir);
         }
         else
         {
             FindBuildProductsToClean(SubDir, NamePrefixes, NameSuffixes, FilesToClean, DirectoriesToClean);
         }
     }
 }
Example #9
0
            /// <summary>
            /// Copies src to dest by comparing files sizes and time stamps and only copying files that are different in src. Basically a more flexible
            /// robocopy
            /// </summary>
            /// <param name="SourcePath"></param>
            /// <param name="DestPath"></param>
            /// <param name="Verbose"></param>
            public static void CopyDirectory(string SourceDirPath, string DestDirPath, CopyOptions Options, Func <string, string> Transform, int RetryCount = 5)
            {
                DateTime StartTime = DateTime.Now;

                DirectoryInfo SourceDir = new DirectoryInfo(SourceDirPath);
                DirectoryInfo DestDir   = new DirectoryInfo(DestDirPath);

                if (DestDir.Exists == false)
                {
                    DestDir = Directory.CreateDirectory(DestDir.FullName);
                }

                System.IO.FileInfo[] SourceFiles = SourceDir.GetFiles("*", SearchOption.AllDirectories);
                System.IO.FileInfo[] DestFiles   = DestDir.GetFiles("*", SearchOption.AllDirectories);

                // Convert dest into a map of relative paths to absolute
                Dictionary <string, System.IO.FileInfo> DestStructure = new Dictionary <string, System.IO.FileInfo>();

                foreach (FileInfo Info in DestFiles)
                {
                    string RelativePath = Info.FullName.Replace(DestDir.FullName, "");

                    // remove leading seperator
                    if (RelativePath.First() == Path.DirectorySeparatorChar)
                    {
                        RelativePath = RelativePath.Substring(1);
                    }

                    DestStructure[RelativePath] = Info;
                }

                // List of relative-path files to copy to dest
                List <string> CopyList = new List <string>();

                // List of relative path files in dest to delete
                List <string> DeletionList = new List <string>();

                foreach (FileInfo SourceInfo in SourceFiles)
                {
                    string SourceFilePath = SourceInfo.FullName.Replace(SourceDir.FullName, "");

                    // remove leading seperator
                    if (SourceFilePath.First() == Path.DirectorySeparatorChar)
                    {
                        SourceFilePath = SourceFilePath.Substring(1);
                    }

                    string DestFilePath = Transform(SourceFilePath);

                    if (DestStructure.ContainsKey(DestFilePath) == false)
                    {
                        // No copy in dest, add it to the list
                        CopyList.Add(SourceFilePath);
                    }
                    else
                    {
                        // Check the file is the same version
                        FileInfo DestInfo = DestStructure[DestFilePath];

                        // Difference in ticks. Even though we set the dest to the src there still appears to be minute
                        // differences in ticks. 1ms is 10k ticks...
                        Int64 TimeDelta  = Math.Abs(DestInfo.LastWriteTime.Ticks - SourceInfo.LastWriteTime.Ticks);
                        Int64 Threshhold = 100000;

                        if (DestInfo.Length != SourceInfo.Length ||
                            TimeDelta > Threshhold)
                        {
                            CopyList.Add(SourceFilePath);
                        }

                        // Remove it from the map
                        DestStructure.Remove(DestFilePath);
                    }
                }

                // If set to mirror, delete all the files that were not in source
                if ((Options & CopyOptions.Mirror) == CopyOptions.Mirror)
                {
                    // Now go through the remaining map items and delete them
                    foreach (var Pair in DestStructure)
                    {
                        DeletionList.Add(Pair.Key);
                    }

                    foreach (string RelativePath in DeletionList)
                    {
                        FileInfo DestInfo = new FileInfo(Path.Combine(DestDir.FullName, RelativePath));

                        Log.Verbose("Deleting extra file {0}", DestInfo.FullName);

                        try
                        {
                            // avoid an UnauthorizedAccessException by making sure file isn't read only
                            DestInfo.IsReadOnly = false;
                            DestInfo.Delete();
                        }
                        catch (Exception Ex)
                        {
                            Log.Warning("Failed to delete file {0}. {1}", DestInfo.FullName, Ex);
                        }
                    }

                    // delete empty directories
                    DirectoryInfo DestDirInfo = new DirectoryInfo(DestDirPath);

                    DirectoryInfo[] AllSubDirs = DestDirInfo.GetDirectories("*", SearchOption.AllDirectories);

                    foreach (DirectoryInfo SubDir in AllSubDirs)
                    {
                        try
                        {
                            if (SubDir.GetFiles().Length == 0 && SubDir.GetDirectories().Length == 0)
                            {
                                Log.Verbose("Deleting empty dir {0}", SubDir.FullName);

                                SubDir.Delete(true);
                            }
                        }
                        catch (Exception Ex)
                        {
                            // handle the case where a file is locked
                            Log.Info("Failed to delete directory {0}. {1}", SubDir.FullName, Ex);
                        }
                    }
                }

                CancellationTokenSource CTS = new CancellationTokenSource();

                // todo - make param..
                var POptions = new ParallelOptions {
                    MaxDegreeOfParallelism = 1, CancellationToken = CTS.Token
                };

                // install a cancel handler so we can stop parallel-for gracefully
                Action CancelHandler = delegate()
                {
                    CTS.Cancel();
                };

                Globals.AbortHandlers.Add(CancelHandler);

                // now do the work
                Parallel.ForEach(CopyList, POptions, RelativePath =>
                {
                    // ensure path exists
                    string DestPath = Path.Combine(DestDir.FullName, RelativePath);

                    if (Transform != null)
                    {
                        DestPath = Transform(DestPath);
                    }

                    FileInfo DestInfo = new FileInfo(DestPath);
                    FileInfo SrcInfo  = new FileInfo(Path.Combine(SourceDir.FullName, RelativePath));

                    // ensure directory exists
                    DestInfo.Directory.Create();

                    string DestFile = DestInfo.FullName;

                    if (Transform != null)
                    {
                        DestFile = Transform(DestFile);
                    }

                    int Tries   = 0;
                    bool Copied = false;

                    do
                    {
                        try
                        {
                            Log.Verbose("Copying to {0}", DestFile);

                            SrcInfo.CopyTo(DestFile, true);

                            // Clear and read-only attrib and set last write time
                            FileInfo DestFileInfo      = new FileInfo(DestFile);
                            DestFileInfo.IsReadOnly    = false;
                            DestFileInfo.LastWriteTime = SrcInfo.LastWriteTime;
                            Copied = true;
                        }
                        catch (Exception ex)
                        {
                            if (Tries++ < RetryCount)
                            {
                                Log.Info("Copy to {0} failed, retrying {1} of {2} in 30 secs..", DestFile, Tries, RetryCount);
                                // todo - make param..
                                Thread.Sleep(30000);
                            }
                            else
                            {
                                Log.Error("File Copy failed with {0}.", ex.Message);
                                throw new Exception(string.Format("File Copy failed with {0}.", ex.Message));
                            }
                        }
                    } while (Copied == false);
                });

                TimeSpan Duration = DateTime.Now - StartTime;

                if (Duration.TotalSeconds > 10)
                {
                    Log.Verbose("Copied Directory in {0}", Duration.ToString(@"mm\m\:ss\s"));
                }

                // remove cancel handler
                Globals.AbortHandlers.Remove(CancelHandler);
            }
Example #10
0
        // 일반적으로 재귀 방식을 쓰지만 복잡하거나 중첩 규모가 크면 스택 오버 플로우 발생 가능성
        private void TraverseTree(string[] SourceDirs)
        {
            Stack <string> dirs = new Stack <string>(30);

            // 스택에 소스 경로 넣기( Push )
            foreach (var SourceDir in SourceDirs)
            {
                dirs.Push(SourceDir);
            }

            while (dirs.Count > 0)
            {
                string   CurrentDir = dirs.Pop();
                string[] SubDirs    = null;
                string[] files      = null;

                try
                {
                    SubDirs = Directory.GetDirectories(CurrentDir);
                    foreach (var SubDir in SubDirs)
                    {
                        // 폴더 일반 속성으로 변경
                        File.SetAttributes(SubDir, FileAttributes.Normal);

                        // 스택에 폴더 넣기( 선입후출 - FILO )
                        DeleteSubDirs.Push(SubDir.ToString());
                    }
                    files = Directory.GetFiles(CurrentDir);
                    ListView_AddFile(files);
                }
                catch (UnauthorizedAccessException e)
                {
                    Debug.WriteLine(e.Message);
                }


                //try
                //{
                //    files = Directory.GetFiles( CurrentDir );
                //    ListView_AddFile( files );
                //}
                //catch( UnauthorizedAccessException e )
                //{
                //    Debug.WriteLine( e.Message );
                //}

                //foreach( string file in files )
                //{
                //    try
                //    {
                //FileInfo fi = new FileInfo( file );
                //Console.WriteLine( "{0}: {1}, {2}", fi.Name, fi.Length, fi.CreationTime );
                //    }
                //    catch( FileNotFoundException e )
                //    {
                //        Debug.WriteLine( e.Message );
                //    }
                //}
                foreach (string SubDir in SubDirs)
                {
                    dirs.Push(SubDir);
                }
            }
        }
Example #11
0
        /// <summary>
        /// Discover and fill in the project info
        /// </summary>
        public static void FillProjectInfo()
        {
            DateTime StartTime = DateTime.Now;

            List <DirectoryInfo> DirectoriesToSearch = new List <DirectoryInfo>();

            // Find all the .uprojectdirs files contained in the root folder and add their entries to the search array
            string EngineSourceDirectory = Path.GetFullPath(Path.Combine(RootDirectory, "Engine", "Source"));

            foreach (FileReference ProjectDirsFile in DirectoryReference.EnumerateFiles(UnrealBuildTool.RootDirectory, "*.uprojectdirs", SearchOption.TopDirectoryOnly))
            {
                Log.TraceVerbose("\tFound uprojectdirs file {0}", ProjectDirsFile.FullName);
                foreach (string Line in File.ReadAllLines(ProjectDirsFile.FullName))
                {
                    string TrimLine = Line.Trim();
                    if (!TrimLine.StartsWith(";"))
                    {
                        DirectoryReference BaseProjectDir = DirectoryReference.Combine(UnrealBuildTool.RootDirectory, TrimLine);
                        if (BaseProjectDir.IsUnderDirectory(UnrealBuildTool.RootDirectory))
                        {
                            DirectoriesToSearch.Add(new DirectoryInfo(BaseProjectDir.FullName));
                        }
                        else
                        {
                            Log.TraceWarning("Project search path '{0}' is not under root directory, ignoring.", TrimLine);
                        }
                    }
                }
            }

            Log.TraceVerbose("\tFound {0} directories to search", DirectoriesToSearch.Count);

            foreach (DirectoryInfo DirToSearch in DirectoriesToSearch)
            {
                Log.TraceVerbose("\t\tSearching {0}", DirToSearch.FullName);
                if (DirToSearch.Exists)
                {
                    foreach (DirectoryInfo SubDir in DirToSearch.EnumerateDirectories())
                    {
                        Log.TraceVerbose("\t\t\tFound subdir {0} ({1})", SubDir.FullName, SubDir.Name);
                        foreach (FileInfo UProjFile in SubDir.EnumerateFiles("*.uproject", SearchOption.TopDirectoryOnly))
                        {
                            Log.TraceVerbose("\t\t\t\t{0}", UProjFile.FullName);
                            AddProject(new FileReference(UProjFile));
                        }
                    }
                }
                else
                {
                    Log.TraceVerbose("ProjectInfo: Skipping directory {0} from .uprojectdirs file as it doesn't exist.", DirToSearch);
                }
            }

            DateTime StopTime = DateTime.Now;

            if (UnrealBuildTool.bPrintPerformanceInfo)
            {
                TimeSpan TotalProjectInfoTime = StopTime - StartTime;
                Log.TraceInformation("FillProjectInfo took {0} milliseconds", TotalProjectInfoTime.TotalMilliseconds);
            }
        }
Example #12
0
            /// <summary>
            /// Copies src to dest by comparing files sizes and time stamps and only copying files that are different in src. Basically a more flexible
            /// robocopy
            /// </summary>
            /// <param name="SourcePath"></param>
            /// <param name="DestPath"></param>
            /// <param name="Verbose"></param>
            public static void CopyDirectory(string SourceDirPath, string DestDirPath, CopyDirectoryOptions Options)
            {
                DateTime StartTime = DateTime.Now;

                DirectoryInfo SourceDir = new DirectoryInfo(SourceDirPath);
                DirectoryInfo DestDir   = new DirectoryInfo(DestDirPath);

                if (DestDir.Exists == false)
                {
                    DestDir = Directory.CreateDirectory(DestDir.FullName);
                }

                bool IsMirroring = (Options.Mode & CopyOptions.Mirror) == CopyOptions.Mirror;

                if (IsMirroring && !Options.IsDirectoryPattern)
                {
                    Log.Warning("Can only use mirror with pattern that includes whole directories (e.g. '*')");
                    IsMirroring = false;
                }

                IEnumerable <FileInfo> SourceFiles = null;

                FileInfo[] DestFiles = null;

                // find all files. If a directory get them all, else use the pattern/regex
                if (Options.IsDirectoryPattern)
                {
                    SourceFiles = SourceDir.GetFiles("*", Options.Recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
                }
                else
                {
                    if (Options.Regex == null)
                    {
                        SourceFiles = SourceDir.GetFiles(Options.Pattern, Options.Recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);
                    }
                    else
                    {
                        SourceFiles = SourceDir.GetFiles("*", Options.Recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);

                        SourceFiles = SourceFiles.Where(F => Options.Regex.IsMatch(F.Name));
                    }
                }

                // Convert dest into a map of relative paths to absolute
                Dictionary <string, System.IO.FileInfo> DestStructure = new Dictionary <string, System.IO.FileInfo>();

                if (IsMirroring)
                {
                    DestFiles = DestDir.GetFiles("*", SearchOption.AllDirectories);

                    foreach (FileInfo Info in DestFiles)
                    {
                        string RelativePath = Info.FullName.Replace(DestDir.FullName, "");

                        // remove leading seperator
                        if (RelativePath.First() == Path.DirectorySeparatorChar)
                        {
                            RelativePath = RelativePath.Substring(1);
                        }

                        DestStructure[RelativePath] = Info;
                    }
                }

                // List of relative-path files to copy to dest
                List <string> CopyList = new List <string>();

                // List of relative path files in dest to delete
                List <string> DeletionList = new List <string>();

                foreach (FileInfo SourceInfo in SourceFiles)
                {
                    string SourceFilePath = SourceInfo.FullName.Replace(SourceDir.FullName, "");

                    // remove leading seperator
                    if (SourceFilePath.First() == Path.DirectorySeparatorChar)
                    {
                        SourceFilePath = SourceFilePath.Substring(1);
                    }

                    string DestFilePath = Options.Transform(SourceFilePath);

                    FileInfo DestInfo = null;

                    // We may have destination info if mirroring where we prebuild it all, if not
                    // grab it now
                    if (DestStructure.ContainsKey(DestFilePath))
                    {
                        DestInfo = DestStructure[DestFilePath];
                    }
                    else
                    {
                        string FullDestPath = Path.Combine(DestDir.FullName, DestFilePath);
                        if (File.Exists(FullDestPath))
                        {
                            DestInfo = new FileInfo(FullDestPath);
                        }
                    }

                    if (DestInfo == null)
                    {
                        // No copy in dest, add it to the list
                        CopyList.Add(SourceFilePath);
                    }
                    else
                    {
                        // Check the file is the same version

                        // Difference in ticks. Even though we set the dest to the src there still appears to be minute
                        // differences in ticks. 1ms is 10k ticks...
                        Int64 TimeDelta  = Math.Abs(DestInfo.LastWriteTime.Ticks - SourceInfo.LastWriteTime.Ticks);
                        Int64 Threshhold = 100000;

                        if (DestInfo.Length != SourceInfo.Length ||
                            TimeDelta > Threshhold)
                        {
                            CopyList.Add(SourceFilePath);
                        }
                        else
                        {
                            if (Options.Verbose)
                            {
                                Log.Info("Will skip copy to {0}. File up to date.", DestInfo.FullName);
                            }
                            else
                            {
                                Log.Verbose("Will skip copy to {0}. File up to date.", DestInfo.FullName);
                            }
                        }

                        // Remove it from the map
                        DestStructure.Remove(DestFilePath);
                    }
                }

                // If set to mirror, delete all the files that were not in source
                if (IsMirroring)
                {
                    // Now go through the remaining map items and delete them
                    foreach (var Pair in DestStructure)
                    {
                        DeletionList.Add(Pair.Key);
                    }

                    foreach (string RelativePath in DeletionList)
                    {
                        FileInfo DestInfo = new FileInfo(Path.Combine(DestDir.FullName, RelativePath));

                        if (Options.Verbose)
                        {
                            Log.Info("Deleting extra file {0}", DestInfo.FullName);
                        }
                        else
                        {
                            Log.Verbose("Deleting extra file {0}", DestInfo.FullName);
                        }

                        try
                        {
                            // avoid an UnauthorizedAccessException by making sure file isn't read only
                            DestInfo.IsReadOnly = false;
                            DestInfo.Delete();
                        }
                        catch (Exception Ex)
                        {
                            Log.Warning("Failed to delete file {0}. {1}", DestInfo.FullName, Ex);
                        }
                    }

                    // delete empty directories
                    DirectoryInfo DestDirInfo = new DirectoryInfo(DestDirPath);

                    DirectoryInfo[] AllSubDirs = DestDirInfo.GetDirectories("*", SearchOption.AllDirectories);

                    foreach (DirectoryInfo SubDir in AllSubDirs)
                    {
                        try
                        {
                            if (SubDir.GetFiles().Length == 0 && SubDir.GetDirectories().Length == 0)
                            {
                                if (Options.Verbose)
                                {
                                    Log.Info("Deleting empty dir {0}", SubDir.FullName);
                                }
                                else
                                {
                                    Log.Verbose("Deleting empty dir {0}", SubDir.FullName);
                                }

                                SubDir.Delete(true);
                            }
                        }
                        catch (Exception Ex)
                        {
                            // handle the case where a file is locked
                            Log.Info("Failed to delete directory {0}. {1}", SubDir.FullName, Ex);
                        }
                    }
                }

                CancellationTokenSource CTS = new CancellationTokenSource();

                // todo - make param..
                var POptions = new ParallelOptions {
                    MaxDegreeOfParallelism = 1, CancellationToken = CTS.Token
                };

                // install a cancel handler so we can stop parallel-for gracefully
                Action CancelHandler = delegate()
                {
                    CTS.Cancel();
                };

                Globals.AbortHandlers.Add(CancelHandler);

                // now do the work
                Parallel.ForEach(CopyList, POptions, RelativePath =>
                {
                    // ensure path exists
                    string DestPath = Path.Combine(DestDir.FullName, RelativePath);

                    if (Options.Transform != null)
                    {
                        DestPath = Options.Transform(DestPath);
                    }

                    string SourcePath = Path.Combine(SourceDir.FullName, RelativePath);
                    FileInfo DestInfo;
                    FileInfo SrcInfo;

                    // wrap FileInfo creation with exception handler as can throw and want informative error
                    try
                    {
                        DestInfo = new FileInfo(DestPath);
                        SrcInfo  = new FileInfo(SourcePath);
                    }
                    catch (Exception Ex)
                    {
                        throw new Exception(string.Format("FileInfo creation failed for Source:{0}, Dest:{1}, with: {2}", SourcePath, DestPath, Ex.Message));
                    }

                    // ensure directory exists
                    DestInfo.Directory.Create();

                    string DestFile = DestInfo.FullName;

                    if (Options.Transform != null)
                    {
                        DestFile = Options.Transform(DestFile);
                    }

                    int Tries   = 0;
                    bool Copied = false;

                    do
                    {
                        try
                        {
                            if (Options.Verbose)
                            {
                                Log.Info("Copying to {0}", DestFile);
                            }
                            else
                            {
                                Log.Verbose("Copying to {0}", DestFile);
                            }

                            SrcInfo.CopyTo(DestFile, true);

                            // Clear and read-only attrib and set last write time
                            FileInfo DestFileInfo      = new FileInfo(DestFile);
                            DestFileInfo.IsReadOnly    = false;
                            DestFileInfo.LastWriteTime = SrcInfo.LastWriteTime;
                            Copied = true;
                        }
                        catch (Exception ex)
                        {
                            if (Tries++ < Options.Retries)
                            {
                                Log.Info("Copy to {0} failed, retrying {1} of {2} in 30 secs..", DestFile, Tries, Options.Retries);
                                // todo - make param..
                                Thread.Sleep(30000);
                            }
                            else
                            {
                                using (var PauseEC = new ScopedSuspendECErrorParsing())
                                {
                                    Log.Error("File Copy failed with {0}.", ex.Message);
                                }
                                throw new Exception(string.Format("File Copy failed with {0}.", ex.Message));
                            }
                        }
                    } while (Copied == false);
                });

                TimeSpan Duration = DateTime.Now - StartTime;

                if (Duration.TotalSeconds > 10)
                {
                    if (Options.Verbose)
                    {
                        Log.Info("Copied Directory in {0}", Duration.ToString(@"mm\m\:ss\s"));
                    }
                    else
                    {
                        Log.Verbose("Copied Directory in {0}", Duration.ToString(@"mm\m\:ss\s"));
                    }
                }

                // remove cancel handler
                Globals.AbortHandlers.Remove(CancelHandler);
            }