Faster DirectoryExists function?
Asked Answered
S

8

15

I use

DirectoryExists (const PathName : String);

to check if a directory is reachable from a computer or not. But if the directory does not exist and the path name is a network path, i.e.

\\computer1\Data

the method takes a very long time to return.

There must be a faster way to determine that a network folder is not reachable. Or can I configure some timeout parameter that DirectoryExists uses internally (I looked at the source code but it just delegates to GetFileAttributes which is defined in kernel32)?

Any ideas?

Sidoney answered 17/9, 2009 at 13:33 Comment(5)
Is the problem that the directory doesn't exist on the remote computer or that you can't connect to the remote computer in the first place?Paff
Could be both, but in most of the cases I can't connect.Sidoney
As a counter-example I'd like to pose my tiny NAS at home: When I haven't used it for a while the disks spin down. When I first access the mounted directory then a simple listing can take about 20 seconds. So during that 20 seconds there's no way to know if any given directory exists on there. Even after 15 seconds you can't be sure that it doesn't exist ... only when you get an actual (negative) answer from the host can you be sure that the file doesn't exist.Cicatrize
any final solution with full source code sample working about it ?Carmencita
@Kiquenet: See my answer if you look for a "final solution with full source code sample"Hippocampus
D
19

There is no faster way:

any function accessing anything on a remote share will timeout when that share is not available.

If the cause of your timeouts is automatic disconnecting of shares, then these link may help you:

If the application can continue without the completion of the check, then you can put the check in a separate thread, and upon completion of the check, you can update your status in the UI.

Be aware that when you try a multi-threading way, that you have to disprove your code is free of race-conditions and memory leaks. Time-outs in combination with exceptions usually make that a hard task.

Decentralization answered 17/9, 2009 at 14:23 Comment(5)
The link is not valid anymore :-(Hippocampus
This answer seems to be wrong. There is a faster way which I show in my answer below.Hippocampus
@Michael - Well, what you've done is to put the check in a thread as this answer tells. Why would implementing a timeout in the main thread prove this answer wrong? If anything, your code proves this answer is correct.Mystify
@SertacAkyuz: There are 3 aspects. 1.) To the question for "a faster way to determine that a network folder is not reachable" this answer says "There is no faster way" (which my answer disproves) / 2.) The link in this answer is not valid / 3.) The idea about the separate thread is correct but the answer doesn't give any source code to do this. That's why I blame this answer to be wrong.Hippocampus
@Michael 1) Your code only proves it is possible to give up on determining if a directory exists after a time period. It is unreliable, but that is your design. Timing may be more important to you than accuracy. 2) A dead link might render a link only answer wrong, but this is not the case here. FWIW link is perhaps this or this. 3) Source code for "there is no faster way"? Not required.Mystify
B
6

There was the same question for C#: How to avoid network stalls in GetFileAttributes?

As codymanix said, use threads. The above link will show you how you can do it with delegates in C#. Don't know Delphi, but maybe you know how to convert the code?

