我正在开发一个多线程组件来加载和管理音乐库,并且我有一个定义多个要包含的根目录的属性。一个线程在这些目录中搜索媒体文件,根据需要添加/删除,另一个线程遍历这些文件并填写 ID3v2 标记信息。我已经有一种机制来检测添加/删除的文件,但我不知道如何检测更改。
我如何检测何时从其他外部应用程序对这些文件中的任何一个进行了更改?我想要一个即时响应,而不必等待线程访问该文件。当这些文件夹中的任何文件被递归更改时,我有没有办法接收警报?
我正在开发一个多线程组件来加载和管理音乐库,并且我有一个定义多个要包含的根目录的属性。一个线程在这些目录中搜索媒体文件,根据需要添加/删除,另一个线程遍历这些文件并填写 ID3v2 标记信息。我已经有一种机制来检测添加/删除的文件,但我不知道如何检测更改。
我如何检测何时从其他外部应用程序对这些文件中的任何一个进行了更改?我想要一个即时响应,而不必等待线程访问该文件。当这些文件夹中的任何文件被递归更改时,我有没有办法接收警报?
您需要使用的功能是ReadDirectoryChangesW
. 这不是世界上最容易使用的功能,值得指出的是它不是 100% 可靠的。它有时会无法通知您修改。根据我的经验,股票更有可能发生这种情况。
此 API 可用于同步或异步模式。与往常一样,同步版本更容易编写代码。但当然它会阻塞调用线程。所以解决这个问题的方法是将调用ReadDirectoryChangesW
放在不同的线程中。如果您有大量的目录要观看,那么每个目录一个观看线程将是一个不可行的负担。如果是这样,那么您将需要处理异步使用。
YoubWatchSubtree
参数允许您监视整个目录树,我认为这是您想要做的。
有关更多详细信息,请参阅这篇文章:了解 ReadDirectoryChangesW。
尝试这个:
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;