Main
This commit is contained in:
Dvurechensky 2024-10-05 10:06:04 +03:00
commit e2bffc8b49
57 changed files with 3581 additions and 0 deletions

261
.gitignore vendored Normal file

@ -0,0 +1,261 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
project.fragment.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
#*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc

25
FileSearch.sln Normal file

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.7.34221.43
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileSearch", "FileSearch\FileSearch.csproj", "{C62B3AF4-1119-4EDF-AEA5-3D00296EB8E1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C62B3AF4-1119-4EDF-AEA5-3D00296EB8E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C62B3AF4-1119-4EDF-AEA5-3D00296EB8E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C62B3AF4-1119-4EDF-AEA5-3D00296EB8E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C62B3AF4-1119-4EDF-AEA5-3D00296EB8E1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CC28CDB4-BCDF-47E2-8527-C8F13943DECF}
EndGlobalSection
EndGlobal

85
FileSearch/ExceptionsForm.Designer.cs generated Normal file

@ -0,0 +1,85 @@
namespace FileSearch
{
partial class ExceptionsForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
lstItems = new ListView();
lstcFile = new ColumnHeader();
lstcException = new ColumnHeader();
SuspendLayout();
//
// lstItems
//
lstItems.Columns.AddRange(new ColumnHeader[] { lstcFile, lstcException });
lstItems.Dock = DockStyle.Fill;
lstItems.FullRowSelect = true;
lstItems.GridLines = true;
lstItems.HeaderStyle = ColumnHeaderStyle.Nonclickable;
lstItems.Location = new Point(0, 0);
lstItems.Margin = new Padding(4, 3, 4, 3);
lstItems.Name = "lstItems";
lstItems.Size = new Size(740, 374);
lstItems.TabIndex = 2;
lstItems.UseCompatibleStateImageBehavior = false;
lstItems.View = View.Details;
lstItems.VirtualMode = true;
lstItems.RetrieveVirtualItem += lstItems_RetrieveVirtualItem;
//
// lstcFile
//
lstcFile.Text = "Файл";
lstcFile.Width = 400;
//
// lstcException
//
lstcException.Text = "Ошибка";
lstcException.Width = 200;
//
// ExceptionsForm
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(740, 374);
Controls.Add(lstItems);
Margin = new Padding(4, 3, 4, 3);
MinimizeBox = false;
Name = "ExceptionsForm";
ShowIcon = false;
ShowInTaskbar = false;
StartPosition = FormStartPosition.CenterParent;
Text = "Ошибки";
ResumeLayout(false);
}
#endregion
private ListView lstItems;
private ColumnHeader lstcFile;
private ColumnHeader lstcException;
}
}

@ -0,0 +1,33 @@
using FileSearch.Logic.Model.Engine;
using FileSearch.Logic.UI.Entries;
namespace FileSearch
{
public partial class ExceptionsForm : Form
{
private IList<SearchException> _exceptions;
private SearchExceptionEntry[] _exceptionEntries;
public ExceptionsForm()
{
InitializeComponent();
}
public void SetContent(IList<SearchException> exceptions)
{
if (exceptions == null) throw new ArgumentNullException("exceptions");
_exceptions = exceptions;
_exceptionEntries = new SearchExceptionEntry[exceptions.Count];
lstItems.VirtualListSize = exceptions.Count;
}
private void lstItems_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
var entry = _exceptionEntries[e.ItemIndex];
if (entry == null)
_exceptionEntries[e.ItemIndex] = entry = new SearchExceptionEntry(_exceptions[e.ItemIndex]);
e.Item = entry;
}
}
}

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Ionic.Zip" Version="1.9.1.8" />
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resource.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resource.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resource.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resource.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

@ -0,0 +1,66 @@
namespace FileSearch
{
partial class LargeListViewUserControl
{
/// <summary>
/// Обязательная переменная конструктора.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Освободить все используемые ресурсы.
/// </summary>
/// <param name="disposing">истинно, если управляемый ресурс должен быть удален; иначе ложно.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Код, автоматически созданный конструктором компонентов
/// <summary>
/// Требуемый метод для поддержки конструктора — не изменяйте
/// содержимое этого метода с помощью редактора кода.
/// </summary>
private void InitializeComponent()
{
lstItems = new ListView();
SuspendLayout();
//
// lstItems
//
lstItems.Dock = System.Windows.Forms.DockStyle.Fill;
lstItems.FullRowSelect = true;
lstItems.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable;
lstItems.Location = new Point(0, 0);
lstItems.Name = "lstItems";
lstItems.Size = new Size(483, 307);
lstItems.TabIndex = 0;
lstItems.UseCompatibleStateImageBehavior = false;
lstItems.View = System.Windows.Forms.View.Details;
lstItems.VirtualMode = true;
lstItems.RetrieveVirtualItem += new System.Windows.Forms.RetrieveVirtualItemEventHandler(this.RetrieveVirtualItem);
lstItems.KeyDown += new System.Windows.Forms.KeyEventHandler(this.lstItems_KeyDown);
lstItems.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.lstItems_MouseDoubleClick);
//
// LargeListViewUserControl
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
Controls.Add(lstItems);
Name = "LargeListViewUserControl";
Size = new Size(483, 307);
SizeChanged += LargeListViewUserControl_SizeChanged;
Enter += LargeListViewUserControl_Enter;
ResumeLayout(false);
}
#endregion
private ListView lstItems;
}
}

@ -0,0 +1,152 @@
using FileSearch.Logic.Model.Engine;
using FileSearch.Logic.UI.Entries;
using FileSearch.Logic.UI.ViewBuilders;
namespace FileSearch
{
internal partial class LargeListViewUserControl : UserControl
{
public event EventHandler<PathEventArgs> FileOpened;
public event EventHandler<PathEventArgs> DirectoryOpened;
private readonly IList<IPathEntry> _collection;
private ListViewItem[] _cache;
private IViewBuilder _viewBuilder;
public LargeListViewUserControl()
{
InitializeComponent();
_collection = new List<IPathEntry>();
}
public int Count
{
get { return _cache.Length; }
}
public void ClearContent()
{
_collection.Clear();
VirtualListChanged();
}
public void ResetViewBuilder()
{
SetViewBuilder(new DefaultViewBuilder());
}
public void SetViewBuilder(IViewBuilder builder)
{
if (_viewBuilder != null && builder.GetType() == _viewBuilder.GetType())
return;
_viewBuilder = builder;
lstItems.Columns.Clear();
foreach (var c in builder.ColumnSizes)
{
lstItems.Columns.Add(new ColumnHeader { Text = c.Item1, Width = c.Item2 });
}
LargeListViewUserControl_SizeChanged(this, EventArgs.Empty);
}
public void AddSearchResults(IEnumerable<SearchResult> searchResults)
{
if (searchResults == null) throw new ArgumentNullException("searchResults");
int index = 0;
var oldName = string.Empty;
foreach (var result in searchResults)
{
foreach (var entry in _viewBuilder.Build(result, index))
{
var directoryInfo = new DirectoryInfo(entry.FileSystemInfo.FullName);
if (directoryInfo.Parent?.FullName != oldName)
{
oldName = directoryInfo.Parent?.FullName;
_collection.Add(entry);
}
}
++index;
}
VirtualListChanged();
}
private void VirtualListChanged()
{
lstItems.VirtualListSize = _collection.Count;
_cache = new ListViewItem[lstItems.VirtualListSize];
if (lstItems.Items.Count > 0)
lstItems.Items[lstItems.Items.Count - 1].EnsureVisible();
}
private void RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
{
e.Item = _cache[e.ItemIndex];
if (e.Item == null)
{
var content = (PathEntry)_collection[e.ItemIndex];
var item = content.BuildListViewItem();
_cache[e.ItemIndex] = e.Item = item;
}
}
private void LargeListViewUserControl_SizeChanged(object sender, EventArgs e)
{
if (lstItems.Columns.Count <= 0)
return;
var fullWidth = this.Width - SystemInformation.VerticalScrollBarWidth - SystemInformation.FrameBorderSize.Width;
var fullColumns = lstItems.Columns.Cast<ColumnHeader>().Where((h, i) => _viewBuilder.ColumnSizes[i].Item2 == -1).ToList();
var otherColumnWidth = 0;
foreach (ColumnHeader c in lstItems.Columns.Cast<ColumnHeader>().Where(h => !fullColumns.Any(h2 => h2 != h)))
{
otherColumnWidth += c.Width;
}
var partWidth = fullWidth / fullColumns.Count;
foreach (var c in fullColumns)
c.Width = partWidth;
}
private void LargeListViewUserControl_Enter(object sender, EventArgs e)
{
lstItems.Focus();
if (lstItems.VirtualListSize > 0 && lstItems.SelectedIndices.Count <= 0)
{
lstItems.SelectedIndices.Add(0);
}
}
private void lstItems_MouseDoubleClick(object sender, MouseEventArgs e)
{
OpenExplorerIfPossible();
}
private void OpenExplorerIfPossible()
{
if (lstItems.SelectedIndices.Count <= 0 || _collection.Count <= 0)
return;
var path = (PathEntry)(_collection[lstItems.SelectedIndices[0]]);
if (path.IsDirectory)
{
if (DirectoryOpened != null)
DirectoryOpened(this, new PathEventArgs(path));
}
else if (FileOpened != null)
FileOpened(this, new PathEventArgs(path));
}
private void lstItems_KeyDown(object sender, KeyEventArgs e)
{
if (!e.Shift && !e.Alt && !e.Control && (e.KeyCode == Keys.Enter || e.KeyCode == Keys.Return))
{
OpenExplorerIfPossible();
}
}
}
}

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

@ -0,0 +1,23 @@
using System.Globalization;
namespace FileSearch.Logic
{
internal static class Extensions
{
public static List<string> ConvertTreeNodes(this string directoryPath)
{
if(directoryPath.Contains("\\"))
return directoryPath.Split(new char[] { '\\' }).ToList();
if (directoryPath.Contains("/"))
return directoryPath.Split(new char[] { '/' }).ToList();
return new List<string>();
}
public static string GetFriendlyNotation(this TimeSpan timeSpan)
{
if (timeSpan.Minutes > 2)
return string.Format(CultureInfo.CurrentCulture, "{0:#,##0.0} мин.", timeSpan.TotalMinutes);
return string.Format(CultureInfo.CurrentCulture, "{0:#,##0.00} сек.", timeSpan.TotalSeconds);
}
}
}

