views:

631

answers:

7

Hi,

In my application my users can import files (pdf/xls/doc) to a table or export them to a folder. Now I want to directly open these files.

So far I'm able to : - get an unique name - save the blob file into the generated file - open it

The problem is that I don't know how to delete (or update) the file after that the file will be closed by the user.

I'll be very happy if someone can help me on this :)

Here is a snapshot of my code :

procedure OpenTemporaryFile(AFileExtension: String; AKey: Integer;
            AMyConnection: TMyConnection);
     Var
       qrDocuments : TMyQuery ;
       TmpName,ExtName: string;
       TempFileName: TFileStream;
    begin
       //Generate an unique tmp file located into user temp folder
       TmpName:=  FileGetTempName('~SI');
       ExtName:= ChangeFileExt(TmpName, AFileExtension);
       //Change files extension so that Shellexecute will be able to open the file
       RenameFile(TmpName,ExtName );
       //Creating the FileStream (data is fetched from an blob field)
       TempFileName := TFileStream.Create(ExtName, fmOpenReadWrite );

       qrDocuments := TMyQuery.create(nil);
       try
        qrDocuments.Connection := AMyConnection;
        qrDocuments.Close;
        qrDocuments.SQL.Clear;
        qrDocuments.SQL.Text:='Select Id,FileName,Data from files where Id = :prId And Data IS NOT NULL';
        qrDocuments.ParamByName('prId').AsInteger := AKey;
        qrDocuments.open;
        TBlobField(qrDocuments.FieldByName('Data')).SaveToStream(TempFileName);
       finally
          TempFileName.Free;
          qrDocuments.free;
       end;
       ShellExecute(Application.Handle, 'open', Pchar(ExtName), '', '', SW_SHOWNORMAL);
       DeleteFile( ExtName);
    end;
+1  A: 

Maybe you can store them in some well-known for you folder (eg subfolder in TEMP folder with a name of your app) and clear contents of this folder whem user next time loads your app? Or you may install additional clear utility and set it to run in autostart.
One more idea about clearing files after reboot - you can clear everything in your subfolder on startup, or make a list of files you created with last modify time, last size, store this list in XML file and later delete or update comparing contents of your temp subfolder with file details from that list?

smok1
Thank for your answer. I'll use gabr library if I don't find a working solution.
Stephane Wierzbicki
A: 

in Unix-like OSs the usual trick is to open it and delete immediately. it won't appear on the directory, but the data is still allocated to the process(es) that hold it open. as soon as it is closed (either nicely of by the process dying), the filesystem would reclaim the space.

it's not a hack, it's documented and supported (a consequence of having the open file handles count as a 'reference' to the file, just like directory entries).

maybe there's some similar trick on windows? i seem to recall that NTFS supports multiple references to the same file (no, shortcuts aren't those). if so, deleting the file but still hanging on to the last reference as an ephemeral resource might work.

obviously, i'm just speculating here...

Javier
My co-worker (Linux geek) already told me that.... but this feature isn't available on Win32 :(
Stephane Wierzbicki
+3  A: 

Use the Win32 API CreateFile() function to open the file, specifying the FILE_FLAG_DELETE_ON_CLOSE flag, and then pass the resulting handle to a THandleStream object so that you can still use SaveToStream().

Also, there is a bug in your code - you are passing the wrong kind of handle to ShellExecute(). It expects a window handle, but you are passing a file handle instead, and worse you are accessing the file handle after you have already freed the TFileStream, thus closing the handle.

Remy Lebeau - TeamB
You can't be sure this will work, as all applications working with the file need to specify FILE_SHARE_DELETE in the CreateFile() call. See the documentation at http://msdn.microsoft.com/en-us/library/aa363858(VS.85).aspx
mghie
See also http://stackoverflow.com/questions/546049
mghie
GetTempFileName is creating the temporary file ? I don't know if I can call CreateFile again ? And how can I pass the resulting handle to a THandleStream object so that I can still use SAveToStream() ?For the handle you were right. Corrected now.I can't access the file when moving the ShellExecute method after the SaveToStream method !When calling
Stephane Wierzbicki
GetTempFileName() may create an actual file, yes. It does not keep the file open, though. As for THandleStream, you can pass the opened file handle to its constructor, it has a parameter for that.
Remy Lebeau - TeamB
+7  A: 

One possibility would be to add each temporary file to the list of files that are deleted during the system startup.

