Sometimes you need to temporarily create a file or even a directory to put some files into. Windows (and most other operating systems, even MS DOS) reserves a special directory for this purpose and helpfully provides API methods for getting this directory (or you could just use the %TEMP% environment variable but while that is convenient for batch files, it is less convenient in a program).
So, there is the GetTempPath Windows API function. For Delphi programmers it is a bit cumbersome to use because like all API functions it works with zero terminated strings (PChar). My dzlib contains a utility function that wraps this call and returns a string:
class function TFileSystem.GetTempPath: string;
var
Res: Integer;
LastError: Integer;
begin
// according to MSDN the maximum length of the TEMP path is MAX_PATH+1 (261)
// an AnsiString / WideString always has one additional character to the length for storing
// the terminating 0
SetLength(Result, MAX_PATH);
Res := Windows.GetTempPath(MAX_PATH + 1, PChar(Result));
if Res <= 0 then begin
// GetLastError must be called before _(), otherwise the error code gets lost
LastError := GetLastError;
RaiseLastOSErrorEx(LastError, _('TFileSystem.GetTempPath: %1:s (code: %0:d) calling Windows.GetTempPath'));
end;
SetLength(Result, Res);
end;
While it is nice to know where to put the files, sometimes you want not just a directory to put the file into but also a unique filename. Again, the Windows API can help here with the GetTempFileName function. And since it too requires you to use PChar I have wrapped it again:
class function TFileSystem.GetTempFileName(_Directory: string = ''; const _Prefix: string = 'dz'; _Unique: Word = 0): string;
var
Res: Integer;
LastError: Cardinal;
begin
if _Directory = '' then
_Directory := GetTempPath;
SetLength(Result, MAX_PATH);
Res := Windows.GetTempFileName(PChar(_Directory), PChar(_Prefix), _Unique, PChar(Result));
if Res = 0 then begin
// GetLastError must be called before _(), otherwise the error code gets lost
LastError := GetLastError;
RaiseLastOSErrorEx(LastError, _('TFileSystem.GetTempFilename: %1:s (Code: %0:d) calling Windows.GetTempFileName'));
end;
Result := PChar(Result); // remove trailing characters
end;
Sometimes you want to not only create a single file but many of them and rather than cluttering the TEMP directory with your files, you might want to create a subdirectory for this purpose. There is no Windows API for this (at least I don’t know any), so I have written my own function for it:
class function TFileSystem.CreateUniqueDirectory(_BaseDir: string = ''; const _Prefix: string = 'dz'): string;
var
Pid: DWORD;
Counter: Integer;
Ok: Boolean;
s: string;
begin
if _BaseDir = '' then
_BaseDir := GetTempPath;
Pid := GetCurrentProcessId;
s := itpd(_BaseDir) + _Prefix + '_' + IntToStr(Pid) + '_';
Counter := 0;
Ok := False;
while not Ok do begin
Result := s + IntToStr(Counter);
Ok := Self.CreateDir(Result, ehReturnFalse);
if not Ok then begin
Inc(Counter);
if Counter > 1000 then
raise ECreateUniqueDir.CreateFmt(_('Could not find a unique directory name based on "%s"'), [Result]);
end;
end;
end;
This function uses the provided prefix (or the default ‘dz’), current process ID and a number (starting at 0) to create a unique directory. If creating the directory fails, the number is incremented and tried again.
Using it is pretty simple:
MyTempoaryWorkingDir := TFileSystem.CreateUniqueDirectory;
This will create the unique directory
%TEMP%\dz_815_0
Or, if you don’t want to use the TEMP directory, specify your own:
MyTempoaryWorkingDir := TFileSystem.CreateUniqueDirectory('c:\rootofallmyworkingdirs');
This will create the unique directory
c:\rootofallmyworkingdirs\dz_815_0
And if you don’t like the ‘dz’ prefix, just specify your own:
MyTempoaryWorkingDir := TFileSystem.CreateUniqueDirectory('', 'bla');
This will create the unique directory
%TEMP%\bla_815_0
Usually you want to clean up any temporary files when your program exits. In the case of a unique temporary working directory, this simply means that you delete the whole directory. Since I am lazy bastard ™ I have of course wrapped that into a helper function:
type
IUniqueTempDir = interface ['{D9A4A428-66AE-4BBC-B1CA-22CE4DE2FACB}']
function Path: string;
///<summary> Path including trailing path delimiter </summary>
function PathBS: string;
end;
// [...]
type
TUniqueTempDir = class(TInterfacedObject, IUniqueTempDir)
private
FPath: string;
FDeleteOnlyIfEmpty: Boolean;
function Path: string;
///<summary> Path including trailing path delimiter </summary>
function PathBS: string;
public
constructor Create(const _Path: string; _DeleteOnlyIfEmpty: Boolean = False);
destructor Destroy; override;
end;
{ TUniqueTempDir }
constructor TUniqueTempDir.Create(const _Path: string; _DeleteOnlyIfEmpty: Boolean = False);
begin
inherited Create;
FPath := _Path;
FDeleteOnlyIfEmpty := _DeleteOnlyIfEmpty;
end;
destructor TUniqueTempDir.Destroy;
begin
// delete directory, fail silently on errors
if FDeleteOnlyIfEmpty then
TFileSystem.RemoveDir(FPath, False)
else
TFileSystem.DelDirTree(FPath, False);
inherited;
end;
function TUniqueTempDir.Path: string;
begin
Result := FPath;
end;
function TUniqueTempDir.PathBS: string;
begin
Result := itpd(FPath);
end;
class function TFileSystem.CreateUniqueTempDir(_DeleteOnlyIfEmpty: Boolean = False; _Prefix: string = 'dz'): IUniqueTempDir;
var
s: string;
begin
s := CreateUniqueDirectory(GetTempPath, _Prefix);
Result := TUniqueTempDir.Create(s, _DeleteOnlyIfEmpty);
end;
The function TFileSystem.CreateUniqueTempDir returns an interface which means it is reference counted. Once the interface goes out of scope, the object is freed and the destructor deletes the whole directory tree. Of course you must make sure that you keep a reference to the interface around as long as you need the files.
procedure DoSomethingTemporary; var TempDir: IUniqueTempDir; st: TFileStream; begin TempDir := TFileSystem.CreateUniqueTempDir; CreateAFileIn(TempDir.Path, st); WorkWithTheFile(st); DontForgetToCloseIt(st); end; // the interface goes out of scope here
There is an overloaded function which allows to specify a different base directory as well:
class function CreateUniqueTempDir(const _BaseDir: string; _DeleteOnlyIfEmpty: Boolean = False; _Prefix: string = 'dz'): IUniqueTempDir;
All those helper functions are in the unit u_dzFileUtils, in case you want to look it up.