Jump to content
Juan C.Cilleruelo

How to know that a file is not used by another program?

Recommended Posts

My application generates a data file with instructions to generate a graphic file and after call an external program that processes this information and generates the graphic file.

 

Next, my program, that is waiting until the graphic file exists, takes this graphic file and incorporates it to his database. 

 

The problem is the the time that the external application take to processing the data file. This time can be very large. An sometimes, my program traying to get the resulting file meanwhile the external program is processing it.  This becomes in an OS exception. 

 

Can I ask to the OS, if the file I want to take is been still processing by another application?

 

The program is cross platform, because of this I can't call the external application waiting to his finallization. And of course, if the solution is for crossplatform, more better. 

Thank in advance.

g4K4Fp25.32g.png

Share this post


Link to post

I assume you are in control of the output location.

f.x.

%somepath%\generated\myimg.png


What if you create a "signal" file that basically is the URL of your graphics output file with f.x. '.processing' at the end before you start generating the file?
%somepath%\generated\myimg.png.processing

Once the processing is complete, you delete the %somepath%\generated\myimg.png.processing

 

That way, your other process could check if the file %somepath%\generated\myimg.png exists, but continue to wait if there is a %somepath%\generated\myimg.png.processing file there?

Share this post


Link to post

If the external program is written by himself then the task can be done with a "lockfile" or with ipc. If not the check for exclusive access in an interval is the next best solution.

Share this post


Link to post

Building on Lars Fosdal's answer: Let your external program generate the file under a different name (or in a different location on the same drive) and when it is done, rename/move it to where your program expects it.

  • Like 1

Share this post


Link to post
Just now, dummzeuch said:

Building on Lars Fosdal's answer: Let your external program generate the file under a different name (or in a different location on the same drive) and when it is done, rename/move it to where your program expects it.

That solves not the problem. The graphic file is generated by the external program. The file exists with start of the process. If the finishing cannot be signaled by the external program the name doesn't matter.

Share this post


Link to post

Just trying to open the file is already the correct solution IMHO. You should catch the exception and retry until it works.

  • Thanks 1

Share this post


Link to post
2 hours ago, Markus Kinzler said:

That solves not the problem. The graphic file is generated by the external program. The file exists with start of the process. If the finishing cannot be signaled by the external program the name doesn't matter.

You are right, unless he is in control of the external program as well.

Share this post


Link to post

The external  program is OpenSCAD. I can't control how it works.

The generated file exists during all the process. I can't control this.

 

Edited by Juan C.Cilleruelo

Share this post


Link to post

I remember a that that discussed a similar topic .. it was something like "How to detect, if I can append data to a file". The solution was: Try it and catch errors. There are just too many variables that you would have to check (file exists, access privileges, exclusive access, ...). 

 

If you need cross-platform, I can just quote myself:

3 hours ago, Zacherl said:

Just trying to open the file is already the correct solution IMHO. You should catch the exception and retry until it works.

 

There are other approaches, if cross-platform support is not needed, but none of them are easy (on Windows you could enumerate all open handles to the file e.g. or work with global `CloseHandle` hooks).

  • Thanks 1

Share this post


Link to post
3 hours ago, Zacherl said:

Just trying to open the file is already the correct solution IMHO. You should catch the exception and retry until it works.

     

This is my current code. I think now is working well. 

 

Any idea!

 

Thanks to all. 
     

 ......
      CountErrors := 0;
      while not (TFile.Exists(PNG_FileName) or (CountErrors > 5)) do begin
         Inc(CountErrors);
         Sleep(1000);
      end;

      Sleep(1000);

      CountErrors := 0;
      SuccessLoad := False;
      while (not SuccessLoad) or (CountErrors > 10) do begin
         try
            BlobField.LoadFromFile(PNG_FileName);
            SuccessLoad := True;
         except
            on EFOpenError do begin
               SuccessLoad := False;
               Inc(CountErrors);
               Sleep(1000);
            end;
         end;
      end;
      .....

 

  • Like 1