On Windows NT platform (since Windows 2000), you can just call MoveFileEx function with a second parameter (destination) set to nil and with a flag MOVEFILE_DELAY_UNTIL_REBOOT.

On Windows 9x, this is much more complicated. You have to edit file %WINDIR%\wininit.ini and write an entry into the [Rename] section.

MSDN entry How To Move Files That Are Currently in Use describes both techniques.

Function DSiMoveOnReboot (part of the free DSiWin32 library) handles both OSes. If you pass an empty string as the second parameter, it will delete the source file on reboot.

function DSiMoveOnReboot(const srcName, destName: string): boolean;
var
  wfile: string;
  winit: text;
  wline: string;
  cont : TStringList;
  i    : integer;
  found: boolean;
  dest : PChar;
begin
  if destName = '' then
    dest := nil
  else
    dest := PChar(destName);
  if DSiIsWinNT then
    Result := MoveFileEx(PChar(srcName), dest, MOVEFILE_DELAY_UNTIL_REBOOT)
  else
    Result := false;
  if not Result then begin
    // not NT, write a Rename entry to WININIT.INI
    wfile := DSiGetWindowsFolder+'\wininit.ini';
    if FileOpenSafe(wfile,winit,500,120{one minute}) then begin
      try
        cont := TStringList.Create;
        try
          Reset(winit);
          while not Eof(winit) do begin
            Readln(winit,wline);
            cont.Add(wline);
          end; //while
          if destName = '' then
            wline := 'NUL='+srcName
          else
            wline := destName+'='+srcName;
          found := false;
          for i := 0 to cont.Count - 1 do begin
            if UpperCase(cont[i]) = '[RENAME]' then begin
              cont.Insert(i+1,wline);
              found := true;
              break;
            end;
          end; //for
          if not found then begin
            cont.Add('[Rename]');
            cont.Add(wline);
          end;
          Rewrite(winit);
          for i := 0 to cont.Count - 1 do
            Writeln(winit,cont[i]);
          Result := true;
        finally cont.Free; end;
      finally Close(winit); end;
    end;
  end;
end; { DSiMoveOnReboot }
gabr
Nice library !I will have a look at it. Thank for pointing out
Stephane Wierzbicki
+1  A: 

If I remember correctly, there is a flag for CreateFile that tells Windows that it should delete the file once the last handle to it has been closed. So, create the file normally, close and reopen it with share deny none and the flag mentioned above. Then let the external app open it and close it yourself. This should result in Windows deleting the file once the external app closes it.

(I have not tried this.)

dummzeuch
As mghie pointed out, that would only work if the external app opens the file with FILE_SHARE_DELETE sharing rights - which most apps do not do.
Remy Lebeau - TeamB
+8  A: 

Unfortunately there are 4 upvotes right now for this answer by Remy Lebeau, when the technique simply won't work with most applications. Maybe one of the upvoters could post a code snippet that allows to open a PDF file with Acrobat Reader while the file is still open with the FILE_FLAG_DELETE_ON_CLOSE flag?

Anyway, you could combine some of the tips here for best results:

  • Have an internal list of temporary files your application uses.
  • On program shutdown walk the list of temporary files and try to delete them. If this fails for some of them (because they are still open in the external application) register these for deletion on reboot with the code gabr gave you.
  • Whenever you need a new temporary file, first walk your internal list of files and try to reuse one of them. Create a new file (and add its name to the list) only if this fails.

I'd prefer this approach to registering all files for deletion on reboot, because I'm not sure how many temporary files your application might open - maybe there is a limit for the number of files that can be registered with MOVEFILE_DELAY_UNTIL_REBOOT? It's a system-wide resource I would use only sparingly.

mghie
As I've haven't found a solution for the FILE_FLAG_DELETE_ON_CLOSE flag I'll rewrite my function with your approach.Thank you again for all your help.
Stephane Wierzbicki
Yep, all valid points. Seconded.
gabr
A: 

Actually our application create de files in a especific temp folder. When the application Close the fies are deleted. If application not close correctly, the next execution (when close) all files are deleted.

Additionally, you can launch a background process to delete the files that are no longer open. ShellExecute return a Handle (internally associate this handle to a FileName). This background process must test the handle of process that not exist and Delete the associated files.

Excuse for bad english. ;-)

Regards.

Neftalí
Tahnk you, I will do something like that.
Stephane Wierzbicki