Быстрое определение, если содержимое папки было изменено - PullRequest
3 голосов
/ 23 июля 2010

Мне нужно определить, какие папки содержат файлы, которые были изменены "недавно" (в течение определенного интервала).Я заметил, что метки даты в папке обновляются всякий раз, когда изменяется содержащийся файл, но такое поведение не распространяется вверх по дереву, то есть метка даты в папке, содержащей папку, содержащую измененный файл, не обновляется.

Я могу работать с этим поведением, но я подозреваю, что это зависит от платформы / файловой системы / сети или локального диска и т. Д. Я все еще хотел бы воспользоваться этим, где мог, поэтому мне нужна логическая функция для возврата trueесли платформа / диск, на котором запущено мое приложение, поддерживает это поведение.

Я очень рад пройтись по дереву.Чего я хочу избежать, так это делать FindFirst / FindNext для каждого файла в каждой папке, чтобы увидеть, были ли какие-либо изменения (скажем) в последний день - если я могу избежать этого для папок, для которых не были изменены метки датыв течение последнего дня это сэкономит много времени.

Ответы [ 4 ]

3 голосов
/ 23 июля 2010

Проверьте функции FindFirstChangeNotification и FindNextChangeNotification другой вариант - использовать TJvChangeNotify компонент JEDI.

Дополнительно вы можете проверить эту ссылку

2 голосов
/ 23 июля 2010

Я написал код для этой цели для одного из моих проектов.При этом используются функции API FindFirstChangeNotification и FindNextChangeNotification.Вот код (я удалил некоторые части, специфичные для проекта):

/// <author> Ali Keshavarz </author>
/// <date> 2010/07/23 </date>

unit uFolderWatcherThread;

interface

uses
  SysUtils, Windows, Classes, Generics.Collections;

type
  TOnThreadFolderChange = procedure(Sender: TObject; PrevModificationTime, CurrModificationTime: TDateTime) of object;
  TOnThreadError = procedure(Sender: TObject; const Msg: string; IsFatal: Boolean) of object;

  TFolderWatcherThread = class(TThread)
  private
    class var TerminationEvent : THandle;
  private
    FPath : string;
    FPrevModificationTime : TDateTime;
    FLatestModification : TDateTime;
    FOnFolderChange : TOnThreadFolderChange;
    FOnError : TOnThreadError;
    procedure DoOnFolderChange;
    procedure DoOnError(const ErrorMsg: string; IsFatal: Boolean);
    procedure HandleException(E: Exception);
  protected
    procedure Execute; override;

  public
    constructor Create(const FolderPath: string;
                       OnFolderChangeHandler: TOnThreadFolderChange;
                       OnErrorHandler: TOnThreadError);
    destructor Destroy; override;
    class procedure PulseTerminationEvent;
    property Path: string read FPath;
    property OnFolderChange: TOnThreadFolderChange read FOnFolderChange write FOnFolderChange;
    property OnError: TOnThreadError read FOnError write FOnError;
  end;

  /// <summary>
  /// Provides a list container for TFolderWatcherThread instances.
  /// TFolderWatcherThreadList can own the objects, and terminate removed items
  ///  automatically. It also uses TFolderWatcherThread.TerminationEvent to unblock
  ///  waiting items if the thread is terminated but blocked by waiting on the
  ///  folder changes.
  /// </summary>
  TFolderWatcherThreadList = class(TObjectList<TFolderWatcherThread>)
  protected
    procedure Notify(const Value: TFolderWatcherThread; Action: TCollectionNotification); override;
  end;

implementation

{ TFolderWatcherThread }

constructor TFolderWatcherThread.Create(const FolderPath: string;
  OnFolderChangeHandler: TOnThreadFolderChange; OnErrorHandler: TOnThreadError);
begin
  inherited Create(True);
  FPath := FolderPath;
  FOnFolderChange := OnFolderChangeHandler;
  Start;
end;

destructor TFolderWatcherThread.Destroy;
begin
  inherited;
end;

procedure TFolderWatcherThread.DoOnFolderChange;
begin
  Queue(procedure
        begin
          if Assigned(FOnFolderChange) then
            FOnFolderChange(Self, FPrevModificationTime, FLatestModification);
        end);
end;

procedure TFolderWatcherThread.DoOnError(const ErrorMsg: string; IsFatal: Boolean);
begin
  Synchronize(procedure
              begin
                if Assigned(Self.FOnError) then
                  FOnError(Self,ErrorMsg,IsFatal);
              end);
end;

procedure TFolderWatcherThread.Execute;
var
  NotifierFielter : Cardinal;
  WaitResult : Cardinal;
  WaitHandles : array[0..1] of THandle;