@ -0,0 +1,227 @@
using FileSearch.Logic.Model.CriterionSchemas;
using FileSearch.Logic.Model.Engine;
using System.Collections.ObjectModel;
namespace FileSearch.Logic
{
internal class FileSearcher
{
private readonly IList<ICriterion> _additionalCriterion;
private readonly EngineOptions _engineOptions;
private readonly IList<SearchException> _expections;
private bool _stop;
private bool _pause;
private Task<IList<SearchResult>> _currentTask;
private DateTime _startTime;
public FileSearcher(EngineOptions engineOptions, IEnumerable<ICriterion> additionalCriterion)
{
if (engineOptions == null) throw new ArgumentNullException("engineOptions");
_engineOptions = engineOptions;
_additionalCriterion = additionalCriterion.ToList();
this.RefreshTimer = TimeSpan.FromSeconds(1);
_expections = new List<SearchException>();
}
/// <summary>
/// Получает или задает интервал времени для тайм-аута обратного вызова сопоставления.
/// </summary>
public TimeSpan RefreshTimer { get; set; }
/// <summary>
/// Получает время, в течение которого поисковая система работала над последней операцией.
/// </summary>
public TimeSpan OperatingTime { get; private set; }
/// <summary>
/// Получает время, в течение которого поисковая система работает.
/// </summary>
public TimeSpan CurrentTime { get; private set; }
/// <summary>
/// Получает список всех исключений поиска последней операции.
/// </summary>
public IList<SearchException> Exceptions { get { return new ReadOnlyCollection<SearchException>(_expections); } }
/// <summary>
/// Значение, указывающее, работает ли поисковая система.
/// </summary>
public bool IsRunning { get { return _currentTask != null; } }
/// <summary>
/// Получает список всех критериев, которые использовались в текущей или последней операции.
/// </summary>
public IList<ICriterion> UsedCriteria { get; private set; }
/// <summary>
/// Запускает операцию поиска.
/// </summary>
/// <param name="matchCallback">Обратный вызов при обнаружении совпадений.</param>
/// <param name="finishCallback">Обратный вызов после завершения поиска.</param>
public void Start(Action<IEnumerable<SearchResult>> matchCallback, Action finishCallback)
{
this.OperatingTime = new TimeSpan();
_startTime = DateTime.UtcNow;
_expections.Clear();
var timeout = new TimedCallback<SearchResult>(this.RefreshTimer, matchCallback);
_stop = false;
_currentTask = Task.Factory.StartNew<IList<SearchResult>>(() => Search(timeout));
_currentTask.ContinueWith(t => {
timeout.SetData(t.Result);
})
.ContinueWith(t => {
_currentTask = null;
OperatingTime = DateTime.UtcNow - _startTime;
finishCallback();
});
}
/// <summary>
/// Прерывает текущую операцию поиска.
/// </summary>
public void Stop()
{
if (IsRunning)
{
_pause = false;
_stop = true;
}
}
/// <summary>
/// Приостанавливает текущую операцию поиска.
/// </summary>
public void Pause(Action<bool> state, bool update)
{
if (IsRunning)
_pause = update;
state(_pause);
}
private IList<ICriterion> BuildCriteria()
{
// Разрешить только один IPostProcessingCriterion. В противном случае результаты будут странными.
var criteria = CriteriaFactory.Build(_engineOptions).Union(_additionalCriterion).OrderBy(c => c is IPostProcessingCriterion).ThenBy(c => c.Weight).ToList();
UsedCriteria = new ReadOnlyCollection<ICriterion>(criteria);
return criteria;
}
private IList<SearchResult> Search(object state)
{
var timer = (TimedCallback<SearchResult>)state;
var criteria = BuildCriteria();
var list = new List<SearchResult>(64);
var requiresPostProcessing = criteria.Any(c => c is IPostProcessingCriterion);
foreach (var rootDirectory in _engineOptions.RootDirectories)
{
foreach (var fileSystemInfo in ListAllFileSystemInfo(rootDirectory, -1))
{
var contexts = new Dictionary<Type, ICriterionContext>();
var isDir = fileSystemInfo is DirectoryInfo;
var match = true;
this.CurrentTime = DateTime.UtcNow - _startTime;
//Приостановить цикл
while (_pause)
{
Console.WriteLine("");
}
try
{
foreach (var c in criteria)
{
var context = c.BuildContext();
// Проверьте, поддерживает ли критерий тип целевой файловой системы.
if ((c.DirectorySupport && isDir) || (c.FileSupport && !isDir))
{
if (c.IsMatch(fileSystemInfo, context))
{
// Добавьте контекст, если он совпадает.
if (context != null)
contexts.Add(c.GetType(), context);
}
else
{
match = false;
break;
}
}
}
}
catch (Exception ex)
{
_expections.Add(SearchExceptionFactory.Build(fileSystemInfo, ex));
match = false;
}
if (match && !requiresPostProcessing)
list.Add(new SearchResult(fileSystemInfo) { Metadata = null });
// Есть верно, и результат не важен.
if (list.Count > 0 && !requiresPostProcessing && timer.DataNeeded)
{
timer.SetData(list);
list = new List<SearchResult>(64);
}
// Остановить цикл
if (_stop)
break;
}
// Остановить цикл
if (_stop)
break;
}
if (requiresPostProcessing)
{
// Выбираем последний, это самый интенсивный критерий.
var resultLists = criteria.OfType<IPostProcessingCriterion>().Single();
return resultLists.PostProcess().ToList();
}
return list;
}
private IEnumerable<FileSystemInfo> ListAllFileSystemInfo(FileSystemInfo fileSystemInfo, int level)
{
var directoryInfo = fileSystemInfo as DirectoryInfo;
var isRoot = level == -1;
// Возвращает папку или, если это файл, всегда. Пропускает корневой уровень.
if (!isRoot && (directoryInfo == null))
yield return fileSystemInfo;
if (directoryInfo != null || isRoot)
{
FileSystemInfo[] infos = null;
try
{
infos = directoryInfo.GetFileSystemInfos();
}
catch (UnauthorizedAccessException ex)
{
_expections.Add(SearchExceptionFactory.Build(directoryInfo, ex));
}
if (infos == null)
yield break;
foreach (var item in infos.SelectMany(s => ListAllFileSystemInfo(s, level + 1)))
{
if (_stop) yield break;
yield return item;
}
}
}
}
}

@ -0,0 +1,15 @@
using FileSearch.Logic.Model.Engine;
using System.Collections.ObjectModel;
namespace FileSearch.Logic.Model.Contexts
{
internal sealed class ZipCriterionContext : ICriterionContext
{
public ZipCriterionContext()
{
this.Childs = new Collection<string>();
}
public IList<string> Childs { get; private set; }
}
}

@ -0,0 +1,30 @@
using FileSearch.Logic.Model.Engine;
namespace FileSearch.Logic.Model.CriterionSchemas
{
internal class AttributeCriterion : CriterionBase, ICriterion
{
private readonly FileAttributes _includedAttributes;
private readonly FileAttributes _excludedAttributes;
public AttributeCriterion(FileAttributes includedAttributes, FileAttributes excludedAttributes)
{
_includedAttributes = includedAttributes;
_excludedAttributes = excludedAttributes;
}
public string Name { get { return "Attributes"; } }
public CriterionWeight Weight { get { return CriterionWeight.Medium; } }
public bool DirectorySupport { get { return true; } }
public bool FileSupport { get { return true; } }
public bool IsMatch(FileSystemInfo fileSystemInfo, ICriterionContext context)
{
return (fileSystemInfo.Attributes & _includedAttributes) == _includedAttributes
&& (fileSystemInfo.Attributes & _excludedAttributes) == 0;
}
}
}

@ -0,0 +1,168 @@
using FileSearch.Logic.Model.EncodingDetection;
using FileSearch.Logic.Model.Engine;
using System.Text;
namespace FileSearch.Logic.Model.CriterionSchemas
{
internal class ContentCriterion : CriterionBase, ICriterion
{
private const int BufferSize = 32 * 1024; // 32KB
private readonly string _text;
private readonly char[][] _textInChars;
private readonly bool _ignoreCase;
private readonly bool _matchFullWords;
private readonly IEncodingFactory _encodingFactory;
public ContentCriterion(string text, bool ignoreCase, bool matchFullWords, IEncodingFactory encodingFactory)
{
if (text == null) throw new ArgumentNullException("text");
if (encodingFactory == null) throw new ArgumentNullException("encodingFactory");
_text = text;
_ignoreCase = ignoreCase;
_matchFullWords = matchFullWords;
_encodingFactory = encodingFactory;
_textInChars = StringToCharArrays(text, ignoreCase);
}
public string Name { get { return "File content"; } }
public CriterionWeight Weight { get { return CriterionWeight.Heavy; } }
public bool DirectorySupport { get { return false; } }
public bool FileSupport { get { return true; } }
public bool IsMatch(FileSystemInfo fileSystemInfo, ICriterionContext context)
{
var fileInfo = (FileInfo)fileSystemInfo;
var buffer = new byte[BufferSize];
var textLength = _text.Length;
Encoding[] encodings = new Encoding[1];
// Проверить несколько кодировок
for (int encodingIndex = 0; encodingIndex < encodings.Length; encodingIndex++)
{
// Кодирование текущего цикла. Первая попытка будет NULL.
Encoding encoding = encodings[encodingIndex];
bool characterShouldBeNonWord = false;
char characterBefore = '\0';
using (var stream = fileInfo.OpenRead())
{
int length;
int foundMatchingSymbols = 0;
while ((length = stream.Read(buffer, 0, BufferSize)) > 0)
{
// Если кодировка еще не определена, определите ее сейчас.
if (encoding == null)
{
encodings = _encodingFactory.DetectEncoding(buffer);
encoding = encodings[encodingIndex];
}
var currentString = encoding.GetString(buffer, 0, length);
var currentStringLength = currentString.Length; // Кэш
bool startAtBegin = foundMatchingSymbols > 0;
var charIndex = 0;
// Первый символ должен быть символом, отличным от слова, если предыдущие прочитанные байты заканчиваются соответствующей строкой.
if (_matchFullWords && characterShouldBeNonWord && currentStringLength > 0)
{
characterShouldBeNonWord = false;
if (!CharIsWordChar(currentString[0]))
return true;
}
// Проверьте, находится ли первый или следующий совпадающий символ в текущей строке.
if ((charIndex = currentString.IndexOfAny(_textInChars[foundMatchingSymbols], charIndex)) >= 0)
{
do
{
// Назначьте персонажа заранее.
if (charIndex > 0 && foundMatchingSymbols == 0 && _matchFullWords)
characterBefore = currentString[charIndex - 1];
// Не первый символ _text, поэтому проверьте, находится ли второй символ в первой позиции.
if (startAtBegin)
{
startAtBegin = false;
if (charIndex > 0) // Буква должна быть на первой позиции! Если нет, начните заново с первого символа в _text.
{
foundMatchingSymbols = 0;
continue;
}
}
// Скопируйте переменную, чтобы она не была изменена в приведенном ниже цикле.
var current = charIndex;
// Постарайтесь сопоставить как можно больше символов.
while (++foundMatchingSymbols < textLength && currentStringLength > ++current && (currentString[current] == _text[foundMatchingSymbols] || _ignoreCase && _textInChars[foundMatchingSymbols].Any(c => c == currentString[current]))) ;
// Нашел!
if (foundMatchingSymbols == textLength && (!_matchFullWords || !CharIsWordChar(characterBefore)))
{
if (_matchFullWords)
{
// Попытайтесь определить следующее чтение, заканчивается ли строка символом, отличным от слова.
if (current >= currentStringLength - 1)
characterShouldBeNonWord = true;
// Проверьте, не является ли следующая буква словом char. Если нет, верните true. В противном случае убедитесь, что индекс равен +1, чтобы он был сброшен в следующем операторе IF.
else if (!CharIsWordChar(currentString[++current]))
return true;
}
else
// Возвращает true, если не проверяется слово вместо части строки.
return true;
}
// Сбросить счетчик совпадающих символов, если конец текущей строки не достигнут. Если да, продолжайте тестирование при следующем чтении.
if (currentStringLength != current)
foundMatchingSymbols = 0;
// Проверьте, есть ли следующий соответствующий символ в текущей строке.
} while ((charIndex = currentString.IndexOfAny(_textInChars[foundMatchingSymbols], ++charIndex)) >= 0);
// Назначьте последний символ, чтобы в следующем раунде он знал предыдущий символ.
if (foundMatchingSymbols == 0 && _matchFullWords)
characterBefore = currentString[currentStringLength - 1];
}
else
{
foundMatchingSymbols = 0;
// Переназначьте предыдущий символ последнему символу в строке.
if (_matchFullWords) characterBefore = currentString[currentStringLength - 1];
}
}
if (_matchFullWords && characterShouldBeNonWord)
return true;
}
}
return false;
}
private static bool CharIsWordChar(char c)
{
return char.IsLetterOrDigit(c) || c == '_';
}
private static char[][] StringToCharArrays(string input, bool ignoreCase)
{
var list = new List<char[]>();
foreach (var c in input)
{
if (!ignoreCase || !char.IsLetter(c))
list.Add(new[] { c });
else
{
var u = char.ToUpperInvariant(c);
var l = char.ToLowerInvariant(c);
list.Add(u != l ? new[] { u, l } : new[] { c });
}
}
return list.ToArray();
}
}
}

