Exemple #1
0
        private void SetScreenSizeInPrefs()
        {
            var config = new IniParserConfiguration {
                AllowDuplicateKeys = true, AllowDuplicateSections = true
            };

            foreach (var file in Directory.EnumerateFiles(Path.Combine(OutputFolder, "profiles"), "*refs.ini",
                                                          DirectoryEnumerationOptions.Recursive))
            {
                try
                {
                    var parser = new FileIniDataParser(new IniDataParser(config));
                    var data   = parser.ReadFile(file);
                    if (data.Sections["Display"] == null)
                    {
                        return;
                    }

                    if (data.Sections["Display"]["iSize W"] != null && data.Sections["Display"]["iSize H"] != null)
                    {
                        data.Sections["Display"]["iSize W"] = SystemParameters.ScreenWidth.ToString(CultureInfo.CurrentCulture);
                        data.Sections["Display"]["iSize H"] = SystemParameters.ScreenHeight.ToString(CultureInfo.CurrentCulture);
                    }

                    parser.WriteFile(file, data);
                }
                catch (Exception ex)
                {
                    Utils.Log($"Skipping screen size remap for {file} due to parse error.");
                    continue;
                }
            }
        }
Exemple #2
0
        private void CreateOutputMods()
        {
            Directory.EnumerateFiles(Path.Combine(OutputFolder, "profiles"), "settings.ini", DirectoryEnumerationOptions.Recursive).Do(f =>
            {
                var ini = f.LoadIniFile();
                if (ini == null)
                {
                    Utils.Log($"settings.ini is null for {f}, skipping");
                    return;
                }

                var overwrites = ini.custom_overwrites;
                if (overwrites == null)
                {
                    Utils.Log("No custom overwrites found, skipping");
                    return;
                }

                if (overwrites is SectionData data)
                {
                    data.Coll.Do(keyData =>
                    {
                        var v   = keyData.Value;
                        var mod = Path.Combine(OutputFolder, "mods", v);

                        if (!Directory.Exists(mod))
                        {
                            Directory.CreateDirectory(mod);
                        }
                    });
                }
            });
        }
            public IncludeZEditPatches(Compiler compiler) : base(compiler)
            {
                var zEditPath = FindzEditPath(compiler);
                var havezEdit = zEditPath != null;

                Utils.Log(havezEdit ? $"Found zEdit at {zEditPath}" : $"zEdit not detected, disabling zEdit routines");

                if (!havezEdit)
                {
                    _mergesIndexed = new Dictionary <string, zEditMerge>();
                    return;
                }

                var merges = Directory.EnumerateFiles(Path.Combine(zEditPath, "profiles"),
                                                      DirectoryEnumerationOptions.Files | DirectoryEnumerationOptions.Recursive)
                             .Where(f => f.EndsWith("\\merges.json"))
                             .SelectMany(f => f.FromJSON <List <zEditMerge> >())
                             .GroupBy(f => (f.name, f.filename));

                merges.Where(m => m.Count() > 1)
                .Do(m =>
                {
                    Utils.Warning(
                        $"WARNING, you have two patches named {m.Key.name}\\{m.Key.filename} in your zEdit profiles. We'll pick one at random, this probably isn't what you want.");
                });

                _mergesIndexed =
                    merges.ToDictionary(
                        m => Path.Combine(compiler.MO2Folder, "mods", m.Key.name, m.Key.filename),
                        m => m.First());
            }
        private void IndexPath(string path)
        {
            var file_list = Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories).ToList();

            Utils.Log($"Updating the cache for {file_list.Count} files");
            file_list.PMap(f => UpdateFile(f));
            SyncToDisk();
        }
        private static IEnumerable <string> EnumerateAllFiles(string path, IList <string> patterns, bool includeArchive, bool recursive, bool followSymlinks)
        {
            // without filters, just enumerate files, which is faster

            var fileOptions = baseFileOptions;

            if (recursive)
            {
                fileOptions |= DirectoryEnumerationOptions.Recursive;
            }
            if (followSymlinks)
            {
                fileOptions &= ~DirectoryEnumerationOptions.SkipReparsePoints;
            }

            DirectoryEnumerationFilters fileFilters = new DirectoryEnumerationFilters
            {
                ErrorFilter = (errorCode, errorMessage, pathProcessed) =>
                {
                    logger.Error($"Find file error {errorCode}: {errorMessage} on {pathProcessed}");
                    return(true);
                }
            };


            bool includeAllFiles = patterns.Count == 0 ||
                                   (patterns.Count == 1 && (patterns[0] == "*.*" || patterns[0] == "*"));

            if (!includeAllFiles)
            {
                fileFilters.InclusionFilter = fsei =>
                {
                    foreach (string pattern in patterns)
                    {
                        if (WildcardMatch(fsei.FileName, pattern, true))
                        {
                            return(true);
                        }
                    }

                    if (includeArchive)
                    {
                        foreach (string pattern in ArchiveDirectory.Patterns)
                        {
                            if (WildcardMatch(fsei.FileName, pattern, true))
                            {
                                return(true);
                            }
                        }
                    }

                    return(false);
                };
            }

            return(Directory.EnumerateFiles(path, fileOptions, fileFilters, PathFormat.FullPath));
        }
        private static List <string> EnumerateFiles(string path, ExportFormat format)
        {
            if (!path.EndsWith(Path.DirectorySeparatorChar.ToString()))
            {
                path += Path.DirectorySeparatorChar;
            }

            return(Directory.EnumerateFiles(path, $"*.{format.ToString().ToLower()}", SearchOption.AllDirectories).ToList());
        }
Exemple #7
0
        /// <summary>
        /// The user may already have some files in the OutputFolder. If so we can go through these and
        /// figure out which need to be updated, deleted, or left alone
        /// </summary>
        public async Task OptimizeModlist()
        {
            Utils.Log("Optimizing Modlist directives");
            var indexed = ModList.Directives.ToDictionary(d => d.To);

            UpdateTracker.NextStep("Looking for files to delete");
            await Directory.EnumerateFiles(OutputFolder, "*", DirectoryEnumerationOptions.Recursive)
            .PMap(Queue, UpdateTracker, f =>
            {
                var relative_to = f.RelativeTo(OutputFolder);
                Utils.Status($"Checking if modlist file {relative_to}");
                if (indexed.ContainsKey(relative_to) || f.IsInPath(DownloadFolder))
                {
                    return;
                }

                Utils.Log($"Deleting {relative_to} it's not part of this modlist");
                File.Delete(f);
            });

            UpdateTracker.NextStep("Looking for unmodified files");
            (await indexed.Values.PMap(Queue, UpdateTracker, d =>
            {
                // Bit backwards, but we want to return null for
                // all files we *want* installed. We return the files
                // to remove from the install list.
                Status($"Optimizing {d.To}");
                var path = Path.Combine(OutputFolder, d.To);
                if (!File.Exists(path))
                {
                    return(null);
                }

                var fi = new FileInfo(path);
                if (fi.Length != d.Size)
                {
                    return(null);
                }

                return(path.FileHash() == d.Hash ? d : null);
            }))
            .Where(d => d != null)
            .Do(d => indexed.Remove(d.To));

            UpdateTracker.NextStep("Updating Modlist");
            Utils.Log($"Optimized {ModList.Directives.Count} directives to {indexed.Count} required");
            var requiredArchives = indexed.Values.OfType <FromArchive>()
                                   .GroupBy(d => d.ArchiveHashPath[0])
                                   .Select(d => d.Key)
                                   .ToHashSet();

            ModList.Archives   = ModList.Archives.Where(a => requiredArchives.Contains(a.Hash)).ToList();
            ModList.Directives = indexed.Values.ToList();
        }
Exemple #8
0
        public async Task HashArchives()
        {
            var hashResults = await Directory.EnumerateFiles(DownloadFolder)
                              .Where(e => !e.EndsWith(Consts.HashFileExtension))
                              .PMap(Queue, e => (e.FileHashCached(), e));

            HashedArchives = hashResults
                             .OrderByDescending(e => File.GetLastWriteTime(e.Item2))
                             .GroupBy(e => e.Item1)
                             .Select(e => e.First())
                             .ToDictionary(e => e.Item1, e => e.Item2);
        }
        public static IErrorResponse CheckValidInstallPath(string path)
        {
            var ret = Utils.IsDirectoryPathValid(path);

            if (!ret.Succeeded)
            {
                return(ret);
            }

            if (!Directory.Exists(path))
            {
                return(ErrorResponse.Success);
            }

            // Check folder does not have a wabbajack modlist
            foreach (var file in Directory.EnumerateFiles(path, DirectoryEnumerationOptions.Recursive))
            {
                if (!File.Exists(file))
                {
                    continue;
                }
                if (System.IO.Path.GetExtension(file).Equals(ExtensionManager.Extension))
                {
                    return(ErrorResponse.Fail($"Cannot install into a folder with a wabbajack modlist inside of it."));
                }
            }

            // Check folder is either empty, or a likely valid previous install
            if (!Directory.IsEmpty(path))
            {
                // Some probably naive check, but should be a good starting point to improve later
                if (!Directory.EnumerateFiles(path).Any(file =>
                {
                    var fileName = Path.GetFileName(file);
                    if (fileName.Equals("ModOrganizer.exe", StringComparison.OrdinalIgnoreCase))
                    {
                        return(true);
                    }
                    if (fileName.Equals("ModOrganizer.ini", StringComparison.OrdinalIgnoreCase))
                    {
                        return(true);
                    }
                    return(false);
                }))
                {
                    return(ErrorResponse.Fail($"Cannot install into a non-empty folder that does not look like a previous WJ installation."));
                }
            }

            return(ErrorResponse.Success);
        }
        private void UpdateArchive(VirtualFile f)
        {
            if (!f.IsStaged)
            {
                throw new InvalidDataException("Can't analyze an unstaged file");
            }

            var tmp_dir = Path.Combine(_stagedRoot, Guid.NewGuid().ToString());

            Utils.Status($"Extracting Archive {Path.GetFileName(f.StagedPath)}");

            FileExtractor.ExtractAll(f.StagedPath, tmp_dir);


            Utils.Status($"Updating Archive {Path.GetFileName(f.StagedPath)}");

            var entries = Directory.EnumerateFiles(tmp_dir, "*", SearchOption.AllDirectories)
                          .Select(path => path.RelativeTo(tmp_dir));

            var new_files = entries.Select(e => {
                var new_path = new string[f.Paths.Length + 1];
                f.Paths.CopyTo(new_path, 0);
                new_path[f.Paths.Length] = e;
                var nf = new VirtualFile()
                {
                    Paths = new_path,
                };
                nf._stagedPath = Path.Combine(tmp_dir, e);
                Add(nf);
                return(nf);
            }).ToList();

            // Analyze them
            new_files.PMap(file =>
            {
                Utils.Status($"Analyzing {Path.GetFileName(file.StagedPath)}");
                file.Analyze();
            });
            // Recurse into any archives in this archive
            new_files.Where(file => file.IsArchive).Do(file => UpdateArchive(file));

            f.FinishedIndexing = true;

            if (!_isSyncing)
            {
                SyncToDisk();
            }

            Utils.Status("Cleaning Directory");
            DeleteDirectory(tmp_dir);
        }