Share this post


Link to post
Just now, Juan C.Cilleruelo said:

     

This is my current code. I think now is working well. 

 

Should work 👍 Was just about to recommend adding an additional timeout to the code, but you already did with your error counter 🙂

Share this post


Link to post
Just now, Zacherl said:

Should work 👍 Was just about to recommend adding an additional timeout to the code, but you already did with your error counter 🙂

Each error waits one second. I think is absurd repeat without allow the other process finish.

  • Like 1

Share this post


Link to post

CreateProcess / WaitForSingleObject

 

Ahm, crossplatform! In this case, not! 😉

 

Btw, what is the cross-platform OpenSCAD calling convention? Is there any? ifdef? Maybe you could place the wait code for the process into those sections?

Edited by Attila Kovacs

Share this post


Link to post
6 minutes ago, Lars Fosdal said:

@Juan C.Cilleruelo - Do you invoke a new instance of OpenSCAD for each image? If you do, can't you execute it from a thread that waits for it to complete and return?

This was my first option, effectively, but, how to do this in Mac OS X?

Share this post


Link to post

function IsFileInUse(FileName: TFileName): Boolean;
var
  
HFileRes: HFILE;
begin
  
Result := False;
  if not FileExists(FileName) then Exit;
  HFileRes := CreateFile(PChar(FileName),
                         GENERIC_READ or GENERIC_WRITE,
                         0,
                         nil,
                         OPEN_EXISTING,
                         FILE_ATTRIBUTE_NORMAL,
                         0);
  Result := (HFileRes = INVALID_HANDLE_VALUE);
  if not Result then
    
CloseHandle(HFileRes);
end;


procedure TForm1.Button1Click(Sender: TObject);
begin
  if 
IsFileInUse('c:\Programs\delphi6\bin\delphi32.exe') then
    
ShowMessage('File is in use.');
  else
    
ShowMessage('File not in use.');
end;

  • Like 1

Share this post


Link to post
On 7/12/2019 at 6:06 AM, Emrah said:

  if not FileExists(FileName) then Exit;
  HFileRes := CreateFile(PChar(FileName),
                         GENERIC_READ or GENERIC_WRITE,
                         0,
                         nil,
                         OPEN_EXISTING,
                         FILE_ATTRIBUTE_NORMAL,
                         0);

  Result := (HFileRes = INVALID_HANDLE_VALUE);
  if not Result then
    
CloseHandle(HFileRes);

The FileExists() check is unnecessary and should be removed.  It introduces a race condition (the file may exist before FileExists() is called, and then gets deleted before CreateFile() is called) as well as a possible failure point (FileExists() is not 100% accurate in all possible situations, there are cases where it returns a wrong result).  Calling CreateFile() by itself with OPEN_EXISTING is good enough.  If the file really does not exist, CreateFile() will fail with an ERROR_FILE_NOT_FOUND error code.

 

Also, your code does not take ERROR_SHARING_VIOLATION into account, which would mean the file is actually in use but you don't have access to open it.

 

Try this instead:

 

function IsFileInUse(FileName: TFileName): Boolean;
var
  HFileRes: THandle;
begin
  HFileRes := CreateFile(PChar(FileName),
                         GENERIC_READ or GENERIC_WRITE,
                         0,
                         nil,
                         OPEN_EXISTING,
                         FILE_ATTRIBUTE_NORMAL,
                         0);
  if (HFileRes <> INVALID_HANDLE_VALUE) then
  begin
    CloseHandle(HFileRes);
    Result := False;
  end else
    Result := (GetLastError() = ERROR_SHARING_VIOLATION);
end;

 

Edited by Remy Lebeau
  • Like 2

Share this post


Link to post

Not cross platform, but this might interest you: JCLSYSUTILS.Execute().

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×