如何递归检测文件的更改?

时间:2013-03-24 19:37:29

标签: delphi delphi-xe2 id3v2

我正在研究一个多线程组件来加载和管理音乐库,我有一个属性来定义要包含的多个根目录。一个线程在这些目录中搜索媒体文件,根据需要添加/删除,另一个线程遍历这些文件并填写ID3v2标记信息。我已经有了检测添加/删除文件的机制,但我不知道如何检测更改。

如何检测何时从其他外部应用程序对这些文件进行了更改?我想要一个瞬时响应,而不必等待一个线程到达该文件。有什么方法可以在递归地更改这些文件夹中的任何文件时收到警报吗?

2 个答案:

答案 0 :(得分:3)

您需要使用的功能是ReadDirectoryChangesW。这不是世界上最容易使用的功能,值得指出的是它不是100%可靠。它有时会无法通知您修改。根据我的经验,股票更有可能发生。

此API可用于同步或异步模式。与往常一样,同步版本更容易编码。但当然它阻止了调用线程。因此,解决方法是将ReadDirectoryChangesW的调用放在不同的线程中。如果你有大量的目录需要观察,那么每个目录一个观看线程将是一个不可行的负担。如果是这样,那么你需要努力解决异步使用问题。

bWatchSubtree参数允许您监视我认为您想要执行的整个目录树。

有关详情,请参阅本文:Understanding ReadDirectoryChangesW

答案 1 :(得分:0)

试试这个:

uses
  ShlObj, ActiveX;

const
  FILE_LIST_DIRECTORY   = $0001;
  cDir = 'E:\...'; // The directory to monitor

Type
  PFileNotifyInformation = ^TFileNotifyInformation;
  TFileNotifyInformation = Record
    NextEntryOffset: DWORD;
    Action: DWORD;
    FileNameLength: DWORD;
    FileName: Array[0..0] of WideChar;
  End;

type
  TWaitThread = class(TThread)
  private
    FForm: TMainForm;
    procedure HandleEvent;
  protected
    procedure Execute; override;
  public
    constructor Create(Form: TMainForm);
    Procedure SendFtp(F: String; AddIfError: Boolean);
  end;


procedure TWaitThread.HandleEvent;
  Var
  FileOpNotification: PFileNotifyInformation;
  Offset: Longint;
  F: String;
  AList: TStringList;
  I: Integer;
begin

  AList := TStringList.Create;

  Try

    With FForm Do
    Begin

      Pointer(FileOpNotification) := @FNotificationBuffer[0];

      Repeat
        Offset := FileOpNotification^.NextEntryOffset;
        //lbEvents.Items.Add(Format(SAction[FileOpNotification^.Action], [WideCharToString(@(FileOpNotification^.FileName))]));

        F := cDir + WideCharToString(@(FileOpNotification^.FileName));

        if AList.IndexOf(F) < 0 Then
        AList.Add(F);

        PChar(FileOpNotification) := PChar(FileOpNotification)+Offset;

      Until Offset=0;

      For I := 0 To AList.Count -1 Do
      // do whatever you need

    End;

  Finally
    AList.Free;
  End;

end;


constructor TWaitThread.Create(Form: TMainForm);
begin
  inherited Create(True);
  FForm := Form;
  FreeOnTerminate := False;
end;

procedure TWaitThread.Execute;
  Var
  NumBytes: DWORD;
  CompletionKey: DWORD;
begin

  While Not Terminated Do
  Begin

    GetQueuedCompletionStatus( FForm.FCompletionPort, numBytes, CompletionKey, FForm.FPOverlapped, INFINITE);

    if CompletionKey <> 0 Then
    Begin
      Synchronize(HandleEvent);

      With FForm do
      begin
        FBytesWritten := 0;
        ZeroMemory(@FNotificationBuffer, SizeOf(FNotificationBuffer));
        ReadDirectoryChanges(FDirectoryHandle, @FNotificationBuffer, SizeOf(FNotificationBuffer), False, FNotifyFilter, @FBytesWritten, @FOverlapped, nil);
      End;

    End
    Else
    Terminate;

  End;

end;


{MainForm}

  private

    FDirectoryHandle: THandle;
    FNotificationBuffer: array[0..4096] of Byte;
    FWatchThread: TThread;
    FNotifyFilter: DWORD;
    FOverlapped: TOverlapped;
    FPOverlapped: POverlapped;
    FBytesWritten: DWORD;
    FCompletionPort: THandle;


procedure TMainForm.FormCreate(Sender: TObject);
begin

  FCompletionPort := 0;
  FDirectoryHandle := 0;
  FPOverlapped := @FOverlapped;
  ZeroMemory(@FOverlapped, SizeOf(FOverlapped));

  Start;

end;

procedure TMainForm.Start;
begin

  FNotifyFilter := 0;

  FNotifyFilter := FNotifyFilter or FILE_NOTIFY_CHANGE_FILE_NAME;

  FDirectoryHandle := CreateFile(cDir,
                      FILE_LIST_DIRECTORY,
                      FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE,
                      Nil,
                      OPEN_EXISTING,
                      FILE_FLAG_BACKUP_SEMANTICS or FILE_FLAG_OVERLAPPED,
                      0);

  if FDirectoryHandle = INVALID_HANDLE_VALUE Then
  Begin
    Beep;
    FDirectoryHandle := 0;
    ShowMessage(SysErrorMessage(GetLastError));
    Exit;
  End;

  FCompletionPort := CreateIoCompletionPort(FDirectoryHandle, 0, Longint(pointer(self)), 0);
  ZeroMemory(@FNotificationBuffer, SizeOf(FNotificationBuffer));
  FBytesWritten := 0;

  if Not ReadDirectoryChanges(FDirectoryHandle, @FNotificationBuffer, SizeOf(FNotificationBuffer), False, FNotifyFilter, @FBytesWritten, @FOverlapped, Nil) Then
  Begin
    CloseHandle(FDirectoryHandle);
    FDirectoryHandle := 0;
    CloseHandle(FCompletionPort);
    FCompletionPort := 0;
    ShowMessage(SysErrorMessage(GetLastError));
    Exit;
  End;

  FWatchThread := TWaitThread.Create(self);
  TWaitThread(FWatchThread).Resume;

end;

procedure TMainForm.Stop;
begin

  if FCompletionPort = 0 Then
  Exit;

  PostQueuedCompletionStatus(FCompletionPort, 0, 0, Nil);
  FWatchThread.WaitFor;
  FWatchThread.Free;
  CloseHandle(FDirectoryHandle);
  FDirectoryHandle := 0;
  CloseHandle(FCompletionPort);
  FCompletionPort := 0;

end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  Stop;
end;