开发者

Quickest way to find the oldest file in a directory using Delphi

HI

We have a large number of remote computers that capture video onto disk drives. Each camera has it's own unique directory and there can be up to 16 directories on any one disk.

I'm trying to locate the oldest video file on the disk but using FindFirst/FindNext to compare the File Creation DateTime takes forever.

Does anybody know of a more efficient way of finding the oldest file in a directory? We remotely connect to the pc's from a central HO location.

Regards, Pieter

-- Update

Thank yo开发者_运维百科u all for the answers. In the end I used the following.

  1. Map a drive ('w:') to the remote computer using windows.WNetAddConnection2

    //Execute dir on the remote computer using cmd.exe /c dir

    //NOTE: Drive letters are relative to the remote computer. (psexec -w parameter)

  2. psexec \\<IPAddress> -i /accepteula -w "c:\windows\system32" cmd.exe "/c dir q:\video /OD /TC /B > q:\dir.txt"
  3. //Read the first line of "w:\dir.txt" to get the oldest file in that directory.
  4. //Disconnect from the remote computer using windows.WNetCancelConnection2


You could also try FindFirstFileEx with FindExInfoBasic parameter, and on Windows 7 or Server 2008 R2 or later, FIND_FIRST_EX_LARGE_FETCH which should improve performance.


First, grab the RunDosAppPipedToTStrings routine from this page on how to run a DOS program and pipe its output to a TStrings. The example uses a TMemo's Lines property, but you can pass any TStrings in, such as TStringList. Note that this will fail silently if CreateProcess returns false. You might want to add an else case to the "if CreateProcess" block that raises an exception.

Then create a simple batch file in the same folder as your EXE. Call it getdir.bat. All it should say is:

dir %1

This produces a directory listing of whatever folder you pass to it. Unfortunately, "dir" is a DOS keyword command, not a program, so you can't invoke it directly. Wrapping it in a batch file gets around that. This is a bit of a hack, but it works. If you can find a better way to run DIR, so much the better.

You'll want to invoke RunDosAppPipedToTStrings with code that looks something like this:

procedure GetDirListing(dirname: string; list: TStringList);
const
   CMDNAME = '%s\getdir.bat "%s"';
var
   path: string;
begin
  list.Clear;
  path := ExcludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0)));
  RunDosAppPipedToTStrings(format(CMDNAME, [path, dirname]), list, false);
end;

Then all that's left to do is parse the output, extract date and time and filenames, sort by date and time, and grab the filename of the file with the lowest date. I'll leave that much to you.


If you can run something on the remote computer that can iterate over the directories, that will be the fastest approach. If you wanted to use Mason's example, try launching it with PsExec from SysInternals.

If you can only run an application locally then no, there's no faster way than FindFirst/FindNext, and anything else you do will boil down to that eventually. If your local computer is running Windows 7 you can use FindFirstFileEx instead, which has flags to indicate it should use larger buffers for the transfers and that it shouldn't read the 8.3 alias, which can help the speed a bit.


I had almost the same problem on the fax server software I developed. I had to send the faxes in the order they were received from thousands (all stored in a directory). The solution I adopted (which is slow to start but fast to run) is to make a sorted list of all the files using the

SearchRec.Time 

as the key. After the file is in the list, I'm setting the attributes of the file as a faSysFile:

NewAttributes := Attributes or faSysFile;

Now when I do a new search with

FileAttrs := (faAnyFile and not faDirectory);

only the files that are not faSysFile are shown, so I can add to the list the files that are coming in new. Now you have a list with all the files sorted by time. Don't forget, when you start your application, first step is to remove the faSysFile attribute from the files in the folder so they can be processed again.

procedure FileSetSysAttr(AFileName: string);
var
  Attributes, NewAttributes: Word;
begin
  Attributes := FileGetAttr(AFileName);
  NewAttributes := Attributes or faSysFile;
  FileSetAttr(AFileName, NewAttributes);
end;

procedure FileUnSetSysAttr(AFileName: string);
var
  Attributes, NewAttributes: Word;
begin
  Attributes := FileGetAttr(AFileName);
  NewAttributes := Attributes and not faSysFile;
  FileSetAttr(AFileName, NewAttributes);
end;

procedure PathUnSetSysAttr(APathName: string);
var
  sr: TSearchRec;
  FileAttrs: Integer;
begin
  FileAttrs := (faAnyFile and not faDirectory) and (faAnyFile or faSysFile);
  APathName := IncludeTrailingBackslash(APathName);
  if SysUtils.FindFirst(APathName + '*.*', FileAttrs, sr) = 0 then
  try
    repeat
      if (sr.Attr and faDirectory) = 0 then
        FileUnSetSysAttr(APathName + sr.Name);
    until SysUtils.FindNext(sr) <> 0;
  finally
    SysUtils.FindClose(sr);
  end;
end;

I know this is not the best solution, but works for me.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