@ -0,0 +1,32 @@
using FileSearch.Logic.Model.Engine;
namespace FileSearch.Logic.Model.CriterionSchemas
{
internal class ContentRegexCriterion : CriterionBase, ICriterion
{
private readonly string _regexText;
private readonly bool _ignoreCase;
public ContentRegexCriterion(string regexText, bool ignoreCase)
{
if (regexText == null) throw new ArgumentNullException("regexText");
_regexText = regexText;
_ignoreCase = ignoreCase;
}
public string Name { get { return "File content using regular expressions"; } }
public CriterionWeight Weight { get { return CriterionWeight.Extreme; } }
public bool DirectorySupport { get { return false; } }
public bool FileSupport { get { return true; } }
public bool IsMatch(FileSystemInfo fileSystemInfo, ICriterionContext context)
{
var fileInfo = (FileInfo)fileSystemInfo;
throw new NotImplementedException();
}
}
}

@ -0,0 +1,53 @@
using FileSearch.Logic.Model.Engine;
using FileSearch.Logic.Model.Entities;
namespace FileSearch.Logic.Model.CriterionSchemas
{
internal static class CriteriaFactory
{
public static IList<ICriterion> Build(EngineOptions options)
{
var list = new List<ICriterion>();
// Применить базовые параметры
if (!string.IsNullOrEmpty(options.SearchName))
{
if (options.SearchInArchives)
list.Add(new NameAndZipCriterion(options.SearchName, options.SearchNameIgnoreCasing, options.SearchNameMatchFullPath));
else if (!options.SearchNameAsRegularExpression)
list.Add(new NameCriterion(options.SearchName, options.SearchNameIgnoreCasing, options.SearchNameMatchFullPath));
else
list.Add(new NameRegexCriterion(options.SearchName, options.SearchNameIgnoreCasing, options.SearchNameMatchFullPath));
}
// Добавить атрибуты файла
if (options.AttributesIncluded > 0 || options.AttributesExcluded > 0)
{
list.Add(new AttributeCriterion(options.AttributesIncluded, options.AttributesExcluded));
}
// Размеры
if (options.MinimumSize != null || options.MaximumSize != null)
{
list.Add(new SizeCriterion(options.MinimumSize, options.MaximumSize));
}
// Даты
if (options.DateOption != FileDateOption.None && (options.StartDateTime != null || options.EndDateTime != null))
{
list.Add(new DateCriterion(options.DateOption, options.StartDateTime, options.EndDateTime));
}
// Содержание
if (!string.IsNullOrEmpty(options.ContentText))
{
if (!options.ContentAsRegularExpression)
list.Add(new ContentCriterion(options.ContentText, options.ContentIgnoreCasing, options.ContentWholeWordsOnly, options.ContentEncodingFactory));
else
list.Add(new ContentRegexCriterion(options.ContentText, options.ContentIgnoreCasing));
}
return list.ToArray();
}
}
}

@ -0,0 +1,54 @@
using FileSearch.Logic.Model.Engine;
using FileSearch.Logic.Model.Entities;
namespace FileSearch.Logic.Model.CriterionSchemas
{
internal class DateCriterion : CriterionBase, ICriterion
{
private readonly FileDateOption _dateOption;
private readonly DateTime? _startDateTime;
private readonly DateTime? _endDateTime;
public DateCriterion(FileDateOption dateOption, DateTime? startDateTime, DateTime? endDateTime)
{
if (dateOption == FileDateOption.None)
throw new ArgumentException("There is no date option specified.", "dateOption");
if (startDateTime == null && endDateTime == null)
throw new ArgumentException("No start and end date time are specified.");
_dateOption = dateOption;
_startDateTime = startDateTime != null ? (DateTime?)startDateTime.Value.ToUniversalTime() : null;
_endDateTime = endDateTime != null ? (DateTime?)endDateTime.Value.ToUniversalTime() : null;
}
public string Name { get { return "Dates"; } }
public CriterionWeight Weight { get { return CriterionWeight.Medium; } }
public bool DirectorySupport { get { return true; } }
public bool FileSupport { get { return true; } }
public bool IsMatch(FileSystemInfo fileSystemInfo, ICriterionContext context)
{
DateTime dateTimeUtc;
switch (_dateOption)
{
case FileDateOption.Accessed:
dateTimeUtc = fileSystemInfo.LastAccessTimeUtc;
break;
case FileDateOption.Changed:
dateTimeUtc = fileSystemInfo.LastWriteTimeUtc;
break;
case FileDateOption.Created:
dateTimeUtc = fileSystemInfo.CreationTimeUtc;
break;
default:
throw new NotSupportedException();
}
return (_startDateTime == null || dateTimeUtc >= _startDateTime.Value)
&& (_endDateTime == null || dateTimeUtc <= _endDateTime.Value);
}
}
}

@ -0,0 +1,9 @@
using FileSearch.Logic.Model.Engine;
namespace FileSearch.Logic.Model.CriterionSchemas
{
internal interface IPostProcessingCriterion
{
IEnumerable<SearchResult> PostProcess();
}
}

@ -0,0 +1,51 @@
using FileSearch.Logic.Model.Contexts;
using FileSearch.Logic.Model.Engine;
using Ionic.Zip;
namespace FileSearch.Logic.Model.CriterionSchemas
{
internal class NameAndZipCriterion : NameCriterion
{
public NameAndZipCriterion(string value, bool ignoreCasing, bool matchFullPath)
: base(value, ignoreCasing, matchFullPath)
{
}
public override CriterionWeight Weight
{
get { return CriterionWeight.Light; }
}
public override bool IsMatch(FileSystemInfo fileSystemInfo, ICriterionContext context)
{
// базовое сравнение
if (base.IsMatch(fileSystemInfo, context))
return true;
// Должно заканчиваться на .ZIP.
if (string.IsNullOrEmpty(fileSystemInfo.Extension) || !fileSystemInfo.Extension.Equals(".zip", StringComparison.OrdinalIgnoreCase))
return false;
// Все найденные записи в ZIP-файле.
var myContext = (ZipCriterionContext)context;
using (var zip = ZipFile.Read(fileSystemInfo.FullName))
{
foreach (var entry in zip.EntryFileNames.Select(e => e.Replace('/', '\\')))
{
if (IsMatch(this.MatchFullPath ? Path.Combine(fileSystemInfo.FullName, entry) : Path.GetFileName(entry)))
{
myContext.Childs.Add(entry);
}
}
}
return myContext.Childs.Count > 0;
}
public override ICriterionContext BuildContext()
{
return new ZipCriterionContext();
}
}
}