Exemple #11
0
        private static IEnumerable <string> EnumerateFilesIncludeHidden(string path, IList <string> patterns, bool recursive)
        {
            // when not checking for hidden directories or files, just enumerate files, which is faster

            var fileOptions = baseFileOptions;

            if (recursive)
            {
                fileOptions |= DirectoryEnumerationOptions.Recursive;
            }

            DirectoryEnumerationFilters fileFilters = new DirectoryEnumerationFilters
            {
                ErrorFilter = (errorCode, errorMessage, pathProcessed) =>
                {
                    logger.Error($"Find file error {errorCode}: {errorMessage} on {pathProcessed}");
                    return(true);
                }
            };


            bool includeAllFiles = patterns.Count == 0 ||
                                   (patterns.Count == 1 && (patterns[0] == "*.*" || patterns[0] == "*"));

            if (!includeAllFiles)
            {
                fileFilters.InclusionFilter = fsei =>
                {
                    foreach (string pattern in patterns)
                    {
                        if (WildcardMatch(fsei.FileName, pattern, true))
                        {
                            return(true);
                        }
                        else if (pattern == "*.doc" && WildcardMatch(fsei.FileName, "*.doc*", true))
                        {
                            return(true);
                        }
                        else if (pattern == "*.xls" && WildcardMatch(fsei.FileName, "*.xls*", true))
                        {
                            return(true);
                        }
                    }
                    return(false);
                };
            }

            return(Directory.EnumerateFiles(path, fileOptions, fileFilters, PathFormat.FullPath));
        }
Exemple #12
0
        public void Directory_EnumerateFiles_OK()
        {
            var dir = Path.GetDirectoryName(typeof(AlphaFSToys).Assembly.Location);

            Console.WriteLine($"AlphaFSToys directory name {dir} or {TestContext.CurrentContext.TestDirectory}");
            Directory.SetCurrentDirectory(dir);

            var dirs = Directory.EnumerateFiles(".").ToArray();
            var m    = dirs.Contains("bin");

            Console.WriteLine(string.Join(",", dirs));
            //Assert.That(dirs.Contains(@".\bin"));  // NOW get full paths ? hmm.
            //Assert.That(dirs.Contains(@".\lib"));
            //Assert.That(dirs.Contains(@".\src"));
            Assert.That(dirs.Any(x => x.EndsWith(@"AlphaFSTest.pdb")));
            Assert.That(dirs.Any(x => x.EndsWith(@"AlphaFSTest.dll")));
        }
Exemple #13
0
        public void VerifyAllFiles()
        {
            var skip_files = new HashSet <string> {
                "portable.txt"
            };

            foreach (var dest_file in Directory.EnumerateFiles(InstallFolder, "*", DirectoryEnumerationOptions.Recursive))
            {
                var rel_file = dest_file.RelativeTo(InstallFolder);
                if (rel_file.StartsWith(Consts.LOOTFolderFilesDir) || rel_file.StartsWith(Consts.GameFolderFilesDir))
                {
                    continue;
                }

                if (!skip_files.Contains(rel_file))
                {
                    Assert.IsTrue(File.Exists(Path.Combine(MO2Folder, rel_file)), $"Only in Destination: {rel_file}");
                }
            }

            var skip_extensions = new HashSet <string> {
                ".txt", ".ini"
            };

            foreach (var src_file in Directory.EnumerateFiles(MO2Folder, "*", DirectoryEnumerationOptions.Recursive))
            {
                var rel_file = src_file.RelativeTo(MO2Folder);

                if (rel_file.StartsWith("downloads\\"))
                {
                    continue;
                }

                var dest_file = Path.Combine(InstallFolder, rel_file);
                Assert.IsTrue(File.Exists(dest_file), $"Only in Source: {rel_file}");

                var fi_src  = new FileInfo(src_file);
                var fi_dest = new FileInfo(dest_file);

                if (!skip_extensions.Contains(Path.GetExtension(src_file)))
                {
                    Assert.AreEqual(fi_src.Length, fi_dest.Length, $"Differing sizes {rel_file}");
                    Assert.AreEqual(src_file.FileHash(), dest_file.FileHash(), $"Differing content hash {rel_file}");
                }
            }
        }
Exemple #14
0
        /// <summary>
        /// Generate a list of immediate children from the current folder
        /// </summary>
        /// <param name="omitFromScan">Hash representing the hashes that should be skipped</param>
        /// <param name="date">True if entry dates should be included, false otherwise (default)</param>
        /// <returns>List of BaseFile objects representing the found data</returns>
        /// <remarks>TODO: All instances of Hash.DeepHashes should be made into 0x0 eventually</remarks>
        public virtual List <BaseFile> GetChildren(Hash omitFromScan = Hash.DeepHashes, bool date = false)
        {
            if (_children == null || _children.Count == 0)
            {
                _children = new List <BaseFile>();
                foreach (string file in Directory.EnumerateFiles(_filename, "*", SearchOption.TopDirectoryOnly))
                {
                    BaseFile nf = Utilities.GetFileInfo(file, omitFromScan: omitFromScan, date: date);
                    _children.Add(nf);
                }
                foreach (string dir in Directory.EnumerateDirectories(_filename, "*", SearchOption.TopDirectoryOnly))
                {
                    Folder fl = new Folder(dir);
                    _children.Add(fl);
                }
            }

            return(_children);
        }
        private static void DumpGetAssociation(bool isLocal)
        {
            Console.WriteLine("\n=== TEST {0} ===", isLocal ? "LOCAL" : "NETWORK");
            string path = isLocal ? SysRoot : Path.LocalToUnc(SysRoot);

            Console.WriteLine("\nInput Directory Path: [{0}]\n", path);

            int cnt = 0;

            foreach (string file in Directory.EnumerateFiles(path))
            {
                string association     = Shell32.GetFileAssociation(file);
                string contentType     = Shell32.GetFileContentType(file);
                string defaultIconPath = Shell32.GetFileDefaultIcon(file);
                string friendlyAppName = Shell32.GetFileFriendlyAppName(file);
                string friendlyDocName = Shell32.GetFileFriendlyDocName(file);
                string openWithApp     = Shell32.GetFileOpenWithAppName(file);
                string verbCommand     = Shell32.GetFileVerbCommand(file);

                Console.WriteLine("\t#{0:000}\t[{1}]\n", ++cnt, file);
                Console.WriteLine("\t\tGetFileAssociation()    : [{0}]", association);
                Console.WriteLine("\t\tGetFileContentType()    : [{0}]", contentType);
                Console.WriteLine("\t\tGetFileDefaultIcon()    : [{0}]", defaultIconPath);
                Console.WriteLine("\t\tGetFileFriendlyAppName(): [{0}]", friendlyAppName);
                Console.WriteLine("\t\tGetFileFriendlyDocName(): [{0}]", friendlyDocName);
                Console.WriteLine("\t\tGetFileOpenWithAppName(): [{0}]", openWithApp);
                Console.WriteLine("\t\tGetFileVerbCommand()    : [{0}]", verbCommand);

                StopWatcher(true);
                Shell32Info shell32Info = Shell32.GetShell32Info(file);
                string      report      = Reporter(true);

                string cmd = "print";
                verbCommand = shell32Info.GetVerbCommand(cmd);
                Console.WriteLine("\n\t\tShell32Info.GetVerbCommand(\"{0}\"): [{1}]", cmd, verbCommand);

                Dump(shell32Info, -15);
                Console.WriteLine("\n\t{0}\n\n", report);
            }
            Console.WriteLine("\n");
            Assert.IsTrue(cnt > 0, "No entries enumerated.");
        }
