public FileComparisonResult(FileInfoForComparison fileLeft,
                                    FileInfoForComparison fileRight,
                                    FileComparisonResultType type)
        {
            FileInfoLeft  = fileLeft;
            FileInfoRight = fileRight;
            Type          = type;

            string showPath;

            if (type == FileComparisonResultType.Same_Contents &&
                FileInfoLeft.Filename != FileInfoRight.Filename)
            {
                showPath = StripInitialSlash(FileInfoLeft.Filename) + "; " +
                           StripInitialSlash(FileInfoRight.Filename);
            }
            else
            {
                showPath = StripInitialSlash(FileInfoLeft?.Filename ??
                                             FileInfoRight.Filename);
            }

            ImageIndex = (int)type;
            SubItems.Add(type.ToString().Replace("_", " "));
            SubItems.Add(showPath);

            if (type == FileComparisonResultType.Changed &&
                fileRight.LastModifiedTime > fileLeft.LastModifiedTime)
            {
                // show a red icon if the file on the right is newer.
                ImageIndex = 5;
            }
        }
        public static List <FileComparisonResult> Go(SortFilesSettings settings)
        {
            var filesInLeft = new DirectoryInfo(settings.LeftDirectory).EnumerateFiles(
                "*", SearchOption.AllDirectories);
            var filesInRight = new DirectoryInfo(settings.RightDirectory).EnumerateFiles(
                "*", SearchOption.AllDirectories);

            // first, just make an index that simply maps filesizes to filenames.
            // we don't need to compute any content-hashes yet, because if there
            // is only one file with that filesize, we know it's not a duplicate.
            var results   = new List <FileComparisonResult>();
            var indexLeft = MapFilesizesToFilenames(settings.LeftDirectory, filesInLeft);

            // go through files on the right
            foreach (var infoRight in filesInRight)
            {
                var filenameRight = infoRight.FullName.Substring(
                    settings.RightDirectory.Length);
                var objLeft = FindInMap(indexLeft, settings.LeftDirectory, infoRight.FullName,
                                        infoRight.Length, settings.SearchDuplicatesCanUseFiletimes,
                                        infoRight.LastWriteTimeUtc, filenameRight);

                if (objLeft != null)
                {
                    // these are duplicates, they have the same hash and filesize.
                    var objRight = new FileInfoForComparison(filenameRight,
                                                             infoRight.Length, infoRight.LastWriteTimeUtc, objLeft.ContentHash);
                    results.Add(new FileComparisonResult(
                                    objLeft, objRight, FileComparisonResultType.Same_Contents));
                }
            }

            return(results);
        }
        public static List <FileComparisonResult> Go(SortFilesSettings settings)
        {
            var results     = new List <FileComparisonResult>();
            var filesInLeft = new Dictionary <string, FileInfoForComparison>(
                StringComparer.OrdinalIgnoreCase);

            // go through files in left
            var diLeft = new DirectoryInfo(settings.LeftDirectory);

            foreach (var info in diLeft.EnumerateFiles("*", SearchOption.AllDirectories))
            {
                var filename = info.FullName.Substring(settings.LeftDirectory.Length);
                filesInLeft[filename] = new FileInfoForComparison(
                    filename, info.Length, info.LastWriteTimeUtc);
            }

            // go through files in right
            var diRight = new DirectoryInfo(settings.RightDirectory);

            foreach (var info in diRight.EnumerateFiles("*", SearchOption.AllDirectories))
            {
                var filenameRight = info.FullName.Substring(settings.RightDirectory.Length);
                if (filesInLeft.TryGetValue(filenameRight, out FileInfoForComparison objLeft))
                {
                    objLeft.MarkWhenVisited = true;
                    if (objLeft.FileSize != info.Length ||
                        !AreTimesEqual(objLeft.LastModifiedTime, info.LastWriteTimeUtc, settings))
                    {
                        // looks like a modified file. same path but different filesize/lmt.
                        var filename = info.FullName.Substring(settings.RightDirectory.Length);
                        var objRight = new FileInfoForComparison(
                            filename, info.Length, info.LastWriteTimeUtc);
                        results.Add(new FileComparisonResult(
                                        objLeft, objRight, FileComparisonResultType.Changed));
                    }
                }
                else
                {
                    // looks like a new file
                    var objRight = new FileInfoForComparison(
                        filenameRight, info.Length, info.LastWriteTimeUtc);
                    results.Add(new FileComparisonResult(
                                    null, objRight, FileComparisonResultType.Right_Only));
                }
            }

            // which files did we see in left but not in right?
            foreach (var kvp in filesInLeft)
            {
                if (!kvp.Value.MarkWhenVisited)
                {
                    // looks like a deleted file since it didn't show up on the right.
                    results.Add(new FileComparisonResult(
                                    kvp.Value, null, FileComparisonResultType.Left_Only));
                }
            }

            return(results);
        }
        public static Dictionary <long, List <FileInfoForComparison> > MapFilesizesToFilenames(
            string dirName, IEnumerable <FileInfo> files)
        {
            // map filesize to List<FileInfoForComparison> or HashSet<FileInfoForComparison>?
            // chose List<>; maintaining inserted order makes results that look nicer to the user.
            var map = new Dictionary <long, List <FileInfoForComparison> >();

            foreach (var info in files)
            {
                var filename = info.FullName.Substring(dirName.Length);
                var obj      = new FileInfoForComparison(
                    filename, info.Length, info.LastWriteTimeUtc);

                if (!map.TryGetValue(obj.FileSize, out List <FileInfoForComparison> list))
                {
                    list = map[obj.FileSize] = new List <FileInfoForComparison>();
                }

                list.Add(obj);
            }

            return(map);
        }
        static FileInfoForComparison FindInMap(Dictionary <long, List <FileInfoForComparison> > map,
                                               string baseDir, string fullnameToFind, long lengthOfFileToFind,
                                               bool useLmtShortcut, DateTime lmt, string partialFilename)
        {
            if (lengthOfFileToFind == 0)
            {
                // 0-length files compare unequal; often placeholders intentionally created by user
                return(null);
            }

            if (map.TryGetValue(lengthOfFileToFind, out List <FileInfoForComparison> list))
            {
                // look for an entry with the same filename
                FileInfoForComparison hasSameName = null;
                foreach (var obj in list)
                {
                    if (obj.Filename == partialFilename)
                    {
                        hasSameName = obj;
                        break;
                    }
                }

                // if enabled, treat files with the same filesize, lmt, and name as equal
                if (useLmtShortcut && hasSameName != null &&
                    hasSameName.LastModifiedTime == lmt)
                {
                    return(hasSameName);
                }

                // change order so that an entry with the same name is checked first. why?
                // 1) results look nicer when paired this way
                // 2) same order of results whether or not useLmtShortcut is on
                // (as long as lmt times are accurate). Consider the case with two duplicates,
                // one with the same name, and one that sorts earlier.
                // We prefer to pick the file with the same name to show as the duplicate.
                if (hasSameName != null)
                {
                    list = new List <FileInfoForComparison>(list);
                    list.Insert(0, hasSameName);
                }

                // we found another file(s) with the same filesize, so
                // let's compare hashes of the content to see if they're the same.
                var hash = Utils.GetSha512(fullnameToFind);
                foreach (var obj in list)
                {
                    // compute the hash if it hasn't been computed already, then cache it
                    if (obj.ContentHash == null)
                    {
                        obj.ContentHash = Utils.GetSha512(
                            baseDir + obj.Filename);
                    }

                    if (obj.ContentHash == hash)
                    {
                        return(obj);
                    }
                }
            }

            return(null);
        }