Как я могу обрабатывать временные файлы?
В моем приложении мои пользователи могут импортировать файлы (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;
7 ответов:
К сожалению, сейчас есть 4 апвота дляэтого ответа Реми Лебо , когда техника просто не будет работать с большинством приложений. Может быть, один из upvoters мог бы опубликовать фрагмент кода, который позволяет открыть PDF-файл с помощью Acrobat Reader, пока файл все еще открыт с флагом
FILE_FLAG_DELETE_ON_CLOSE
?В любом случае, вы можете объединить некоторые советы здесь для достижения наилучших результатов:
- имейте внутренний список временных файлов, используемых вашим приложением.
- при завершении работы программы пройдитесь по списку временных файлов и попробуйте удалить их. Если это не удается для некоторых из них (потому что они все еще открыты во внешнем приложении) зарегистрировать их для удаления при перезагрузке с код gabr дал вам.
Всякий раз, когда вам нужен новый временный файл, сначала просмотрите свой внутренний список файлов и попробуйте повторно использовать один из них. Создайте новый файл (и добавьте его имя в список) только в том случае, если это не удастся.Я бы предпочел такой подход регистрации всех файлов для удаления на перезагрузка, потому что я не уверен, сколько временных файлов может открыть ваше приложение - возможно, существует ограничение на количество файлов, которые могут быть зарегистрированы с помощью
MOVEFILE_DELAY_UNTIL_REBOOT
? Это общесистемный ресурс, который я использую только экономно.
Можно было бы добавить каждый временный файл в список файлов, которые удаляются во время запуска системы.
На платформе Windows NT (начиная с Windows 2000) можно просто вызвать функцию MoveFileEx со вторым параметром (destination), равным nil, и флагом MOVEFILE_DELAY_UNTIL_REBOOT.
В Windows 9x это гораздо сложнее. Вы должны отредактировать файл % WINDIR%\wininit.ini и введите запись в раздел [переименовать].
Запись 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 }
Используйте функцию Win32 API CreateFile (), чтобы открыть файл, указав флаг
FILE_FLAG_DELETE_ON_CLOSE
, а затем передайте полученный дескриптор объекту THandleStream, чтобы вы все еще могли использовать SaveToStream().Кроме того, в вашем коде есть ошибка - вы передаете неправильный тип дескриптора ShellExecute(). Он ожидает дескриптор окна, но вместо этого вы передаете дескриптор файла, и хуже того, вы обращаетесь к дескриптору файла после того, как вы уже освободили TFileStream, закрывая дескриптор.
Может быть, вы можете хранить их в какой-нибудь известной вам папке (например, подпапке в папке TEMP с именем вашего приложения) и очистить содержимое этой папки, когда пользователь в следующий раз загрузит ваше приложение? Или вы можете установить дополнительную утилиту clear и настроить ее на запуск в автозапуске.
Еще одна идея об очистке файлов после перезагрузки-вы можете очистить все в вашей подпапке при запуске, или сделать список файлов, которые вы создали с последним временем изменения, последним размером, сохранить этот список в XML-файле и позже удалить или обновить сравнение содержимого вашей временной подпапки с данными файла из этого списка?
Если я правильно помню, есть флаг для CreateFile, который говорит Windows, что она должна удалить файл, как только последний дескриптор к нему был закрыт. Итак, создайте файл нормально, закройте и снова откройте его с помощью share deny none и флага, упомянутого выше. Затем позвольте внешнему приложению открыть его и закройте его самостоятельно. Это должно привести к удалению файла Windows, как только внешнее приложение закроет его.
(я этого не пробовал.)
В Unix-подобных ОС обычный трюк состоит в том, чтобы открыть его и немедленно удалить. он не будет отображаться в Каталоге, но данные по-прежнему выделяются для процесса(процессов), которые держат его открытым. как только он будет закрыт (либо красиво из-за процесса умирания), файловая система восстановит пространство.
Это не взлом, это документировано и поддерживается (следствие того, что открытые дескрипторы файлов считаются "ссылками" на файл, как и записи в каталоге).
Может быть, есть что-то похожее трюк с окнами? я, кажется, припоминаю, что NTFS поддерживает несколько ссылок на один и тот же файл (нет, ярлыки не те). если это так, то удаление файла, но все еще цепляясь за последнюю ссылку как за эфемерный ресурс, может сработать.
Очевидно, я просто спекулирую здесь...
На самом деле наше приложение создает de файлы в специальной папке temp. При закрытии приложения fies удаляются. Если приложение не закрывается корректно, то при следующем выполнении (при закрытии) все файлы удаляются.
Кроме того, вы можете запустить фоновый процесс для удаления файлов, которые больше не открыты. ShellExecute возвращает дескриптор (внутренне связывает этот дескриптор с именем файла). Этот фоновый процесс должен проверить дескриптор процесса, который не существует, и удалить связанный с ним файлы.
Извините за плохой английский. ;- )
С уважением.