Exemple #16
0
        private void DumpGetAssociation(bool isLocal)
        {
            Console.WriteLine("\n=== TEST {0} ===", isLocal ? UnitTestConstants.Local : UnitTestConstants.Network);
            var path = isLocal ? UnitTestConstants.SysRoot : Path.LocalToUnc(UnitTestConstants.SysRoot);

            Console.WriteLine("\nInput Directory Path: [{0}]\n", path);

            var cnt = 0;

            foreach (var file in Directory.EnumerateFiles(path))
            {
                var association     = Shell32.GetFileAssociation(file);
                var contentType     = Shell32.GetFileContentType(file);
                var defaultIconPath = Shell32.GetFileDefaultIcon(file);
                var friendlyAppName = Shell32.GetFileFriendlyAppName(file);
                var friendlyDocName = Shell32.GetFileFriendlyDocName(file);
                var openWithApp     = Shell32.GetFileOpenWithAppName(file);
                var verbCommand     = Shell32.GetFileVerbCommand(file);

                Console.WriteLine("\t#{0:000}\t[{1}]\n", ++cnt, file);
                Console.WriteLine("\t\tGetFileAssociation()    : [{0}]", association);
                Console.WriteLine("\t\tGetFileContentType()    : [{0}]", contentType);
                Console.WriteLine("\t\tGetFileDefaultIcon()    : [{0}]", defaultIconPath);
                Console.WriteLine("\t\tGetFileFriendlyAppName(): [{0}]", friendlyAppName);
                Console.WriteLine("\t\tGetFileFriendlyDocName(): [{0}]", friendlyDocName);
                Console.WriteLine("\t\tGetFileOpenWithAppName(): [{0}]", openWithApp);
                Console.WriteLine("\t\tGetFileVerbCommand()    : [{0}]", verbCommand);

                UnitTestConstants.StopWatcher(true);
                var shell32Info = Shell32.GetShell32Info(file);
                var report      = UnitTestConstants.Reporter(true);

                var cmd = "print";
                verbCommand = shell32Info.GetVerbCommand(cmd);
                Console.WriteLine("\n\t\tShell32Info.GetVerbCommand(\"{0}\"): [{1}]", cmd, verbCommand);

                UnitTestConstants.Dump(shell32Info, -15);
                Console.WriteLine("\n\t{0}\n\n", report);
            }
            Console.WriteLine("\n");
            Assert.IsTrue(cnt > 0, "No entries enumerated.");
        }
Exemple #17
0
        private void SetScreenSizeInPrefs()
        {
            var config = new IniParserConfiguration {
                AllowDuplicateKeys = true, AllowDuplicateSections = true
            };

            foreach (var file in Directory.EnumerateFiles(Path.Combine(OutputFolder, "profiles"), "*refs.ini",
                                                          DirectoryEnumerationOptions.Recursive))
            {
                var parser = new FileIniDataParser(new IniDataParser(config));
                var data   = parser.ReadFile(file);
                if (data.Sections["Display"]["iSize W"] != null && data.Sections["Display"]["iSize H"] != null)
                {
                    data.Sections["Display"]["iSize W"] = SystemParameters.PrimaryScreenWidth.ToString();
                    data.Sections["Display"]["iSize H"] = SystemParameters.PrimaryScreenHeight.ToString();
                }

                parser.WriteFile(file, data);
            }
        }
Exemple #18
0
        private static Package CreatePackageFromPath(string path)
        {
            var package = new Package();

            if (!path.EndsWith(Path.DirectorySeparatorChar.ToString()))
            {
                path += Path.DirectorySeparatorChar;
            }

            Dictionary <string, string> files = Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories)
                                                .ToDictionary(k => k.Replace(path, String.Empty), v => v);

            foreach (KeyValuePair <string, string> file in files)
            {
                FilesystemFileInfo fileInfo = FilesystemFileInfo.CreateFromEntry(file.Value, file.Key);
                package.Files.Add(fileInfo);
            }

            return(package);
        }
Exemple #19
0
        public void AlphaFS_Shell32_PathFileExists()
        {
            Console.WriteLine("Filesystem.Shell32.PathFileExists()");

            var path = UnitTestConstants.SysRoot;

            DumpPathFileExists(path, true);
            DumpPathFileExists(Path.LocalToUnc(path), true);
            DumpPathFileExists("BlaBlaBla", false);
            DumpPathFileExists(Path.Combine(UnitTestConstants.SysRoot, "BlaBlaBla"), false);

            var cnt = 0;

            UnitTestConstants.StopWatcher(true);
            foreach (var file in Directory.EnumerateFiles(UnitTestConstants.SysRoot))
            {
                var fileExists = Shell32.PathFileExists(file);

                Console.WriteLine("\t#{0:000}\tShell32.PathFileExists() == [{1}]: {2}\t\t[{3}]", ++cnt, UnitTestConstants.TextTrue, fileExists, file);
                Assert.IsTrue(fileExists);
            }
            Console.WriteLine("\n\t{0}\n", UnitTestConstants.Reporter(true));
        }
        public void PathFileExists()
        {
            Console.WriteLine("Filesystem.Shell32.PathFileExists()");

            string path = SysRoot;

            DumpPathFileExists(path, true);
            DumpPathFileExists(Path.LocalToUnc(path), true);
            DumpPathFileExists("BlaBlaBla", false);
            DumpPathFileExists(Path.Combine(SysRoot, "BlaBlaBla"), false);

            int cnt = 0;

            StopWatcher(true);
            foreach (string file in Directory.EnumerateFiles(SysRoot))
            {
                bool fileExists = Shell32.PathFileExists(file);

                Console.WriteLine("\t#{0:000}\tShell32.PathFileExists() == [{1}]: {2}\t\t[{3}]", ++cnt, TextTrue, fileExists, file);
                Assert.IsTrue(fileExists);
            }
            Console.WriteLine("\n\t{0}\n", Reporter(true));
        }
