spiegel-keyman/common/windows/delphi/general/utildir.pas
Marc Durdin 55c0c6fd08 fix(developer): projects 2.0 internal path enumeration
Restricts enumeration of files for the project to the project folder and
the SourcePath folder. This prevents problems where a project may be in
a folder with many subfolders which would take a long time to enumerate,
and avoids confusion where there are source-type files in other folders.

At the same time, sorts out forward slash vs backslash in paths. While
forward slash works in many scenarios, there are several filename
manipulation functions, such as ExpandFileName, which would build valid
but non-optimal paths when forward slashes were encountered, which
cascaded into files appearing to be different and presentation issues.
2023-11-16 11:47:35 +07:00

270 lines
7.5 KiB
ObjectPascal

(*
Name: utildir
Copyright: Copyright (C) SIL International.
Documentation:
Description:
Create Date: 20 Jun 2006
Modified Date: 3 Feb 2015
Authors: mcdurdin
Related Files:
Dependencies:
Bugs:
Todo:
Notes:
History: 20 Jun 2006 - mcdurdin - Initial version
01 Aug 2006 - mcdurdin - Refactor util functions into multiple units
06 Oct 2006 - mcdurdin - Fix KGetTempFileName not returning correct extension
20 Jul 2008 - mcdurdin - Raise excpetion if GetTempFileName fails to avoid possible deletion of wrong files
16 Jan 2009 - mcdurdin - Widestring directory functions
16 Jan 2009 - mcdurdin - Fix crash when two copies of Keyman start simultaneously and try and grab the same temp folder name
04 May 2012 - mcdurdin - I3306 - V9.0 - Remove TntControls + Win9x support
01 May 2014 - mcdurdin - I4181 - V9.0 - Stop using DeleteFileAlways, MOVEFILE_DELAY_UNTIL_REBOOT
03 Feb 2015 - mcdurdin - I4574 - V9.0 - If any files are read-only, they need the read-only flag removed on install
*)
unit utildir; // I3306
interface
function DirectoryEmpty(dir: WideString): Boolean;
function DirectoryExists(const Name: string): Boolean;
function ForceDirectories(Dir: string): Boolean;
procedure UnforceDirectories(Dir: string);
function EmptyDirectory(Dir: WideString): Boolean;
function RecursiveDelete(Dir: WideString): Boolean; // I4181
function CopyFileCleanAttr(const src, dst: string; failIfExists: Boolean): Boolean; // I4574
function DeleteFileCleanAttr(const filename: string): Boolean; // I4574
function CreateTempPath: string;
procedure DeleteTempPath(const Path: string);
function KGetTempFileName(const ext: string = ''): string; // deprecated 'has race condition, use TTempFileManager';--> only used by debug functionality now // I4181
function KGetTempPath: string;
function GetLongFileName(const fname: string): string;
function DosSlashes(const filename: string): string;
implementation
uses
System.StrUtils,
System.SysUtils,
Winapi.Windows;
function DosSlashes(const filename: string): string;
begin
Result := ReplaceStr(filename, '/', '\');
end;
function DirectoryEmpty(dir: WideString): Boolean;
var
f: TSearchRec;
n: Integer;
begin
Result := False;
if dir = '' then Exit;
if dir[Length(dir)] <> '\' then dir := dir + '\';
n := FindFirst(dir+'*.*', faDirectory, f);
try
while n = 0 do
begin
if (f.Name <> '.') and (f.Name <> '..') then Exit;
n := FindNext(f);
end;
finally
System.SysUtils.FindClose(f);
end;
Result := True;
end;
function EmptyDirectory(Dir: WideString): Boolean;
var
f: TSearchRec;
n: Integer;
begin
Result := True;
if dir = '' then Exit;
if dir[Length(dir)] <> '\' then dir := dir + '\';
n := FindFirst(dir+'*.*', faDirectory, f);
try
while n = 0 do
begin
if (f.Name <> '.') and (f.Name <> '..') then
begin
if (f.Attr and faDirectory) = faDirectory
then Result := Result and EmptyDirectory(Dir + f.Name)
else Result := Result and DeleteFileCleanAttr(Dir + f.Name); // I4574
end;
n := FindNext(f);
end;
finally
System.SysUtils.FindClose(f);
end;
end;
function DirectoryExists(const Name: string): Boolean;
var
Code: Dword;
begin
Code := GetFileAttributes(PChar(Name));
Result := (Code <> $FFFFFFFF) and (FILE_ATTRIBUTE_DIRECTORY and Code <> 0);
end;
function ForceDirectories(Dir: string): Boolean;
begin
Result := False;
if Length(Dir) = 0 then
raise Exception.Create('Cannot create directory ' + Dir);
if (AnsiLastChar(Dir) <> nil) and (AnsiLastChar(Dir)^ = '\') then
Delete(Dir, Length(Dir), 1);
if (Length(Dir) < 3) or DirectoryExists(Dir)
or (ExtractFilePath(Dir) = Dir) then
begin
Result := True;
Exit; // avoid 'xyz:\' problem.
end;
if not ForceDirectories(ExtractFilePath(Dir)) then Exit;
Result := CreateDir(Dir);
end;
procedure UnforceDirectories(Dir: string);
{var
f: TSearchRec;
n: Integer;
Found: Boolean;}
begin
if (AnsiLastChar(Dir) <> nil) and (AnsiLastChar(Dir)^ = '\') then
Delete(Dir, Length(Dir), 1);
{n := FindFirst(Dir + '\*.*', faDirectory, f);
try
while n = 0 do
begin
if (f.Name <> '.') and (f.Name <> '..') then Exit;
n := FindNext(f);
end;
finally
FindClose(f);
end;}
if RemoveDirectory(PChar(Dir)) then UnforceDirectories(ExtractFilePath(Dir));
end;
function RecursiveDelete(Dir: WideString): Boolean; // I4181
var
f: TSearchRec;
n: Integer;
begin
if (Dir <> '') and (Dir[Length(Dir)] = '\') then
Delete(Dir, Length(Dir), 1);
n := FindFirst(Dir + '\*.*', faDirectory, f);
if n = 0 then
try
while n = 0 do
begin
if (f.Name <> '.') and (f.Name <> '..') then
begin
if (f.Attr and faDirectory) = faDirectory then RecursiveDelete(Dir + '\' + f.Name)
else DeleteFileCleanAttr(Dir + '\' + f.Name); // I4574
end;
n := FindNext(f);
end;
finally
System.SysUtils.FindClose(f);
end;
Result := RemoveDir(Dir); // I4181
end;
function KGetTempPath: string;
var
buf: array[0..260] of char;
begin
GetTempPath(260, buf);
Result := IncludeTrailingPathDelimiter(buf);
end;
function KGetTempFileName(const ext: string = ''): string;
var
buf: array[0..260] of char;
begin
// This has a race condition -- with the file extension change...
Result := ExcludeTrailingPathDelimiter(KGetTempPath);
if GetTempFileName(PChar(Result), 'kmn', 0, buf) = 0 then
RaiseLastOSError;
if (ext <> '') then
begin
if FileExists(buf) then DeleteFileCleanAttr(buf); // I4574
Result := ChangeFileExt(buf, ext);
end
else
Result := buf;
end;
function CreateTempPath: string;
var
buf: array[0..260] of char;
tempfile: string;
begin
tempfile := '';
repeat
Result := ExcludeTrailingPathDelimiter(KGetTempPath);
if (tempfile <> '') and FileExists(tempfile) then DeleteFileCleanAttr(tempfile); // I4574
if GetTempFileName(PChar(Result), 'kmn', 0, buf) = 0 then RaiseLastOSError;
tempfile := buf;
Result := ChangeFileExt(tempfile, '.dir');
if DirectoryExists(Result) then Continue;
if not CreateDir(Result) then
raise Exception.Create('Unable to create temporary folder: '+SysErrorMessage(GetLastError));
until True;
if FileExists(tempfile) then DeleteFileCleanAttr(tempfile); // I4574
end;
procedure DeleteTempPath(const Path: string);
begin
RecursiveDelete(Path);
end;
function GetLongFileName(const fname: string): string;
var
buf: array[0..260] of char;
p: PChar;
begin
buf[0] := #0;
if GetFullPathName(PChar(fname), 260, buf, p) = 0
then Result := fname
else Result := buf;
end;
function CopyFileCleanAttr(const src, dst: string; failIfExists: Boolean): Boolean; // I4574
begin
// Remove read-only on destination file before overwrite
if FileExists(dst) then
SetFileAttributes(PChar(dst), FILE_ATTRIBUTE_NORMAL);
Result := CopyFile(PChar(src), PChar(dst), failIfExists);
if Result then
// Remove read-only on destination file after copy
SetFileAttributes(PChar(dst), FILE_ATTRIBUTE_NORMAL);
end;
function DeleteFileCleanAttr(const filename: string): Boolean; // I4574
begin
if FileExists(filename) then
SetFileAttributes(PChar(filename), FILE_ATTRIBUTE_NORMAL);
Result := System.SysUtils.DeleteFile(filename);
end;
end.