Bozeman answered 17/9, 2009 at 13:48 Comment(1)
(threads will only help if you can do something in paralel. If your next action is dependant on the share (e.g. for loading config), in the thread case you only gain the ability to show the user some movement, it doesn't speed up )Romulus
C
5

If you test for lots of directories you should use threads to do all the queries in parallel because for network shares ther are usually long timeouts.

Chaker answered 17/9, 2009 at 13:38 Comment(1)
I'm not testing for a lots of directories. Only for one. But DirectoryExists can take about 30 seconds to return, which is annoying.Sidoney
H
4

This function worked for me very well: NetDirectoryExists(Path, Timeout)
It uses Threading and is the perfect alternative for TDirectory.Exists(Path)

Usage:
if NetDirectoryExists('\\computer1\Data', 1000) then ...
if NetDirectoryExists('C:\Folder', 500) then ...

If the Folder exists, the function needs only some milliseconds, same with not existing folders (C:\NotExisting). If it is a not reachable network path (\\ServerNotReady\C$) then it will consume the number of milliseconds given by the second parameter.

Function NetDirectoryExists( const dirname: String; 
                              timeoutMSecs: Dword ): Boolean; 

implementation 


uses 
   Classes, Sysutils, Windows; 


type 
   ExceptionClass = Class Of Exception; 
   TTestResult = (trNoDirectory, trDirectoryExists, trTimeout ); 
   TNetDirThread = class(TThread) 
   private 
     FDirname: String; 
     FErr    : String; 
     FErrclass: ExceptionClass; 
     FResult : Boolean; 
   protected 
     procedure Execute; override; 
   public 
     Function TestForDir( const dirname: String; 
                timeoutMSecs: Dword ):TTestResult; 
   end; 


Function NetDirectoryExists( 
            const dirname: String; timeoutMSecs: Dword ): Boolean; 
 Var 
   res: TTestResult; 
   thread: TNetDirThread; 
 Begin 
   Assert( dirname <> '', 'NetDirectoryExists: dirname cannot be empty.' ); 
   Assert( timeoutMSecs > 0, 'NetDirectoryExists: timeout cannot be 0.' ); 
   thread:= TNetDirThread.Create( true ); 
   try 
     res:= thread.TestForDir( dirname, timeoutMSecs ); 
     Result := res = trDirectoryExists; 
     If res <> trTimeout Then 
       thread.Free; 
     {Note: if the thread timed out it will free itself when it finally 
      terminates on its own. } 
   except 
     thread.free; 
     raise 
   end; 
 End; 


procedure TNetDirThread.Execute; 
 begin 
   try 
     FResult := DirectoryExists( FDirname ); 
   except 
     On E: Exception Do Begin 
       FErr := E.Message; 
       FErrclass := ExceptionClass( E.Classtype ); 
     End; 
   end; 
 end; 


function TNetDirThread.TestForDir(const dirname: String; 
   timeoutMSecs: Dword): TTestResult; 
 begin 
   FDirname := dirname; 
   Resume; 
   If WaitForSingleObject( Handle, timeoutMSecs ) = WAIT_TIMEOUT 
   Then Begin 
     Result := trTimeout; 
     FreeOnTerminate := true; 
   End 
   Else Begin 
     If Assigned( FErrclass ) Then 
       raise FErrClass.Create( FErr ); 
     If FResult Then 
       Result := trDirectoryExists 
     Else 
       Result := trNoDirectory; 
   End; 
 end; 
Hippocampus answered 7/2, 2019 at 13:56 Comment(1)
This might be a viable approach if you know and disclose the consequences. Your NetDirectoryExists will return more false negatives than the OS defaults. What if you set a timeout of 1000ms and the directory is reachable in 3 seconds?. Or 6 seconds... Or a minute... FWIW, I find it quite optimistic to make a few tests on a single environment and think that one can come up with a better timeout value than protocol and system developers with years of experience.Mystify
M
3

This is the best way. You could add some code to ping the machine to insure it exists, but this would still leave the routine up to fail as many computers today have software firewalls set up to ignore ping requests, as well as the possibility that the share requested doesn't exist.

Also, on some machines if the UNC path is on the local machine and the local machine does not have an active network card (a wi-fi disconnected laptop for instance in "Airplane" mode) then UNC requests will also fail.

Malanie answered 17/9, 2009 at 15:9 Comment(0)
B
3

I use the following code...

private delegate bool DirectoryExistsDelegate(string folder);

bool DirectoryExistsTimeout(string path, int millisecondsTimeout)
{
    try
    {
        DirectoryExistsDelegate callback = new DirectoryExistsDelegate(Directory.Exists);
        IAsyncResult result = callback.BeginInvoke(path, null, null);

        if (result.AsyncWaitHandle.WaitOne(millisecondsTimeout, false))
        {
            return callback.EndInvoke(result);
        }
        else
        {
            callback.EndInvoke(result);  // Needed to terminate thread?

            return false;
        }
    }

    catch (Exception)
    {
        return false;
    }
}

...which allows me to have a timeout version of Directory.Exist. I call it with something like...

bool a = DirectoryExistsTimeout("\\\\machine\\folder", 5000);

Would this be OK for you needs?


To be safe/legal then you need to call "callback.EndInvoke(result);" but calling it locks until the async finishes so this defeats the object of the code. Perhaps this needs to be done at the end of you code - exit maybe?

Bradytelic answered 17/11, 2010 at 12:7 Comment(1)
Looks nice - but this question is about Delphi. Your solution seems to be C#.Hippocampus
C
2

In a similar situation like you prescribed, I've added an ICMP ping to the server first. If the server doesn't respond to the ping, I assume it is down. You can decide which timeout to use on the ping yourself, so you can set it much shorter than the timeout used internally when trying to open a file-share.

Countermine answered 18/9, 2009 at 6:18 Comment(1)
Something like this, modern Windows has something special with IcmpSendEcho so it still works without elevated privileges.Countermine
M
0

If both computers are on the same domain it will speed-up file operations when dealing with shares.

Masonmasonic answered 17/9, 2009 at 16:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.