如何处理临时文件?

发布于 2024-07-23 12:26:16 字数 1551 浏览 6 评论 0原文

在我的应用程序中,我的用户可以将文件(pdf/xls/doc)导入到表中或将它们导出到文件夹中。 现在我想直接打开这些文件。

到目前为止我能够: - 获得一个独特的名字 - 将 blob 文件保存到生成的文件中 - 打开它

问题是我不知道如何删除(或更新)该文件,之后该文件将被用户关闭。

如果有人可以帮助我,我会非常高兴:)

这是我的代码的快照:

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;

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;

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

落日海湾 2024-07-30 12:26:16

不幸的是,现在有 4 个赞成票 Remy Lebeau 的这个答案,当该技术根本不适用于大多数应用程序时。 也许其中一位支持者可以发布一段代码片段,允许在文件仍然使用 FILE_FLAG_DELETE_ON_CLOSE 标志打开的情况下使用 Acrobat Reader 打开 PDF 文件?

无论如何,您可以结合此处的一些技巧以获得最佳结果:

  • 拥有应用程序使用的临时文件的内部列表。
  • 程序关闭时,浏览临时文件列表并尝试删除它们。 如果其中一些失败(因为它们仍然在外部应用程序中打开),请使用 gabr 给您的代码
  • 每当您需要新的临时文件时,请首先浏览内部文件列表并尝试重用其中一个。 仅当失败时才创建一个新文件(并将其名称添加到列表中)。

我更喜欢这种方法来注册所有文件以在重新启动时删除,因为我不确定您的应用程序可能会打开多少个临时文件 - 也许可以使用 MOVEFILE_DELAY_UNTIL_REBOOT< 注册的文件数量有限制/代码>? 这是一个系统范围的资源,我只会很少使用。

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.

叹沉浮 2024-07-30 12:26:16

一种可能性是将每个临时文件添加到系统启动期间删除的文件列表中。

在 Windows NT 平台上(自 Windows 2000 起),您只需调用 MoveFileEx 函数,第二个参数(目标)设置为 nil,并带有标志 MOVEFILE_DELAY_UNTIL_REBOOT。

在 Windows 9x 上,这要复杂得多。 您必须编辑文件 %WINDIR%\wininit.ini 并将条目写入 [Rename] 部分。

MSDN 条目如何移动当前正在使用的文件描述了这两种技术。

函数 DSiMoveOnReboot(免费 DSiWin32 库的一部分)可以处理这两种操作系统。 如果您传递一个空字符串作为第二个参数,它将在重新启动时删除源文件。

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 }

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 }
忆沫 2024-07-30 12:26:16

使用 Win32 API CreateFile() 函数打开文件,指定 FILE_FLAG_DELETE_ON_CLOSE 标志,然后将生成的句柄传递给 THandleStream 对象,以便您仍然可以使用 SaveToStream()。

另外,您的代码中存在一个错误 - 您向 ShellExecute() 传递了错误类型的句柄。 它需要一个窗口句柄,但您正在传递一个文件句柄,更糟糕的是,您在释放 TFileStream 之后访问文件句柄,从而关闭该句柄。

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.

梦断已成空 2024-07-30 12:26:16

也许您可以将它们存储在您熟悉的某个文件夹中(例如 TEMP 文件夹中带有您的应用程序名称的子文件夹),并在用户下次加载您的应用程序时清除该文件夹的内容? 或者您可以安装额外的清除实用程序并将其设置为在自动启动中运行。

关于重新启动后清除文件的另一个想法 - 您可以在启动时清除子文件夹中的所有内容,或者列出您创建的文件的上次修改时间、上次大小,将此列表存储在 XML 文件中,然后删除或更新比较您的临时子文件夹的内容以及该列表中的文件详细信息?

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?

甚是思念 2024-07-30 12:26:16

如果我没记错的话,CreateFile 有一个标志告诉 Windows 一旦文件的最后一个句柄被关闭,它应该删除该文件。 因此,正常创建文件,关闭并使用共享拒绝无和上面提到的标志重新打开它。 然后让外部应用程序打开它并自行关闭它。 一旦外部应用程序关闭该文件,这将导致 Windows 删除该文件。

(我没有尝试过这个。)

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.)

沐歌 2024-07-30 12:26:16

在类 Unix 操作系统中,通常的技巧是打开它并立即删除。 它不会出现在目录中,但数据仍然分配给保持其打开的进程。 一旦它关闭(无论是进程死亡),文件系统都会回收空间。

这不是黑客攻击,它已被​​记录和支持(将打开的文件句柄算作文件的“引用”的结果,就像目录条目一样)。

也许Windows上有一些类似的技巧? 我似乎记得 NTFS 支持对同一文件的多个引用(不,不是快捷方式)。 如果是这样,删除该文件但仍然保留最后一个引用作为临时资源可能会起作用。

显然,我只是在这里猜测......

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...

花海 2024-07-30 12:26:16

实际上,我们的应用程序在特定的临时文件夹中创建文件。 当应用程序关闭时,文件将被删除。 如果应用程序未正确关闭,则下次执行(关闭时)所有文件都将被删除。

此外,您可以启动后台进程来删除不再打开的文件。 ShellExecute 返回一个句柄(在内部将此句柄与文件名关联)。 该后台进程必须测试不存在的进程的句柄并删除关联的文件。

请原谅英语不好。 ;-)

问候。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文