Exemple #21
0
        private static void DeleteDirectory(string path)
        {
            Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)
            .DoProgress("Cleaning VFS Files", file =>
            {
                try
                {
                    var fi         = new FileInfo(file);
                    fi.Attributes &= ~FileAttributes.ReadOnly;
                    File.Delete(file);
                }
                catch (Exception ex)
                {
                    Utils.Log(ex.ToString());
                }
            });

            Directory.EnumerateDirectories(path, DirectoryEnumerationOptions.Recursive)
            .OrderByDescending(d => d.Length)
            .DoProgress("Cleaning VFS Folders", folder =>
            {
                try
                {
                    if (!Directory.Exists(folder))
                    {
                        return;
                    }
                    var di         = new DirectoryInfo(folder);
                    di.Attributes &= ~FileAttributes.ReadOnly;
                    Directory.Delete(path, true);
                }
                catch (Exception ex)
                {
                    Utils.Log(ex.ToString());
                }
            });
        }
        public static IList <string> GetGitignoreDirectories(string path, bool recursive, bool followSymlinks)
        {
            if (File.Exists(Path.Combine(path, ".gitignore")))
            {
                return new List <string> {
                           path
                }
            }
            ;

            var fileOptions = baseFileOptions;

            if (recursive)
            {
                fileOptions |= DirectoryEnumerationOptions.Recursive;
            }
            if (followSymlinks)
            {
                fileOptions &= ~DirectoryEnumerationOptions.SkipReparsePoints;
            }

            List <string> dontRecurseBelow = new List <string>();

            DirectoryEnumerationFilters fileFilters = new DirectoryEnumerationFilters
            {
                ErrorFilter = (errorCode, errorMessage, pathProcessed) =>
                {
                    logger.Error($"Find file error {errorCode}: {errorMessage} on {pathProcessed}");
                    return(true);
                },
                RecursionFilter = fsei =>
                {
                    if (fsei.IsDirectory && dontRecurseBelow.Any(p => fsei.FullPath.StartsWith(p, StringComparison.CurrentCulture)))
                    {
                        return(false);
                    }
                    return(true);
                },
                InclusionFilter = fsei =>
                {
                    if (fsei.FileName == ".gitignore")
                    {
                        dontRecurseBelow.Add(Path.GetDirectoryName(fsei.FullPath));
                        return(true);
                    }
                    return(false);
                }
            };

            var list = Directory.EnumerateFiles(path, fileOptions, fileFilters, PathFormat.FullPath)
                       .Select(s => Path.GetDirectoryName(s)).ToList();

            if (list.Count == 0)
            {
                DirectoryInfo di = new DirectoryInfo(path);
                while (di.Parent != null)
                {
                    if (File.Exists(Path.Combine(di.Parent.FullName, ".gitignore")))
                    {
                        list.Add(path);
                        break;
                    }

                    di = di.Parent;
                }
            }

            return(list);
        }
        protected override async Task <ExitCode> Run()
        {
            var modListPath = (AbsolutePath)Modlist;

            if (modListPath.Extension != Consts.ModListExtension && modListPath.FileName != (RelativePath)"modlist.txt")
            {
                return(CLIUtils.Exit($"The file {Modlist} is not a valid modlist file!", ExitCode.BadArguments));
            }

            if (Copy && Move)
            {
                return(CLIUtils.Exit("You can't set both copy and move flags!", ExitCode.BadArguments));
            }

            var isModlist = modListPath.Extension == Consts.ModListExtension;

            var list = new List <TransferFile>();

            if (isModlist)
            {
                ModList modlist;

                try
                {
                    modlist = AInstaller.LoadFromFile(modListPath);
                }
                catch (Exception e)
                {
                    return(CLIUtils.Exit($"Error while loading the Modlist!\n{e}", ExitCode.Error));
                }

                if (modlist == null)
                {
                    return(CLIUtils.Exit("The Modlist could not be loaded!", ExitCode.Error));
                }

                CLIUtils.Log($"Modlist contains {modlist.Archives.Count} archives.");

                modlist.Archives.Do(a =>
                {
                    var inputPath  = Path.Combine(Input, a.Name);
                    var outputPath = Path.Combine(Output, a.Name);

                    if (!File.Exists(inputPath))
                    {
                        CLIUtils.Log($"File {inputPath} does not exist, skipping.");
                        return;
                    }

                    CLIUtils.Log($"Adding {inputPath} to the transfer list.");
                    list.Add(new TransferFile(inputPath, outputPath));

                    var metaInputPath  = Path.Combine(inputPath, ".meta");
                    var metaOutputPath = Path.Combine(outputPath, ".meta");

                    if (File.Exists(metaInputPath))
                    {
                        CLIUtils.Log($"Found meta file {metaInputPath}");
                        if (IncludeMeta)
                        {
                            CLIUtils.Log($"Adding {metaInputPath} to the transfer list.");
                            list.Add(new TransferFile(metaInputPath, metaOutputPath));
                        }
                        else
                        {
                            CLIUtils.Log($"Meta file {metaInputPath} will be ignored.");
                        }
                    }
                    else
                    {
                        CLIUtils.Log($"Found no meta file for {inputPath}");
                        if (IncludeMeta)
                        {
                            if (string.IsNullOrWhiteSpace(a.Meta))
                            {
                                CLIUtils.Log($"Meta for {a.Name} is empty, this should not be possible but whatever.");
                                return;
                            }

                            CLIUtils.Log("Adding meta from archive info the transfer list");
                            list.Add(new TransferFile(a.Meta, metaOutputPath, true));
                        }
                        else
                        {
                            CLIUtils.Log($"Meta will be ignored for {a.Name}");
                        }
                    }
                });
            }
            else
            {
                if (!Directory.Exists(Mods))
                {
                    return(CLIUtils.Exit($"Mods directory {Mods} does not exist!", ExitCode.BadArguments));
                }

                CLIUtils.Log($"Reading modlist.txt from {Modlist}");
                string[] modlist = File.ReadAllLines(Modlist);

                if (modlist == null || modlist.Length == 0)
                {
                    return(CLIUtils.Exit($"Provided modlist.txt file at {Modlist} is empty or could not be read!", ExitCode.BadArguments));
                }

                var mods = modlist.Where(s => s.StartsWith("+")).Select(s => s.Substring(1)).ToHashSet();

                if (mods.Count == 0)
                {
                    return(CLIUtils.Exit("Counted mods from modlist.txt are 0!", ExitCode.BadArguments));
                }

                CLIUtils.Log($"Found {mods.Count} mods in modlist.txt");

                var downloads = new HashSet <string>();

                Directory.EnumerateDirectories(Mods, "*", SearchOption.TopDirectoryOnly)
                .Where(d => mods.Contains(Path.GetRelativePath(Path.GetDirectoryName(d), d)))
                .Do(d =>
                {
                    var meta = Path.Combine(d, "meta.ini");
                    if (!File.Exists(meta))
                    {
                        CLIUtils.Log($"Mod meta file {meta} does not exist, skipping");
                        return;
                    }

                    string[] ini = File.ReadAllLines(meta);
                    if (ini == null || ini.Length == 0)
                    {
                        CLIUtils.Log($"Mod meta file {meta} could not be read or is empty!");
                        return;
                    }

                    ini.Where(i => !string.IsNullOrWhiteSpace(i) && i.StartsWith("installationFile="))
                    .Select(i => i.Replace("installationFile=", ""))
                    .Do(i =>
                    {
                        CLIUtils.Log($"Found installationFile {i}");
                        downloads.Add(i);
                    });
                });

                CLIUtils.Log($"Found {downloads.Count} installationFiles from mod metas.");

                Directory.EnumerateFiles(Input, "*", SearchOption.TopDirectoryOnly)
                .Where(f => downloads.Contains(Path.GetFileNameWithoutExtension(f)))
                .Do(f =>
                {
                    CLIUtils.Log($"Found archive {f}");

                    var outputPath = Path.Combine(Output, Path.GetFileName(f));

                    CLIUtils.Log($"Adding {f} to the transfer list");
                    list.Add(new TransferFile(f, outputPath));

                    var metaInputPath = Path.Combine(f, ".meta");
                    if (File.Exists(metaInputPath))
                    {
                        CLIUtils.Log($"Found meta file for {f} at {metaInputPath}");
                        if (IncludeMeta)
                        {
                            var metaOutputPath = Path.Combine(outputPath, ".meta");
                            CLIUtils.Log($"Adding {metaInputPath} to the transfer list.");
                            list.Add(new TransferFile(metaInputPath, metaOutputPath));
                        }
                        else
                        {
                            CLIUtils.Log("Meta file will be ignored");
                        }
                    }
                    else
                    {
                        CLIUtils.Log($"Found no meta file for {f}");
                    }
                });
            }

            CLIUtils.Log($"Transfer list contains {list.Count} items");
            var success = 0;
            var failed  = 0;
            var skipped = 0;

            list.Do(f =>
            {
                if (File.Exists(f.Output))
                {
                    if (Overwrite)
                    {
                        CLIUtils.Log($"Output file {f.Output} already exists, it will be overwritten");
                        if (f.IsMeta || Move)
                        {
                            CLIUtils.Log($"Deleting file at {f.Output}");
                            try
                            {
                                File.Delete(f.Output);
                            }
                            catch (Exception e)
                            {
                                CLIUtils.Log($"Could not delete file {f.Output}!\n{e}");
                                failed++;
                            }
                        }
                    }
                    else
                    {
                        CLIUtils.Log($"Output file {f.Output} already exists, skipping");
                        skipped++;
                        return;
                    }
                }

                if (f.IsMeta)
                {
                    CLIUtils.Log($"Writing meta data to {f.Output}");
                    try
                    {
                        File.WriteAllText(f.Output, f.Input, Encoding.UTF8);
                        success++;
                    }
                    catch (Exception e)
                    {
                        CLIUtils.Log($"Error while writing meta data to {f.Output}!\n{e}");
                        failed++;
                    }
                }
                else
                {
                    if (Copy)
                    {
                        CLIUtils.Log($"Copying file {f.Input} to {f.Output}");
                        try
                        {
                            File.Copy(f.Input, f.Output, Overwrite ? CopyOptions.None : CopyOptions.FailIfExists, CopyMoveProgressHandler, null);
                            success++;
                        }
                        catch (Exception e)
                        {
                            CLIUtils.Log($"Error while copying file {f.Input} to {f.Output}!\n{e}");
                            failed++;
                        }
                    }
                    else if (Move)
                    {
                        CLIUtils.Log($"Moving file {f.Input} to {f.Output}");
                        try
                        {
                            File.Move(f.Input, f.Output, Overwrite ? MoveOptions.ReplaceExisting : MoveOptions.None, CopyMoveProgressHandler, null);
                            success++;
                        }
                        catch (Exception e)
                        {
                            CLIUtils.Log($"Error while moving file {f.Input} to {f.Output}!\n{e}");
                            failed++;
                        }
                    }
                }
            });

            CLIUtils.Log($"Skipped transfers: {skipped}");
            CLIUtils.Log($"Failed transfers: {failed}");
            CLIUtils.Log($"Successful transfers: {success}");

            return(0);
        }
Exemple #24
0
        private static IEnumerable <string> EnumerateFilesExcludeHidden(string path, IList <string> patterns, bool recursive)
        {
            // when checking for hidden directories, enumerate the directories separately from files to check for hidden flag on directories

            DirectoryInfo di = new DirectoryInfo(path);

            // the root of the drive has the hidden attribute set, so don't stop on this hidden directory
            if (di.Attributes.HasFlag(FileAttributes.Hidden) && (di.Root != di))
            {
                yield break;
            }

            var dirOptions = baseDirOptions;

            if (recursive)
            {
                dirOptions |= DirectoryEnumerationOptions.Recursive;
            }


            DirectoryEnumerationFilters dirFilters = new DirectoryEnumerationFilters
            {
                ErrorFilter = (errorCode, errorMessage, pathProcessed) =>
                {
                    logger.Error($"Find file error {errorCode}: {errorMessage} on {pathProcessed}");
                    return(true);
                }
            };

            dirFilters.InclusionFilter = fsei =>
            {
                return(!fsei.IsHidden);
            };


            DirectoryEnumerationFilters fileFilters = new DirectoryEnumerationFilters
            {
                ErrorFilter = (errorCode, errorMessage, pathProcessed) =>
                {
                    logger.Error($"Find file error {errorCode}: {errorMessage} on {pathProcessed}");
                    return(true);
                }
            };


            bool includeAllFiles = patterns.Count == 0 ||
                                   (patterns.Count == 1 && (patterns[0] == "*.*" || patterns[0] == "*"));

            if (includeAllFiles)
            {
                fileFilters.InclusionFilter = fsei =>
                {
                    return(!fsei.IsHidden);
                };
            }
            else
            {
                fileFilters.InclusionFilter = fsei =>
                {
                    if (fsei.IsHidden)
                    {
                        return(false);
                    }

                    foreach (string pattern in patterns)
                    {
                        if (WildcardMatch(fsei.FileName, pattern, true))
                        {
                            return(true);
                        }
                        else if (pattern == "*.doc" && WildcardMatch(fsei.FileName, "*.doc*", true))
                        {
                            return(true);
                        }
                        else if (pattern == "*.xls" && WildcardMatch(fsei.FileName, "*.xls*", true))
                        {
                            return(true);
                        }
                    }
                    return(false);
                };
            }

            IEnumerable <string> directories = new string[] { path };

            if (recursive)
            {
                directories = directories.Concat(Directory.EnumerateDirectories(path, dirOptions, dirFilters, PathFormat.FullPath));
            }

            foreach (var directory in directories)
            {
                IEnumerable <string> matches = Directory.EnumerateFiles(directory, baseFileOptions, fileFilters, PathFormat.FullPath);

                foreach (var file in matches)
                {
                    yield return(file);
                }
            }
        }
        private static IEnumerable <string> EnumerateFilesImpl(string path, IList <string> patterns,
                                                               FileFilter filter, Gitignore gitignore)
        {
            DirectoryEnumerationFilters fileFilters = new DirectoryEnumerationFilters
            {
                ErrorFilter = (errorCode, errorMessage, pathProcessed) =>
                {
                    logger.Error($"Find file error {errorCode}: {errorMessage} on {pathProcessed}");
                    return(true);
                }
            };


            bool includeAllFiles = (patterns.Count == 0 ||
                                    (patterns.Count == 1 && (patterns[0] == "*.*" || patterns[0] == "*"))) &&
                                   (gitignore == null || gitignore.Files.Count == 0);

            if (includeAllFiles)
            {
                fileFilters.InclusionFilter = fsei =>
                {
                    if (!filter.IncludeHidden && fsei.IsHidden)
                    {
                        return(false);
                    }

                    return(true);
                };
            }
            else
            {
                fileFilters.InclusionFilter = fsei =>
                {
                    if (!filter.IncludeHidden && fsei.IsHidden)
                    {
                        return(false);
                    }

                    if (gitignore != null && gitignore.Files.Contains(fsei.FullPath))
                    {
                        return(false);
                    }

                    foreach (string pattern in patterns)
                    {
                        if (WildcardMatch(fsei.FileName, pattern, true))
                        {
                            return(true);
                        }
                    }

                    if (filter.IncludeArchive)
                    {
                        foreach (string pattern in ArchiveDirectory.Patterns)
                        {
                            if (WildcardMatch(fsei.FileName, pattern, true))
                            {
                                return(true);
                            }
                        }
                    }
                    return(false);
                };
            }

            var fileOptions = baseFileOptions;

            if (filter.FollowSymlinks)
            {
                fileOptions &= ~DirectoryEnumerationOptions.SkipReparsePoints;
            }

            return(Directory.EnumerateFiles(path, fileOptions, fileFilters, PathFormat.FullPath));
        }