@ -0,0 +1,93 @@
using FileSearch.Logic.Model.Engine;
using System.Text.RegularExpressions;
namespace FileSearch.Logic.Model.CriterionSchemas
{
internal class NameCriterion : CriterionBase, ICriterion
{
private static readonly string StarWildcard = Regex.Escape("*");
private static readonly string QuestionWildcard = Regex.Escape("?");
private string[] _exactMatches;
private Regex[] _regexMatches;
private readonly bool _ignoreCasing;
protected readonly bool MatchFullPath;
public NameCriterion(string value, bool ignoreCasing, bool matchFullPath)
{
if (value == null) throw new ArgumentNullException("value");
_ignoreCasing = ignoreCasing;
this.MatchFullPath = matchFullPath;
BuildMatches(value);
}
public string Name { get { return "File and directory names"; } }
public virtual CriterionWeight Weight
{
get { return CriterionWeight.None; }
}
public virtual bool DirectorySupport
{
get { return true; }
}
public virtual bool FileSupport
{
get { return true; }
}
public virtual bool IsMatch(FileSystemInfo fileSystemInfo, ICriterionContext context)
{
var name = this.MatchFullPath ? fileSystemInfo.FullName : fileSystemInfo.Name;
return IsMatch(name);
}
protected bool IsMatch(string fileName)
{
if (_exactMatches.Length > 0 && _exactMatches.Any(m => SimpleMatch(fileName, m)))
return true;
if (_regexMatches.Length > 0 && _regexMatches.Any(m => WildcardMatch(fileName, m)))
return true;
return false;
}
private bool SimpleMatch(string filePath, string value)
{
return filePath.IndexOf(value, _ignoreCasing ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) >= 0;
}
private static bool WildcardMatch(string filePath, Regex value)
{
return value.IsMatch(filePath);
}
private void BuildMatches(string value)
{
var exactMatches = new List<string>();
var regexMatches = new List<Regex>();
var split = value.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var item in split)
{
// Build regex for a like statement
if (item.Contains("*") || item.Contains("?"))
{
var valueRegex = Regex.Escape(item)
.Replace(QuestionWildcard, ".{1}")
.Replace(StarWildcard, ".*");
regexMatches.Add(new Regex(string.Concat("^", valueRegex, "$"), RegexOptions.Compiled | (_ignoreCasing ? RegexOptions.IgnoreCase : RegexOptions.None)));
}
else
exactMatches.Add(item);
}
_exactMatches = exactMatches.ToArray();
_regexMatches = regexMatches.ToArray();
}
}
}

@ -0,0 +1,50 @@
using FileSearch.Logic.Model.Engine;
using System.Text.RegularExpressions;
namespace FileSearch.Logic.Model.CriterionSchemas
{
internal class NameRegexCriterion : CriterionBase, ICriterion
{
private readonly string _regularExpression;
private readonly bool _ignoreCase;
private readonly bool _matchFullPath;
private Regex _cachedRegex;
public NameRegexCriterion(string regularExpression, bool ignoreCase, bool matchFullPath)
{
if (regularExpression == null) throw new ArgumentNullException("regularExpression");
_regularExpression = regularExpression;
_ignoreCase = ignoreCase;
_matchFullPath = matchFullPath;
}
public string Name { get { return "File and directory names using regular expressions"; } }
public CriterionWeight Weight
{
get { return CriterionWeight.None; }
}
public bool DirectorySupport
{
get { return true; }
}
public bool FileSupport
{
get { return true; }
}
public bool IsMatch(FileSystemInfo fileSystemInfo, ICriterionContext context)
{
return IsMatch(_matchFullPath ? fileSystemInfo.FullName : fileSystemInfo.Name);
}
protected virtual bool IsMatch(string fileName)
{
if (_cachedRegex == null)
_cachedRegex = new Regex(_regularExpression, RegexOptions.Compiled | (_ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None));
return _cachedRegex.IsMatch(fileName);
}
}
}

@ -0,0 +1,40 @@
using FileSearch.Logic.Model.Engine;
namespace FileSearch.Logic.Model.CriterionSchemas
{
internal class SizeCriterion : CriterionBase, ICriterion
{
private readonly long? _minLengthInBytes;
private readonly long? _maxLengthInBytes;
public SizeCriterion(long? minLengthInBytes, long? maxLengthInBytes)
{
if (minLengthInBytes != null && minLengthInBytes < 0)
throw new ArgumentOutOfRangeException("minLengthInBytes", "Указанная минимальная длина не может быть меньше 0 байт.");
if (maxLengthInBytes != null && maxLengthInBytes < 0)
throw new ArgumentOutOfRangeException("maxLengthInBytes", "Указанная максимальная длина не может быть меньше 0 байт.");
if (minLengthInBytes == null && maxLengthInBytes == null)
throw new ArgumentException("Невозможно установить как минимальное, так и максимальное значения равными нулю.");
if (minLengthInBytes != null && maxLengthInBytes != null && minLengthInBytes > maxLengthInBytes)
throw new ArgumentException("Максимальное значение должно быть больше или равно минимальному.");
_minLengthInBytes = minLengthInBytes;
_maxLengthInBytes = maxLengthInBytes;
}
public string Name { get { return "File sizes"; } }
public CriterionWeight Weight { get { return CriterionWeight.Medium; } }
public bool DirectorySupport { get { return false; } }
public bool FileSupport { get { return true; } }
public bool IsMatch(FileSystemInfo fileSystemInfo, ICriterionContext context)
{
var file = (FileInfo)fileSystemInfo;
return (_minLengthInBytes == null || file.Length >= _minLengthInBytes.Value)
&& (_maxLengthInBytes == null || file.Length <= _maxLengthInBytes.Value);
}
}
}

@ -0,0 +1,9 @@
using System.Text;
namespace FileSearch.Logic.Model.EncodingDetection
{
internal interface IEncodingFactory
{
Encoding[] DetectEncoding(byte[] firstBytes);
}
}

@ -0,0 +1,18 @@
namespace FileSearch.Logic.Model.Engine
{
public class CriterionBase
{
public virtual ICriterionContext BuildContext()
{
return null;
}
}
public class CriterionBase<TContext> : CriterionBase where TContext : ICriterionContext, new()
{
public override ICriterionContext BuildContext()
{
return new TContext();
}
}
}

@ -0,0 +1,26 @@
namespace FileSearch.Logic.Model.Engine
{
public enum CriterionWeight
{
/// <summary>
/// Простая проверка переменной
/// </summary>
None,
/// <summary>
/// Простая проверка переменной с помощью некоторых продвинутых алгоритмов.
/// </summary>
Light,
/// <summary>
/// Проверка переменной, которая еще не разрешена.
/// </summary>
Medium,
/// <summary>
/// Проверка очень большой переменной, которая еще не разрешена.
/// </summary>
Heavy,
/// <summary>
/// Проверка очень большой переменной со сложными вычислениями, которые лучше не использовать.
/// </summary>
Extreme
}
}

@ -0,0 +1,78 @@
using FileSearch.Logic.Model.EncodingDetection;
using FileSearch.Logic.Model.Entities;
namespace FileSearch.Logic.Model.Engine
{
internal class EngineOptions
{
public EngineOptions(DirectoryInfo[] rootDirectories)
{
if (rootDirectories == null) throw new ArgumentNullException("rootDirectories");
if (rootDirectories.Length <= 0) throw new ArgumentException(@"Должен быть указан минимум 1 корневой каталог.", "rootDirectories");
RootDirectories = rootDirectories;
}
public DirectoryInfo[] RootDirectories { get; private set; }
#region Basic
public string SearchName { get; set; }
public bool SearchNameIgnoreCasing { get; set; }
public bool SearchNameMatchFullPath { get; set; }
public bool SearchNameAsRegularExpression { get; set; }
public bool SearchRecursive { get; set; }
public bool SearchIncludesFolders { get; set; }
public bool SearchInArchives { get; set; }
#endregion
#region Attributes
public FileAttributes AttributesIncluded { get; set; }
public FileAttributes AttributesExcluded { get; set; }
#endregion
#region Size
public long? MinimumSize { get; set; }
public long? MaximumSize { get; set; }
#endregion
#region Dates
public FileDateOption DateOption { get; set; }
public DateTime? StartDateTime { get; set; }
public DateTime? EndDateTime { get; set; }
#endregion
#region File content
public string ContentText { get; set; }
public IEncodingFactory ContentEncodingFactory { get; set; }
public bool ContentIgnoreCasing { get; set; }
public bool ContentWholeWordsOnly { get; set; }
public bool ContentAsRegularExpression { get; set; }
public bool ContentForOfficeXml { get; set; }
#endregion
}
}

@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace FileSearch.Logic.Model.Engine
{
internal class FileCounter
{
}
}

@ -0,0 +1,40 @@
namespace FileSearch.Logic.Model.Engine
{
public interface ICriterion
{
/// <summary>
/// Имя критерия фильтра.
/// </summary>
string Name { get; }
/// <summary>
/// Значение, указывающее усилия, необходимые системе для сопоставления файла.
/// Чем выше число, тем позже проверяется критерий.
/// </summary>
CriterionWeight Weight { get; }
/// <summary>
/// Указывает, поддерживает ли этот экземпляр критерия каталоги.
/// </summary>
bool DirectorySupport { get; }
/// <summary>
/// Указывает, поддерживает ли этот экземпляр критерия файлы.
/// </summary>
bool FileSupport { get; }
/// <summary>
/// Проверяет, соответствует ли файл или каталог этому критерию.
/// </summary>
/// <param name="fileSystemInfo">Entry файловой системы.</param>
/// <param name="context">Контекст текущей работы всех критериев.</param>
/// <returns></returns>
bool IsMatch(FileSystemInfo fileSystemInfo, ICriterionContext context);
/// <summary>
/// Создает новый контекст для этого критерия.
/// </summary>
/// <returns>Новый контекст для сопоставления файлов.</returns>
ICriterionContext BuildContext();
}
}

@ -0,0 +1,6 @@
namespace FileSearch.Logic.Model.Engine
{
public interface ICriterionContext
{
}
}

@ -0,0 +1,35 @@
namespace FileSearch.Logic.Model.Engine
{
public class SearchException
{
private readonly FileSystemInfo _fileSystemInfo;
private readonly Exception _originalException;
private readonly string _friendlyDescription;
public SearchException(FileSystemInfo fileSystemInfo, Exception originalException, string friendlyDescription)
{
if (fileSystemInfo == null) throw new ArgumentNullException("fileSystemInfo");
if (originalException == null) throw new ArgumentNullException("originalException");
if (friendlyDescription == null) throw new ArgumentNullException("friendlyDescription");
_fileSystemInfo = fileSystemInfo;
_originalException = originalException;
_friendlyDescription = friendlyDescription;
}
public Exception OriginalException
{
get { return _originalException; }
}
public string FriendlyDescription
{
get { return _friendlyDescription; }
}
public FileSystemInfo FileSystemInfo
{
get { return _fileSystemInfo; }
}
}
}

@ -0,0 +1,28 @@
using System.Xml;
using Ionic.Zip;
namespace FileSearch.Logic.Model.Engine
{
internal static class SearchExceptionFactory
{
public static SearchException Build(FileSystemInfo fileSystemInfo, Exception originalException)
{
if (originalException is PathTooLongException)
return new SearchException(fileSystemInfo, originalException, "Путь слишком длинный.");
if (originalException is IOException)
return new SearchException(fileSystemInfo, originalException, "Файл заблокирован для чтения.");
if (originalException is UnauthorizedAccessException)
return new SearchException(fileSystemInfo, originalException, "Недостаточно прав на чтение файла или папки.");
if (originalException is XmlException)
return new SearchException(fileSystemInfo, originalException, "XML содержит недопустимые символы.");
if (originalException is ZipException)
return new SearchException(fileSystemInfo, originalException, "Невозможно обработать ZIP-файл.");
return new SearchException(fileSystemInfo, originalException, "Неизвестное необработанное исключение.");
}
}
}