begin
 try
    NotifierFielter := FILE_NOTIFY_CHANGE_DIR_NAME +
                       FILE_NOTIFY_CHANGE_LAST_WRITE +
                       FILE_NOTIFY_CHANGE_FILE_NAME +
                       FILE_NOTIFY_CHANGE_ATTRIBUTES +
                       FILE_NOTIFY_CHANGE_SIZE;
    WaitHandles[0] := FindFirstChangeNotification(PChar(FPath),True,NotifierFielter);
    if WaitHandles[0] = INVALID_HANDLE_VALUE then
      RaiseLastOSError;
    try
      WaitHandles[1] := TerminationEvent;
      while not Terminated do
      begin
        //If owner list has created an event, then wait for both handles;
        //otherwise, just wait for change notification handle.
        if WaitHandles[1] > 0 then
         //Wait for change notification in the folder, and event signaled by
         //TWatcherThreads (owner list).
          WaitResult := WaitForMultipleObjects(2,@WaitHandles,False,INFINITE)
        else
          //Wait just for change notification in the folder
          WaitResult := WaitForSingleObject(WaitHandles[0],INFINITE);

        case WaitResult of
          //If a change in the monitored folder occured
          WAIT_OBJECT_0 :
          begin
            // notifiy caller.
            FLatestModification := Now;
            DoOnFolderChange;
            FPrevModificationTime := FLatestModification;
          end;

          //If event handle is signaled, let the loop to iterate, and check
          //Terminated status.
          WAIT_OBJECT_0 + 1: Continue;
        end;
        //Continue folder change notification job
        if not FindNextChangeNotification(WaitHandles[0]) then
          RaiseLastOSError;
      end;
    finally
      FindCloseChangeNotification(WaitHandles[0]);
    end;  
  except
    on E: Exception do
      HandleException(E);
  end;
end;

procedure TFolderWatcherThread.HandleException(E: Exception);
begin
  if E is EExternal then
  begin
    DoOnError(E.Message,True);
    Terminate;
  end
  else
    DoOnError(E.Message,False);
end;

class procedure TFolderWatcherThread.PulseTerminationEvent;
begin
  /// All instances of TFolderChangeTracker which are waiting will be unblocked,
  ///  and blocked again immediately to check their Terminated property.
  ///  If an instance is terminated, then it will end its execution, and the rest
  ///  continue their work.
  PulseEvent(TerminationEvent);
end;


{ TFolderWatcherThreadList }

procedure TFolderWatcherThreadList.Notify(const Value: TFolderWatcherThread;
  Action: TCollectionNotification);
begin
  if OwnsObjects and (Action = cnRemoved) then
  begin
    /// If the thread is running, terminate it, before freeing it.
    Value.Terminate;
    /// Pulse global termination event to all TFolderWatcherThread instances.
    TFolderWatcherThread.PulseTerminationEvent;
    Value.WaitFor;
  end;

  inherited;
end;

end.

Это обеспечивает два класса;класс потока, который контролирует папку на предмет изменений и, если изменение обнаружено, возвращает текущее время изменения и предыдущее время изменения через событие OnFolderChange.И класс list для хранения списка потоков мониторинга.Этот список автоматически завершает каждый собственный поток, когда он удаляется из списка.

Надеюсь, он вам поможет.

2 голосов
/ 23 июля 2010

Решения, которые были опубликованы до сих пор, касаются получения уведомлений по мере их поступления, и они будут хорошо работать для этой цели.Если вы хотите заглянуть в прошлое и посмотреть, когда что-то изменилось в последний раз, а не в реальном времени, тогда это становится хитрее.Я думаю, что нет никакого способа сделать это, кроме как путем рекурсивного поиска по дереву папок и проверки меток дат.

РЕДАКТИРОВАТЬ: В ответ на комментарий ОП, да, это не похожелюбой способ настроить FindFirst / FindNext так, чтобы он попадал только в каталоги, а не в файлы.Но вы можете пропустить проверку дат файлов с помощью этого фильтра: (SearchRec.Attr and SysUtils.faDirectory <> 0).Это должно немного ускорить процесс.Не проверяйте даты в файлах вообще.Тем не менее, вам, вероятно, все равно придется сканировать все, поскольку Windows API не предоставляет какого-либо способа (насколько мне известно) запрашивать только папки, а не файлы.

0 голосов
/ 23 июля 2010

Вы должны взглянуть на http://help.delphi -jedi.org / item.php? Id = 172977 , который является готовым решением. Если вы не хотите загружать и устанавливать весь JVCL (что, впрочем, является отличным фрагментом кода;)), вы можете захотеть увидеть исходный файл в Интернете - http://jvcl.svn.sourceforge.net/viewvc/jvcl/trunk/jvcl/run/JvChangeNotify.pas?revision=12481&view=markup

...