Exemple #26
0
        public static IErrorResponse CheckValidInstallPath(string path, string downloadFolder)
        {
            var ret = Utils.IsDirectoryPathValid(path);

            if (!ret.Succeeded)
            {
                return(ret);
            }

            if (!Directory.Exists(path))
            {
                return(ErrorResponse.Success);
            }

            // Check folder does not have a Wabbajack ModList
            foreach (var file in Directory.EnumerateFiles(path))
            {
                if (!File.Exists(file))
                {
                    continue;
                }
                if (System.IO.Path.GetExtension(file).Equals(Consts.ModListExtension))
                {
                    return(ErrorResponse.Fail($"Cannot install into a folder with a Wabbajack ModList inside of it"));
                }
            }

            // Check folder is either empty, or a likely valid previous install
            if (!Directory.IsEmpty(path))
            {
                // If we have a MO2 install, assume good to go
                if (Directory.EnumerateFiles(path).Any(file =>
                {
                    var fileName = Path.GetFileName(file);
                    if (fileName.Equals("ModOrganizer.exe", StringComparison.OrdinalIgnoreCase))
                    {
                        return(true);
                    }
                    if (fileName.Equals("ModOrganizer.ini", StringComparison.OrdinalIgnoreCase))
                    {
                        return(true);
                    }
                    return(false);
                }))
                {
                    return(ErrorResponse.Success);
                }

                // If we don't have a MO2 install, and there's any file that's not in the downloads folder, mark failure
                if (Directory.EnumerateFiles(path).Any(file =>
                {
                    var fileName = Path.GetFileName(file);
                    if (string.IsNullOrWhiteSpace(downloadFolder))
                    {
                        return(true);
                    }
                    return(!Utils.IsUnderneathDirectory(file, downloadFolder));
                }))
                {
                    return(ErrorResponse.Fail($"Cannot install into a non-empty folder that does not look like a previous WJ installation.\n" +
                                              $"To override, delete all installed files from your target installation folder.  Any files in your download folder are okay to keep."));
                }
            }

            return(ErrorResponse.Success);
        }
Exemple #27
0
        /// <summary>
        /// The user may already have some files in the OutputFolder. If so we can go through these and
        /// figure out which need to be updated, deleted, or left alone
        /// </summary>
        public async Task OptimizeModlist()
        {
            Utils.Log("Optimizing ModList directives");

            // Clone the ModList so our changes don't modify the original data
            ModList = ModList.Clone();

            var indexed = ModList.Directives.ToDictionary(d => d.To);

            UpdateTracker.NextStep("Looking for files to delete");
            await Directory.EnumerateFiles(OutputFolder, "*", DirectoryEnumerationOptions.Recursive)
            .PMap(Queue, UpdateTracker, f =>
            {
                var relative_to = f.RelativeTo(OutputFolder);
                Utils.Status($"Checking if ModList file {relative_to}");
                if (indexed.ContainsKey(relative_to) || f.IsInPath(DownloadFolder))
                {
                    return;
                }

                Utils.Log($"Deleting {relative_to} it's not part of this ModList");
                File.Delete(f);
            });

            UpdateTracker.NextStep("Looking for unmodified files");
            (await indexed.Values.PMap(Queue, UpdateTracker, d =>
            {
                // Bit backwards, but we want to return null for
                // all files we *want* installed. We return the files
                // to remove from the install list.
                Status($"Optimizing {d.To}");
                var path = Path.Combine(OutputFolder, d.To);
                if (!File.Exists(path))
                {
                    return(null);
                }

                var fi = new FileInfo(path);
                if (fi.Length != d.Size)
                {
                    return(null);
                }

                return(path.FileHash() == d.Hash ? d : null);
            }))
            .Where(d => d != null)
            .Do(d => indexed.Remove(d.To));

            Utils.Log("Cleaning empty folders");
            var expectedFolders = indexed.Keys
                                  // We ignore the last part of the path, so we need a dummy file name
                                  .Append(Path.Combine(DownloadFolder, "_"))
                                  .SelectMany(path =>
            {
                // Get all the folders and all the folder parents
                // so for foo\bar\baz\qux.txt this emits ["foo", "foo\\bar", "foo\\bar\\baz"]
                var split = path.Split('\\');
                return(Enumerable.Range(1, split.Length - 1).Select(t => string.Join("\\", split.Take(t))));
            }).Distinct()
                                  .Select(p => Path.Combine(OutputFolder, p))
                                  .ToHashSet();

            try
            {
                Directory.EnumerateDirectories(OutputFolder, DirectoryEnumerationOptions.Recursive)
                .Where(p => !expectedFolders.Contains(p))
                .OrderByDescending(p => p.Length)
                .Do(Utils.DeleteDirectory);
            }
            catch (Exception)
            {
                // ignored because it's not worth throwing a fit over
                Utils.Log("Error when trying to clean empty folders. This doesn't really matter.");
            }

            UpdateTracker.NextStep("Updating ModList");
            Utils.Log($"Optimized {ModList.Directives.Count} directives to {indexed.Count} required");
            var requiredArchives = indexed.Values.OfType <FromArchive>()
                                   .GroupBy(d => d.ArchiveHashPath[0])
                                   .Select(d => d.Key)
                                   .ToHashSet();

            ModList.Archives   = ModList.Archives.Where(a => requiredArchives.Contains(a.Hash)).ToList();
            ModList.Directives = indexed.Values.ToList();
        }
Exemple #28
0
        private void DumpGetSetAttributes(bool isLocal)
        {
            Console.WriteLine("\n=== TEST {0} ===", isLocal ? UnitTestConstants.Local : UnitTestConstants.Network);
            var tmp      = Path.Combine(Path.GetTempPath(), "File.SetAttributes()-" + Path.GetRandomFileName());
            var tempPath = isLocal ? tmp : Path.LocalToUnc(tmp);
            var sys32    = isLocal ? UnitTestConstants.SysRoot32 : Path.LocalToUnc(UnitTestConstants.SysRoot32);

            Console.WriteLine("\nInput Path: [{0}]", sys32);

            // Just enumerate and compare attributes in folder: C:\Windows\System32
            foreach (var file in Directory.EnumerateFiles(sys32))
            {
                var actual   = File.GetAttributes(file);
                var expected = System.IO.File.GetAttributes(file);

                Assert.AreEqual(expected, actual, "AlphaFS != System.IO");
            }


            Console.WriteLine("\nInput Path: [{0}]", tempPath);

            // Create some folders and files.
            UnitTestConstants.CreateDirectoriesAndFiles(tempPath, 10, true);

            var apply = FileAttributes.Hidden | FileAttributes.Archive | FileAttributes.System | FileAttributes.ReadOnly;

            Console.WriteLine("\nSetAttributes(): [{0}]", apply);

            var allOk = true;
            var cnt   = 0;

            UnitTestConstants.StopWatcher(true);
            foreach (var file in Directory.EnumerateFiles(tempPath))
            {
                try
                {
                    File.SetAttributes(file, apply);

                    var actual   = File.GetAttributes(file);
                    var expected = System.IO.File.GetAttributes(file);

                    Console.WriteLine("\n\t#{0:000}\tFile     : [{1}]\n\t\tAlphaFS  : [{2}]\n\t\tSystem.IO: [{3}]", ++cnt, file, expected, actual);

                    if (cnt == 0)
                    {
                        Assert.Inconclusive("Nothing was enumerated, but it was expected.");
                    }

                    Assert.AreEqual(expected, actual, "AlphaFS != System.IO");
                }
                catch (Exception ex)
                {
                    allOk = false;
                    Console.WriteLine("\n\tCaught (unexpected) {0}: [{1}]", ex.GetType().FullName, ex.Message.Replace(Environment.NewLine, "  "));
                }
            }
            Console.WriteLine();
            Console.WriteLine(UnitTestConstants.Reporter());
            Assert.IsTrue(allOk);


            apply = FileAttributes.Normal;
            Console.WriteLine("\nSetAttributes(): [{0}]", apply);

            allOk = true;
            cnt   = 0;
            UnitTestConstants.StopWatcher(true);
            foreach (var file in Directory.EnumerateFiles(tempPath))
            {
                try
                {
                    File.SetAttributes(file, apply);

                    var actual   = File.GetAttributes(file);
                    var expected = System.IO.File.GetAttributes(file);

                    Console.WriteLine("\n\t#{0:000}\tFile     : [{1}]\n\t\tAlphaFS  : [{2}]\n\t\tSystem.IO: [{3}]", ++cnt, file, expected, actual);

                    if (cnt == 0)
                    {
                        Assert.Inconclusive("Nothing was enumerated, but it was expected.");
                    }

                    Assert.AreEqual(expected, actual, "AlphaFS != System.IO");
                }
                catch (Exception ex)
                {
                    allOk = false;
                    Console.WriteLine("\n\tCaught (unexpected) {0}: [{1}]", ex.GetType().FullName, ex.Message.Replace(Environment.NewLine, "  "));
                }
            }
            Console.WriteLine();
            Console.WriteLine(UnitTestConstants.Reporter());


            Directory.Delete(tempPath, true);
            Assert.IsFalse(Directory.Exists(tempPath), "Cleanup failed: Directory should have been removed.");
            Assert.IsTrue(allOk);
            Console.WriteLine();
        }