@ -0,0 +1,21 @@
namespace FileSearch.Logic.Model.Engine
{
public class SearchResult
{
public SearchResult(FileSystemInfo fileSystemInfo)
{
this.FileSystemInfo = fileSystemInfo;
}
/// <summary>
/// Получает файл или каталог для данного результата поиска.
/// </summary>
public FileSystemInfo FileSystemInfo { get; private set; }
/// <summary>
/// Получает или устанавливает коллекцию со всеми метаданными для этого результата поиска.
/// Тип критерия, задавшего контекст.
/// </summary>
public IDictionary<Type, ICriterionContext> Metadata { get; set; }
}
}

@ -0,0 +1,46 @@
namespace FileSearch.Logic.Model.Engine
{
internal class TimedCallback<T>
{
private readonly TimeSpan _timeout;
private readonly Action<IEnumerable<T>> _callback;
private DateTime _lastTrigger;
private bool _isRunning = false;
public TimedCallback(TimeSpan timeout, Action<IEnumerable<T>> callback)
{
if (callback == null) throw new ArgumentNullException("callback");
if (timeout.TotalSeconds < 0.1) throw new ArgumentException(@"The timeout should be minimal 0.1 second.", "timeout");
_timeout = timeout;
_callback = callback;
_lastTrigger = DateTime.UtcNow;
}
/// <summary>
/// Значение, указывающее, превышен ли период тайм-аута и можно ли получить новые данные.
/// </summary>
public bool DataNeeded
{
get { return !_isRunning && DateTime.UtcNow - _lastTrigger >= _timeout; }
}
/// <summary>
/// Устанавливает данные для отправки делегату обратного вызова.
/// </summary>
/// <param name="collection">Сбор с данными.</param>
public void SetData(IEnumerable<T> collection)
{
try
{
_isRunning = true;
_callback(collection);
_lastTrigger = DateTime.UtcNow;
}
finally
{
_isRunning = false;
}
}
}
}

@ -0,0 +1,10 @@
namespace FileSearch.Logic.Model.Entities
{
internal enum FileDateOption
{
None,
Accessed,
Changed,
Created
}
}

@ -0,0 +1,67 @@
namespace FileSearch.Logic.Model.Tree
{
public class TreeViewBuilder
{
public static void BuildTreeView(TreeView treeView, string[] paths)
{
if(paths.Length == 0) return;
// Проверяем, существует ли уже корневой узел
TreeNode rootNode = treeView.Nodes.Cast<TreeNode>().FirstOrDefault();
// Если корневой узел еще не создан, создаем его
if (rootNode == null)
{
// Извлекаем первое слово из первого пути
string rootName = GetRootName(paths[0]);
rootNode = new TreeNode(rootName);
treeView.Nodes.Add(rootNode);
}
treeView.BeginUpdate();
// Добавляем каждый путь как узел в TreeView
foreach (string path in paths)
{
AddPathToTree(rootNode, path);
}
treeView.EndUpdate();
}
private static string GetRootName(string path)
{
// Извлекаем первое слово из пути
string[] parts = path.Split('\\');
return parts[0];
}
private static void AddPathToTree(TreeNode parentNode, string path)
{
// Разбиваем путь на части
string[] parts = path.Split('\\');
// Проходим по каждой части пути
TreeNode currentNode = parentNode;
// Пропускаем первое слово, так как оно является корневым узлом
for (int i = 1; i < parts.Length; i++)
{
// Проверяем, есть ли узел с текущим именем среди дочерних узлов текущего узла
TreeNode existingNode = currentNode.Nodes.Cast<TreeNode>()
.FirstOrDefault(node => node.Text == parts[i]);
// Если узел с таким именем уже существует, переходим к следующему уровню
if (existingNode != null)
{
currentNode = existingNode;
}
// Если узел не существует, создаем новый
else
{
TreeNode newNode = new TreeNode(parts[i]);
currentNode.Nodes.Add(newNode);
currentNode = newNode;
}
}
}
}
}

@ -0,0 +1,46 @@
namespace FileSearch.Logic.Plugin
{
internal class ErrorPlugin : IPluginFacade
{
private readonly string _errorMessage;
public ErrorPlugin(string tabTitle, string errorMessage = null)
{
_errorMessage = errorMessage;
this.TabTitle = string.Concat(tabTitle, " [ERROR]");
}
public Guid PluginId { get { return Guid.Empty; } }
public string TabTitle { get; private set; }
public UserControl BuildTabPage()
{
var uc = new UserControl();
if (!string.IsNullOrEmpty(_errorMessage))
{
var label = new Label
{
Text = string.Concat("PLUGIN ERROR: ", _errorMessage),
Location = new Point(3, 3),
ForeColor = Color.Red,
Font = new Font(SystemFonts.DefaultFont, FontStyle.Italic),
AutoSize = true
};
uc.Controls.Add(label);
}
return uc;
}
public ICriterionPlugin[] GetCriterion()
{
return new ICriterionPlugin[0];
}
public IViewBuilderFactory GetViewBuilderFactory()
{
return null;
}
}
}

@ -0,0 +1,9 @@
using FileSearch.Logic.Model.Engine;
namespace FileSearch.Logic.Plugin
{
public interface ICriterionPlugin : ICriterion
{
}
}

@ -0,0 +1,17 @@
using FileSearch.Logic.UI.ViewBuilders;
namespace FileSearch.Logic.Plugin
{
public interface IPluginFacade
{
Guid PluginId { get; }
string TabTitle { get; }
UserControl BuildTabPage();
ICriterionPlugin[] GetCriterion();
IViewBuilderFactory GetViewBuilderFactory();
}
}

@ -0,0 +1,9 @@
using FileSearch.Logic.UI.ViewBuilders;
namespace FileSearch.Logic.Plugin
{
public interface IViewBuilderFactory
{
IViewBuilder CreateViewBuilder(ICriterionPlugin criteria);
}
}

@ -0,0 +1,55 @@
using System.Reflection;
namespace FileSearch.Logic.Plugin
{
internal static class Plugins
{
private static IPluginFacade[] _plugins;
public static IPluginFacade[] All()
{
return _plugins ?? (_plugins = DetectAll());
}
public static IEnumerable<IPluginFacade> Loaded()
{
return All().Where(p => p.PluginId != Guid.Empty);
}
private static IPluginFacade[] DetectAll()
{
var collection = new List<IPluginFacade>();
var path = Path.GetDirectoryName(Application.ExecutablePath);
if (path == null) return new IPluginFacade[0];
foreach (var file in new DirectoryInfo(path).GetFiles("FileSearch.Plugin.*.dll", SearchOption.TopDirectoryOnly))
{
try
{
var assembly = Assembly.LoadFile(file.FullName);
var facades = assembly.GetTypes().Where(t => typeof(IPluginFacade).IsAssignableFrom(t));
foreach (var facade in facades)
{
var instance = (IPluginFacade)Activator.CreateInstance(facade);
var duplicatePluginId = collection.FirstOrDefault(p => p.PluginId == instance.PluginId);
if (duplicatePluginId != null)
collection.Add(new ErrorPlugin(instance.TabTitle, "The plugin ID is already registered by another plugin named '" + duplicatePluginId.TabTitle + "'."));
else
collection.Add(instance);
}
}
catch (ReflectionTypeLoadException ex)
{
collection.Add(new ErrorPlugin(Path.GetFileNameWithoutExtension(file.FullName), ex.Message));
}
catch (Exception ex)
{
collection.Add(new ErrorPlugin(Path.GetFileNameWithoutExtension(file.FullName), ex.Message));
}
}
return collection.ToArray();
}
}
}

@ -0,0 +1,11 @@
namespace FileSearch.Logic.UI.Entries
{
public interface IPathEntry
{
FileSystemInfo FileSystemInfo { get; }
bool IsDirectory { get; }
ListViewItem BuildListViewItem();
}
}

@ -0,0 +1,32 @@
namespace FileSearch.Logic.UI.Entries
{
internal class PathEntry : IPathEntry
{
private readonly FileSystemInfo _fileSystemInfo;
public PathEntry(FileSystemInfo fileSystemInfo)
{
if (fileSystemInfo == null) throw new ArgumentNullException("fileSystemInfo");
_fileSystemInfo = fileSystemInfo;
}
public FileSystemInfo FileSystemInfo
{
get { return _fileSystemInfo; }
}
public bool IsDirectory
{
get { return _fileSystemInfo is DirectoryInfo; }
}
public virtual ListViewItem BuildListViewItem()
{
var name = _fileSystemInfo.FullName;
if (this.IsDirectory)
name = string.Concat("[", name, "]");
return new ListViewItem(name);
}
}
}

@ -0,0 +1,19 @@
namespace FileSearch.Logic.UI.Entries
{
internal class PathEventArgs : EventArgs
{
private readonly PathEntry _entry;
public PathEventArgs(PathEntry entry)
{
if (entry == null) throw new ArgumentNullException("entry");
_entry = entry;
}
public PathEntry Entry
{
get { return _entry; }
}
}
}

@ -0,0 +1,22 @@
using FileSearch.Logic.Model.Engine;
namespace FileSearch.Logic.UI.Entries
{
internal sealed class SearchExceptionEntry : ListViewItem
{
private readonly SearchException _exception;
public SearchExceptionEntry(SearchException exception)
{
_exception = exception;
this.Text = exception.FileSystemInfo.FullName;
this.SubItems.Add(exception.FriendlyDescription);
}
public SearchException Exception
{
get { return _exception; }
}
}
}

@ -0,0 +1,20 @@
using FileSearch.Logic.UI.Entries;
namespace FileSearch.Logic.UI.ViewBuilders
{
internal class DefaultViewBuilder : IViewBuilder
{
public IEnumerable<IPathEntry> Build(Model.Engine.SearchResult entry, int entryIndex)
{
return new[] { new PathEntry(entry.FileSystemInfo) };
}
public Tuple<string, int>[] ColumnSizes
{
get
{
return new[] { new Tuple<string, int>("Сканируемые директории", -1) };
}
}
}
}

