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.