Exemple #29
0
        private static void Main(string[] args)
        {
            ExceptionlessClient.Default.Startup("Kruacm8p1B6RFAw2WMnKcEqkQcnWRkF3RmPSOzlW");

            SetupNLog();
            SetupPatterns();

            _logger = LogManager.GetCurrentClassLogger();

            _fluentCommandLineParser = new FluentCommandLineParser <ApplicationArguments>
            {
                IsCaseSensitive = false
            };

            _fluentCommandLineParser.Setup(arg => arg.File)
            .As('f')
            .WithDescription("File to search. Either this or -d is required");

            _fluentCommandLineParser.Setup(arg => arg.Directory)
            .As('d')
            .WithDescription("Directory to recursively process. Either this or -f is required");

            _fluentCommandLineParser.Setup(arg => arg.SaveTo)
            .As('o')
            .WithDescription("File to save results to");

            _fluentCommandLineParser.Setup(arg => arg.GetAscii)
            .As('a')
            .SetDefault(true)
            .WithDescription("If set, look for ASCII strings. Default is true. Use -a false to disable");

            _fluentCommandLineParser.Setup(arg => arg.GetUnicode)
            .As('u')
            .SetDefault(true)
            .WithDescription("If set, look for Unicode strings. Default is true. Use -u false to disable");

            _fluentCommandLineParser.Setup(arg => arg.MinimumLength)
            .As('m').SetDefault(3).WithDescription("Minimum string length. Default is 3");

            _fluentCommandLineParser.Setup(arg => arg.BlockSizeMb)
            .As('b').SetDefault(512).WithDescription("Chunk size in MB. Valid range is 1 to 1024. Default is 512");

            _fluentCommandLineParser.Setup(arg => arg.Quiet)
            .As('q').SetDefault(false).WithDescription("Quiet mode (Do not show header or total number of hits)");

            _fluentCommandLineParser.Setup(arg => arg.QuietQuiet)
            .As('s')
            .SetDefault(false)
            .WithDescription(
                "Really Quiet mode (Do not display hits to console. Speeds up processing when using -o)");

            _fluentCommandLineParser.Setup(arg => arg.MaximumLength)
            .As('x').SetDefault(-1).WithDescription("Maximum string length. Default is unlimited\r\n");

            _fluentCommandLineParser.Setup(arg => arg.GetPatterns)
            .As('p').SetDefault(false).WithDescription("Display list of built in regular expressions");

            _fluentCommandLineParser.Setup(arg => arg.LookForString)
            .As("ls")
            .SetDefault(string.Empty)
            .WithDescription("String to look for. When set, only matching strings are returned");

            _fluentCommandLineParser.Setup(arg => arg.LookForRegex)
            .As("lr")
            .SetDefault(string.Empty)
            .WithDescription("Regex to look for. When set, only strings matching the regex are returned");

            _fluentCommandLineParser.Setup(arg => arg.StringFile)
            .As("fs")
            .SetDefault(string.Empty)
            .WithDescription("File containing strings to look for. When set, only matching strings are returned");

            _fluentCommandLineParser.Setup(arg => arg.RegexFile)
            .As("fr")
            .SetDefault(string.Empty)
            .WithDescription(
                "File containing regex patterns to look for. When set, only strings matching regex patterns are returned\r\n");


            _fluentCommandLineParser.Setup(arg => arg.AsciiRange)
            .As("ar")
            .SetDefault("[\x20-\x7E]")
            .WithDescription(
                @"Range of characters to search for in 'Code page' strings. Specify as a range of characters in hex format and enclose in quotes. Default is [\x20 -\x7E]");

            _fluentCommandLineParser.Setup(arg => arg.UnicodeRange)
            .As("ur")
            .SetDefault("[\u0020-\u007E]")
            .WithDescription(
                "Range of characters to search for in Unicode strings. Specify as a range of characters in hex format and enclose in quotes. Default is [\\u0020-\\u007E]\r\n");

            _fluentCommandLineParser.Setup(arg => arg.CodePage)
            .As("cp")
            .SetDefault(1252)
            .WithDescription(
                "Code page to use. Default is 1252. Use the Identifier value for code pages at https://goo.gl/ig6DxW");

            _fluentCommandLineParser.Setup(arg => arg.FileMask)
            .As("mask")
            .SetDefault(string.Empty)
            .WithDescription(
                "When using -d, file mask to search for. * and ? are supported. This option has no effect when using -f");

            _fluentCommandLineParser.Setup(arg => arg.RegexOnly)
            .As("ro")
            .SetDefault(false)
            .WithDescription(
                "When true, list the string matched by regex pattern vs string the pattern was found in (This may result in duplicate strings in output. ~ denotes approx. offset)");

            _fluentCommandLineParser.Setup(arg => arg.ShowOffset)
            .As("off")
            .SetDefault(false)
            .WithDescription(
                $"Show offset to hit after string, followed by the encoding (A={_fluentCommandLineParser.Object.CodePage}, U=Unicode)\r\n");

            _fluentCommandLineParser.Setup(arg => arg.SortAlpha)
            .As("sa").SetDefault(false).WithDescription("Sort results alphabetically");

            _fluentCommandLineParser.Setup(arg => arg.SortLength)
            .As("sl").SetDefault(false).WithDescription("Sort results by length");


            var header =
                $"bstrings version {Assembly.GetExecutingAssembly().GetName().Version}" +
                "\r\n\r\nAuthor: Eric Zimmerman ([email protected])" +
                "\r\nhttps://github.com/EricZimmerman/bstrings";

            var footer = @"Examples: bstrings.exe -f ""C:\Temp\UsrClass 1.dat"" --ls URL" + "\r\n\t " +
                         @" bstrings.exe -f ""C:\Temp\someFile.txt"" --lr guid" + "\r\n\t " +
                         @" bstrings.exe -f ""C:\Temp\aBigFile.bin"" --fs c:\temp\searchStrings.txt --fr c:\temp\searchRegex.txt -s" +
                         "\r\n\t " +
                         @" bstrings.exe -d ""C:\Temp"" --mask ""*.dll""" + "\r\n\t " +
                         @" bstrings.exe -d ""C:\Temp"" --ar ""[\x20-\x37]""" + "\r\n\t " +
                         @" bstrings.exe -d ""C:\Temp"" --cp 10007" + "\r\n\t " +
                         @" bstrings.exe -d ""C:\Temp"" --ls test" + "\r\n\t " +
                         @" bstrings.exe -f ""C:\Temp\someOtherFile.txt"" --lr cc -sa" + "\r\n\t " +
                         @" bstrings.exe -f ""C:\Temp\someOtherFile.txt"" --lr cc -sa -m 15 -x 22" + "\r\n\t " +
                         @" bstrings.exe -f ""C:\Temp\UsrClass 1.dat"" --ls mui -sl" + "\r\n\t ";

            _fluentCommandLineParser.SetupHelp("?", "help")
            .WithHeader(header)
            .Callback(text => _logger.Info(text + "\r\n" + footer));

            var result = _fluentCommandLineParser.Parse(args);

            if (result.HelpCalled)
            {
                return;
            }

            if (_fluentCommandLineParser.Object.GetPatterns)
            {
                _logger.Warn("Name \t\tDescription");
                foreach (var regExPattern in RegExPatterns.OrderBy(t => t.Key))
                {
                    var desc = RegExDesc[regExPattern.Key];
                    _logger.Info($"{regExPattern.Key}\t{desc}");
                }

                _logger.Info("");
                _logger.Info("To use a built in pattern, supply the Name to the --lr switch\r\n");

                return;
            }

            if (result.HasErrors)
            {
                _logger.Error("");
                _logger.Error(result.ErrorText);

                _fluentCommandLineParser.HelpOption.ShowHelp(_fluentCommandLineParser.Options);

                return;
            }

            if (_fluentCommandLineParser.Object.File.IsNullOrEmpty() &&
                _fluentCommandLineParser.Object.Directory.IsNullOrEmpty())
            {
                _fluentCommandLineParser.HelpOption.ShowHelp(_fluentCommandLineParser.Options);

                _logger.Warn("Either -f or -d is required. Exiting");
                return;
            }

            if (_fluentCommandLineParser.Object.File.IsNullOrEmpty() == false &&
                !File.Exists(_fluentCommandLineParser.Object.File) &&
                _fluentCommandLineParser.Object.FileMask.Length == 0)
            {
                _logger.Warn($"File '{_fluentCommandLineParser.Object.File}' not found. Exiting");
                return;
            }

            if (_fluentCommandLineParser.Object.Directory.IsNullOrEmpty() == false &&
                !Directory.Exists(_fluentCommandLineParser.Object.Directory) &&
                _fluentCommandLineParser.Object.FileMask.Length == 0)
            {
                _logger.Warn($"Directory '{_fluentCommandLineParser.Object.Directory}' not found. Exiting");
                return;
            }

            if (!_fluentCommandLineParser.Object.Quiet)
            {
                _logger.Info(header);
                _logger.Info("");
            }

            var files = new List <string>();

            if (_fluentCommandLineParser.Object.File.IsNullOrEmpty() == false)
            {
                files.Add(Path.GetFullPath(_fluentCommandLineParser.Object.File));
            }
            else
            {
                try
                {
                    if (_fluentCommandLineParser.Object.FileMask.Length > 0)
                    {
                        files.AddRange(Directory.EnumerateFiles(Path.GetFullPath(_fluentCommandLineParser.Object.Directory),
                                                                _fluentCommandLineParser.Object.FileMask, SearchOption.AllDirectories));
                    }
                    else
                    {
                        files.AddRange(Directory.EnumerateFiles(Path.GetFullPath(_fluentCommandLineParser.Object.Directory), "*",
                                                                SearchOption.AllDirectories));
                    }
                }
                catch (Exception ex)
                {
                    _logger.Error(
                        $"Error getting files in '{_fluentCommandLineParser.Object.Directory}'. Error message: {ex.Message}");
                    return;
                }
            }

            if (!_fluentCommandLineParser.Object.Quiet)
            {
                _logger.Info($"Command line: {string.Join(" ", args)}");
                _logger.Info("");
            }

            StreamWriter sw = null;

            var    globalCounter    = 0;
            var    globalHits       = 0;
            double globalTimespan   = 0;
            var    withBoundaryHits = false;

            if (_fluentCommandLineParser.Object.SaveTo.IsNullOrEmpty() == false && _fluentCommandLineParser.Object.SaveTo.Length > 0)
            {
                _fluentCommandLineParser.Object.SaveTo = _fluentCommandLineParser.Object.SaveTo.TrimEnd('\\');

                var dir = Path.GetDirectoryName(_fluentCommandLineParser.Object.SaveTo);

                if (dir != null && Directory.Exists(dir) == false)
                {
                    try
                    {
                        Directory.CreateDirectory(dir);
                    }
                    catch (Exception)
                    {
                        _logger.Warn(
                            $"Invalid path: '{_fluentCommandLineParser.Object.SaveTo}'. Results will not be saved to a file.");
                        _logger.Info("");
                        _fluentCommandLineParser.Object.SaveTo = string.Empty;
                    }
                }
                else
                {
                    if (dir == null)
                    {
                        _logger.Warn($"Invalid path: '{_fluentCommandLineParser.Object.SaveTo}");
                        _fluentCommandLineParser.Object.SaveTo = string.Empty;
                    }
                }

                if (_fluentCommandLineParser.Object.SaveTo.Length > 0 && !_fluentCommandLineParser.Object.Quiet)
                {
                    _logger.Info($"Saving hits to '{_fluentCommandLineParser.Object.SaveTo}'");
                    _logger.Info("");
                }

                if (_fluentCommandLineParser.Object.SaveTo.Length > 0)
                {
//                    try
//                    {
//                        File.Create(_fluentCommandLineParser.Object.SaveTo);
//                    }
//                    catch (Exception e)
//                    {
//                        _logger.Fatal($"Unable to create output file '{_fluentCommandLineParser.Object.SaveTo}'! Check permissions and try again! Error: {e.Message}");
//                        return;
//                    }
                    sw = new StreamWriter(_fluentCommandLineParser.Object.SaveTo, true);
                }
            }


            foreach (var file in files)
            {
                if (File.Exists(file) == false)
                {
                    _logger.Warn($"'{file}' does not exist! Skipping");
                }

                _sw = new Stopwatch();
                _sw.Start();
                var counter = 0;
                var hits    = new HashSet <string>();


                var regPattern = _fluentCommandLineParser.Object.LookForRegex;

                if (RegExPatterns.ContainsKey(_fluentCommandLineParser.Object.LookForRegex))
                {
                    regPattern = RegExPatterns[_fluentCommandLineParser.Object.LookForRegex];
                }

                if (regPattern.Length > 0 && !_fluentCommandLineParser.Object.Quiet)
                {
                    _logger.Info($"Searching via RegEx pattern: {regPattern}");
                    _logger.Info("");
                }

                var minLength = 3;
                if (_fluentCommandLineParser.Object.MinimumLength > 0)
                {
                    minLength = _fluentCommandLineParser.Object.MinimumLength;
                }

                var maxLength = -1;

                if (_fluentCommandLineParser.Object.MaximumLength > minLength)
                {
                    maxLength = _fluentCommandLineParser.Object.MaximumLength;
                }

                var chunkSizeMb = _fluentCommandLineParser.Object.BlockSizeMb < 1 ||
                                  _fluentCommandLineParser.Object.BlockSizeMb > 1024
                    ? 512
                    : _fluentCommandLineParser.Object.BlockSizeMb;
                var chunkSizeBytes = chunkSizeMb * 1024 * 1024;

                var  fileSizeBytes  = new FileInfo(file).Length;
                var  bytesRemaining = fileSizeBytes;
                long offset         = 0;

                var chunkIndex  = 1;
                var totalChunks = fileSizeBytes / chunkSizeBytes + 1;
                var hsuffix     = totalChunks == 1 ? "" : "s";

                if (!_fluentCommandLineParser.Object.Quiet)
                {
                    _logger.Info(
                        $"Searching {totalChunks:N0} chunk{hsuffix} ({chunkSizeMb} MB each) across {GetSizeReadable(fileSizeBytes)} in '{file}'");
                    _logger.Info("");
                }

                try
                {
                    MappedStream ms = null;

                    try
                    {
                        var fs =
                            File.Open(File.GetFileSystemEntryInfo(file).LongFullPath, FileMode.Open, FileAccess.Read,
                                      FileShare.Read, PathFormat.LongFullPath);

                        ms = MappedStream.FromStream(fs, Ownership.None);
                    }
                    catch (Exception)
                    {
                        _logger.Warn($"Unable to open file directly. This usually means the file is in use. Switching to raw access\r\n");
                    }

                    if (ms == null)
                    {
                        //raw mode
                        var ss = OpenFile(file);

                        ms = MappedStream.FromStream(ss, Ownership.None);
                    }


                    using (ms)
                    {
                        while (bytesRemaining > 0)
                        {
                            if (bytesRemaining <= chunkSizeBytes)
                            {
                                chunkSizeBytes = (int)bytesRemaining;
                            }
                            var chunk = new byte[chunkSizeBytes];

                            ms.Read(chunk, 0, chunkSizeBytes);

                            if (_fluentCommandLineParser.Object.GetUnicode)
                            {
                                var uh = GetUnicodeHits(chunk, minLength, maxLength, offset,
                                                        _fluentCommandLineParser.Object.ShowOffset);
                                foreach (var h in uh)
                                {
                                    hits.Add(h);
                                }
                            }

                            if (_fluentCommandLineParser.Object.GetAscii)
                            {
                                var ah = GetAsciiHits(chunk, minLength, maxLength, offset,
                                                      _fluentCommandLineParser.Object.ShowOffset);
                                foreach (var h in ah)
                                {
                                    hits.Add(h);
                                }
                            }

                            offset         += chunkSizeBytes;
                            bytesRemaining -= chunkSizeBytes;

                            if (!_fluentCommandLineParser.Object.Quiet)
                            {
                                _logger.Info(
                                    $"Chunk {chunkIndex:N0} of {totalChunks:N0} finished. Total strings so far: {hits.Count:N0} Elapsed time: {_sw.Elapsed.TotalSeconds:N3} seconds. Average strings/sec: {hits.Count/_sw.Elapsed.TotalSeconds:N0}");
                            }

                            chunkIndex += 1;
                        }

                        //do chunk boundary checks to make sure we get everything and not split things

                        if (!_fluentCommandLineParser.Object.Quiet)
                        {
                            _logger.Info(
                                "Primary search complete. Looking for strings across chunk boundaries...");
                        }

                        bytesRemaining = fileSizeBytes;
                        chunkSizeBytes = chunkSizeMb * 1024 * 1024;
                        offset         = chunkSizeBytes - _fluentCommandLineParser.Object.MinimumLength * 10 * 2;
                        //move starting point backwards for our starting point
                        chunkIndex = 0;

                        var boundaryChunkSize = _fluentCommandLineParser.Object.MinimumLength * 10 * 2 * 2;
                        //grab the same # of bytes on both sides of the boundary

                        while (bytesRemaining > 0)
                        {
                            if (offset + boundaryChunkSize > fileSizeBytes)
                            {
                                break;
                            }


                            var chunk = new byte[boundaryChunkSize];

                            ms.Read(chunk, 0, boundaryChunkSize);

                            if (_fluentCommandLineParser.Object.GetUnicode)
                            {
                                var uh = GetUnicodeHits(chunk, minLength, maxLength, offset,
                                                        _fluentCommandLineParser.Object.ShowOffset);
                                foreach (var h in uh)
                                {
                                    hits.Add("  " + h);
                                }

                                if (withBoundaryHits == false && uh.Count > 0)
                                {
                                    withBoundaryHits = uh.Count > 0;
                                }
                            }

                            if (_fluentCommandLineParser.Object.GetAscii)
                            {
                                var ah = GetAsciiHits(chunk, minLength, maxLength, offset,
                                                      _fluentCommandLineParser.Object.ShowOffset);
                                foreach (var h in ah)
                                {
                                    hits.Add("  " + h);
                                }

                                if (withBoundaryHits == false && ah.Count > 0)
                                {
                                    withBoundaryHits = true;
                                }
                            }

                            offset         += chunkSizeBytes;
                            bytesRemaining -= chunkSizeBytes;

                            chunkIndex += 1;
                        }
                    }
                }
                catch (Exception ex)
                {
                    _logger.Info("");
                    _logger.Error($"Error: {ex.Message}");
                }
                _sw.Stop();

                if (!_fluentCommandLineParser.Object.Quiet)
                {
                    _logger.Info("Search complete.");
                    _logger.Info("");
                }

                if (_fluentCommandLineParser.Object.SortAlpha)
                {
                    _logger.Info("Sorting alphabetically...");
                    _logger.Info("");
                    var tempList = hits.ToList();
                    tempList.Sort();
                    hits = new HashSet <string>(tempList);
                }
                else if (_fluentCommandLineParser.Object.SortLength)
                {
                    _logger.Info("Sorting by length...");
                    _logger.Info("");
                    var tempList = SortByLength(hits.ToList()).ToList();
                    hits = new HashSet <string>(tempList);
                }

                var fileStrings  = new HashSet <string>();
                var regexStrings = new HashSet <string>();

                //set up highlighting
                if (_fluentCommandLineParser.Object.LookForString.Length > 0)
                {
                    fileStrings.Add(_fluentCommandLineParser.Object.LookForString);
                }

                if (_fluentCommandLineParser.Object.LookForRegex.Length > 0)
                {
                    regexStrings.Add(regPattern);
                }

                if (_fluentCommandLineParser.Object.StringFile.IsNullOrEmpty() == false || _fluentCommandLineParser.Object.RegexFile.IsNullOrEmpty() == false)
                {
                    if (_fluentCommandLineParser.Object.StringFile.Length > 0)
                    {
                        if (File.Exists(_fluentCommandLineParser.Object.StringFile))
                        {
                            fileStrings.UnionWith(new HashSet <string>(File.ReadAllLines(_fluentCommandLineParser.Object.StringFile)));
                        }
                        else
                        {
                            _logger.Error($"Strings file '{_fluentCommandLineParser.Object.StringFile}' not found.");
                        }
                    }

                    if (_fluentCommandLineParser.Object.RegexFile.Length > 0)
                    {
                        if (File.Exists(_fluentCommandLineParser.Object.RegexFile))
                        {
                            regexStrings.UnionWith(new HashSet <string>(File.ReadAllLines(_fluentCommandLineParser.Object.RegexFile)));
                        }
                        else
                        {
                            _logger.Error($"Regex file '{_fluentCommandLineParser.Object.RegexFile}' not found.");
                        }
                    }
                }

                AddHighlightingRules(fileStrings.ToList());

                if (_fluentCommandLineParser.Object.RegexOnly == false)
                {
                    AddHighlightingRules(regexStrings.ToList(), true);
                }


                if (!_fluentCommandLineParser.Object.Quiet)
                {
                    _logger.Info("Processing strings...");
                    _logger.Info("");
                }

                foreach (var hit in hits)
                {
                    if (hit.Length == 0)
                    {
                        continue;
                    }

                    if (fileStrings.Count > 0 || regexStrings.Count > 0)
                    {
                        foreach (var fileString in fileStrings)
                        {
                            if (fileString.Trim().Length == 0)
                            {
                                continue;
                            }
                            if (hit.IndexOf(fileString, StringComparison.InvariantCultureIgnoreCase) < 0)
                            {
                                continue;
                            }

                            counter += 1;

                            if (_fluentCommandLineParser.Object.QuietQuiet == false)
                            {
                                _logger.Info(hit);
                            }

                            sw?.WriteLine(hit);
                        }

                        var hitoffset = "";
                        if (_fluentCommandLineParser.Object.ShowOffset)
                        {
                            hitoffset = $"~{hit.Split('\t').Last()}";
                        }

                        foreach (var regString in regexStrings)
                        {
                            if (regString.Trim().Length == 0)
                            {
                                continue;
                            }

                            try
                            {
                                var reg1 = new Regex(regString,
                                                     RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);

                                if (reg1.IsMatch(hit) == false)
                                {
                                    continue;
                                }

                                counter += 1;

                                if (_fluentCommandLineParser.Object.RegexOnly)
                                {
                                    foreach (var match in reg1.Matches(hit))
                                    {
                                        if (_fluentCommandLineParser.Object.QuietQuiet == false)
                                        {
                                            _logger.Info($"{match}\t{hitoffset}");
                                        }

                                        sw?.WriteLine($"{match}\t{hitoffset}");
                                    }
                                }
                                else
                                {
                                    if (_fluentCommandLineParser.Object.QuietQuiet == false)
                                    {
                                        _logger.Info(hit);
                                    }

                                    sw?.WriteLine(hit);
                                }
                            }
                            catch (Exception ex)
                            {
                                _logger.Error($"Error setting up regular expression '{regString}': {ex.Message}");
                            }
                        }
                    }
                    else
                    {
                        //dump all strings
                        counter += 1;

                        if (_fluentCommandLineParser.Object.QuietQuiet == false)
                        {
                            _logger.Info(hit);
                        }

                        sw?.WriteLine(hit);
                    }
                }

                if (_fluentCommandLineParser.Object.Quiet)
                {
                    continue;
                }

                var suffix = counter == 1 ? "" : "s";

                _logger.Info("");

                if (withBoundaryHits)
                {
                    _logger.Info("** Strings prefixed with 2 spaces are hits found across chunk boundaries **");
                    _logger.Info("");
                }

                _logger.Info(
                    $"Found {counter:N0} string{suffix} in {_sw.Elapsed.TotalSeconds:N3} seconds. Average strings/sec: {hits.Count/_sw.Elapsed.TotalSeconds:N0}");
                globalCounter  += counter;
                globalHits     += hits.Count;
                globalTimespan += _sw.Elapsed.TotalSeconds;
                if (files.Count > 1)
                {
                    _logger.Info(
                        "-------------------------------------------------------------------------------------\r\n");
                }
            }
            if (sw != null)
            {
                sw.Flush();
                sw.Close();
            }

            if (!_fluentCommandLineParser.Object.Quiet && files.Count > 1)
            {
                var suffix = globalCounter == 1 ? "" : "s";

                _logger.Info("");
                _logger.Info(
                    $"Found {globalCounter:N0} string{suffix} in {globalTimespan:N3} seconds. Average strings/sec: {globalHits/globalTimespan:N0}");
            }
        }
