FileSearchWindows/FileSearch/Logic/FileSearcher.cs
Dvurechensky e2bffc8b49 1.0
Main
2024-10-05 10:06:04 +03:00

228 lines
9.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}
}
}
}
}