228 lines
9.1 KiB
C#
228 lines
9.1 KiB
C#
|
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;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|