├── .gitattributes ├── .gitignore ├── FastSearchLibrary.sln ├── FastSearchLibrary ├── DirectorySearcher │ ├── DirectoryCancellationDelegateSearcher.cs │ ├── DirectoryCancellationPatternSearcher.cs │ ├── DirectoryCancellationSearcherBase.cs │ ├── DirectoryEventArgs.cs │ ├── DirectorySearcher.cs │ └── DirectorySearcherMultiple.cs ├── ExecuteHandlers.cs ├── FastSearchLibrary.csproj ├── FileSearcher │ ├── FileCancellationDelegateSearcher.cs │ ├── FileCancellationPatternSearcher.cs │ ├── FileCancellationSearcherBase.cs │ ├── FileDelegateSearcher.cs │ ├── FileEventArgs.cs │ ├── FilePatternSearcher.cs │ ├── FileSearcher.cs │ ├── FileSearcherBase.cs │ └── FileSearcherMultiple.cs ├── Properties │ └── AssemblyInfo.cs └── SearchCompletedEventArgs.cs ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Windows Store app package directory 170 | AppPackages/ 171 | BundleArtifacts/ 172 | 173 | # Visual Studio cache files 174 | # files ending in .cache can be ignored 175 | *.[Cc]ache 176 | # but keep track of directories ending in .cache 177 | !*.[Cc]ache/ 178 | 179 | # Others 180 | ClientBin/ 181 | [Ss]tyle[Cc]op.* 182 | ~$* 183 | *~ 184 | *.dbmdl 185 | *.dbproj.schemaview 186 | *.pfx 187 | *.publishsettings 188 | node_modules/ 189 | orleans.codegen.cs 190 | 191 | # RIA/Silverlight projects 192 | Generated_Code/ 193 | 194 | # Backup & report files from converting an old project file 195 | # to a newer Visual Studio version. Backup files are not needed, 196 | # because we have git ;-) 197 | _UpgradeReport_Files/ 198 | Backup*/ 199 | UpgradeLog*.XML 200 | UpgradeLog*.htm 201 | 202 | # SQL Server files 203 | *.mdf 204 | *.ldf 205 | 206 | # Business Intelligence projects 207 | *.rdl.data 208 | *.bim.layout 209 | *.bim_*.settings 210 | 211 | # Microsoft Fakes 212 | FakesAssemblies/ 213 | 214 | # GhostDoc plugin setting file 215 | *.GhostDoc.xml 216 | 217 | # Node.js Tools for Visual Studio 218 | .ntvs_analysis.dat 219 | 220 | # Visual Studio 6 build log 221 | *.plg 222 | 223 | # Visual Studio 6 workspace options file 224 | *.opt 225 | 226 | # Visual Studio LightSwitch build output 227 | **/*.HTMLClient/GeneratedArtifacts 228 | **/*.DesktopClient/GeneratedArtifacts 229 | **/*.DesktopClient/ModelManifest.xml 230 | **/*.Server/GeneratedArtifacts 231 | **/*.Server/ModelManifest.xml 232 | _Pvt_Extensions 233 | 234 | # LightSwitch generated files 235 | GeneratedArtifacts/ 236 | ModelManifest.xml 237 | 238 | # Paket dependency manager 239 | .paket/paket.exe 240 | 241 | # FAKE - F# Make 242 | .fake/ 243 | -------------------------------------------------------------------------------- /FastSearchLibrary.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FastSearchLibrary", "FastSearchLibrary\FastSearchLibrary.csproj", "{6561BFFB-65CC-4384-A7E3-2717F918D474}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {6561BFFB-65CC-4384-A7E3-2717F918D474}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {6561BFFB-65CC-4384-A7E3-2717F918D474}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {6561BFFB-65CC-4384-A7E3-2717F918D474}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {6561BFFB-65CC-4384-A7E3-2717F918D474}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /FastSearchLibrary/DirectorySearcher/DirectoryCancellationDelegateSearcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Threading; 5 | 6 | namespace FastSearchLibrary 7 | { 8 | internal class DirectoryCancellationDelegateSearcher : DirectoryCancellationSearcherBase 9 | { 10 | 11 | private Func isValid; 12 | 13 | public DirectoryCancellationDelegateSearcher(string folder, Func isValid, CancellationToken token, ExecuteHandlers handlerOption, bool suppressOperationCanceledException) 14 | :base(folder, token, handlerOption, suppressOperationCanceledException) 15 | { 16 | this.isValid = isValid; 17 | } 18 | 19 | protected override void GetDirectories(string folder) 20 | { 21 | token.ThrowIfCancellationRequested(); 22 | 23 | DirectoryInfo dirInfo = null; 24 | DirectoryInfo[] directories = null; 25 | 26 | try 27 | { 28 | dirInfo = new DirectoryInfo(folder); 29 | directories = dirInfo.GetDirectories(); 30 | 31 | if (directories.Length == 0) return; 32 | } 33 | catch (UnauthorizedAccessException ex) 34 | { 35 | return; 36 | } 37 | catch (PathTooLongException ex) 38 | { 39 | return; 40 | } 41 | catch (DirectoryNotFoundException ex) 42 | { 43 | return; 44 | } 45 | 46 | 47 | foreach (var dir in directories) 48 | { 49 | token.ThrowIfCancellationRequested(); 50 | 51 | GetDirectories(dir.FullName); 52 | } 53 | 54 | token.ThrowIfCancellationRequested(); 55 | 56 | try 57 | { 58 | List resultDirs = new List(); 59 | 60 | foreach (var dir in directories) 61 | { 62 | if (isValid(dir)) 63 | resultDirs.Add(dir); 64 | } 65 | 66 | if (resultDirs.Count > 0) 67 | OnDirectoriesFound(resultDirs); 68 | 69 | } 70 | catch (UnauthorizedAccessException ex) 71 | { 72 | } 73 | catch (PathTooLongException ex) 74 | { 75 | } 76 | catch (DirectoryNotFoundException ex) 77 | { 78 | } 79 | } 80 | 81 | protected override List GetStartDirectories(string folder) 82 | { 83 | token.ThrowIfCancellationRequested(); 84 | 85 | DirectoryInfo dirInfo = null; 86 | DirectoryInfo[] directories = null; 87 | List resultDirs = new List(); 88 | 89 | try 90 | { 91 | dirInfo = new DirectoryInfo(folder); 92 | directories = dirInfo.GetDirectories(); 93 | 94 | if (directories.Length > 1) 95 | { 96 | 97 | foreach (var dir in directories) 98 | { 99 | if (isValid(dir)) 100 | resultDirs.Add(dir); 101 | } 102 | 103 | if (resultDirs.Count > 0) 104 | OnDirectoriesFound(resultDirs); 105 | 106 | return new List(directories); 107 | } 108 | 109 | if (directories.Length == 0) 110 | return new List(); 111 | 112 | } 113 | catch (UnauthorizedAccessException ex) 114 | { 115 | return new List(); 116 | } 117 | catch (PathTooLongException ex) 118 | { 119 | return new List(); 120 | } 121 | catch (DirectoryNotFoundException ex) 122 | { 123 | return new List(); 124 | } 125 | 126 | // if directories.Length == 1 127 | foreach (var dir in directories) 128 | { 129 | if (isValid(dir)) 130 | OnDirectoriesFound(new List { dir }); 131 | } 132 | 133 | return GetStartDirectories(directories[0].FullName); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /FastSearchLibrary/DirectorySearcher/DirectoryCancellationPatternSearcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading; 6 | 7 | namespace FastSearchLibrary 8 | { 9 | internal class DirectoryCancellationPatternSearcher : DirectoryCancellationSearcherBase 10 | { 11 | 12 | private string pattern; 13 | 14 | public DirectoryCancellationPatternSearcher(string folder, string pattern, CancellationToken token, ExecuteHandlers handlerOption, bool suppressOperationCanceledException) 15 | : base(folder, token, handlerOption, suppressOperationCanceledException) 16 | { 17 | this.pattern = pattern; 18 | } 19 | 20 | 21 | protected override void GetDirectories(string folder) 22 | { 23 | token.ThrowIfCancellationRequested(); 24 | 25 | DirectoryInfo dirInfo = null; 26 | DirectoryInfo[] directories = null; 27 | 28 | try 29 | { 30 | dirInfo = new DirectoryInfo(folder); 31 | directories = dirInfo.GetDirectories(); 32 | 33 | if (directories.Length == 0) return; 34 | } 35 | catch (UnauthorizedAccessException ex) 36 | { 37 | return; 38 | } 39 | catch (PathTooLongException ex) 40 | { 41 | return; 42 | } 43 | catch (DirectoryNotFoundException ex) 44 | { 45 | return; 46 | } 47 | 48 | 49 | foreach (var d in directories) 50 | { 51 | token.ThrowIfCancellationRequested(); 52 | 53 | GetDirectories(d.FullName); 54 | } 55 | 56 | 57 | token.ThrowIfCancellationRequested(); 58 | 59 | try 60 | { 61 | var resultDirs = dirInfo.GetDirectories(pattern); 62 | if (resultDirs.Length > 0) 63 | OnDirectoriesFound(resultDirs.ToList()); 64 | } 65 | catch (UnauthorizedAccessException ex) 66 | { 67 | } 68 | catch (PathTooLongException ex) 69 | { 70 | } 71 | catch (DirectoryNotFoundException ex) 72 | { 73 | } 74 | } 75 | 76 | protected override List GetStartDirectories(string folder) 77 | { 78 | token.ThrowIfCancellationRequested(); 79 | 80 | DirectoryInfo dirInfo = null; 81 | DirectoryInfo[] directories = null; 82 | DirectoryInfo[] resultDirs = null; 83 | 84 | try 85 | { 86 | dirInfo = new DirectoryInfo(folder); 87 | directories = dirInfo.GetDirectories(); 88 | 89 | 90 | if (directories.Length > 1) 91 | { 92 | resultDirs = dirInfo.GetDirectories(pattern); 93 | if (resultDirs.Length > 0) 94 | OnDirectoriesFound(resultDirs.ToList()); 95 | 96 | return new List(directories); 97 | } 98 | 99 | if (directories.Length == 0) 100 | return new List(); 101 | 102 | } 103 | catch (UnauthorizedAccessException ex) 104 | { 105 | return new List(); 106 | } 107 | catch (PathTooLongException ex) 108 | { 109 | return new List(); 110 | } 111 | catch (DirectoryNotFoundException ex) 112 | { 113 | return new List(); 114 | } 115 | 116 | // if directories.Length == 1 117 | resultDirs = dirInfo.GetDirectories(pattern); 118 | if (resultDirs.Length > 0) 119 | OnDirectoriesFound(resultDirs.ToList()); 120 | 121 | return GetStartDirectories(directories[0].FullName); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /FastSearchLibrary/DirectorySearcher/DirectoryCancellationSearcherBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace FastSearchLibrary 10 | { 11 | internal abstract class DirectoryCancellationSearcherBase 12 | { 13 | 14 | /// 15 | /// Determines where execute event DirectoriesFound handlers 16 | /// 17 | protected ExecuteHandlers handlerOption { get; set; } 18 | 19 | private string folder; 20 | 21 | private ConcurrentBag taskHandlers; 22 | 23 | protected CancellationToken token; 24 | 25 | protected bool SuppressOperationCanceledException { get; set; } 26 | 27 | public DirectoryCancellationSearcherBase(string folder, CancellationToken token, ExecuteHandlers handlerOption, bool suppressOperationCanceledException) 28 | { 29 | this.folder = folder; 30 | this.token = token; 31 | this.handlerOption = handlerOption; 32 | this.SuppressOperationCanceledException = suppressOperationCanceledException; 33 | taskHandlers = new ConcurrentBag(); 34 | } 35 | 36 | 37 | public event EventHandler DirectoriesFound; 38 | 39 | public event EventHandler SearchCompleted; 40 | 41 | 42 | protected virtual void OnDirectoriesFound(List directories) 43 | { 44 | EventHandler handler = DirectoriesFound; 45 | 46 | if (handler != null) 47 | { 48 | var arg = new DirectoryEventArgs(directories); 49 | 50 | if (handlerOption == ExecuteHandlers.InNewTask) 51 | taskHandlers.Add(Task.Run(() => DirectoriesFound(this, arg), token)); 52 | else 53 | handler(this, arg); 54 | } 55 | } 56 | 57 | 58 | protected virtual void OnSearchCompleted(bool isCanceled) 59 | { 60 | EventHandler handler = SearchCompleted; 61 | 62 | if (handler != null) 63 | { 64 | if (handlerOption == ExecuteHandlers.InNewTask) 65 | { 66 | try 67 | { 68 | Task.WaitAll(taskHandlers.ToArray()); 69 | } 70 | catch (AggregateException ex) 71 | { 72 | if (!(ex.InnerException is TaskCanceledException)) 73 | throw; 74 | 75 | if (!isCanceled) 76 | isCanceled = true; 77 | } 78 | } 79 | 80 | var arg = new SearchCompletedEventArgs(isCanceled); 81 | 82 | handler(this, arg); 83 | } 84 | } 85 | 86 | 87 | 88 | /// 89 | /// Starts a directory search operation with realtime reporting using several threads in thread pool. 90 | /// 91 | public virtual void StartSearch() 92 | { 93 | try 94 | { 95 | GetDirectoriesFast(); 96 | } 97 | catch (OperationCanceledException ex) 98 | { 99 | OnSearchCompleted(true); // isCanceled == true 100 | 101 | if (!SuppressOperationCanceledException) 102 | token.ThrowIfCancellationRequested(); 103 | 104 | return; 105 | } 106 | 107 | OnSearchCompleted(false); 108 | } 109 | 110 | 111 | 112 | protected virtual void GetDirectoriesFast() 113 | { 114 | List startDirs = GetStartDirectories(folder); 115 | 116 | startDirs.AsParallel().WithCancellation(token).ForAll((d) => 117 | { 118 | GetStartDirectories(d.FullName).AsParallel().WithCancellation(token).ForAll((dir) => 119 | { 120 | GetDirectories(dir.FullName); 121 | }); 122 | }); 123 | } 124 | 125 | 126 | protected abstract void GetDirectories(string folder); 127 | 128 | protected abstract List GetStartDirectories(string folder); 129 | 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /FastSearchLibrary/DirectorySearcher/DirectoryEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace FastSearchLibrary 6 | { 7 | /// 8 | /// Provides data for DirectoriesFound event. 9 | /// 10 | public class DirectoryEventArgs: EventArgs 11 | { 12 | /// 13 | /// Gets a list of finding directories. 14 | /// 15 | public List Directories { get; private set;} 16 | 17 | /// 18 | /// Initialize a new instance of DirectoryEventArgs class that describes a FilesFound event. 19 | /// 20 | /// The list of finding directories. 21 | public DirectoryEventArgs(List directories) 22 | { 23 | Directories = directories; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /FastSearchLibrary/DirectorySearcher/DirectorySearcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | 10 | namespace FastSearchLibrary 11 | { 12 | /// 13 | /// Represents a class for fast directory search. 14 | /// 15 | public class DirectorySearcher 16 | { 17 | #region Instance members 18 | 19 | private DirectoryCancellationSearcherBase searcher; 20 | 21 | private CancellationTokenSource tokenSource; 22 | 23 | 24 | /// 25 | /// Event fires when next portion of directories is found. Event handlers are not thread safe. 26 | /// 27 | public event EventHandler DirectoriesFound 28 | { 29 | add 30 | { 31 | searcher.DirectoriesFound += value; 32 | } 33 | 34 | remove 35 | { 36 | searcher.DirectoriesFound -= value; 37 | } 38 | } 39 | 40 | 41 | /// 42 | /// Event fires when search process is completed or stopped. 43 | /// 44 | public event EventHandler SearchCompleted 45 | { 46 | add 47 | { 48 | searcher.SearchCompleted += value; 49 | } 50 | 51 | remove 52 | { 53 | searcher.SearchCompleted -= value; 54 | } 55 | } 56 | 57 | 58 | #region DirectoryCancellationPatternSearcher constructors 59 | 60 | /// 61 | /// Initialize a new instance of DirectorySearch class. 62 | /// 63 | /// The start search directory. 64 | /// The search pattern. 65 | /// Instance of CancellationTokenSource for search process cancellation possibility. 66 | /// Specifies where DirectoriesFound event handlers are executed. 67 | /// Determines whether necessary suppress OperationCanceledException if it possible. 68 | /// 69 | /// 70 | public DirectorySearcher(string folder, string pattern, CancellationTokenSource tokenSource, ExecuteHandlers handlerOption, bool suppressOperationCanceledException) 71 | { 72 | CheckFolder(folder); 73 | 74 | CheckPattern(pattern); 75 | 76 | CheckTokenSource(tokenSource); 77 | 78 | searcher = new DirectoryCancellationPatternSearcher(folder, pattern, tokenSource.Token, handlerOption, suppressOperationCanceledException); 79 | this.tokenSource = tokenSource; 80 | } 81 | 82 | 83 | /// 84 | /// Initialize a new instance of DirectorySearch class. 85 | /// 86 | /// The start search directory. 87 | /// The search pattern. 88 | /// Instance of CancellationTokenSource for search process cancellation possibility. 89 | /// Specifies where DirectoriesFound event handlers are executed. 90 | /// 91 | /// 92 | public DirectorySearcher(string folder, string pattern, CancellationTokenSource tokenSource, ExecuteHandlers handlerOption) 93 | : this (folder, pattern, tokenSource, handlerOption, true) 94 | { 95 | } 96 | 97 | 98 | /// 99 | /// Initialize a new instance of DirectorySearch class. 100 | /// 101 | /// The start search directory. 102 | /// The search pattern. 103 | /// Instance of CancellationTokenSource for search process cancellation possibility. 104 | /// 105 | /// 106 | public DirectorySearcher(string folder, string pattern, CancellationTokenSource tokenSource) 107 | : this (folder, pattern, tokenSource, ExecuteHandlers.InCurrentTask, true) 108 | { 109 | } 110 | 111 | 112 | /// 113 | /// Initialize a new instance of DirectorySearch class. 114 | /// 115 | /// The start search directory. 116 | /// Instance of CancellationTokenSource for search process cancellation possibility. 117 | /// 118 | /// 119 | public DirectorySearcher(string folder, CancellationTokenSource tokenSource) 120 | : this (folder, "*", tokenSource, ExecuteHandlers.InCurrentTask, true) 121 | { 122 | 123 | } 124 | 125 | #endregion 126 | 127 | 128 | #region DirectoryCancellationDelegateSearcher constructors 129 | 130 | /// 131 | /// Initialize a new instance of DirectorySearch class. 132 | /// 133 | /// The start search directory. 134 | /// The delegate that determines algorithm of directory selection. 135 | /// Instance of CancellationTokenSource for search process cancellation possibility. 136 | /// Specifies where DirectoriesFound event handlers are executed. 137 | /// Determines whether necessary suppress OperationCanceledException if it possible. 138 | /// 139 | /// 140 | public DirectorySearcher(string folder, Func isValid, CancellationTokenSource tokenSource, ExecuteHandlers handlerOption, bool suppressOperationCanceledException) 141 | { 142 | CheckFolder(folder); 143 | 144 | CheckDelegate(isValid); 145 | 146 | CheckTokenSource(tokenSource); 147 | 148 | searcher = new DirectoryCancellationDelegateSearcher(folder, isValid, tokenSource.Token, handlerOption, suppressOperationCanceledException); 149 | this.tokenSource = tokenSource; 150 | } 151 | 152 | 153 | /// 154 | /// Initialize a new instance of DirectorySearch class. 155 | /// 156 | /// The start search directory. 157 | /// The delegate that determines algorithm of directory selection. 158 | /// Instance of CancellationTokenSource for search process cancellation possibility. 159 | /// Specifies where DirectoriesFound event handlers are executed. 160 | /// 161 | /// 162 | public DirectorySearcher(string folder, Func isValid, CancellationTokenSource tokenSource, ExecuteHandlers handlerOption) 163 | : this(folder, isValid, tokenSource, ExecuteHandlers.InCurrentTask, true) 164 | { 165 | } 166 | 167 | 168 | /// 169 | /// Initialize a new instance of DirectorySearch class. 170 | /// 171 | /// The start search directory. 172 | /// The delegate that determines algorithm of directory selection. 173 | /// Instance of CancellationTokenSource for search process cancellation possibility. 174 | /// 175 | /// 176 | public DirectorySearcher(string folder, Func isValid, CancellationTokenSource tokenSource) 177 | : this(folder, isValid, tokenSource, ExecuteHandlers.InCurrentTask, true) 178 | { 179 | } 180 | 181 | 182 | #endregion 183 | 184 | 185 | #region Checking methods 186 | 187 | private void CheckFolder(string folder) 188 | { 189 | if (folder == null) 190 | throw new ArgumentNullException(nameof(folder), "Argument is null."); 191 | 192 | if (folder == String.Empty) 193 | throw new ArgumentException("Argument is not valid.", nameof(folder)); 194 | 195 | DirectoryInfo dir = new DirectoryInfo(folder); 196 | 197 | if (!dir.Exists) 198 | throw new ArgumentException("Argument does not represent an existing directory.", nameof(folder)); 199 | } 200 | 201 | 202 | private void CheckPattern(string pattern) 203 | { 204 | if (pattern == null) 205 | throw new ArgumentNullException(nameof(pattern), "Argument is null."); 206 | 207 | if (pattern == String.Empty) 208 | throw new ArgumentException("Argument is not valid.", nameof(pattern)); 209 | } 210 | 211 | 212 | private void CheckDelegate(Func isValid) 213 | { 214 | if (isValid == null) 215 | throw new ArgumentNullException(nameof(isValid), "Argument is null."); 216 | } 217 | 218 | 219 | private void CheckTokenSource(CancellationTokenSource tokenSource) 220 | { 221 | if (tokenSource == null) 222 | throw new ArgumentNullException(nameof(tokenSource), "Argument \"tokenSource\" is null."); 223 | } 224 | 225 | #endregion 226 | 227 | 228 | /// 229 | /// Starts a directory search operation with realtime reporting using several threads in thread pool. 230 | /// 231 | public void StartSearch() 232 | { 233 | searcher.StartSearch(); 234 | } 235 | 236 | 237 | /// 238 | /// Starts a directory search operation with realtime reporting using several threads in thread pool as an asynchronous operation. 239 | /// 240 | public Task StartSearchAsync() 241 | { 242 | return Task.Run(() => 243 | { 244 | StartSearch(); 245 | 246 | }, tokenSource.Token); 247 | } 248 | 249 | /// 250 | /// Stops a directory search operation. 251 | /// 252 | public void StopSearch() 253 | { 254 | tokenSource.Cancel(); 255 | } 256 | 257 | #endregion 258 | 259 | 260 | #region Static members 261 | 262 | #region Public members 263 | 264 | /// 265 | /// Returns a list of directories that are contained in directory and all subdirectories. 266 | /// 267 | /// The start search directory. 268 | /// The search pattern. 269 | /// List of finding directories. 270 | /// 271 | /// 272 | /// 273 | /// 274 | static public List GetDirectories(string folder, string pattern = "*") 275 | { 276 | List directories = new List(); 277 | GetDirectories(folder, directories, pattern); 278 | 279 | return directories; 280 | } 281 | 282 | 283 | 284 | /// 285 | /// Returns a list of directories that are contained in directory and all subdirectories. 286 | /// 287 | /// The start search directory. 288 | /// The delegate that determines algorithm of directory selection. 289 | /// List of finding directories. 290 | /// 291 | /// 292 | /// 293 | /// 294 | static public List GetDirectories(string folder, Func isValid) 295 | { 296 | List directories = new List(); 297 | GetDirectories(folder, directories, isValid); 298 | 299 | return directories; 300 | } 301 | 302 | 303 | 304 | /// 305 | /// Returns a list of directories that are contained in directory and all subdirectories as an asynchronous operation. 306 | /// 307 | /// The start search directory. 308 | /// The search pattern. 309 | /// 310 | /// 311 | /// 312 | /// 313 | static public Task> GetDirectoriesAsync(string folder, string pattern = "*") 314 | { 315 | return Task.Run>(() => 316 | { 317 | return GetDirectories(folder, pattern); 318 | }); 319 | } 320 | 321 | 322 | 323 | /// 324 | /// Returns a list of directories that are contained in directory and all subdirectories as an asynchronous operation. 325 | /// 326 | /// The start search directory. 327 | /// The delegate that determines algorithm of directory selection. 328 | /// 329 | /// 330 | /// 331 | /// 332 | static public Task> GetDirectoriesAsync(string folder, Func isValid) 333 | { 334 | return Task.Run>(() => 335 | { 336 | return GetDirectories(folder, isValid); 337 | }); 338 | } 339 | 340 | 341 | 342 | /// 343 | /// Returns a list of directories that are contained in directory and all subdirectories using several threads in thread pool. 344 | /// 345 | /// The start search directory. 346 | /// The search pattern. 347 | /// List of finding directories. 348 | /// 349 | /// 350 | /// 351 | /// 352 | static public List GetDirectoriesFast(string folder, string pattern = "*") 353 | { 354 | ConcurrentBag dirs = new ConcurrentBag(); 355 | 356 | List startDirs = GetStartDirectories(folder, dirs, pattern); 357 | 358 | startDirs.AsParallel().ForAll((d) => 359 | { 360 | GetStartDirectories(d.FullName, dirs, pattern).AsParallel().ForAll((dir) => 361 | { 362 | GetDirectories(dir.FullName, pattern).ForEach((r) => dirs.Add(r)); 363 | }); 364 | }); 365 | 366 | return dirs.ToList(); 367 | } 368 | 369 | 370 | 371 | /// 372 | /// Returns a list of directories that are contained in directory and all subdirectories using several threads in thread pool. 373 | /// 374 | /// The start search directory. 375 | /// The delegate that determines algorithm of directory selection. 376 | /// List of finding directories. 377 | /// 378 | /// 379 | /// 380 | /// 381 | static public List GetDirectoriesFast(string folder, Func isValid) 382 | { 383 | ConcurrentBag dirs = new ConcurrentBag(); 384 | 385 | List startDirs = GetStartDirectories(folder, dirs, isValid); 386 | 387 | startDirs.AsParallel().ForAll((d) => 388 | { 389 | GetStartDirectories(d.FullName, dirs, isValid).AsParallel().ForAll((dir) => 390 | { 391 | GetDirectories(dir.FullName, isValid).ForEach((r) => dirs.Add(r)); 392 | }); 393 | }); 394 | 395 | return dirs.ToList(); 396 | } 397 | 398 | 399 | 400 | /// 401 | /// Returns a list of directories that are contained in directory and all subdirectories using several threads in thread pool as an asynchronous operation. 402 | /// 403 | /// The start search directory. 404 | /// The search pattern. 405 | /// 406 | /// 407 | /// 408 | /// 409 | static public Task> GetDirectoriesFastAsync(string folder, string pattern = "*") 410 | { 411 | return Task.Run>(() => 412 | { 413 | return GetDirectoriesFast(folder, pattern); 414 | }); 415 | } 416 | 417 | 418 | 419 | /// 420 | /// Returns a list of directories that are contained in directory and all subdirectories using several threads in thread pool as an asynchronous operation. 421 | /// 422 | /// The start search directory. 423 | /// The delegate that determines algorithm of directory selection. 424 | /// 425 | /// 426 | /// 427 | /// 428 | static public Task> GetDirectoriesFastAsync(string folder, Func isValid) 429 | { 430 | return Task.Run>(() => 431 | { 432 | return GetDirectoriesFast(folder, isValid); 433 | }); 434 | } 435 | 436 | #endregion 437 | 438 | 439 | #region Private members 440 | 441 | static private void GetDirectories(string folder, List result, string pattern) 442 | { 443 | DirectoryInfo dirInfo = null; 444 | DirectoryInfo[] directories = null; 445 | 446 | try 447 | { 448 | dirInfo = new DirectoryInfo(folder); 449 | directories = dirInfo.GetDirectories(); 450 | 451 | if (directories.Length == 0) return; 452 | } 453 | catch (UnauthorizedAccessException ex) 454 | { 455 | return; 456 | } 457 | catch (PathTooLongException ex) 458 | { 459 | return; 460 | } 461 | catch (DirectoryNotFoundException ex) 462 | { 463 | return; 464 | } 465 | 466 | Array.ForEach(directories, (d) => GetDirectories(d.FullName, result, pattern)); 467 | 468 | try 469 | { 470 | Array.ForEach(dirInfo.GetDirectories(pattern), (d) => result.Add(d)); 471 | } 472 | catch (UnauthorizedAccessException ex) 473 | { 474 | } 475 | catch (DirectoryNotFoundException ex) 476 | { 477 | } 478 | } 479 | 480 | 481 | 482 | static private void GetDirectories(string folder, List result, Func isValid) 483 | { 484 | DirectoryInfo dirInfo = null; 485 | DirectoryInfo[] directories = null; 486 | 487 | try 488 | { 489 | dirInfo = new DirectoryInfo(folder); 490 | directories = dirInfo.GetDirectories(); 491 | 492 | if (directories.Length == 0) return; 493 | } 494 | catch (UnauthorizedAccessException ex) 495 | { 496 | return; 497 | } 498 | catch (PathTooLongException ex) 499 | { 500 | return; 501 | } 502 | catch (DirectoryNotFoundException ex) 503 | { 504 | return; 505 | } 506 | 507 | Array.ForEach(directories, (d) => GetDirectories(d.FullName, result, isValid)); 508 | 509 | try 510 | { 511 | Array.ForEach(dirInfo.GetDirectories(), (d) => 512 | { 513 | if (isValid(d)) 514 | result.Add(d); 515 | }); 516 | } 517 | catch (UnauthorizedAccessException ex) 518 | { 519 | } 520 | catch (PathTooLongException ex) 521 | { 522 | } 523 | catch (DirectoryNotFoundException ex) 524 | { 525 | } 526 | } 527 | 528 | 529 | 530 | static private List GetStartDirectories(string folder, ConcurrentBag dirs, string pattern) 531 | { 532 | DirectoryInfo dirInfo = null; 533 | DirectoryInfo[] directories = null; 534 | try 535 | { 536 | dirInfo = new DirectoryInfo(folder); 537 | directories = dirInfo.GetDirectories(); 538 | 539 | if (directories.Length > 1) 540 | { 541 | Array.ForEach(dirInfo.GetDirectories(pattern), (d) => dirs.Add(d)); 542 | return new List(directories); 543 | } 544 | 545 | if (directories.Length == 0) 546 | return new List(); 547 | 548 | } 549 | catch (UnauthorizedAccessException ex) 550 | { 551 | return new List(); 552 | } 553 | catch (PathTooLongException ex) 554 | { 555 | return new List(); 556 | } 557 | catch (DirectoryNotFoundException ex) 558 | { 559 | return new List(); 560 | } 561 | 562 | // if directories.Length == 1 563 | Array.ForEach(dirInfo.GetDirectories(pattern), (d) => dirs.Add(d)); 564 | 565 | return GetStartDirectories(directories[0].FullName, dirs, pattern); 566 | } 567 | 568 | 569 | 570 | static private List GetStartDirectories(string folder, ConcurrentBag dirs, Func isValid) 571 | { 572 | DirectoryInfo dirInfo = null; 573 | DirectoryInfo[] directories = null; 574 | try 575 | { 576 | dirInfo = new DirectoryInfo(folder); 577 | directories = dirInfo.GetDirectories(); 578 | 579 | if (directories.Length > 1) 580 | { 581 | Array.ForEach(directories, (d) => 582 | { 583 | if (isValid(d)) 584 | dirs.Add(d); 585 | }); 586 | 587 | return new List(directories); 588 | } 589 | 590 | if (directories.Length == 0) 591 | return new List(); 592 | 593 | } 594 | catch (UnauthorizedAccessException ex) 595 | { 596 | return new List(); 597 | } 598 | catch (PathTooLongException ex) 599 | { 600 | return new List(); 601 | } 602 | catch (DirectoryNotFoundException ex) 603 | { 604 | return new List(); 605 | } 606 | 607 | // if directories.Length == 1 608 | Array.ForEach(directories, (d) => 609 | { 610 | if (isValid(d)) 611 | dirs.Add(d); 612 | }); 613 | 614 | return GetStartDirectories(directories[0].FullName, dirs, isValid); 615 | } 616 | 617 | 618 | #endregion 619 | 620 | #endregion 621 | } 622 | } 623 | -------------------------------------------------------------------------------- /FastSearchLibrary/DirectorySearcher/DirectorySearcherMultiple.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace FastSearchLibrary 8 | { 9 | /// 10 | /// Represents a class for fast directory search in multiple directories. 11 | /// 12 | public class DirectorySearcherMultiple 13 | { 14 | #region Instance members 15 | 16 | private List searchers; 17 | 18 | private CancellationTokenSource tokenSource; 19 | 20 | private bool suppressOperationCanceledException; 21 | 22 | 23 | /// 24 | /// Event fires when next portion of directories is found. Event handlers are not thread safe. 25 | /// 26 | public event EventHandler DirectoriesFound 27 | { 28 | add 29 | { 30 | searchers.ForEach((s) => s.DirectoriesFound += value); 31 | } 32 | 33 | remove 34 | { 35 | searchers.ForEach((s) => s.DirectoriesFound -= value); 36 | } 37 | } 38 | 39 | 40 | /// 41 | /// Event fires when search process is completed or stopped. 42 | /// 43 | public event EventHandler SearchCompleted; 44 | 45 | 46 | /// 47 | /// Calls a SearchCompleted event. 48 | /// 49 | /// Determines whether search process canceled. 50 | protected virtual void OnSearchCompleted(bool isCanceled) 51 | { 52 | EventHandler handler = SearchCompleted; 53 | 54 | if (handler != null) 55 | { 56 | var arg = new SearchCompletedEventArgs(isCanceled); 57 | 58 | handler(this, arg); 59 | } 60 | } 61 | 62 | 63 | #region DirectoryCancellationDelegateSearcher constructors 64 | 65 | /// 66 | /// Initialize a new instance of DirectorySearcherMultiple class. 67 | /// 68 | /// Start search directories. 69 | /// The delegate that determines algorithm of directory selection. 70 | /// Instance of CancellationTokenSource for search process cancellation possibility. 71 | /// Specifies where DirectoriesFound event handlers are executed. 72 | /// Determines whether necessary suppress OperationCanceledException if it possible. 73 | /// 74 | /// 75 | public DirectorySearcherMultiple(List folders, Func isValid, CancellationTokenSource tokenSource, ExecuteHandlers handlerOption, bool suppressOperationCanceledException) 76 | { 77 | CheckFolders(folders); 78 | 79 | CheckDelegate(isValid); 80 | 81 | CheckTokenSource(tokenSource); 82 | 83 | searchers = new List(); 84 | 85 | this.suppressOperationCanceledException = suppressOperationCanceledException; 86 | 87 | foreach (var folder in folders) 88 | { 89 | searchers.Add(new DirectoryCancellationDelegateSearcher(folder, isValid, tokenSource.Token, handlerOption, false)); 90 | } 91 | 92 | this.tokenSource = tokenSource; 93 | } 94 | 95 | 96 | /// 97 | /// Initialize a new instance of DirectorySearcherMultiple class. 98 | /// 99 | /// Start search directories. 100 | /// The delegate that determines algorithm of directory selection. 101 | /// Instance of CancellationTokenSource for search process cancellation possibility. 102 | /// Specifies where DirectoriesFound event handlers are executed. 103 | /// 104 | /// 105 | public DirectorySearcherMultiple(List folders, Func isValid, CancellationTokenSource tokenSource, ExecuteHandlers handlerOption) 106 | : this(folders, isValid, tokenSource, ExecuteHandlers.InCurrentTask, true) 107 | { 108 | } 109 | 110 | 111 | /// 112 | /// Initialize a new instance of DirectorySearcherMultiple class. 113 | /// 114 | /// Start search directories. 115 | /// The delegate that determines algorithm of directory selection. 116 | /// Instance of CancellationTokenSource for search process cancellation possibility. 117 | /// 118 | /// 119 | public DirectorySearcherMultiple(List folders, Func isValid, CancellationTokenSource tokenSource) 120 | : this(folders, isValid, tokenSource, ExecuteHandlers.InCurrentTask, true) 121 | { 122 | } 123 | 124 | 125 | #endregion 126 | 127 | 128 | #region DirectoryCancellationPatternSearcher constructors 129 | 130 | /// 131 | /// Initialize a new instance of DirectorySearchMultiple class. 132 | /// 133 | /// Start search directories. 134 | /// The search pattern. 135 | /// Instance of CancellationTokenSource for search process cancellation possibility. 136 | /// Specifies where DirectoriesFound event handlers are executed. 137 | /// Determines whether necessary suppress OperationCanceledException if it possible. 138 | /// 139 | /// 140 | public DirectorySearcherMultiple(List folders, string pattern, CancellationTokenSource tokenSource, ExecuteHandlers handlerOption, bool suppressOperationCanceledException) 141 | { 142 | CheckFolders(folders); 143 | 144 | CheckPattern(pattern); 145 | 146 | CheckTokenSource(tokenSource); 147 | 148 | searchers = new List(); 149 | 150 | this.suppressOperationCanceledException = suppressOperationCanceledException; 151 | 152 | foreach (var folder in folders) 153 | { 154 | searchers.Add(new DirectoryCancellationPatternSearcher(folder, pattern, tokenSource.Token, handlerOption, false)); 155 | } 156 | 157 | this.tokenSource = tokenSource; 158 | } 159 | 160 | 161 | /// 162 | /// Initialize a new instance of DirectorySearchMultiple class. 163 | /// 164 | /// Start search directories. 165 | /// The search pattern. 166 | /// Instance of CancellationTokenSource for search process cancellation possibility. 167 | /// Specifies where DirectoriesFound event handlers are executed. 168 | /// 169 | /// 170 | public DirectorySearcherMultiple(List folders, string pattern, CancellationTokenSource tokenSource, ExecuteHandlers handlerOption) 171 | : this (folders, pattern, tokenSource, handlerOption, true) 172 | { 173 | } 174 | 175 | 176 | /// 177 | /// Initialize a new instance of DirectorySearchMultiple class. 178 | /// 179 | /// Start search directories. 180 | /// The search pattern. 181 | /// Instance of CancellationTokenSource for search process cancellation possibility. 182 | /// 183 | /// 184 | public DirectorySearcherMultiple(List folders, string pattern, CancellationTokenSource tokenSource) 185 | : this (folders, pattern, tokenSource, ExecuteHandlers.InCurrentTask, true) 186 | { 187 | } 188 | 189 | 190 | /// 191 | /// Initialize a new instance of DirectorySearchMultiple class. 192 | /// 193 | /// Start search directories. 194 | /// Instance of CancellationTokenSource for search process cancellation possibility. 195 | /// 196 | /// 197 | public DirectorySearcherMultiple(List folders, CancellationTokenSource tokenSource) 198 | : this (folders, "*", tokenSource, ExecuteHandlers.InCurrentTask, true) 199 | { 200 | 201 | } 202 | 203 | #endregion 204 | 205 | 206 | #region Checking methods 207 | 208 | private void CheckFolders(List folders) 209 | { 210 | if (folders == null) 211 | throw new ArgumentNullException(nameof(folders), "Argument is null."); 212 | 213 | if (folders.Count == 0) 214 | throw new ArgumentException("Argument is an empty list.", nameof(folders)); 215 | 216 | foreach (var folder in folders) 217 | CheckFolder(folder); 218 | } 219 | 220 | 221 | private void CheckFolder(string folder) 222 | { 223 | if (folder == null) 224 | throw new ArgumentNullException(nameof(folder), "Argument is null."); 225 | 226 | if (folder == String.Empty) 227 | throw new ArgumentException("Argument is not valid.", nameof(folder)); 228 | 229 | DirectoryInfo dir = new DirectoryInfo(folder); 230 | 231 | if (!dir.Exists) 232 | throw new ArgumentException("Argument does not represent an existing directory.", nameof(folder)); 233 | } 234 | 235 | 236 | private void CheckPattern(string pattern) 237 | { 238 | if (pattern == null) 239 | throw new ArgumentNullException(nameof(pattern), "Argument is null."); 240 | 241 | if (pattern == String.Empty) 242 | throw new ArgumentException("Argument is not valid.", nameof(pattern)); 243 | } 244 | 245 | 246 | private void CheckDelegate(Func isValid) 247 | { 248 | if (isValid == null) 249 | throw new ArgumentNullException(nameof(isValid), "Argument is null."); 250 | } 251 | 252 | 253 | private void CheckTokenSource(CancellationTokenSource tokenSource) 254 | { 255 | if (tokenSource == null) 256 | throw new ArgumentNullException(nameof(tokenSource), "Argument is null."); 257 | } 258 | 259 | #endregion 260 | 261 | 262 | /// 263 | /// Starts a directory search operation with realtime reporting using several threads in thread pool. 264 | /// 265 | public void StartSearch() 266 | { 267 | try 268 | { 269 | searchers.ForEach(s => 270 | { 271 | s.StartSearch(); 272 | }); 273 | } 274 | catch (OperationCanceledException ex) 275 | { 276 | OnSearchCompleted(true); 277 | if (!suppressOperationCanceledException) 278 | throw; 279 | return; 280 | } 281 | 282 | OnSearchCompleted(false); 283 | } 284 | 285 | 286 | /// 287 | /// Starts a directory search operation with realtime reporting using several threads in thread pool as an asynchronous operation. 288 | /// 289 | public Task StartSearchAsync() 290 | { 291 | return Task.Run(() => 292 | { 293 | StartSearch(); 294 | 295 | }, tokenSource.Token); 296 | } 297 | 298 | 299 | /// 300 | /// Stops a directory search operation. 301 | /// 302 | public void StopSearch() 303 | { 304 | tokenSource.Cancel(); 305 | } 306 | 307 | #endregion 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /FastSearchLibrary/ExecuteHandlers.cs: -------------------------------------------------------------------------------- 1 | namespace FastSearchLibrary 2 | { 3 | /// 4 | /// Specifies where event handlers are executed. 5 | /// 6 | public enum ExecuteHandlers 7 | { 8 | /// 9 | /// To execute event handlers in current task. 10 | /// 11 | InCurrentTask = 0, 12 | 13 | /// 14 | /// To execute event handlers in new task. 15 | /// 16 | InNewTask = 1 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /FastSearchLibrary/FastSearchLibrary.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {6561BFFB-65CC-4384-A7E3-2717F918D474} 8 | Library 9 | Properties 10 | FastSearchLibrary 11 | FastSearchLibrary 12 | v4.6.2 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | true 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | bin\Debug\FastSearchLibrary.XML 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 73 | -------------------------------------------------------------------------------- /FastSearchLibrary/FileSearcher/FileCancellationDelegateSearcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Threading; 5 | 6 | namespace FastSearchLibrary 7 | { 8 | internal class FileCancellationDelegateSearcher : FileCancellationSearcherBase 9 | { 10 | private Func isValid; 11 | 12 | public FileCancellationDelegateSearcher(string folder, Func isValid, CancellationToken token, ExecuteHandlers handlerOption, bool suppressOperationCanceledException) 13 | : base(folder, token, handlerOption, suppressOperationCanceledException) 14 | { 15 | this.isValid = isValid; 16 | } 17 | 18 | 19 | protected override void GetFiles(string folder) 20 | { 21 | token.ThrowIfCancellationRequested(); 22 | 23 | DirectoryInfo dirInfo = null; 24 | DirectoryInfo[] directories = null; 25 | List resultFiles = new List(); 26 | 27 | try 28 | { 29 | dirInfo = new DirectoryInfo(folder); 30 | directories = dirInfo.GetDirectories(); 31 | 32 | if (directories.Length == 0) 33 | { 34 | FileInfo[] files = dirInfo.GetFiles(); 35 | 36 | foreach (var file in files) 37 | if (isValid(file)) 38 | resultFiles.Add(file); 39 | 40 | if (resultFiles.Count > 0) 41 | OnFilesFound(resultFiles); 42 | 43 | return; 44 | } 45 | } 46 | catch (UnauthorizedAccessException ex) 47 | { 48 | return; 49 | } 50 | catch (PathTooLongException ex) 51 | { 52 | return; 53 | } 54 | catch (DirectoryNotFoundException ex) 55 | { 56 | return; 57 | } 58 | 59 | 60 | foreach (var d in directories) 61 | { 62 | token.ThrowIfCancellationRequested(); 63 | 64 | GetFiles(d.FullName); 65 | } 66 | 67 | token.ThrowIfCancellationRequested(); 68 | 69 | try 70 | { 71 | var files = dirInfo.GetFiles(); 72 | 73 | foreach (var file in files) 74 | if (isValid(file)) 75 | resultFiles.Add(file); 76 | 77 | if (resultFiles.Count > 0) 78 | OnFilesFound(resultFiles); 79 | } 80 | catch (UnauthorizedAccessException ex) 81 | { 82 | } 83 | catch (PathTooLongException ex) 84 | { 85 | } 86 | catch (DirectoryNotFoundException ex) 87 | { 88 | } 89 | 90 | return; 91 | } 92 | 93 | 94 | 95 | protected override List GetStartDirectories(string folder) 96 | { 97 | token.ThrowIfCancellationRequested(); 98 | 99 | DirectoryInfo dirInfo = null; 100 | DirectoryInfo[] directories = null; 101 | List resultFiles = new List(); 102 | 103 | try 104 | { 105 | dirInfo = new DirectoryInfo(folder); 106 | directories = dirInfo.GetDirectories(); 107 | 108 | FileInfo[] files = dirInfo.GetFiles(); 109 | 110 | foreach (var file in files) 111 | if (isValid(file)) 112 | resultFiles.Add(file); 113 | 114 | if (resultFiles.Count > 0) 115 | OnFilesFound(resultFiles); 116 | 117 | if (directories.Length > 1) 118 | return new List(directories); 119 | 120 | if (directories.Length == 0) 121 | return new List(); 122 | } 123 | catch (UnauthorizedAccessException ex) 124 | { 125 | return new List(); 126 | } 127 | catch (PathTooLongException ex) 128 | { 129 | return new List(); 130 | } 131 | catch (DirectoryNotFoundException ex) 132 | { 133 | return new List(); 134 | } 135 | 136 | return GetStartDirectories(directories[0].FullName); 137 | } 138 | 139 | 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /FastSearchLibrary/FileSearcher/FileCancellationPatternSearcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading; 6 | 7 | namespace FastSearchLibrary 8 | { 9 | internal class FileCancellationPatternSearcher : FileCancellationSearcherBase 10 | { 11 | 12 | private string pattern; 13 | 14 | public FileCancellationPatternSearcher(string folder, string pattern, CancellationToken token, ExecuteHandlers handlerOption, bool suppressOperationCanceledException) 15 | : base(folder, token, handlerOption, suppressOperationCanceledException) 16 | { 17 | this.pattern = pattern; 18 | } 19 | 20 | 21 | protected override void GetFiles(string folder) 22 | { 23 | token.ThrowIfCancellationRequested(); 24 | 25 | DirectoryInfo dirInfo = null; 26 | DirectoryInfo[] directories = null; 27 | 28 | try 29 | { 30 | dirInfo = new DirectoryInfo(folder); 31 | directories = dirInfo.GetDirectories(); 32 | 33 | if (directories.Length == 0) 34 | { 35 | var resFiles = dirInfo.GetFiles(pattern); 36 | if (resFiles.Length > 0) 37 | OnFilesFound(resFiles.ToList()); 38 | 39 | return; 40 | } 41 | } 42 | catch (UnauthorizedAccessException ex) 43 | { 44 | return; 45 | } 46 | catch (PathTooLongException ex) 47 | { 48 | return; 49 | } 50 | catch (DirectoryNotFoundException ex) 51 | { 52 | return; 53 | } 54 | 55 | foreach (var d in directories) 56 | { 57 | token.ThrowIfCancellationRequested(); 58 | 59 | GetFiles(d.FullName); 60 | } 61 | 62 | token.ThrowIfCancellationRequested(); 63 | 64 | try 65 | { 66 | var resFiles = dirInfo.GetFiles(pattern); 67 | if (resFiles.Length > 0) 68 | OnFilesFound(resFiles.ToList()); 69 | } 70 | catch (UnauthorizedAccessException ex) 71 | { 72 | } 73 | catch (PathTooLongException ex) 74 | { 75 | } 76 | catch (DirectoryNotFoundException ex) 77 | { 78 | } 79 | } 80 | 81 | 82 | 83 | protected override List GetStartDirectories(string folder) 84 | { 85 | token.ThrowIfCancellationRequested(); 86 | 87 | DirectoryInfo dirInfo = null; 88 | DirectoryInfo[] directories = null; 89 | try 90 | { 91 | dirInfo = new DirectoryInfo(folder); 92 | directories = dirInfo.GetDirectories(); 93 | 94 | var resFiles = dirInfo.GetFiles(pattern); 95 | if (resFiles.Length > 0) 96 | OnFilesFound(resFiles.ToList()); 97 | 98 | if (directories.Length > 1) 99 | return new List(directories); 100 | 101 | if (directories.Length == 0) 102 | return new List(); 103 | } 104 | catch (UnauthorizedAccessException ex) 105 | { 106 | return new List(); 107 | } 108 | catch (PathTooLongException ex) 109 | { 110 | return new List(); 111 | } 112 | catch (DirectoryNotFoundException ex) 113 | { 114 | return new List(); 115 | } 116 | 117 | return GetStartDirectories(directories[0].FullName); 118 | } 119 | 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /FastSearchLibrary/FileSearcher/FileCancellationSearcherBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace FastSearchLibrary 9 | { 10 | internal abstract class FileCancellationSearcherBase : FileSearcherBase 11 | { 12 | 13 | protected CancellationToken token; 14 | 15 | protected bool SuppressOperationCanceledException { get; set; } 16 | 17 | public FileCancellationSearcherBase(string folder, CancellationToken token, ExecuteHandlers handlerOption, bool suppressOperationCanceledException) 18 | : base(folder, handlerOption) 19 | { 20 | this.token = token; 21 | this.SuppressOperationCanceledException = suppressOperationCanceledException; 22 | } 23 | 24 | 25 | public override void StartSearch() 26 | { 27 | try 28 | { 29 | GetFilesFast(); 30 | } 31 | catch (OperationCanceledException ex) 32 | { 33 | OnSearchCompleted(true); // isCanceled == true 34 | 35 | if (!SuppressOperationCanceledException) 36 | token.ThrowIfCancellationRequested(); 37 | 38 | return; 39 | } 40 | 41 | OnSearchCompleted(false); 42 | } 43 | 44 | 45 | protected override void OnFilesFound(List files) 46 | { 47 | var arg = new FileEventArgs(files); 48 | 49 | if (handlerOption == ExecuteHandlers.InNewTask) 50 | taskHandlers.Add(Task.Run(() => CallFilesFound(files), token)); 51 | else 52 | CallFilesFound(files); 53 | } 54 | 55 | 56 | protected override void OnSearchCompleted(bool isCanceled) 57 | { 58 | if (handlerOption == ExecuteHandlers.InNewTask) 59 | { 60 | try 61 | { 62 | Task.WaitAll(taskHandlers.ToArray()); 63 | } 64 | catch (AggregateException ex) 65 | { 66 | if (!(ex.InnerException is TaskCanceledException)) 67 | throw; 68 | 69 | if (!isCanceled) 70 | isCanceled = true; 71 | } 72 | 73 | CallSearchCompleted(isCanceled); 74 | } 75 | else 76 | CallSearchCompleted(isCanceled); 77 | } 78 | 79 | 80 | protected override void GetFilesFast() 81 | { 82 | List startDirs = GetStartDirectories(folder); 83 | 84 | startDirs.AsParallel().WithCancellation(token).ForAll((d) => 85 | { 86 | GetStartDirectories(d.FullName).AsParallel().WithCancellation(token).ForAll((dir) => 87 | { 88 | GetFiles(dir.FullName); 89 | }); 90 | }); 91 | } 92 | 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /FastSearchLibrary/FileSearcher/FileDelegateSearcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace FastSearchLibrary 6 | { 7 | internal class FileDelegateSearcher: FileSearcherBase 8 | { 9 | 10 | private Func isValid = null; 11 | 12 | public FileDelegateSearcher(string folder, Func isValid, ExecuteHandlers handlerOption): base(folder, handlerOption) 13 | { 14 | this.isValid = isValid; 15 | } 16 | 17 | 18 | public FileDelegateSearcher(string folder, Func isValid): this(folder, isValid, ExecuteHandlers.InCurrentTask) 19 | { 20 | } 21 | 22 | 23 | public FileDelegateSearcher(string folder): this(folder, (arg) => true, ExecuteHandlers.InCurrentTask) 24 | { 25 | } 26 | 27 | 28 | 29 | /// 30 | /// Starts a file search operation with realtime reporting using several threads in thread pool. 31 | /// 32 | public override void StartSearch() 33 | { 34 | GetFilesFast(); 35 | } 36 | 37 | 38 | 39 | protected override void GetFiles(string folder) 40 | { 41 | DirectoryInfo dirInfo = null; 42 | DirectoryInfo[] directories = null; 43 | List resultFiles = new List(); 44 | 45 | try 46 | { 47 | dirInfo = new DirectoryInfo(folder); 48 | directories = dirInfo.GetDirectories(); 49 | 50 | if (directories.Length == 0) 51 | { 52 | FileInfo[] files = dirInfo.GetFiles(); 53 | 54 | foreach (var file in files) 55 | if (isValid(file)) 56 | resultFiles.Add(file); 57 | 58 | if (resultFiles.Count > 0) 59 | OnFilesFound(resultFiles); 60 | 61 | return; 62 | } 63 | } 64 | catch (UnauthorizedAccessException ex) 65 | { 66 | return; 67 | } 68 | catch (PathTooLongException ex) 69 | { 70 | return; 71 | } 72 | catch (DirectoryNotFoundException ex) 73 | { 74 | return; 75 | } 76 | 77 | foreach (var d in directories) 78 | { 79 | GetFiles(d.FullName); 80 | } 81 | 82 | try 83 | { 84 | var files = dirInfo.GetFiles(); 85 | 86 | foreach (var file in files) 87 | if (isValid(file)) 88 | resultFiles.Add(file); 89 | 90 | if (resultFiles.Count > 0) 91 | OnFilesFound(resultFiles); 92 | } 93 | catch (UnauthorizedAccessException ex) 94 | { 95 | } 96 | catch (PathTooLongException ex) 97 | { 98 | } 99 | catch (DirectoryNotFoundException ex) 100 | { 101 | } 102 | } 103 | 104 | 105 | protected override List GetStartDirectories(string folder) 106 | { 107 | DirectoryInfo dirInfo = null; 108 | DirectoryInfo[] directories = null; 109 | List resultFiles = new List(); 110 | 111 | try 112 | { 113 | dirInfo = new DirectoryInfo(folder); 114 | directories = dirInfo.GetDirectories(); 115 | 116 | FileInfo[] files = dirInfo.GetFiles(); 117 | 118 | foreach (var file in files) 119 | if (isValid(file)) 120 | resultFiles.Add(file); 121 | 122 | if (resultFiles.Count > 0) 123 | OnFilesFound(resultFiles); 124 | 125 | if (directories.Length > 1) 126 | return new List(directories); 127 | 128 | if (directories.Length == 0) 129 | return new List(); 130 | } 131 | catch (UnauthorizedAccessException ex) 132 | { 133 | return new List(); 134 | } 135 | catch (PathTooLongException ex) 136 | { 137 | return new List(); 138 | } 139 | catch (DirectoryNotFoundException ex) 140 | { 141 | return new List(); 142 | } 143 | 144 | return GetStartDirectories(directories[0].FullName); 145 | } 146 | 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /FastSearchLibrary/FileSearcher/FileEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace FastSearchLibrary 6 | { 7 | /// 8 | /// Provides data for FilesFound event. 9 | /// 10 | public class FileEventArgs: EventArgs 11 | { 12 | /// 13 | /// Gets a list of finding files. 14 | /// 15 | public List Files { get; private set; } 16 | 17 | /// 18 | /// Initialize a new instance of FileEventArgs class that describes a FilesFound event. 19 | /// 20 | /// The list of finding files. 21 | public FileEventArgs(List files) 22 | { 23 | Files = files; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /FastSearchLibrary/FileSearcher/FilePatternSearcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | namespace FastSearchLibrary 7 | { 8 | internal class FilePatternSearcher : FileSearcherBase 9 | { 10 | 11 | private string pattern; 12 | 13 | public FilePatternSearcher(string folder, string pattern, ExecuteHandlers handlerOption): base(folder, handlerOption) 14 | { 15 | this.pattern = pattern; 16 | } 17 | 18 | 19 | public FilePatternSearcher(string folder, string pattern): this(folder, pattern, ExecuteHandlers.InCurrentTask) 20 | { 21 | } 22 | 23 | 24 | public FilePatternSearcher(string folder): this(folder, "*", ExecuteHandlers.InCurrentTask) 25 | { 26 | } 27 | 28 | 29 | 30 | /// 31 | /// Starts a file search operation with realtime reporting using several threads in thread pool. 32 | /// 33 | public override void StartSearch() 34 | { 35 | GetFilesFast(); 36 | } 37 | 38 | 39 | 40 | protected override void GetFiles(string folder) 41 | { 42 | DirectoryInfo dirInfo = null; 43 | DirectoryInfo[] directories = null; 44 | 45 | try 46 | { 47 | dirInfo = new DirectoryInfo(folder); 48 | directories = dirInfo.GetDirectories(); 49 | 50 | if (directories.Length == 0) 51 | { 52 | var resFiles = dirInfo.GetFiles(pattern); 53 | if (resFiles.Length > 0) 54 | OnFilesFound(resFiles.ToList()); 55 | return; 56 | } 57 | } 58 | catch (UnauthorizedAccessException ex) 59 | { 60 | return; 61 | } 62 | catch (PathTooLongException ex) 63 | { 64 | return; 65 | } 66 | catch (DirectoryNotFoundException ex) 67 | { 68 | return; 69 | } 70 | 71 | foreach (var d in directories) 72 | { 73 | GetFiles(d.FullName); 74 | } 75 | 76 | try 77 | { 78 | var resFiles = dirInfo.GetFiles(pattern); 79 | if (resFiles.Length > 0) 80 | OnFilesFound(resFiles.ToList()); 81 | } 82 | catch (UnauthorizedAccessException ex) 83 | { 84 | } 85 | catch (PathTooLongException ex) 86 | { 87 | } 88 | catch (DirectoryNotFoundException ex) 89 | { 90 | } 91 | } 92 | 93 | 94 | 95 | protected override List GetStartDirectories(string folder) 96 | { 97 | DirectoryInfo dirInfo = null; 98 | DirectoryInfo[] directories = null; 99 | try 100 | { 101 | dirInfo = new DirectoryInfo(folder); 102 | directories = dirInfo.GetDirectories(); 103 | 104 | var resFiles = dirInfo.GetFiles(pattern); 105 | if (resFiles.Length > 0) 106 | OnFilesFound(resFiles.ToList()); 107 | 108 | if (directories.Length > 1) 109 | return new List(directories); 110 | 111 | if (directories.Length == 0) 112 | return new List(); 113 | } 114 | catch (UnauthorizedAccessException ex) 115 | { 116 | return new List(); 117 | } 118 | catch (PathTooLongException ex) 119 | { 120 | return new List(); 121 | } 122 | catch (DirectoryNotFoundException ex) 123 | { 124 | return new List(); 125 | } 126 | 127 | return GetStartDirectories(directories[0].FullName); 128 | } 129 | 130 | 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /FastSearchLibrary/FileSearcher/FileSearcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace FastSearchLibrary 10 | { 11 | 12 | /// 13 | /// Represents a class for fast file search. 14 | /// 15 | public class FileSearcher 16 | { 17 | #region Instance members 18 | 19 | private FileSearcherBase searcher; 20 | 21 | private CancellationTokenSource tokenSource; 22 | 23 | /// 24 | /// Event fires when next portion of files is found. Event handlers are not thread safe. 25 | /// 26 | public event EventHandler FilesFound 27 | { 28 | add 29 | { 30 | searcher.FilesFound += value; 31 | } 32 | 33 | remove 34 | { 35 | searcher.FilesFound -= value; 36 | } 37 | } 38 | 39 | 40 | /// 41 | /// Event fires when search process is completed or stopped. 42 | /// 43 | public event EventHandler SearchCompleted 44 | { 45 | add 46 | { 47 | searcher.SearchCompleted += value; 48 | } 49 | 50 | remove 51 | { 52 | searcher.SearchCompleted -= value; 53 | } 54 | } 55 | 56 | 57 | #region FilePatternSearcher constructors 58 | 59 | /// 60 | /// Initializes a new instance of FileSearcher class. 61 | /// 62 | /// The start search directory. 63 | /// The search pattern. 64 | /// Specifies where FilesFound event handlers are executed. 65 | /// 66 | /// 67 | public FileSearcher(string folder, string pattern, ExecuteHandlers handlerOption) 68 | { 69 | CheckFolder(folder); 70 | 71 | CheckPattern(pattern); 72 | 73 | searcher = new FilePatternSearcher(folder, pattern, handlerOption); 74 | } 75 | 76 | 77 | /// 78 | /// Initializes a new instance of FileSearcher class. 79 | /// 80 | /// The start search directory. 81 | /// The search pattern. 82 | /// 83 | /// 84 | public FileSearcher(string folder, string pattern) : this(folder, pattern, ExecuteHandlers.InCurrentTask) 85 | { 86 | } 87 | 88 | 89 | /// 90 | /// Initializes a new instance of FileSearcher class. 91 | /// 92 | /// The start search directory. 93 | /// 94 | /// 95 | public FileSearcher(string folder) : this(folder, "*", ExecuteHandlers.InCurrentTask) 96 | { 97 | } 98 | 99 | #endregion 100 | 101 | 102 | #region FileDelegateSearcher constructors 103 | 104 | 105 | /// 106 | /// Initializes a new instance of FileSearcher class. 107 | /// 108 | /// The start search directory. 109 | /// The delegate that determines algorithm of file selection. 110 | /// Specifies where FilesFound event handlers are executed. 111 | /// 112 | /// 113 | public FileSearcher(string folder, Func isValid, ExecuteHandlers handlerOption) 114 | { 115 | CheckFolder(folder); 116 | 117 | CheckDelegate(isValid); 118 | 119 | searcher = new FileDelegateSearcher(folder, isValid, handlerOption); 120 | } 121 | 122 | 123 | /// 124 | /// Initializes a new instance of FileSearcher class. 125 | /// 126 | /// The start search directory. 127 | /// The delegate that determines algorithm of file selection. 128 | /// 129 | /// 130 | public FileSearcher(string folder, Func isValid) 131 | : this(folder, isValid, ExecuteHandlers.InCurrentTask) 132 | { 133 | } 134 | 135 | #endregion 136 | 137 | 138 | #region FileCancellationPatternSearcher constructors 139 | 140 | /// 141 | /// Initializes a new instance of FileSearcher class. 142 | /// 143 | /// The start search directory. 144 | /// The search pattern. 145 | /// Instance of CancellationTokenSource for search process cancellation possibility. 146 | /// Specifies where FilesFound event handlers are executed. 147 | /// Determines whether necessary suppress OperationCanceledException if it possible. 148 | /// 149 | /// 150 | public FileSearcher(string folder, string pattern, CancellationTokenSource tokenSource, ExecuteHandlers handlerOption, bool suppressOperationCanceledException) 151 | { 152 | CheckFolder(folder); 153 | 154 | CheckPattern(pattern); 155 | 156 | CheckTokenSource(tokenSource); 157 | 158 | searcher = new FileCancellationPatternSearcher(folder, pattern, tokenSource.Token, handlerOption, suppressOperationCanceledException); 159 | this.tokenSource = tokenSource; 160 | } 161 | 162 | 163 | /// 164 | /// Initializes a new instance of FileSearcher class. 165 | /// 166 | /// The start search directory. 167 | /// The search pattern. 168 | /// Instance of CancellationTokenSource for search process cancellation possibility. 169 | /// Specifies where FilesFound event handlers are executed. 170 | /// 171 | /// 172 | public FileSearcher(string folder, string pattern, CancellationTokenSource tokenSource, ExecuteHandlers handlerOption) 173 | : this(folder, pattern, tokenSource, handlerOption, true) 174 | { 175 | } 176 | 177 | 178 | /// 179 | /// Initializes a new instance of FileSearcher class. 180 | /// 181 | /// The start search directory. 182 | /// The search pattern. 183 | /// Instance of CancellationTokenSource for search process cancellation possibility. 184 | /// 185 | /// 186 | public FileSearcher(string folder, string pattern, CancellationTokenSource tokenSource) 187 | : this(folder, pattern, tokenSource, ExecuteHandlers.InCurrentTask, true) 188 | { 189 | } 190 | 191 | 192 | /// 193 | /// Initializes a new instance of FileSearcher class. 194 | /// 195 | /// The start search directory. 196 | /// Instance of CancellationTokenSource for search process cancellation possibility. 197 | /// 198 | /// 199 | public FileSearcher(string folder, CancellationTokenSource tokenSource) 200 | : this(folder, "*", tokenSource, ExecuteHandlers.InCurrentTask, true) 201 | { 202 | } 203 | 204 | #endregion 205 | 206 | 207 | #region FileCancellationDelegateSearcher constructors 208 | 209 | /// 210 | /// Initializes a new instance of FileSearcher class. 211 | /// 212 | /// The start search directory. 213 | /// The delegate that determines algorithm of file selection. 214 | /// Instance of CancellationTokenSource for search process cancellation possibility. 215 | /// Specifies where FilesFound event handlers are executed. 216 | /// Determines whether necessary suppress OperationCanceledException if it possible. 217 | /// 218 | /// 219 | public FileSearcher(string folder, Func isValid, CancellationTokenSource tokenSource, ExecuteHandlers handlerOption, bool suppressOperationCanceledException) 220 | { 221 | CheckFolder(folder); 222 | 223 | CheckDelegate(isValid); 224 | 225 | CheckTokenSource(tokenSource); 226 | 227 | searcher = new FileCancellationDelegateSearcher(folder, isValid, tokenSource.Token, handlerOption, suppressOperationCanceledException); 228 | this.tokenSource = tokenSource; 229 | } 230 | 231 | 232 | /// 233 | /// Initializes a new instance of FileSearcher class. 234 | /// 235 | /// The start search directory. 236 | /// The delegate that determines algorithm of file selection. 237 | /// Instance of CancellationTokenSource for search process cancellation possibility. 238 | /// Specifies where FilesFound event handlers are executed. 239 | /// 240 | /// 241 | public FileSearcher(string folder, Func isValid, CancellationTokenSource tokenSource, ExecuteHandlers handlerOption) 242 | : this(folder, isValid, tokenSource, handlerOption, true) 243 | { 244 | } 245 | 246 | 247 | /// 248 | /// Initializes a new instance of FileSearcher class. 249 | /// 250 | /// The start search directory. 251 | /// The delegate that determines algorithm of file selection. 252 | /// Instance of CancellationTokenSource for search process cancellation possibility. 253 | /// 254 | /// 255 | public FileSearcher(string folder, Func isValid, CancellationTokenSource tokenSource) 256 | : this(folder, isValid, tokenSource, ExecuteHandlers.InCurrentTask, true) 257 | { 258 | } 259 | 260 | #endregion 261 | 262 | 263 | #region Checking methods 264 | 265 | private void CheckFolder(string folder) 266 | { 267 | if (folder == null) 268 | throw new ArgumentNullException(nameof(folder), "Argument is null."); 269 | 270 | if (folder == String.Empty) 271 | throw new ArgumentException("Argument is not valid.", nameof(folder)); 272 | 273 | DirectoryInfo dir = new DirectoryInfo(folder); 274 | 275 | if (!dir.Exists) 276 | throw new ArgumentException("Argument does not represent an existing directory.", nameof(folder)); 277 | } 278 | 279 | 280 | private void CheckPattern(string pattern) 281 | { 282 | if (pattern == null) 283 | throw new ArgumentNullException(nameof(pattern), "Argument is null."); 284 | 285 | if (pattern == String.Empty) 286 | throw new ArgumentException("Argument is not valid.", nameof(pattern)); 287 | } 288 | 289 | 290 | private void CheckDelegate(Func isValid) 291 | { 292 | if (isValid == null) 293 | throw new ArgumentNullException(nameof(isValid), "Argument is null."); 294 | } 295 | 296 | 297 | private void CheckTokenSource(CancellationTokenSource tokenSource) 298 | { 299 | if (tokenSource == null) 300 | throw new ArgumentNullException(nameof(tokenSource), "Argument is null."); 301 | } 302 | 303 | 304 | #endregion 305 | 306 | 307 | /// 308 | /// Starts a file search operation with realtime reporting using several threads in thread pool. 309 | /// 310 | public void StartSearch() 311 | { 312 | searcher.StartSearch(); 313 | } 314 | 315 | 316 | /// 317 | /// Starts a file search operation with realtime reporting using several threads in thread pool as an asynchronous operation. 318 | /// 319 | public Task StartSearchAsync() 320 | { 321 | if (searcher is FileCancellationSearcherBase) 322 | { 323 | return Task.Run(() => 324 | { 325 | StartSearch(); 326 | 327 | }, tokenSource.Token); 328 | } 329 | 330 | return Task.Run(() => 331 | { 332 | StartSearch(); 333 | }); 334 | } 335 | 336 | 337 | /// 338 | /// Stops a file search operation. 339 | /// 340 | /// 341 | public void StopSearch() 342 | { 343 | if (tokenSource == null) 344 | throw new InvalidOperationException("Impossible to stop operation without instance of CancellationTokenSource."); 345 | 346 | tokenSource.Cancel(); 347 | } 348 | 349 | #endregion 350 | 351 | 352 | #region Static members 353 | 354 | #region Public members 355 | 356 | 357 | /// 358 | /// Returns a list of files that are contained in directory and all subdirectories. 359 | /// 360 | /// The start search directory. 361 | /// The search pattern. 362 | /// List of finding files 363 | /// 364 | /// 365 | static public List GetFiles(string folder, string pattern = "*") 366 | { 367 | DirectoryInfo dirInfo = null; 368 | DirectoryInfo[] directories = null; 369 | try 370 | { 371 | dirInfo = new DirectoryInfo(folder); 372 | directories = dirInfo.GetDirectories(); 373 | 374 | if (directories.Length == 0) 375 | return new List(dirInfo.GetFiles(pattern)); 376 | } 377 | catch (UnauthorizedAccessException ex) 378 | { 379 | return new List(); 380 | } 381 | catch (DirectoryNotFoundException ex) 382 | { 383 | return new List(); 384 | } 385 | 386 | List result = new List(); 387 | 388 | foreach (var d in directories) 389 | { 390 | result.AddRange(GetFiles(d.FullName, pattern)); 391 | } 392 | 393 | try 394 | { 395 | result.AddRange(dirInfo.GetFiles(pattern)); 396 | } 397 | catch (UnauthorizedAccessException ex) 398 | { 399 | } 400 | catch (PathTooLongException ex) 401 | { 402 | } 403 | catch (DirectoryNotFoundException ex) 404 | { 405 | } 406 | 407 | return result; 408 | } 409 | 410 | 411 | 412 | /// 413 | /// Returns a list of files that are contained in directory and all subdirectories. 414 | /// 415 | /// The start search directory. 416 | /// The delegate that determines algorithm of file selection. 417 | /// List of finding files. 418 | /// 419 | /// 420 | static public List GetFiles(string folder, Func isValid) 421 | { 422 | DirectoryInfo dirInfo = null; 423 | DirectoryInfo[] directories = null; 424 | List resultFiles = new List(); 425 | 426 | try 427 | { 428 | dirInfo = new DirectoryInfo(folder); 429 | directories = dirInfo.GetDirectories(); 430 | 431 | if (directories.Length == 0) 432 | { 433 | FileInfo[] files = dirInfo.GetFiles(); 434 | 435 | foreach (var file in files) 436 | if (isValid(file)) 437 | resultFiles.Add(file); 438 | 439 | return resultFiles; 440 | } 441 | } 442 | catch (UnauthorizedAccessException ex) 443 | { 444 | return new List(); 445 | } 446 | catch (PathTooLongException ex) 447 | { 448 | return new List(); 449 | } 450 | catch (DirectoryNotFoundException ex) 451 | { 452 | return new List(); 453 | } 454 | 455 | foreach (var d in directories) 456 | { 457 | resultFiles.AddRange(GetFiles(d.FullName, isValid)); 458 | } 459 | 460 | try 461 | { 462 | FileInfo[] files = dirInfo.GetFiles(); 463 | 464 | foreach (var file in files) 465 | if (isValid(file)) 466 | resultFiles.Add(file); 467 | } 468 | catch (UnauthorizedAccessException ex) 469 | { 470 | } 471 | catch (PathTooLongException ex) 472 | { 473 | } 474 | catch (DirectoryNotFoundException ex) 475 | { 476 | } 477 | 478 | return resultFiles; 479 | } 480 | 481 | 482 | 483 | /// 484 | /// Returns a list of files that are contained in directory and all subdirectories as an asynchronous operation. 485 | /// 486 | /// The start search directory. 487 | /// The search pattern. 488 | /// 489 | /// 490 | static public Task> GetFilesAsync(string folder, string pattern = "*") 491 | { 492 | return Task.Run>(() => 493 | { 494 | return GetFiles(folder, pattern); 495 | }); 496 | } 497 | 498 | 499 | 500 | /// 501 | /// Returns a list of files that are contained in directory and all subdirectories as an asynchronous operation. 502 | /// 503 | /// The start search directory. 504 | /// The delegate that determines algorithm of file selection. 505 | /// 506 | /// 507 | static public Task> GetFilesAsync(string folder, Func isValid) 508 | { 509 | return Task.Run>(() => 510 | { 511 | return GetFiles(folder, isValid); 512 | }); 513 | } 514 | 515 | 516 | 517 | /// 518 | /// Returns a list of files that are contained in directory and all subdirectories using several threads of thread pool. 519 | /// 520 | /// The start search directory. 521 | /// The search pattern. 522 | /// List of finding files. 523 | /// 524 | /// 525 | static public List GetFilesFast(string folder, string pattern = "*") 526 | { 527 | ConcurrentBag files = new ConcurrentBag(); 528 | 529 | List startDirs = GetStartDirectories(folder, files, pattern); 530 | 531 | startDirs.AsParallel().ForAll((d) => 532 | { 533 | GetStartDirectories(d.FullName, files, pattern).AsParallel().ForAll((dir) => 534 | { 535 | GetFiles(dir.FullName, pattern).ForEach((f) => files.Add(f)); 536 | }); 537 | }); 538 | 539 | return files.ToList(); 540 | } 541 | 542 | 543 | 544 | /// 545 | /// Returns a list of files that are contained in directory and all subdirectories using several threads of thread pool. 546 | /// 547 | /// The start search directory. 548 | /// The delegate that determines algorithm of file selection. 549 | /// List of finding files. 550 | /// 551 | /// 552 | static public List GetFilesFast(string folder, Func isValid) 553 | { 554 | ConcurrentBag files = new ConcurrentBag(); 555 | 556 | List startDirs = GetStartDirectories(folder, files, isValid); 557 | 558 | startDirs.AsParallel().ForAll((d) => 559 | { 560 | GetStartDirectories(d.FullName, files, isValid).AsParallel().ForAll((dir) => 561 | { 562 | GetFiles(dir.FullName, isValid).ForEach((f) => files.Add(f)); 563 | }); 564 | }); 565 | 566 | return files.ToList(); 567 | } 568 | 569 | 570 | 571 | /// 572 | /// Returns a list of files that are contained in directory and all subdirectories using several threads of thread pool as an asynchronous operation. 573 | /// 574 | /// The start search directory. 575 | /// The search pattern. 576 | /// 577 | /// 578 | static public Task> GetFilesFastAsync(string folder, string pattern = "*") 579 | { 580 | return Task.Run>(() => 581 | { 582 | return GetFilesFast(folder, pattern); 583 | }); 584 | } 585 | 586 | 587 | 588 | /// 589 | /// Returns a list of files that are contained in directory and all subdirectories using several threads of thread pool as an asynchronous operation. 590 | /// 591 | /// The start search directory. 592 | /// The delegate that determines algorithm of file selection. 593 | /// 594 | /// 595 | static public Task> GetFilesFastAsync(string folder, Func isValid) 596 | { 597 | return Task.Run>(() => 598 | { 599 | return GetFilesFast(folder, isValid); 600 | }); 601 | } 602 | 603 | 604 | #endregion 605 | 606 | #region Private members 607 | 608 | static private List GetStartDirectories(string folder, ConcurrentBag files, string pattern) 609 | { 610 | DirectoryInfo dirInfo = null; 611 | DirectoryInfo[] directories = null; 612 | try 613 | { 614 | dirInfo = new DirectoryInfo(folder); 615 | directories = dirInfo.GetDirectories(); 616 | 617 | foreach (var f in dirInfo.GetFiles(pattern)) 618 | { 619 | files.Add(f); 620 | } 621 | 622 | if (directories.Length > 1) 623 | return new List(directories); 624 | 625 | if (directories.Length == 0) 626 | return new List(); 627 | 628 | } 629 | catch (UnauthorizedAccessException ex) 630 | { 631 | return new List(); 632 | } 633 | catch (PathTooLongException ex) 634 | { 635 | return new List(); 636 | } 637 | catch (DirectoryNotFoundException ex) 638 | { 639 | return new List(); 640 | } 641 | 642 | return GetStartDirectories(directories[0].FullName, files, pattern); 643 | } 644 | 645 | 646 | 647 | static private List GetStartDirectories(string folder, ConcurrentBag resultFiles, Func isValid) 648 | { 649 | DirectoryInfo dirInfo = null; 650 | DirectoryInfo[] directories = null; 651 | 652 | try 653 | { 654 | dirInfo = new DirectoryInfo(folder); 655 | directories = dirInfo.GetDirectories(); 656 | 657 | FileInfo[] files = dirInfo.GetFiles(); 658 | 659 | foreach (var file in files) 660 | if (isValid(file)) 661 | resultFiles.Add(file); 662 | 663 | if (directories.Length > 1) 664 | return new List(directories); 665 | 666 | if (directories.Length == 0) 667 | return new List(); 668 | } 669 | catch (UnauthorizedAccessException ex) 670 | { 671 | return new List(); 672 | } 673 | catch (PathTooLongException ex) 674 | { 675 | return new List(); 676 | } 677 | catch (DirectoryNotFoundException ex) 678 | { 679 | return new List(); 680 | } 681 | 682 | return GetStartDirectories(directories[0].FullName, resultFiles, isValid); 683 | } 684 | 685 | #endregion 686 | 687 | #endregion 688 | 689 | } 690 | } 691 | -------------------------------------------------------------------------------- /FastSearchLibrary/FileSearcher/FileSearcherBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace FastSearchLibrary 9 | { 10 | internal abstract class FileSearcherBase 11 | { 12 | /// 13 | /// Specifies where FilesFound event handlers are executed. 14 | /// 15 | protected ExecuteHandlers handlerOption { get; set; } 16 | 17 | protected string folder; 18 | 19 | protected ConcurrentBag taskHandlers; 20 | 21 | 22 | public FileSearcherBase(string folder, ExecuteHandlers handlerOption) 23 | { 24 | this.folder = folder; 25 | this.handlerOption = handlerOption; 26 | taskHandlers = new ConcurrentBag(); 27 | } 28 | 29 | 30 | public event EventHandler FilesFound; 31 | 32 | public event EventHandler SearchCompleted; 33 | 34 | 35 | protected virtual void GetFilesFast() 36 | { 37 | List startDirs = GetStartDirectories(folder); 38 | 39 | startDirs.AsParallel().ForAll((d) => 40 | { 41 | GetStartDirectories(d.FullName).AsParallel().ForAll((dir) => 42 | { 43 | GetFiles(dir.FullName); 44 | }); 45 | }); 46 | 47 | OnSearchCompleted(false); 48 | } 49 | 50 | 51 | protected virtual void OnFilesFound(List files) 52 | { 53 | if (handlerOption == ExecuteHandlers.InNewTask) 54 | { 55 | taskHandlers.Add(Task.Run(() => CallFilesFound(files))); 56 | } 57 | else 58 | { 59 | CallFilesFound(files); 60 | } 61 | } 62 | 63 | 64 | protected virtual void CallFilesFound(List files) 65 | { 66 | EventHandler handler = FilesFound; 67 | 68 | if (handler != null) 69 | { 70 | var arg = new FileEventArgs(files); 71 | handler(this, arg); 72 | } 73 | } 74 | 75 | 76 | protected virtual void OnSearchCompleted(bool isCanceled) 77 | { 78 | if (handlerOption == ExecuteHandlers.InNewTask) 79 | { 80 | Task.WaitAll(taskHandlers.ToArray()); 81 | } 82 | 83 | CallSearchCompleted(isCanceled); 84 | } 85 | 86 | 87 | protected virtual void CallSearchCompleted(bool isCanceled) 88 | { 89 | EventHandler handler = SearchCompleted; 90 | 91 | if (handler != null) 92 | { 93 | var arg = new SearchCompletedEventArgs(isCanceled); 94 | 95 | handler(this, arg); 96 | } 97 | } 98 | 99 | 100 | protected abstract void GetFiles(string folder); 101 | 102 | 103 | protected abstract List GetStartDirectories(string folder); 104 | 105 | 106 | public abstract void StartSearch(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /FastSearchLibrary/FileSearcher/FileSearcherMultiple.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace FastSearchLibrary 8 | { 9 | /// 10 | /// Represents a class for fast file search in multiple directories. 11 | /// 12 | public class FileSearcherMultiple 13 | { 14 | private List searchers; 15 | 16 | private CancellationTokenSource tokenSource; 17 | 18 | private bool suppressOperationCanceledException; 19 | 20 | 21 | /// 22 | /// Event fires when next portion of files is found. Event handlers are not thread safe. 23 | /// 24 | public event EventHandler FilesFound 25 | { 26 | add 27 | { 28 | searchers.ForEach((s) => s.FilesFound += value); 29 | } 30 | 31 | remove 32 | { 33 | searchers.ForEach((s) => s.FilesFound -= value); 34 | } 35 | } 36 | 37 | 38 | /// 39 | /// Event fires when search process is completed or stopped. 40 | /// 41 | public event EventHandler SearchCompleted; 42 | 43 | 44 | 45 | /// 46 | /// Calls a SearchCompleted event. 47 | /// 48 | /// Determines whether search process canceled. 49 | protected virtual void OnSearchCompleted(bool isCanceled) 50 | { 51 | EventHandler handler = SearchCompleted; 52 | 53 | if (handler != null) 54 | { 55 | var arg = new SearchCompletedEventArgs(isCanceled); 56 | 57 | handler(this, arg); 58 | } 59 | } 60 | 61 | 62 | #region FileCancellationDelegateSearcher constructors 63 | 64 | /// 65 | /// Initializes a new instance of FileSearcherMultiple class. 66 | /// 67 | /// Start search directories. 68 | /// The delegate that determines algorithm of file selection. 69 | /// Instance of CancellationTokenSource for search process cancellation possibility. 70 | /// Specifies where FilesFound event handlers are executed. 71 | /// Determines whether necessary suppress OperationCanceledException if it possible. 72 | /// 73 | /// 74 | public FileSearcherMultiple(List folders, Func isValid, CancellationTokenSource tokenSource, ExecuteHandlers handlerOption, bool suppressOperationCanceledException) 75 | { 76 | CheckFolders(folders); 77 | 78 | CheckDelegate(isValid); 79 | 80 | CheckTokenSource(tokenSource); 81 | 82 | searchers = new List(); 83 | 84 | this.suppressOperationCanceledException = suppressOperationCanceledException; 85 | 86 | foreach (var folder in folders) 87 | { 88 | searchers.Add(new FileCancellationDelegateSearcher(folder, isValid, tokenSource.Token, handlerOption, false)); 89 | } 90 | 91 | this.tokenSource = tokenSource; 92 | } 93 | 94 | 95 | /// 96 | /// Initializes a new instance of FileSearcherMultiple class. 97 | /// 98 | /// Start search directories. 99 | /// The delegate that determines algorithm of file selection. 100 | /// Instance of CancellationTokenSource for search process cancellation possibility. 101 | /// Specifies where FilesFound event handlers are executed. 102 | /// 103 | /// 104 | public FileSearcherMultiple(List folders, Func isValid, CancellationTokenSource tokenSource, ExecuteHandlers handlerOption) 105 | : this(folders, isValid, tokenSource, handlerOption, true) 106 | { 107 | } 108 | 109 | 110 | /// 111 | /// Initializes a new instance of FileSearcherMultiple class. 112 | /// 113 | /// Start search directories. 114 | /// The delegate that determines algorithm of file selection. 115 | /// Instance of CancellationTokenSource for search process cancellation possibility. 116 | /// 117 | /// 118 | public FileSearcherMultiple(List folders, Func isValid, CancellationTokenSource tokenSource) 119 | : this(folders, isValid, tokenSource, ExecuteHandlers.InCurrentTask, true) 120 | { 121 | } 122 | 123 | #endregion 124 | 125 | 126 | #region FileCancellationPatternSearcher constructors 127 | 128 | /// 129 | /// Initializes a new instance of FileSearcherMultiple class. 130 | /// 131 | /// Start search directories. 132 | /// The search pattern. 133 | /// Instance of CancellationTokenSource for search process cancellation possibility. 134 | /// Specifies where FilesFound event handlers are executed. 135 | /// Determines whether necessary suppress OperationCanceledException if it possible. 136 | /// 137 | /// 138 | public FileSearcherMultiple(List folders, string pattern, CancellationTokenSource tokenSource, ExecuteHandlers handlerOption, bool suppressOperationCanceledException) 139 | { 140 | CheckFolders(folders); 141 | 142 | CheckPattern(pattern); 143 | 144 | CheckTokenSource(tokenSource); 145 | 146 | searchers = new List(); 147 | 148 | this.suppressOperationCanceledException = suppressOperationCanceledException; 149 | 150 | foreach (var folder in folders) 151 | { 152 | searchers.Add(new FileCancellationPatternSearcher(folder, pattern, tokenSource.Token, handlerOption, false)); 153 | } 154 | 155 | this.tokenSource = tokenSource; 156 | } 157 | 158 | 159 | /// 160 | /// Initializes a new instance of FileSearcherMultiple class. 161 | /// 162 | /// Start search directories. 163 | /// The search pattern. 164 | /// Instance of CancellationTokenSource for search process cancellation possibility. 165 | /// Specifies where FilesFound event handlers are executed. 166 | /// 167 | /// 168 | public FileSearcherMultiple(List folders, string pattern, CancellationTokenSource tokenSource, ExecuteHandlers handlerOption) 169 | : this(folders, pattern, tokenSource, handlerOption, true) 170 | { 171 | } 172 | 173 | 174 | /// 175 | /// Initializes a new instance of FileSearcherMultiple class. 176 | /// 177 | /// Start search directories. 178 | /// The search pattern. 179 | /// Instance of CancellationTokenSource for search process cancellation possibility. 180 | /// 181 | /// 182 | public FileSearcherMultiple(List folders, string pattern, CancellationTokenSource tokenSource) 183 | : this(folders, pattern, tokenSource, ExecuteHandlers.InCurrentTask, true) 184 | { 185 | } 186 | 187 | 188 | /// 189 | /// Initializes a new instance of FileSearcherMultiple class. 190 | /// 191 | /// Start search directories. 192 | /// Instance of CancellationTokenSource for search process cancellation possibility. 193 | /// 194 | /// 195 | public FileSearcherMultiple(List folders, CancellationTokenSource tokenSource) 196 | : this(folders, "*", tokenSource, ExecuteHandlers.InCurrentTask, true) 197 | { 198 | } 199 | 200 | #endregion 201 | 202 | 203 | #region Checking methods 204 | private void CheckFolders(List folders) 205 | { 206 | if (folders == null) 207 | throw new ArgumentNullException(nameof(folders), "Argument is null."); 208 | 209 | if (folders.Count == 0) 210 | throw new ArgumentException("Argument is an empty list.", nameof(folders)); 211 | 212 | foreach (var folder in folders) 213 | CheckFolder(folder); 214 | } 215 | 216 | 217 | private void CheckFolder(string folder) 218 | { 219 | if (folder == null) 220 | throw new ArgumentNullException(nameof(folder), "Argument is null."); 221 | 222 | if (folder == String.Empty) 223 | throw new ArgumentException("Argument is not valid.", nameof(folder)); 224 | 225 | DirectoryInfo dir = new DirectoryInfo(folder); 226 | 227 | if (!dir.Exists) 228 | throw new ArgumentException("Argument does not represent an existing directory.", nameof(folder)); 229 | } 230 | 231 | 232 | private void CheckPattern(string pattern) 233 | { 234 | if (pattern == null) 235 | throw new ArgumentNullException(nameof(pattern), "Argument is null."); 236 | 237 | if (pattern == String.Empty) 238 | throw new ArgumentException("Argument is not valid.", nameof(pattern)); 239 | } 240 | 241 | 242 | private void CheckDelegate(Func isValid) 243 | { 244 | if (isValid == null) 245 | throw new ArgumentNullException(nameof(isValid), "Argument is null."); 246 | } 247 | 248 | 249 | private void CheckTokenSource(CancellationTokenSource tokenSource) 250 | { 251 | if (tokenSource == null) 252 | throw new ArgumentNullException(nameof(tokenSource), "Argument is null."); 253 | } 254 | 255 | 256 | #endregion 257 | 258 | 259 | /// 260 | /// Starts a file search operation with realtime reporting using several threads in thread pool. 261 | /// 262 | public void StartSearch() 263 | { 264 | try 265 | { 266 | searchers.ForEach(s => 267 | { 268 | s.StartSearch(); 269 | }); 270 | } 271 | catch(OperationCanceledException ex) 272 | { 273 | OnSearchCompleted(true); 274 | if (!suppressOperationCanceledException) 275 | throw; 276 | return; 277 | } 278 | 279 | OnSearchCompleted(false); 280 | } 281 | 282 | 283 | /// 284 | /// Starts a file search operation with realtime reporting using several threads in thread pool as an asynchronous operation. 285 | /// 286 | public Task StartSearchAsync() 287 | { 288 | return Task.Run(() => 289 | { 290 | StartSearch(); 291 | 292 | }, tokenSource.Token); 293 | } 294 | 295 | 296 | /// 297 | /// Stops a file search operation. 298 | /// 299 | public void StopSearch() 300 | { 301 | tokenSource.Cancel(); 302 | } 303 | 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /FastSearchLibrary/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("FastSearchLibrary")] 8 | [assembly: AssemblyDescription("Multithreading library for fast file and directory search")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("FastSearchLibrary")] 12 | [assembly: AssemblyCopyright("Copyright © VladPVS 2018")] 13 | [assembly: AssemblyTrademark("VladPVS")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("6561bffb-65cc-4384-a7e3-2717f918d474")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("1.1.8.0")] 35 | [assembly: AssemblyFileVersion("1.1.8.0")] 36 | -------------------------------------------------------------------------------- /FastSearchLibrary/SearchCompletedEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FastSearchLibrary 4 | { 5 | /// 6 | /// Provides data for SearchCompleted event. 7 | /// 8 | public class SearchCompletedEventArgs : EventArgs 9 | { 10 | /// 11 | /// Gets whether this search process has completed due cancellation. 12 | /// 13 | public bool IsCanceled { get; private set; } 14 | 15 | /// 16 | /// Initialize a new instance of SearchCompletedEventArgs class that describes a SearchCompleted event. 17 | /// 18 | /// Determines whether search process canceled. 19 | public SearchCompletedEventArgs(bool isCanceled) 20 | { 21 | IsCanceled = isCanceled; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Attention! 2 | 3 | We have been at state of war with Russia since February 24th, 2022. 4 | To help Ukraine achieve victory as soon as possible, please, ignore all Russian products, companies, projects... Everything. 5 | 6 | Also you may help Armed Forces of Ukraine here: https://bank.gov.ua/en/news/all/natsionalniy-bank-vidkriv-spetsrahunok-dlya-zboru-koshtiv-na-potrebi-armiyi 7 | 8 | We will release our Motherland from Russian invaders and save Europe from aggressive inhuman Russian regime. I promise. 9 | #### Glory to Ukraine! 10 | 11 | # FastSearchLibrary 12 | The multithreading .NET library that provides opportunity to fast find files or directories using different search criteria. 13 | 14 | .NET Core version is available [here](https://github.com/VladPVS/FastSearchLibraryNetCore ".NET Core version"). 15 | 16 | [The MIF](https://github.com/VladPVS/The-MIF "The MIF search tool") file search tool is based on this library. You can [try](https://github.com/VladPVS/The-MIF/releases "Download The MIF") it if you want to estimate speed of work right now. 17 | #### Works really fast. Check it yourself! 18 | ![Downloads](https://img.shields.io/github/downloads/VladPVS/FastSearchLibrary/total.svg) 19 | 20 | ## ADVANTAGES 21 | * Library uses recursive search algorithm that is splitted on subtasks executing in thread pool 22 | * **UnauthorizedAccessException** is never thrown while search is executed 23 | * It's possible to choose different search criteria 24 | * It's possible to stop search process when it is necessary 25 | * It's possible to set different search paths at the same time 26 | 27 | ## INSTALLATION 28 | 1. Download archive with last [release](https://github.com/VladPVS/FastSearchLibrary/releases "Last release") if you use .NET 4.6.2 or higher otherwise download v1.1.6.1 29 | 2. Extract content to some directory. 30 | 3. Copy .dll and .xml files in directory of your project. 31 | 4. Add library to your project: Solution Explorer -> Reference -> item AddReference in context menu -> Browse 32 | 5. Add appropriate namespace: `using FastSearchLibrary;` 33 | 6. Set target .NET version at least as `4.5.1` if you use v1.1.6.1 of library or `4.6.2` if you use at least v1.1.7.2: Project -> Properties -> Target framework 34 | 35 | ## CONTENT 36 | 37 | Next classes provide search functionality: 38 | * FileSearcher 39 | * DirectorySearcher 40 | * FileSearcherMultiple 41 | * DirectorySearcherMultiple 42 | 43 | ## USE PRINCIPLES 44 | ### Basic opportunities 45 | * Classes `FileSearcher` and `DirectorySearcher` contain static methods that allow to execute search by different criteria. 46 | These methods return result only when they fully complete execution. 47 | * Methods that have "Fast" ending divide task on several 48 | subtasks that execute simultaneously in thread pool. 49 | * Methods that have "Async" ending return Task and don't block the called thread. 50 | * First group of methods accepts 2 parameters: 51 | * `string folder` - start search directory 52 | * `string pattern` - the search string to match against the names of files in path. 53 | This parameter can contain a combination of valid literal path and wildcard (* and ?) 54 | characters, but doesn't support regular expressions. 55 | 56 | Examples: 57 | ````csharp 58 | List files = FileSearcher.GetFiles(@"C:\Users", "*.txt"); 59 | ```` 60 | Finds all `*.txt` files in `C:\Users` using one thread method. 61 | 62 | ````csharp 63 | List files = FileSearcher.GetFilesFast(@"C:\Users", "*SomePattern*.txt"); 64 | ```` 65 | Finds all files that match appropriate pattern using several threads in thread pool. 66 | 67 | ````csharp 68 | Task> task = FileSearcher.GetFilesFastAsync(@"C:\", "a?.txt"); 69 | ```` 70 | Finds all files that match appropriate pattern using several threads in thread pool as 71 | an asynchronous operation. 72 | 73 | * Second group of methods accepts 2 parameters: 74 | * `string folder` - start search directory 75 | * `Func isValid` - delegate that determines algorithm of file selection. 76 | 77 | Examples: 78 | ````csharp 79 | Task> task = FileSearcher.GetFilesFastAsync(@"D:\", (f) => 80 | { 81 | return (f.Name.Contains("Pattern") || f.Name.Contains("Pattern2")) && f.LastAccessTime >= new DateTime(2018, 3, 1) && f.Length > 1073741824; 82 | }); 83 | ```` 84 | Finds all files that match appropriate conditions using several threads in thread pool as 85 | an asynchronous operation. 86 | 87 | You also can use regular expressions: 88 | ````csharp 89 | Task> task = FileSearcher.GetFilesFastAsync(@"D:\", (f) => 90 | { 91 | return Regex.IsMatch(f.Name, @".*Imagine[\s_-]Dragons.*.mp3$"); 92 | }); 93 | ```` 94 | Finds all files that match appropriate regular expression using several thread in thread pool as 95 | an asynchronous operation. 96 | 97 | ### Advanced opportunities 98 | If you want to execute some complicated search with realtime result getting you should use instance of `FileSearcher` class, 99 | that has various constructor overloads. 100 | `FileSearcher` class includes next events: 101 | * `event EventHandler FilesFound` - fires when next portion of files is found. 102 | Event includes `List Files { get; }` property that contains list of finding files. 103 | * `event EventHandler SearchCompleted` - fires when search process is completed or stopped. 104 | Event includes `bool IsCanceled { get; }` property that contains value that defines whether search process stopped by calling 105 | `StopSearch()` method. 106 | To get stop search process possibility one has to use constructor that accepts CancellationTokenSource parameter. 107 | 108 | Example: 109 | ````csharp 110 | class Searcher 111 | { 112 | private static object locker = new object(); // locker object 113 | 114 | private FileSearcher searcher; 115 | 116 | private List files; 117 | 118 | public Searcher() 119 | { 120 | files = new List(); // create list that will contain search result 121 | } 122 | 123 | public void StartSearch() 124 | { 125 | CancellationTokenSource tokenSource = new CancellationTokenSource(); 126 | // create tokenSource to get stop search process possibility 127 | 128 | searcher = new FileSearcher(@"C:\", (f) => 129 | { 130 | return Regex.IsMatch(f.Name, @".*[iI]magine[\s_-][dD]ragons.*.mp3$"); 131 | }, tokenSource); // give tokenSource in constructor 132 | 133 | 134 | searcher.FilesFound += (sender, arg) => // subscribe on FilesFound event 135 | { 136 | lock (locker) // using a lock is obligatory 137 | { 138 | arg.Files.ForEach((f) => 139 | { 140 | files.Add(f); // add the next part of the received files to the results list 141 | Console.WriteLine($"File location: {f.FullName}, \nCreation.Time: {f.CreationTime}"); 142 | }); 143 | 144 | if (files.Count >= 10) // one can choose any stopping condition 145 | searcher.StopSearch(); 146 | } 147 | }; 148 | 149 | searcher.SearchCompleted += (sender, arg) => // subscribe on SearchCompleted event 150 | { 151 | if (arg.IsCanceled) // check whether StopSearch() called 152 | Console.WriteLine("Search stopped."); 153 | else 154 | Console.WriteLine("Search completed."); 155 | 156 | Console.WriteLine($"Quantity of files: {files.Count}"); // show amount of finding files 157 | }; 158 | 159 | searcher.StartSearchAsync(); 160 | // start search process as an asynchronous operation that doesn't block the called thread 161 | } 162 | } 163 | ```` 164 | Note that all `FilesFound` event handlers are not thread safe so to prevent result loosing one should use 165 | `lock` keyword as you can see in example above or use thread safe collection from `System.Collections.Concurrent` namespace. 166 | 167 | ### Extended opportunities 168 | There are 2 additional parameters that one can set. These are `handlerOption` and `suppressOperationCanceledException`. 169 | `ExecuteHandlers handlerOption` parameter represents instance of `ExecuteHandlers` enumeration that specifies where 170 | FilesFound event handlers are executed: 171 | * `InCurrentTask` value means that `FileFound` event handlers will be executed in that task where files were found. 172 | * `InNewTask` value means that `FilesFound` event handlers will be executed in new task. 173 | Default value is `InCurrentTask`. It is more preferably in most cases. `InNewTask` value one should use only if handlers execute 174 | very sophisticated work that takes a lot of time, e.g. parsing of each found file. 175 | 176 | `bool suppressOperationCanceledException` parameter determines whether necessary to suppress 177 | OperationCanceledException. 178 | If `suppressOperationCanceledException` parameter has value `false` and StopSearch() method is called the `OperationCanceledException` 179 | will be thrown. In this case you have to process the exception manually. 180 | If `suppressOperationCanceledException` parameter has value `true` and StopSearch() method is called the `OperationCanceledException` 181 | is processed automatically and you don't need to catch it. 182 | Default value is `true`. 183 | 184 | Example: 185 | ````csharp 186 | CancellationTokenSource tokenSource = new CancellationTokenSource(); 187 | 188 | FileSearcher searcher = new FileSearcher(@"D:\Program Files", (f) => 189 | { 190 | return Regex.IsMatch(f.Name, @".{1,5}[Ss]ome[Pp]attern.txt$") && (f.Length >= 8192); // 8192b == 8Kb 191 | }, tokenSource, ExecuteHandlers.InNewTask, true); // suppressOperationCanceledException == true 192 | ```` 193 | ### MULTIPLE SEARCH 194 | `FileSearcher` and `DirectorySearcher` classes can search only in one directory (and in all subdirectories surely) 195 | but what if you want to perform search in several directories at the same time? 196 | Of course, you can create some instances of `FileSearcher` (or `DirectorySearcher`) class and launch them simultaneously, 197 | but `FilesFound` (or `DirectoriesFound`) events will occur for each instance you create. As a rule, it's inconveniently. 198 | Classes `FileSearcherMultiple` and `DirectorySearcherMultiple` are intended to solve this problem. 199 | They are similar to `FileSearcher` and `DirectorySearcher` but can execute search in several directories. 200 | The difference between `FileSearcher` and `FileSearcherMultiple` is that constructor of `Multiple` class accepts list of 201 | directories instead one directory. 202 | 203 | Example: 204 | ````csharp 205 | List folders = new List 206 | { 207 | @"C:\Users\Public", 208 | @"C:\Windows\System32", 209 | @"D:\Program Files", 210 | @"D:\Program Files (x86)" 211 | }; // list of search directories 212 | 213 | List keywords = new List { "word1", "word2", "word3" }; // list of search keywords 214 | 215 | FileSearcherMultiple multipleSearcher = new FileSearcherMultiple(folders, (f) => 216 | { 217 | if (f.CreationTime >= new DateTime(2015, 3, 15) && 218 | (f.Extension == ".cs" || f.Extension == ".sln")) 219 | { 220 | foreach (var keyword in keywords) 221 | if (f.Name.Contains(keyword)) 222 | return true; 223 | } 224 | 225 | return false; 226 | }, tokenSource, ExecuteHandlers.InCurrentTask, true); 227 | ```` 228 | ### NOTES 229 | #### Using "await" keyword 230 | It is highly recommend to use "await" keyword when you use any asynchronous method. It allows to get possible 231 | exceptions from method for following processing, that is demonstrated next code example. Error processing in previous 232 | examples has been skipped for simplicity. 233 | 234 | Example: 235 | ````csharp 236 | using System; 237 | using System.Collections.Generic; 238 | using System.Diagnostics; 239 | using System.IO; 240 | using System.Text.RegularExpressions; 241 | using System.Threading; 242 | using FastSearchLibrary; 243 | 244 | namespace SearchWithAwait 245 | { 246 | class Program 247 | { 248 | private static object locker = new object(); 249 | 250 | private static List files; 251 | 252 | private static Stopwatch stopWatch; 253 | 254 | 255 | static void Main(string[] args) 256 | { 257 | string searchPattern = @"\.mp4$"; 258 | 259 | StartSearch(searchPattern); 260 | 261 | Console.ReadKey(true); 262 | } 263 | 264 | 265 | private static async void StartSearch(string pattern) 266 | { 267 | stopWatch = new Stopwatch(); 268 | 269 | stopWatch.Start(); 270 | 271 | Console.WriteLine("Search has been started.\n"); 272 | 273 | files = new List(); 274 | 275 | List searchDirectories = new List 276 | { 277 | @"C:\", 278 | @"D:\" 279 | }; 280 | 281 | FileSearcherMultiple searcher = new FileSearcherMultiple(searchDirectories, (f) => 282 | { 283 | return Regex.IsMatch(f.Name, pattern); 284 | }, new CancellationTokenSource()); 285 | 286 | searcher.FilesFound += Searcher_FilesFound; 287 | searcher.SearchCompleted += Searcher_SearchCompleted; 288 | 289 | try 290 | { 291 | await searcher.StartSearchAsync(); 292 | } 293 | catch (AggregateException ex) 294 | { 295 | Console.WriteLine($"Error occurred: {ex.InnerException.Message}"); 296 | } 297 | catch (Exception ex) 298 | { 299 | Console.WriteLine($"Error occurred: {ex.Message}"); 300 | } 301 | finally 302 | { 303 | Console.Write("\nPress any key to continue..."); 304 | } 305 | } 306 | 307 | 308 | private static void Searcher_FilesFound(object sender, FileEventArgs arg) 309 | { 310 | lock (locker) // using of the lock is mandatory 311 | { 312 | arg.Files.ForEach((f) => 313 | { 314 | files.Add(f); // add the next part of the received files to the results list 315 | Console.WriteLine($"File location: {f.FullName}\nCreation.Time: {f.CreationTime}\n"); 316 | }); 317 | } 318 | } 319 | 320 | 321 | private static void Searcher_SearchCompleted(object sender, SearchCompletedEventArgs arg) 322 | { 323 | stopWatch.Stop(); 324 | 325 | if (arg.IsCanceled) // check whether StopSearch() called 326 | Console.WriteLine("Search stopped."); 327 | else 328 | Console.WriteLine("Search completed."); 329 | 330 | Console.WriteLine($"Quantity of files: {files.Count}"); // show amount of finding files 331 | 332 | Console.WriteLine($"Spent time: {stopWatch.Elapsed.Minutes} min {stopWatch.Elapsed.Seconds} s {stopWatch.Elapsed.Milliseconds} ms"); 333 | } 334 | } 335 | } 336 | ```` 337 | #### Long paths Windows limitation 338 | There is a 260 symbols Windows limitation on full name of files. In common case library will ignore such "long" paths. But if you want to circumvent this limitation you should follow next steps: 339 | 1. Use Windows 10 (assembly 1607 or higher). 340 | 2. Download the last [release](https://github.com/VladPVS/FastSearchLibrary/releases "Last release") of this library. 341 | 3. Use Visual Studio 2017 or newer. 342 | 4. Set the version of .NET Framework at least 4.6.2 343 | 5. Add the manifest file to your project. 344 | Select `` in Solution explorer, click right button of mouse -> `Add` -> `New item` -> `Application manifest file`. Then add content of [this](https://github.com/VladPVS/FastSearchLibrary/files/2267462/manifest.txt) file to the manifest before the last closed tag. 345 | 6. A registry key allows to enable or disable the new long path behavior in Windows. To enable long path behavior open registry editor and follow next path `HKLM\SYSTEM\CurrentControlSet\Control\FileSystem` Then create parameter `LongPathsEnabled` (type REG_DWORD) with `1` value. 346 | 7. Reboot your computer. 347 | 348 | ### SPEED OF WORK 349 | It depends on your computer performance, current loading, but usually `Fast` methods and instance method `StartSearch()` are 350 | performed at least in 2 times faster than simple one-thread recursive algorithm. 351 | --------------------------------------------------------------------------------