@ -0,0 +1,12 @@
using FileSearch.Logic.Model.Engine;
using FileSearch.Logic.UI.Entries;
namespace FileSearch.Logic.UI.ViewBuilders
{
public interface IViewBuilder
{
IEnumerable<IPathEntry> Build(SearchResult entry, int entryIndex);
Tuple<string, int>[] ColumnSizes { get; }
}
}

@ -0,0 +1,12 @@
namespace FileSearch.Logic.UI.ViewBuilders
{
internal static class ViewBuilderFactory
{
public static IViewBuilder Create()
{
IViewBuilder builder = null;
return builder ?? new DefaultViewBuilder();
}
}
}

340
FileSearch/MainForm.Designer.cs generated Normal file

@ -0,0 +1,340 @@
namespace FileSearch
{
partial class MainForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
FileRegexPathTxt = new TextBox();
SearchBtn = new Button();
labelNameDir = new Label();
labelNameFileRegex = new Label();
DirsTreeView = new TreeView();
FindFilesTxt = new TextBox();
AllFilesTxt = new TextBox();
timerTxt = new TextBox();
PauseBtn = new Button();
lstResults = new LargeListViewUserControl();
statusProgress = new StatusStrip();
toolStripProgressBar1 = new ToolStripProgressBar();
statusLabel = new ToolStripStatusLabel();
statusLabelExceptions = new ToolStripStatusLabel();
btnBrowse = new Button();
folderBrowserDialog = new FolderBrowserDialog();
DirPathTxt = new ComboBox();
labelInfo1 = new Label();
labelInfo2 = new Label();
labelInfo3 = new Label();
pictureBoxLoading = new PictureBox();
statusProgress.SuspendLayout();
((System.ComponentModel.ISupportInitialize)pictureBoxLoading).BeginInit();
SuspendLayout();
//
// FileRegexPathTxt
//
FileRegexPathTxt.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
FileRegexPathTxt.Location = new Point(2, 87);
FileRegexPathTxt.Name = "FileRegexPathTxt";
FileRegexPathTxt.Size = new Size(329, 27);
FileRegexPathTxt.TabIndex = 1;
FileRegexPathTxt.Text = "\\.png";
//
// SearchBtn
//
SearchBtn.Anchor = AnchorStyles.Top | AnchorStyles.Right;
SearchBtn.BackColor = Color.Red;
SearchBtn.Cursor = Cursors.Hand;
SearchBtn.Font = new Font("Segoe UI Symbol", 12F, FontStyle.Bold, GraphicsUnit.Point);
SearchBtn.ForeColor = SystemColors.ControlLightLight;
SearchBtn.Location = new Point(545, 19);
SearchBtn.Margin = new Padding(4);
SearchBtn.Name = "SearchBtn";
SearchBtn.Size = new Size(139, 40);
SearchBtn.TabIndex = 0;
SearchBtn.Text = "Поиск";
SearchBtn.UseVisualStyleBackColor = false;
SearchBtn.Click += SearchBtn_Click;
//
// labelNameDir
//
labelNameDir.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
labelNameDir.AutoSize = true;
labelNameDir.Location = new Point(97, 2);
labelNameDir.Name = "labelNameDir";
labelNameDir.Size = new Size(149, 20);
labelNameDir.TabIndex = 0;
labelNameDir.Text = "Директория поиска";
//
// labelNameFileRegex
//
labelNameFileRegex.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
labelNameFileRegex.AutoSize = true;
labelNameFileRegex.Location = new Point(59, 64);
labelNameFileRegex.Name = "labelNameFileRegex";
labelNameFileRegex.Size = new Size(221, 20);
labelNameFileRegex.TabIndex = 4;
labelNameFileRegex.Text = "Шаблон имени файла (Regex)";
//
// DirsTreeView
//
DirsTreeView.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
DirsTreeView.Location = new Point(0, 124);
DirsTreeView.Name = "DirsTreeView";
DirsTreeView.Size = new Size(684, 430);
DirsTreeView.TabIndex = 9;
DirsTreeView.NodeMouseDoubleClick += DirsTreeView_NodeMouseDoubleClick;
//
// FindFilesTxt
//
FindFilesTxt.Anchor = AnchorStyles.Top | AnchorStyles.Right;
FindFilesTxt.Enabled = false;
FindFilesTxt.Location = new Point(374, 26);
FindFilesTxt.Name = "FindFilesTxt";
FindFilesTxt.Size = new Size(72, 27);
FindFilesTxt.TabIndex = 10;
FindFilesTxt.Text = "0";
FindFilesTxt.TextAlign = HorizontalAlignment.Center;
//
// AllFilesTxt
//
AllFilesTxt.Anchor = AnchorStyles.Top | AnchorStyles.Right;
AllFilesTxt.BackColor = Color.White;
AllFilesTxt.BorderStyle = BorderStyle.FixedSingle;
AllFilesTxt.Enabled = false;
AllFilesTxt.Location = new Point(452, 26);
AllFilesTxt.Name = "AllFilesTxt";
AllFilesTxt.Size = new Size(86, 27);
AllFilesTxt.TabIndex = 11;
AllFilesTxt.Text = "0";
AllFilesTxt.TextAlign = HorizontalAlignment.Center;
//
// timerTxt
//
timerTxt.Anchor = AnchorStyles.Top | AnchorStyles.Right;
timerTxt.Enabled = false;
timerTxt.Font = new Font("Arial", 14.25F, FontStyle.Bold, GraphicsUnit.Point);
timerTxt.Location = new Point(384, 85);
timerTxt.Name = "timerTxt";
timerTxt.Size = new Size(129, 29);
timerTxt.TabIndex = 12;
timerTxt.Text = "0";
timerTxt.TextAlign = HorizontalAlignment.Center;
//
// PauseBtn
//
PauseBtn.Anchor = AnchorStyles.Top | AnchorStyles.Right;
PauseBtn.BackColor = Color.Red;
PauseBtn.Font = new Font("Segoe UI Symbol", 12F, FontStyle.Bold, GraphicsUnit.Point);
PauseBtn.ForeColor = SystemColors.ControlLightLight;
PauseBtn.Location = new Point(545, 74);
PauseBtn.Margin = new Padding(4);
PauseBtn.Name = "PauseBtn";
PauseBtn.Size = new Size(139, 40);
PauseBtn.TabIndex = 13;
PauseBtn.Text = "Пауза";
PauseBtn.UseVisualStyleBackColor = false;
PauseBtn.Click += PauseBtn_Click;
//
// lstResults
//
lstResults.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
lstResults.Location = new Point(2, 560);
lstResults.Name = "lstResults";
lstResults.Size = new Size(684, 376);
lstResults.TabIndex = 4;
lstResults.FileOpened += MainForm_FileOpened;
lstResults.DirectoryOpened += MainForm_DirectoryOpened;
//
// statusProgress
//
statusProgress.Items.AddRange(new ToolStripItem[] { toolStripProgressBar1, statusLabel, statusLabelExceptions });
statusProgress.Location = new Point(0, 939);
statusProgress.MinimumSize = new Size(682, 0);
statusProgress.Name = "statusProgress";
statusProgress.Size = new Size(684, 22);
statusProgress.TabIndex = 14;
statusProgress.Text = "statusStrip1";
//
// toolStripProgressBar1
//
toolStripProgressBar1.Name = "toolStripProgressBar1";
toolStripProgressBar1.Size = new Size(200, 16);
//
// statusLabel
//
statusLabel.Margin = new Padding(50, 3, 50, 2);
statusLabel.Name = "statusLabel";
statusLabel.Size = new Size(31, 17);
statusLabel.Text = "2024";
//
// statusLabelExceptions
//
statusLabelExceptions.AutoToolTip = true;
statusLabelExceptions.BackColor = Color.DodgerBlue;
statusLabelExceptions.ForeColor = Color.White;
statusLabelExceptions.Margin = new Padding(20, 3, 0, 2);
statusLabelExceptions.Name = "statusLabelExceptions";
statusLabelExceptions.Size = new Size(54, 17);
statusLabelExceptions.Text = "Ошибки";
statusLabelExceptions.ToolTipText = "Открыть панель ошибок";
statusLabelExceptions.Click += statusLabelExceptions_Click;
//
// btnBrowse
//
btnBrowse.Anchor = AnchorStyles.Top | AnchorStyles.Right;
btnBrowse.BackColor = Color.DodgerBlue;
btnBrowse.Cursor = Cursors.Hand;
btnBrowse.FlatStyle = FlatStyle.Popup;
btnBrowse.ForeColor = Color.White;
btnBrowse.Location = new Point(337, 24);
btnBrowse.Name = "btnBrowse";
btnBrowse.Size = new Size(31, 29);
btnBrowse.TabIndex = 16;
btnBrowse.Text = "...";
btnBrowse.TextAlign = ContentAlignment.TopCenter;
btnBrowse.UseVisualStyleBackColor = false;
btnBrowse.Click += btnBrowse_Click;
//
// DirPathTxt
//
DirPathTxt.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;
DirPathTxt.FormattingEnabled = true;
DirPathTxt.Location = new Point(0, 25);
DirPathTxt.Name = "DirPathTxt";
DirPathTxt.Size = new Size(331, 28);
DirPathTxt.TabIndex = 17;
DirPathTxt.TextChanged += DirPathTxt_TextChanged;
//
// labelInfo1
//
labelInfo1.Anchor = AnchorStyles.Top | AnchorStyles.Right;
labelInfo1.AutoSize = true;
labelInfo1.Location = new Point(374, 2);
labelInfo1.Name = "labelInfo1";
labelInfo1.Size = new Size(72, 20);
labelInfo1.TabIndex = 18;
labelInfo1.Text = "Найдено";
//
// labelInfo2
//
labelInfo2.Anchor = AnchorStyles.Top | AnchorStyles.Right;
labelInfo2.AutoSize = true;
labelInfo2.Location = new Point(472, 2);
labelInfo2.Name = "labelInfo2";
labelInfo2.Size = new Size(48, 20);
labelInfo2.TabIndex = 19;
labelInfo2.Text = "Всего";
//
// labelInfo3
//
labelInfo3.Anchor = AnchorStyles.Top | AnchorStyles.Right;
labelInfo3.AutoSize = true;
labelInfo3.Location = new Point(421, 62);
labelInfo3.Name = "labelInfo3";
labelInfo3.Size = new Size(54, 20);
labelInfo3.TabIndex = 20;
labelInfo3.Text = "Время";
//
// pictureBoxLoading
//
pictureBoxLoading.Anchor = AnchorStyles.Top | AnchorStyles.Right;
pictureBoxLoading.Image = Resource.Loading_icon;
pictureBoxLoading.Location = new Point(461, 27);
pictureBoxLoading.Name = "pictureBoxLoading";
pictureBoxLoading.Size = new Size(68, 25);
pictureBoxLoading.SizeMode = PictureBoxSizeMode.StretchImage;
pictureBoxLoading.TabIndex = 21;
pictureBoxLoading.TabStop = false;
pictureBoxLoading.Visible = false;
//
// MainForm
//
AutoScaleMode = AutoScaleMode.None;
BackColor = Color.FromArgb(255, 224, 192);
ClientSize = new Size(684, 961);
Controls.Add(pictureBoxLoading);
Controls.Add(labelInfo3);
Controls.Add(labelInfo2);
Controls.Add(labelInfo1);
Controls.Add(DirPathTxt);
Controls.Add(btnBrowse);
Controls.Add(statusProgress);
Controls.Add(PauseBtn);
Controls.Add(timerTxt);
Controls.Add(AllFilesTxt);
Controls.Add(FindFilesTxt);
Controls.Add(DirsTreeView);
Controls.Add(labelNameFileRegex);
Controls.Add(labelNameDir);
Controls.Add(FileRegexPathTxt);
Controls.Add(SearchBtn);
Controls.Add(lstResults);
Font = new Font("Segoe UI Semibold", 11.25F, FontStyle.Bold, GraphicsUnit.Point);
Margin = new Padding(4);
MaximizeBox = false;
MaximumSize = new Size(1200, 1200);
MdiChildrenMinimizedAnchorBottom = false;
MinimizeBox = false;
MinimumSize = new Size(700, 1000);
Name = "MainForm";
ShowIcon = false;
StartPosition = FormStartPosition.CenterScreen;
Text = "Поиск файлов на диске";
FormClosed += MainForm_FormClosed;
Load += MainForm_Load;
FileOpened += MainForm_FileOpened;
DirectoryOpened += MainForm_DirectoryOpened;
statusProgress.ResumeLayout(false);
statusProgress.PerformLayout();
((System.ComponentModel.ISupportInitialize)pictureBoxLoading).EndInit();
ResumeLayout(false);
PerformLayout();
}
#endregion
private TextBox FileRegexPathTxt;
private Button SearchBtn;
private Label labelNameDir;
private Label labelNameFileRegex;
private TreeView DirsTreeView;
private TextBox FindFilesTxt;
private TextBox AllFilesTxt;
private TextBox timerTxt;
private Button PauseBtn;
private LargeListViewUserControl lstResults;
private StatusStrip statusProgress;
private ToolStripProgressBar toolStripProgressBar1;
private Button btnBrowse;
private FolderBrowserDialog folderBrowserDialog;
private ComboBox DirPathTxt;
private ToolStripStatusLabel statusLabel;
private Label labelInfo1;
private Label labelInfo2;
private Label labelInfo3;
private ToolStripStatusLabel statusLabelExceptions;
private PictureBox pictureBoxLoading;
}
}