Exemple #30
0
        public static IList <string> GetGitignoreDirectories(string path, bool recursive, bool followSymlinks)
        {
            if (File.Exists(Path.Combine(path, ".gitignore")))
            {
                return new List <string> {
                           path
                }
            }
            ;

            var fileOptions = baseFileOptions;

            if (recursive)
            {
                fileOptions |= DirectoryEnumerationOptions.Recursive;
            }
            if (followSymlinks)
            {
                fileOptions &= ~DirectoryEnumerationOptions.SkipReparsePoints;
            }

            List <string> dontRecurseBelow = new List <string>
            {
                @"C:\$Recycle.Bin"
            };

            foreach (var sf in new[]
            {
                Environment.SpecialFolder.Windows,
                Environment.SpecialFolder.ProgramFiles,
                Environment.SpecialFolder.ProgramFilesX86,
            })
            {
                string p = Environment.GetFolderPath(sf);
                if (!string.IsNullOrEmpty(p))
                {
                    dontRecurseBelow.Add(p);
                }
            }

            DirectoryEnumerationFilters fileFilters = new DirectoryEnumerationFilters
            {
                ErrorFilter = (errorCode, errorMessage, pathProcessed) =>
                {
                    logger.Error($"Find file error {errorCode}: {errorMessage} on {pathProcessed}");
                    return(true);
                },
                RecursionFilter = fsei =>
                {
                    if (Utils.CancelSearch)
                    {
                        throw new OperationCanceledException();
                    }

                    if (fsei.IsDirectory && dontRecurseBelow.Any(p => fsei.FullPath.StartsWith(p, true, CultureInfo.CurrentCulture)))
                    {
                        return(false);
                    }
                    return(true);
                },
                InclusionFilter = fsei =>
                {
                    if (Utils.CancelSearch)
                    {
                        throw new OperationCanceledException();
                    }

                    if (fsei.FileName == ".gitignore")
                    {
                        dontRecurseBelow.Add(Path.GetDirectoryName(fsei.FullPath));
                        return(true);
                    }
                    return(false);
                }
            };

            try
            {
                // search down subdirectories
                var list = Directory.EnumerateFiles(path, fileOptions, fileFilters, PathFormat.FullPath)
                           .Select(s => Path.GetDirectoryName(s)).ToList();

                if (list.Count == 0)
                {
                    // not found, search up the tree
                    DirectoryInfo di = new DirectoryInfo(path);
                    while (di.Parent != null)
                    {
                        if (File.Exists(Path.Combine(di.Parent.FullName, ".gitignore")))
                        {
                            list.Add(path);
                            break;
                        }

                        di = di.Parent;
                    }
                }

                return(list);
            }
            catch (OperationCanceledException)
            {
                return(new List <string>());
            }
        }