1

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

我如何检测何时从其他外部应用程序对这些文件中的任何一个进行了更改?我想要一个即时响应,而不必等待线程访问该文件。当这些文件夹中的任何文件被递归更改时,我有没有办法接收警报?

4

2 回答 2

3

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

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

YoubWatchSubtree参数允许您监视整个目录树,我认为这是您想要做的。

有关更多详细信息,请参阅这篇文章:了解 ReadDirectoryChangesW

于 2013-03-24T20:07:14.240 回答
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;
于 2013-03-25T10:58:18.420 回答