450
FileSearch/MainForm.cs Normal file

@ -0,0 +1,450 @@
using FileSearch.Logic;
using FileSearch.Logic.Model.Engine;
using FileSearch.Logic.Model.Tree;
using FileSearch.Logic.Plugin;
using FileSearch.Logic.UI.Entries;
using FileSearch.Logic.UI.ViewBuilders;
using System.Diagnostics;
using System.Globalization;
namespace FileSearch
{
internal partial class MainForm : Form
{
private ExceptionsForm _exceptionsForm;
private event EventHandler<PathEventArgs> FileOpened;
private event EventHandler<PathEventArgs> DirectoryOpened;
private static FileSearcher _fileSearcher;
private bool _resultsViewIsUpdated = false;
private static CancellationTokenSource _tokenGetFilesSourceSearch = new CancellationTokenSource();
private string _searchPath = string.Empty;
public MainForm() => InitializeComponent();
#region Logic
/// <summary>
/// Ïîäãîòîâêà ê çàïóñêó ñåàíñà ïîèñêà
/// </summary>
private DirectoryInfo SearchInit()
{
toolStripProgressBar1.Style = ProgressBarStyle.Marquee;
lstResults.ClearContent();
_resultsViewIsUpdated = false;
DirsTreeView.Controls.Clear();
DirsTreeView.Nodes.Clear();
_tokenGetFilesSourceSearch.Cancel();
SaveInfo(comboBox: true);
SearchBtn.Text = @"Îñòàíîâèòü";
statusLabel.Text = @"Ïîèñê... Ïîæàëóéñòà ïîäîæäèòå.";
statusLabelExceptions.Text = null;
return new DirectoryInfo(DirPathTxt.Text.Trim('\\') + "\\");
}
/// <summary>
/// Îáðàáîòêà ñîáûòèÿ îáíàðóæåíèÿ äàííûõ
/// </summary>
/// <param name="searchResults">searchResults</param>
private void LoadList(IEnumerable<SearchResult> searchResults)
{
if (lstResults.InvokeRequired)
{
lstResults.BeginInvoke((Action<IEnumerable<SearchResult>>)LoadList, searchResults);
return;
}
if (!_resultsViewIsUpdated)
{
lstResults.SetViewBuilder(ViewBuilderFactory.Create());
_resultsViewIsUpdated = true;
}
lstResults.AddSearchResults(searchResults);
AddTimerTextInfo(_fileSearcher.CurrentTime.GetFriendlyNotation()); // îáíîâëÿåì òàéìåð
AddFindFilesCount(lstResults.Count); // Îáíîâëÿåì ÷èñëî îáíàðóæåííûõ ôàéëîâ
AddThreeViewData(searchResults); // Îáíîâëÿåì äåðåâî äèðåêòîðèé
}
/// <summary>
/// Îáðàáîòêà îêîí÷àíèÿ ñåàíñà ïîèñêà
/// </summary>
private void LoadListFinished()
{
if (lstResults.InvokeRequired)
{
lstResults.Invoke((Action)LoadListFinished);
return;
}
var exceptions = _fileSearcher.Exceptions;
if (exceptions.Count > 0)
statusLabelExceptions.Text = string.Format(CultureInfo.InvariantCulture, "{0} îøèáîê â ïîèñêå", exceptions.Count);
SearchBtn.Text = @"Ïîèñê";
PauseBtn.Text = @"Ïàóçà";
SearchBtn.Enabled = true;
statusLabel.Text = @"Çàâåðøåíî çà " + _fileSearcher.OperatingTime.GetFriendlyNotation() + @". Íàéäåíî: " + lstResults.Count;
toolStripProgressBar1.Style = ProgressBarStyle.Blocks;
if (DirsTreeView.Nodes.Count > 0)
DirsTreeView.Focus();
}
/// <summary>
/// Êîíôèãóðèðîâàíèå êðèòåðèé ïîèñêà
///
/// TODO: Â áóäóùåì ìîæíî ñäåëàòü òîíêóþ íàñòðîéêó
/// êðèòåðèé ïîèñêà â UI óêàçàâ Control äëÿ âêëþ÷åíèÿ òåõ èëè èíûõ
/// </summary>
/// <param name="directoryInfo">directoryInfo</param>
/// <param name="text">text</param>
/// <returns></returns>
private EngineOptions BuildOptions(DirectoryInfo directoryInfo, string text)
{
var options = new EngineOptions(new[] { directoryInfo })
{
SearchName = text,
SearchIncludesFolders = true,
SearchNameIgnoreCasing = false,
SearchNameMatchFullPath = false,
SearchRecursive = true,
SearchNameAsRegularExpression = true,
SearchInArchives = false,
ContentAsRegularExpression = true,
ContentText = string.Empty,
ContentIgnoreCasing = true,
ContentWholeWordsOnly = false,
ContentEncodingFactory = null,
ContentForOfficeXml = false
};
return options;
}
/// <summary>
/// Ïîñòðîåíèå äåðåâà äèðåêòîðèé
/// </summary>
/// <param name="searchResults">IEnumerable</param>
private void AddThreeViewData(IEnumerable<SearchResult> searchResults)
{
TreeViewBuilder.BuildTreeView(DirsTreeView, searchResults.Select(res => res.FileSystemInfo.FullName).ToArray());
}
/// <summary>
/// Èíèöèàëèçàöèÿ ÷òåíèÿ ìàêñèìàëüíîãî ÷èñëà ôàéëîâ â äèðåêòîðèè
/// </summary>
private void LoadingMaxFiles()
{
if (_searchPath == DirPathTxt.Text) return;
_searchPath = DirPathTxt.Text;
var task = Task.Run(async () =>
{
AddProgressBarLoadMax(true);
await GetAllFilesExecute(_searchPath);
});
}
/// <summary>
/// Ôîðìèðîâàíèå async çàäà÷è íà ÷òåíèå ìàêñèìàëüíîãî ÷èñëà ôàéëîâ â äèðåêòîðèè
/// </summary>
/// <param name="searchPath">path</param>
public async Task GetAllFilesExecute(string searchPath)
{
AddMaxFilesTxtInfo("0");
await Task.Factory.StartNew(() =>
{
GetAllFiles(searchPath);
});
}
/// <summary>
/// Ïîëó÷åíèå ìàêñèìàëüíîãî ÷èñëà ôàéëîâ â äèðåêòîðèè (ñ âëîæåííûìè âíóòðè)
/// </summary>
/// <param name="searchPath">path</param>
private void GetAllFiles(string searchPath)
{
long count = 0;
_tokenGetFilesSourceSearch = new CancellationTokenSource();
var searchTask = Task.Run(async () =>
{
try
{
count = await GetFileCountAsync(searchPath, _tokenGetFilesSourceSearch.Token);
}
catch (OperationCanceledException)
{
// Îáðàáîòêà îòìåíû îïåðàöèè
AddMaxFilesTxtInfo(count.ToString());
AddProgressBarLoadMax(false);
}
}, _tokenGetFilesSourceSearch.Token);
searchTask.ContinueWith(t =>
{
searchTask = null;
AddMaxFilesTxtInfo(count.ToString());
AddProgressBarLoadMax(false);
});
}
public async Task<long> GetFileCountAsync(string directory, CancellationToken cancellationToken)
{
try
{
long count = 0;
foreach (var file in Directory.GetFiles(directory))
{
if (cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
}
count++;
}
foreach (var subDir in Directory.GetDirectories(directory))
{
if (cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
}
count += await GetFileCountAsync(subDir, cancellationToken);
}
return count;
}
catch (OperationCanceledException)
{
throw; // Ïðîáðàñûâàåì èñêëþ÷åíèå äàëüøå äëÿ îáðàáîòêè â âûçûâàþùåì êîäå
}
catch (UnauthorizedAccessException)
{
return 0; // Ïðîïóñêàåì êàòàëîãè, ê êîòîðûì íåò äîñòóïà
}
catch (DirectoryNotFoundException)
{
return 0; // Ïðîïóñêàåì îòñóòñòâóþùèå êàòàëîãè
}
}
/// <summary>
/// Ñîõðàíåíèå ïîëåé (ïðîñòåéøåå)
/// </summary>
/// <param name="comboBox">ñîõðàíåíèå âûáðàííîãî ïóòè â comboBox â òåêóùåé ñåññèè</param>
private void SaveInfo(bool comboBox = false)
{
File.WriteAllText(@"DirInfo.dat", DirPathTxt.Text);
File.WriteAllText(@"FileRegex.dat", FileRegexPathTxt.Text);
//ñîõðàíåíèå âûáðàííîãî ïóòè äëÿ ïîèñêà â òåêóùåé ñåññèè
if (comboBox)
{
var content = DirPathTxt.SelectedItem != null ? (string)DirPathTxt.SelectedItem : DirPathTxt.Text;
if (!string.IsNullOrEmpty(content))
{
if (DirPathTxt.Items.Contains(content))
DirPathTxt.Items.Remove(content);
DirPathTxt.Items.Insert(0, content);
DirPathTxt.SelectedItem = 0;
DirPathTxt.Text = content;
}
}
}
#endregion
#region Events UI
private void MainForm_Load(object sender, EventArgs e)
{
if (File.Exists(@"DirInfo.dat"))
DirPathTxt.Text = File.ReadAllText(@"DirInfo.dat");
if (File.Exists(@"FileRegex.dat"))
FileRegexPathTxt.Text = File.ReadAllText(@"FileRegex.dat");
}
private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
{
SaveInfo();
_tokenGetFilesSourceSearch.Cancel();
}
private void MainForm_DirectoryOpened(object? sender, PathEventArgs e)
{
Process.Start(new ProcessStartInfo("explorer.exe", "\"" + e.Entry.FileSystemInfo.FullName + "\""));
}
private void MainForm_FileOpened(object? sender, PathEventArgs e)
{
var file = e.Entry.FileSystemInfo.FullName;
Process.Start(new ProcessStartInfo("explorer.exe", file));
}
private void SearchBtn_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(DirPathTxt.Text)) return;
if (_fileSearcher != null && _fileSearcher.IsRunning)
{
_fileSearcher.Stop();
SearchBtn.Enabled = false;
return;
}
var dirInfo = SearchInit();
if (!dirInfo.Exists)
{
MessageBox.Show(@"Ïóòü íå ñóùåñòâóåò.", @"Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
LoadingMaxFiles();
_fileSearcher = new FileSearcher(BuildOptions(dirInfo, FileRegexPathTxt.Text), Plugins.All().Select(f => f.GetCriterion()).Where(f => f != null).SelectMany(f => f));
_fileSearcher.Start(LoadList, LoadListFinished);
}
private void PauseBtn_Click(object sender, EventArgs e)
{
_fileSearcher?.Pause((state) =>
{
PauseBtn.Text = (state) ? "Äàëåå" : "Ïàóçà";
}, (PauseBtn.Text == "Äàëåå") ? false : true);
}
private void DirPathTxt_TextChanged(object sender, EventArgs e)
{
DirPathTxt.ForeColor = Directory.Exists(DirPathTxt.Text) ? SystemColors.WindowText : Color.Red;
}
private void DirsTreeView_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
{
TreeNode node = e.Node;
var paths = e.Node.FullPath;
DirectoryInfo dir = new DirectoryInfo(paths);
FileSystemInfo fsi = dir as FileSystemInfo;
var path = new PathEntry(fsi);
if (path.IsDirectory)
{
if (DirectoryOpened != null)
DirectoryOpened(this, new PathEventArgs(path));
}
else if (FileOpened != null)
FileOpened(this, new PathEventArgs(path));
}
private void btnBrowse_Click(object sender, EventArgs e)
{
if (_fileSearcher != null && _fileSearcher.IsRunning)
{
MessageBox.Show("Ïîèñê åù¸ èä¸ò!");
return;
}
folderBrowserDialog.SelectedPath = DirPathTxt.Text;
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
{
// Set the selected index to -1 so when the text is set the index is forgotten.
DirPathTxt.SelectedIndex = -1;
DirPathTxt.Text = folderBrowserDialog.SelectedPath;
}
}
private void statusLabelExceptions_Click(object sender, EventArgs e)
{
if (_fileSearcher == null || _fileSearcher.Exceptions.Count <= 0)
return;
var form = _exceptionsForm ?? (_exceptionsForm = new ExceptionsForm());
form.SetContent(_fileSearcher.Exceptions);
form.ShowDialog();
}
#endregion
#region Async UI
/// <summary>
/// Îáíîâëåíèå AllFilesTxt
/// </summary>
/// <param name="text">count</param>
private void AddMaxFilesTxtInfo(string text)
{
if (string.IsNullOrEmpty(text)) { return; }
if (InvokeRequired)
{
Invoke((Action<string>)AddMaxFilesTxtInfo, text);
return;
}
AllFilesTxt.Text = text;
AllFilesTxt.Update();
}
/// <summary>
/// Îáíîâëåíèå pictureBoxLoading
/// </summary>
/// <param name="visible">bool</param>
private void AddProgressBarLoadMax(bool visible)
{
if (InvokeRequired)
{
Invoke((Action<bool>)AddProgressBarLoadMax, visible);
return;
}
pictureBoxLoading.Visible = visible;
pictureBoxLoading.Update();
pictureBoxLoading.Refresh();
}
/// <summary>
/// Îáíîâëåíèå timerTxt
/// </summary>
/// <param name="text">count</param>
private void AddTimerTextInfo(string text)
{
if (string.IsNullOrEmpty(text)) { return; }
if (InvokeRequired)
{
Invoke((Action<string>)AddTimerTextInfo, text);
return;
}
timerTxt.Text = text;
timerTxt.Update();
}
/// <summary>
/// Îáíîâëåíèå FindFilesTxt
/// </summary>
/// <param name="count">count</param>
private void AddFindFilesCount(int count)
{
if (InvokeRequired)
{
Invoke((Action<int>)AddFindFilesCount, count);
return;
}
FindFilesTxt.Text = count.ToString();
FindFilesTxt.Update();
}
#endregion
}
}

