views:

303

answers:

4

i have a routine that searches files:

procedure RecSearch(const sPathName, sFile : String; const subDir : Boolean);
var
   sr    : TSearchRec;
   sPath : String;
begin
   Application.ProcessMessages;
   sPath:=IncludeTrailingBackslash(sPathName);
   if FindFirst(sPath + sFile, faAnyFile - faDirectory, sr) = 0 then
   repeat
      lstBox.Items.Add(sPath + sr.Name); // send files into a ListBox
   until FindNext(sr) <> 0;
   FindClose(sr);

   If not subDir then Exit;

   if FindFirst(sPath + '*.*', faDirectory, sr) = 0 then
   repeat
      if ((sr.Attr and faDirectory) <> 0)  and (sr.Name<>'.') and (sr.Name<>'..') then
         RecSearch(sPath + sr.Name, sFile, True);
   until FindNext(sr) <> 0;
   FindClose(sr);
end;

my problem is that i want to use a thread that does all the work and i can't get it done

i tryed this and it only searches in the current/selected dir, not in subdirs

const
  WM_ThreadDoneMsg = WM_User + 8;

type TfrmSearch = class;

CSearchThread = class(TThread)
   private
      OwnerForm      : TfrmSearch;
      cntFFound      : Integer;
      inPath, inFile : String;
      inFileAttr     : Integer;
      inFileSize     : LongInt;
      procedure RecSearch(const sPath, sFile : String; const subDir : Boolean);
      procedure AddFile;
   protected
      procedure Execute; override;
   published
      constructor Create(owner : TfrmSearch);
      destructor  Destroy; override;
end;

TfrmSearch = class(TForm)
...
   edPath: TEdit;
   edSearchFor: TEdit;
   chkSubfolders: TCheckBox;
   lvFiles: TListView;
...
   private
   public
      srcThread : CSearchThread;
      procedure SearchThreadDone(var msg : TMessage); message WM_ThreadDoneMsg;
end;

var
  frmSearch: TfrmSearch;

implementation

{$R *.dfm}

constructor CSearchThread.Create(owner : TfrmSearch);
begin
   inherited Create(True);
   OwnerForm:=owner;
   FreeOnTerminate:=True;
   Suspended:=False;
   Priority:=tpHigher;
   cntFFound:=0;
   // clear previous entryes
   ownerForm.lvFiles.Clear;
   ownerForm.StatusBar.Panels[0].Text:='';
end;

destructor CSearchThread.Destroy;
begin
   PostMessage(OwnerForm.Handle, WM_ThreadDoneMsg, Self.ThreadID, 0);
   inherited destroy;
end;

procedure CSearchThread.AddFile;
var
   li    : TListItem;
begin
   li:=OwnerForm.lvFiles.Items.Add;
   li.Caption:=inFile;
   li.SubItems.Add(inPath);
   OwnerForm.StatusBar.Panels[0].Text:=IntToStr(cntFFound)+' files found';
end;

procedure CSearchThread.RecSearch(const sPath, sFile : String; const subDir : Boolean);
var
  sr   : TSearchRec;
  attr : Integer;
begin
   OwnerForm.StatusBar.Panels[1].Text:=IntToStr(1+StrToInt(OwnerForm.StatusBar.Panels[1].Text));
   if FindFirst(IncludeTrailingBackslash(sPath)+sFile, faAnyFile - faDirectory, sr) = 0 then
   repeat
         inPath:=sPath;
         inFile:=sr.Name;
         inFileAttr:=sr.Attr;
         inFileSize:=sr.Size;
         Synchronize(AddFile);
   until FindNext(sr) <> 0;
   FindClose(sr);

   if not subDir then Exit;

   if FindFirst(sPath + '*.*', faDirectory, sr) = 0 then
   repeat
      if ((sr.Attr and faDirectory) <> 0)  and (sr.Name<>'.') and (sr.Name<>'..') then
         RecSearch(sPath + sr.Name, sFile, True);
   until FindNext(sr) <> 0;
   FindClose(sr);
end;

procedure CSearchThread.Execute;
begin
   if DirectoryExists(ownerForm.edPath.Text) then
   begin
      RecSearch(ownerForm.edPath.Text, OwnerForm.edSearchFor.Text, OwnerForm.chkSubfolders.Checked);
   end
   else
      ShowMessage('Path not found');
end;

procedure TfrmSearch.SearchThreadDone(var msg : TMessage);
begin
   bbtnPause.Enabled:=False;
end;
+1  A: 

In the first procedure, it looks like you add a path separator to the end of sPath:

sPath:=IncludeTrailingBackslash(sPathName);

Whereas in the second, you only add the separator in the call to FindFirst

if FindFirst(IncludeTrailingBackslash(sPath)+sFile, faAnyFile - faDirectory, sr) = 0 then

When you later append a path component to sPath, there's thus no separator between the new component and the rest of the path

if FindFirst(sPath + '*.*', faDirectory, sr) = 0 then
   ...
      RecSearch(sPath + sr.Name, sFile, True);
outis
+2  A: 

You can try FindFile component, which can search for a given path in a separate thread.

Linas
+2  A: 

I see two cases of the thread accessing VCL components--a big no-no. Build up your list of files in a list that's not part of a visual component and is not touched by anything else while the thread is running.

Also, post back a message giving the number of files found, don't update it directly.

Finally, don't update the files found count for every file. I've seen a program become completely nonresponsive to user input because of such excessive updating. I'd do something like updating after every directory and every 100 files in a directory or something like that.

Loren Pechtel
+1, good advice. Even better would be to let the VCL thread decide how often it will update the UI.
mghie
A: 

i found what i was looking for @ pascal newsletter #01 i'll look again on my code and search for my error

Remus Rigo
The code in this newsletter leaves much to be desired. Loren Pechtel highlights some of the shortcomings in his answer, but the biggest blunder is to use `WaitFor` on a self-destroying thread - this is a crash waiting to happen. If you want to see a better example for file scanning in a worker thread, check out the OTL implementation here: http://17slon.com/blogs/gabr/2008/11/background-file-scanning-with.html
mghie