126
FileSearch/MainForm.resx Normal file

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="statusProgress.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="folderBrowserDialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>147, 17</value>
</metadata>
</root>

17
FileSearch/Program.cs Normal file

@ -0,0 +1,17 @@
namespace FileSearch
{
internal static class Program
{
/// <summary>
/// Îñíîâíàÿ òî÷êà âõîäà â ïðèëîæåíèå.
/// </summary>
[STAThread]
static void Main()
{
// ×òîáû íàñòðîèòü êîíôèãóðàöèþ ïðèëîæåíèÿ, íàïðèìåð óñòàíîâèòü íàñòðîéêè âûñîêîãî ðàçðåøåíèÿ èëè øðèôò ïî óìîë÷àíèþ,
// https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
Application.Run(new MainForm());
}
}
}

73
FileSearch/Resource.Designer.cs generated Normal file

@ -0,0 +1,73 @@
//------------------------------------------------------------------------------
// <auto-generated>
// Этот код создан программой.
// Исполняемая версия:4.0.30319.42000
//
// Изменения в этом файле могут привести к неправильной работе и будут потеряны в случае
// повторной генерации кода.
// </auto-generated>
//------------------------------------------------------------------------------
namespace FileSearch {
using System;
/// <summary>
/// Класс ресурса со строгой типизацией для поиска локализованных строк и т.д.
/// </summary>
// Этот класс создан автоматически классом StronglyTypedResourceBuilder
// с помощью такого средства, как ResGen или Visual Studio.
// Чтобы добавить или удалить член, измените файл .ResX и снова запустите ResGen
// с параметром /str или перестройте свой проект VS.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resource {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resource() {
}
/// <summary>
/// Возвращает кэшированный экземпляр ResourceManager, использованный этим классом.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("FileSearch.Resource", typeof(Resource).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Перезаписывает свойство CurrentUICulture текущего потока для всех
/// обращений к ресурсу с помощью этого класса ресурса со строгой типизацией.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Поиск локализованного ресурса типа System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap Loading_icon {
get {
object obj = ResourceManager.GetObject("Loading_icon", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
}
}

124
FileSearch/Resource.resx Normal file

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="Loading_icon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>Resources\Loading_icon.gif;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
GIT_Media/Wp69ZvG87t.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
GIT_Media/view.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 MiB

56
README.md Normal file

@ -0,0 +1,56 @@
<p align="center">
<img alt="BetterCap" src="https://avatars.githubusercontent.com/u/46356631?v=4" height="140" />
<p align="center">
<a href="https://sites.google.com/view/dvurechensky" target="_blank"><img alt="Static Badge" src="https://img.shields.io/badge/Dvurechensky-АРМО_СИСТЕМЫ-blue"></a>
</p>
</p>
# Проводник
>
Проводник для Windows
>
## 🌐 **Что внутри?** 🌐
> Критерии:
1. Стартовая директория (с которой начинается поиск)
2. Шаблон имени файла в виде regex выражения
<p align="center">
<img src="GIT_Media/Wp69ZvG87t.png" height="300" width="500">
</p>
> Что отображает в режиме реального времени:
1. Все найденные по критериям файлы
в виде дерева (как в левой части проводника).
Дерево не должно подвисать, моргать, тормозить и т.д.
Во время поиска пользователь может ходить по дереву, открывать/закрывать.
2. Название директории, в которой идет текущий поиск
3. Количество найденных и общее количество файлов. (Примечательно что поиск всех файлов в той или иной директории может затянуться в связи с чем число файлов появляется позднее чем заканчивается основной поиск файлов)
4. Прошедшее от начала запуска поиска время
<p align="center">
<img src="GIT_Media/view.gif" height="500" width="360">
</p>
> Особенности:
1. Пользователь имеет возможность остановить поиск в любой момент и затем
либо продолжить его либо начать новый поиск.
---
В дополнение к пунктам выше ◀️ При нажатии на синюю строку с числом ошибок которые были обнаружены - откроется форма которая выведет их список.
### <g-emoji class="g-emoji" alias="scroll" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/1f4dc.png">📜</g-emoji> Установка
---
📏 Visual Studio 2022 Professional
-
📏 .NET 6.0
-