Files
yapetavi/uutils.pas
2022-10-24 22:07:20 +02:00

1036 lines
29 KiB
ObjectPascal

{
********************************************************************************
YaPeTaVi - Yet another Periodic Table Viewer
Copyright (C) 2009-2012 Geoffray Levasseur <geoffray.levasseurbrandin@numericable.fr>.
Parts inspired from PeaZip souce code:
Copyright (C) 2006 Giorgio Tani <giorgiotani@interfree.it>
Parts inspired from Lazarus source code:
Copyright (c) 2001-2010 The Lazarus developpers
All rights reserved.
http://www.geoffray-levasseur.org/
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA.
********************************************************************************
Description:
Various utilities and functions
}
unit uUtils;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Graphics, StdCtrls;
type
TOperatingSystem = (osWindows, osWin32, osWin64, osWinCE, osGo32V2, osOS2,
osGenUnix, osFreeBSD, osNetBSD, osLinux, osBeOS, osQNX, osSun, osDarwin,
osAmiga, osAtari, osMacOS, osPalmOS, osUnknow);
TPathType = (ptIsDircetory, ptIsFile, ptIsExecutable, ptNotExists,
ptInvalid, ptDosPathOnUnix, ptUnixPathOnDos);
TDesktopEnv = (deUnknow, deGnome, deKDE, deMacOSX, deWindows, deLXDE, deXFCE);
Str4 = string[4];
const
AOSDesc: array[TOperatingSystem] of string = ('Microsoft Windows (generic)',
'Microsoft Windows (32 bits)', 'Microsoft Windows (64 bits)',
'Microsoft Windows CE', 'Microsoft Windows with Go32 v2', 'IBM OS/2',
'Generic Unix', 'Free BSD', 'Net BSD', 'Linux', 'BeOS', 'QNX',
'Sun Solaris', 'Mac OS X (Darwin)', 'Amiga OS', 'Atari OS', 'MacOS',
'Palm OS', 'Unknow Operating System');
{$warning we should be able to detect more desktop environment}
ADescEnvDesc: array[TDesktopEnv] of string = ('Unknow', 'Gnome', 'KDE',
'Mac OS X', 'Microsoft Windows', 'LXDE', 'XFCE');
cpuUnknow = $00;
cpu32Bit = $10;
cpu64Bit = $20;
cpuGenericMoto = $30;
cpuMiscRisc = $40;
cpuGenericPPC = $50;
cpu86 = $11;
cpu87 = $12;
cpu386 = $13;
cpuX86_64 = $21;
cpu68k = $31;
cpuM68020 = $32;
cpu68 = $33;
cpuAlpha = $41;
cpuSparc = $42;
cpuMips = $43;
cpuPowerPC = $51;
cpuArm = $60;
cpuBigEndian = $80;
{$IFDEF WIN32}
Delimiter = '"';
{$ELSE}
{$IFDEF WIN64}
Delimiter = '"';
{$ELSE}
{$IFDEF LINUX}
Delimiter = '''';
{$ELSE}
{$IFDEF FREEBSD}
Delimiter = '''';
{$ELSE}
{$IFDEF NETBSD}
Delimiter = '''';
{$ELSE}
{$IFDEF DARWIN}
Delimiter = '''';
{$ELSE}
Delimiter = ''''; //Default value (for other Unixes)
{$ENDIF}
{$ENDIF}
{$ENDIF}
{$ENDIF}
{$ENDIF}
{$ENDIF}
var //default values and variable declaration for configuration
TwoDigitDateLimit: byte = 30; {$deprecated} //means 29 is 2029 and 30 is 1930 (deprecated)
DateFormat: string = 'dd/mm/yyyy';
//get the user name from personal directory path (multiplatform way)
//note that builtin FPC GetUserName function is for Windows only
//on unix su command is keeping the username as before launching it: find a workarround
function GetUserName: string;
//get computer name
function GetComputerName: string;
//Get the running operating system
function GetCurrentOS: TOperatingSystem;
//Find the default browser according system configuration
function FindDefaultBrowser(out ABrowser, AParams: String): Boolean;
//Open URL in the default browser
function OpenURL(AURL: String): Boolean;
//convert a 4 char string (maximum) into a Word
function StrToWord(const S: Str4): Word;
//Convert a 3 number date into a string (deprecated)
function DateToStr(Y, M, D: Word): string; deprecated;
//Remove the extention of a file name
function RemoveFileExt(const aFileName: string): string;
//Get the executable path of the program with final / or \
function GetProgramPath: string;
//get program prefix (see explanation in its code)
function GetProgramPrefix: string;
//Give the number of items present in a listed string for the given separator
function CountItemsInStr(Str: string; Separator: Char): integer;
//Return the item in a listed string at the given index
function GetItemInStr(Str: string; Separator: Char; Index: integer): string;
//find which desktop environment is currently running
function GetDesktopEnv: TDesktopEnv; //0 unknown, 1 Gnome, 2 KDE, 20 MS Windows, 30 OSX
//return the position of a string in a stringlist, -1 if no occurence
function StrExistsInList(const StrList: TStringList; S: string): integer;
function StrExistsInListBox(const StrList: TListBox; S: string): integer;
//remove and change (repectively) a string from a StringList, returning position
//of the change in the SL, -1 if no occurence
function StrRemove(var StrList: TStringList; S: string): integer;
function StrChange(var StrList: TStringList; const sOld, sNew: string): integer;
//check filename before use regarding the environment
function FormatFileName(S: String; out VS: string): Integer;
//check if a directory is empty
function DirectoryIsEmpty(ADir: string): Boolean;
//check for possible problematic chars in a string that could be used as filename
function RemoveSpecialChar(const S: UnicodeString): string;
//create a lock file containing the computer name and user name of the locker
function CreateLockFile(const AName: string): Boolean;
procedure GetLockingInfo(const AName: string; out User, Computer: string);
implementation
uses
{$ifdef WINDOWS}Windows,{$endif}
{$ifdef UNIX}BaseUnix,{$endif}
Forms, FileUtil, UTF8Process, Dos, Dialogs, uDebug, uConst;
{$ifdef UNIX}
function GetUserName: string;
begin
{$warning Find a workarround to get the good username in case where su is being used}
Result := GetEnvironmentVariable('USER');
end;
{$endif}
{$ifdef WINDOWS}
function GetUserName: string;
var
pcName: PChar;
nSize : DWord;
begin
nSize := 100;
pcName := StrAlloc(nSize);
Windows.GetUserName(pcName, nSize);
Result := StrPas(pcName);
StrDispose(pcName);
end;
{$endif}
{$ifdef UNIX}
function GetComputerName: String;
var
AUtsName: UtsName;
begin
Result := '';
AUtsName.Nodename[0] := #0; //this is just to avoid the compiler warning
FillChar(AUtsName, SizeOf(AUtsName), 0);
if FpUname(AUtsName) <> -1 then
Result := AUtsName.Nodename;
end;
{$endif}
{$ifdef WINDOWS}
function GetComputerName: String;
var
aBuf: array[0..255] of Char;
bRet: Boolean;
nSize: DWord;
begin
aBuf[0] := #0;
nSize := SizeOf(aBuf);
bRet := Windows.GetComputerName(aBuf, nSize);
aBuf[SizeOf(aBuf) - 1] := #0; // Just for safety reasons
Result := Trim(aBuf);
end;
{$endif}
function GetCurrentOS: TOperatingSystem;
begin
//unrecognized system, it is overwritten if one of the following conditions is met
Result := osUnknow;
// DOS/Windows type systems
{$IFDEF MSWINDOWS}
//generic Windows, replaced with following $0* codes
//if a more specific match is found
Result := osWindows;
{$ENDIF}
{$IFDEF WIN32}
Result := osWin32;
{$ENDIF}
{$IFDEF WIN64}
Result := osWin64;
{$ENDIF}
{$IFDEF WINCE}
Result := osWinCE;
{$ENDIF}
{$IFDEF GO32V2}
Result := Go32V2;
{$ENDIF}
{$IFDEF OS2}
Result := osOS2;
{$ENDIF}
// Unix type systems
{$IFDEF UNIX}
//generic UNIX, replaced with some following codes
//if a more specific match is found
Result := osGenUnix;
{$ENDIF}
{$IFDEF FREEBSD}
Result := osFreeBSD;
{$ENDIF}
{$IFDEF NETBSD}
Result := osNetBSD;
{$ENDIF}
{$IFDEF LINUX}
Result := osLinux;
{$ENDIF}
{$IFDEF BEOS}
Result := osBeOS;
{$ENDIF}
{$IFDEF QNX}
Result := osQNX;
{$ENDIF}
{$IFDEF SUNOS}
Result := osSunOS;
{$ENDIF}
{$IFDEF DARWIN} // MacOS <= 10.x
Result := osDarwin;
{$ENDIF}
//Other systems
{$IFDEF AMIGA}
Result := osAmiga;
{$ENDIF}
{$IFDEF ATARI} //Atari OS
Result := osAtari;
{$ENDIF}
{$IFDEF MAC} // MacOS => 9.x
Result := osMacOS;
{$ENDIF}
{$IFDEF PALMOS}
Result := osPalmOS;
{$ENDIF}
end;
//if cpu type, < $80 (encoded in ls 7 bit);
//0 if unrecognized, replaced if a match is found
function GetUsedCPU: Word; //CPU and endianness (in MSB)
begin
Result := cpuUnknow; //cpu type, < $80 (encoded in ls 7 bit); 0:= unrecognized, replaced if a match is found
{$IFDEF CPU32}
//generic 32 bit CPU, replaced if a more specific match is found
Result := cpu32Bit;
{$ENDIF}
{$IFDEF CPU64}
//generic 64 bit CPU, replaced if a more specific match is found
Result := cpu64Bit;
{$ENDIF}
{$IFDEF CPU86}
Result := cpu86;
{$ENDIF}
{$IFDEF CPU87}
Result := cpu87;
{$ENDIF}
{$IFDEF CPUI386}
Result := cpu386;
{$ENDIF}
{$IFDEF CPUX86_64}
Result := cpuX86_64;
{$ENDIF}
{$IFDEF CPU68k}
Result := cpu68k;
{$ENDIF}
{$IFDEF CPUM68020}
Result := cpuM68020;
{$ENDIF}
{$IFDEF CPU68}
Result := cpu68;
{$ENDIF}
{$IFDEF CPUSPARC}
Result := cpuSpark;
{$ENDIF}
{$IFDEF CPUALPHA}
Result := cpuAlpha;
{$ENDIF}
{$IFDEF CPUMIPS}
Result := cpuMips;
{$ENDIF}
{$IFDEF CPUPOWERPC}
Result := cpuPowerPC;
{$ENDIF}
{$IFDEF CPUARM}
Result := cpuArm;
{$ENDIF}
{$IFDEF ENDIAN_BIG}
//if processor is declared big endian (some processors don't have a fixed
//endian setting) the msb is set to 1, otherwise is 0
Result := Result + cpuBigEndian;
{$ENDIF}
end;
//This is a somewhat altered version of the Lazarus' source code
//FindDefaultBrowser function
function FindDefaultBrowser(out ABrowser, AParams: String): Boolean;
function Find(const ShortFilename: String; out ABrowser: String): Boolean; inline;
begin
ABrowser := SearchFileInPath(ShortFilename + GetExeExt, '',
GetEnvironmentVariable('PATH'), PathSeparator, [sffDontSearchInBasePath]);
Result := ABrowser <> '';
end;
begin
{$IFDEF MSWindows}
Find('rundll32', ABrowser);
AParams := 'url.dll,FileProtocolHandler %s';
{$ELSE}
{$IFDEF DARWIN}
// open command launches url in the appropriate browser under Mac OS X
Find('open', ABrowser);
AParams := '%s';
{$ELSE}
ABrowser := '';
{$ENDIF}
{$ENDIF}
if ABrowser = '' then
begin
AParams := '%s';
// Then search in path. Prefer open source ;)
if Find('xdg-open', ABrowser) // Portland OSDL/FreeDesktop standard on Linux
or Find('htmlview', ABrowser) // some redhat systems
or Find('konqueror', ABrowser)
or Find('firefox', ABrowser)
or Find('mozilla', ABrowser)
or Find('galeon', ABrowser)
or Find('safari', ABrowser)
or Find('netscape', ABrowser)
or Find('opera', ABrowser)
or Find('iexplore', ABrowser) then ;// some windows systems
end;
Result := ABrowser <> '';
end;
//each system has a different OpenURL working. this is mainly took from
//Lazarus source code with minor changes
{$IFDEF Windows}
function OpenURL(AURL: String): Boolean;
var
{$IFDEF WinCE}
Info: SHELLEXECUTEINFO;
{$ELSE}
ws: WideString;
ans: AnsiString;
{$ENDIF}
begin
Result := False;
if AURL = '' then
Exit;
{$IFDEF WinCE}
FillChar(Info, SizeOf(Info), 0);
Info.cbSize := SizeOf(Info);
Info.fMask := SEE_MASK_FLAG_NO_UI;
Info.lpVerb := 'open';
Info.lpFile := PWideChar(UTF8Decode(AURL));
Result := ShellExecuteEx(@Info);
{$ELSE}
if (GetCurrentOS = osWin32) or (GetCurrentOS = osWin64) then
begin
ws := UTF8Decode(AURL);
Result := ShellExecuteW(0, 'open', PWideChar(ws), nil, nil, 0) > 32;
end else
begin
ans := Utf8ToAnsi(AURL); // utf8 must be converted to Windows Ansi-codepage
//this should handle every kind of address if software available
Result := ShellExecute(0, 'open', PAnsiChar(ans), nil, nil, 0) > 32;
end;
{$ENDIF}
end;
{$ELSE}
{$IFDEF DARWIN}
function OpenURL(AURL: string): Boolean;
var
cf: CFStringRef;
url: CFURLRef;
w: WideString;
begin
if AURL = '' then
Exit(False);
cf := CFStringCreateWithCString(kCFAllocatorDefault, @AURL[1],
kCFStringEncodingUTF8);
if not Assigned(cf) then
Exit(False);
url := CFURLCreateWithString(nil, cf, nil);
Result := LSOpenCFURLRef(url, nil) = 0;
CFRelease(url);
CFRelease(cf);
end;
{$ELSE} //we suppose here a Unix system... exotic or very old OS not supported.
function OpenURL(AURL: String): Boolean;
var
ABrowser, AParams: String;
BrowserProcess: TProcessUTF8;
begin
Result := FindDefaultBrowser(ABrowser, AParams) and
FileExists(ABrowser);
if not Result then
Exit;
//run
BrowserProcess := TProcessUTF8.Create(nil);
try
{$warning test if @ exists and add mailto to open it with default mail composer!}
BrowserProcess.CommandLine := ABrowser + ' ' + Format(AParams, [AURL]);
BrowserProcess.Execute;
finally
BrowserProcess.Free;
end;
end;
{$ENDIF}
{$ENDIF}
//if your string is more than 4 char you may have a range error
function StrToWord(const S: Str4): Word;
var
I: Integer;
begin
Result := 0;
I := 1;
while I <= Length(S) do
begin
Result := Result * 10 + Ord(S[I]) - Ord('0');
Inc(I);
end;
end;
// Deprecated with Lazarus > 0.9.27
function DateToStr(Y, M, D: Word): string; deprecated;
begin
{$ifdef fpc}
if Y < TwoDigitDateLimit then
Inc(Y, 2000);
if (Y < 100) and (Y > TwoDigitDateLimit) then
Inc(Y, 1900);
Result := FormatDateTime(DateFormat, EncodeDate(Y, M, D));
{$endif}
{$ifdef delphi} // FormatDateTime seems to be buggy with Delphi 7 at least
if D < 10 then
Result := '0' + IntToStr(D) + '/'
else Result := IntToStr(D) + '/';
if M < 10 then
Result := Result + '0' + IntToStr(M) + '/'
else Result := Result + IntToStr(M) + '/';
if Y <= 99 then
if Y < TwoDigitDateLimit then
Result := Result + '20' + IntToStr(Y)
else Result := Result + '19' + IntToStr(Y)
else Result := Result + IntToStr(Y);
{$endif}
end;
function RemoveFileExt(const aFileName: string): string;
var
I: integer;
begin
I := Length(aFileName);
while aFileName[I] <> '.' do
Dec(I);
if I > 1 then
Result := Copy(aFileName, 1, I - 1)
else Result := aFileName; //on unix this is not an extention but an hidden file
end;
function GetProgramPath: string; //this may work on all platform
begin
Result := Trim(ExtractFilePath(Application.ExeName));
if Result[Length(Result)] <> DirectorySeparator then
Result := Result + DirectorySeparator;
end;
function GetProgramPrefix: string;
{this is finding the program prefix dependant on platform...
On Windows / OS/2 this look like:
Program Dir
|- bin\yapetavi.exe
|- db\
|- icons\
|- pics\
|- i18n\
That simply means ProgramPrefix = ..\ProgramPath
On Unix like we have:
Prefix
|- bin/yapetavi (executable is in ProgramDir)
|- share
|- icons (shared icons)
|- yapetavi (yapetavi specific data files)
| |- pics/
| |- db/
|- locale
|- <lang_code>
|- LC_MESSAGES (where to find langage file)
This is less complex than we can think... Prefix = ../ProgramPath
Nevertheless if share/yapetavi is not found we should try standard Unix prefix
like /usr or /usr/local
}
var
Err
: integer;
begin //I don't know if it's really reliable, but that's quitte simple
{$ifdef WINDOWS}
Result := GetProgramPath + '..\';
if (Result[1] = '\') and (Result[2] <> '\') then //in case where the program path is on a server
Result := '\' + Result;
{$else}
{$ifdef wince}
Result := GetProgramPath + '..\';
{$else} //many others OS have unix directory structure but this have to be checked
Result := GetProgramPath + '../';
Err := FormatFileName(Result, Result);
if Err <= 0 then
begin
PrintLnDbg('Invalid installation prefix, is it dev mode?', vlLow);
if DirectoryExists('/usr/local/share/yapetavi') then
Result := '/usr/local/';
if DirectoryExists('/usr/share/yapetavi') then //overhide last result
Result := '/usr/';
end;
{$endif}
{$endif}
PrintLnDbg('Found prefix: ' + Result, vlHigh);
end;
function CountItemsInStr(Str: string; Separator: Char): integer;
var
I: integer;
begin
Str := Trim(Str);
if Str = '' then
begin
Result := 0;
Exit;
end;
Result := 1;
for I := 1 to Length(Str) do
if Str[I] = Separator then
Inc(Result);
if Str[Length(Str)] = Separator then
Dec(Result);
end;
function GetItemInStr(Str: string; Separator: Char; Index: integer): string;
var
I, J: integer;
S: string;
begin
//note that result = '' is equivalent to an error
Result := '';
S := '';
Str := Trim(Str);
if Str = '' then
Exit;
I := 1;
J := 1;
while (I < Index) and (J < Length(Str)) do
begin
if Str[J] = Separator then
Inc(I);
Inc(J);
end;
if J >= Length(Str) then //in case of a terminal separator
Exit;
if Str[J] = Separator then
Inc(J);
repeat
S := S + Str[J];
Inc(J);
until (J > Length(Str)) or (Str[J] = Separator);
Result := Trim(S);
end;
function GetDesktopEnv: TDesktopEnv;
begin
Result := deUnknow; //unrecognized desktop environment
{$IFDEF MSWINDOWS}
Result := deWindows;
{$ENDIF}
{$IFDEF DARWIN}
Result := deMacOSX;
{$ENDIF}
{$IFDEF UNIX}
//if this Gnome specific env variable is set, probably the user is running Gnome
if GetEnvironmentVariable('GNOME_DESKTOP_SESSION_ID') <> '' then
Result := deGnome;
//if this KDE specific env variable is set, probably the user is running KDE
if GetEnvironmentVariable('KDE_FULL_SESSION') <> '' then
Result := deKDE;
//if gnome or kde is explicitely declared in DESKTOP_SESSION env variable,
//override previously assumed result
{$warning Complete with other unixes desktop environment}
if GetEnvironmentVariable('DESKTOP_SESSION') = 'gnome' then
Result := deGnome
else
if getenvironmentvariable('DESKTOP_SESSION') = 'kde' then
Result := deKDE
else
if GetEnvironmentVariable('DESKTOP_SESSION') = 'xfce' then
Result := deXFCE
else
if GetEnvironmentVariable('DESKTOP_SESSION') = 'lxde' then
Result := deLXDE;
{$ENDIF}
end;
function GetDesktopPath: string;
//superseeded in Windows
begin
{$IFDEF MSWINDOWS}
Result := SysUtils.GetEnvironmentVariable('USERPROFILE') + '\Desktop\';
{$ENDIF}
{$IFDEF LINUX} //this should work on every unixes but have to be tested
Result := GetEnvironmentVariable('HOME') + '/Desktop/';
{$ENDIF}
{$IFDEF FREEBSD}
Result := GetEnvironmentVariable('HOME') + '/Desktop/';
{$ENDIF}
{$IFDEF NETBSD}
Result := GetEnvironmentVariable('HOME') + '/Desktop/';
{$ENDIF}
//generic, superseeded by system specific values, if not empty
if Result = '' then Result := GetCurrentDir;
if Result[Length(Result)] <> DirectorySeparator then
Result := Result + DirectorySeparator;
end;
function GetHomePath: string;
//superseeded in Windows
begin
{$IFDEF MSWINDOWS}
Result := SysUtils.GetEnvironmentVariable('USERPROFILE');
{$ENDIF}
{$IFDEF LINUX} //this should work on every unixes but have to be tested
Result := GetEnvironmentVariable('HOME');
{$ENDIF}
{$IFDEF FREEBSD}
Result := GetEnvironmentVariable('HOME');
{$ENDIF}
{$IFDEF NETBSD}
Result := GetEnvironmentVariable('HOME');
{$ENDIF}
//generic, superseeded by system specific values, if not empty
if Result = '' then Result := GetCurrentDir;
if Result[Length(Result)] <> DirectorySeparator then
Result := Result + DirectorySeparator;
end;
//return the first occurence, not usefull if you need multiple ones
function StrExistsInList(const StrList: TStringList; S: string): integer;
var
I: Integer;
begin
Result := -1;
if (StrList = nil) or (not Assigned(StrList)) then
Exit;
for I := 0 to StrList.Count - 1 do
if StrList[I] = S then
begin
Result := I;
Exit;
end;
end;
function StrExistsInListBox(const StrList: TListBox; S: string): integer;
var
SL: TStringList;
begin
SL := TStringList.Create;
SL.Assign(StrList.Items);
Result := StrExistsInList(SL, S);
end;
//for both following functions a "repeat [...] until StrXXX(<...>) = -1;"
//will make it happen on every possible occurence if any
function StrRemove(var StrList: TStringList; S: string): integer;
begin
Result := StrExistsInList(StrList, S);
StrList.Delete(Result);
end;
function StrChange(var StrList: TStringList; const sOld, sNew: string): integer;
begin
Result := StrExistsInList(StrList, sOld);
StrList[Result] := sNew;
end;
////////////////////////////////////////////////////////////////////////////////
//in Linux and BSDs if filename contains delimiter ' change the character in ?,
//and checking special cases for Gnome and KDE in Windows delimiter is " and it's
//not a valid character in filenames, so this control returns the input string
//(which doesn't need to be variable) on other systems filenames are not escaped
//
//- convert %XX (where XX is an hexa value) to the equivalent char by code
//- braces management (' sous unix et " sous Windows) as unneeded with Lazarus.
//- deleting « file:// » if here.
//- Supporting environnement variables (eg. $(USER)\MonProg).
//- deleting double directory separators (eg. \\ or // -> \ or /).
//- automatic separator convertion (eg. with Windows a / would be \)
//- adds terminal \ or / for directories if not exists.
//- detect invalid chars (depending on the OS)
//
//Returns: Negative when errors, positive when Ok where
//Low(Result) is pos of the error when applicable and High(Result) the error code
//
//List of error codes:
//-$01XX: error in %XX convertion (mostly non hexa value)
//-$02XX: "$(" found but no ")" while checking environment (XX pos of "$(")
//-$0300: directory separator in a filename
//-$0400: Comma in a filename
//-$0500: windows only: use of a system reserved file extention
//-$10XX: empty env var (XX pos of "$(")
//-$20XX: invalid char where XX is the invalid char code
//+$0000: valid filename, no such file or directory
//+$1000: valid filename, directory exists
//+$2000: valid filename, file exist
function FormatFileName(S: String; out VS: string): Integer;
var
Str, TmpS, EnvVar: UnicodeString;
I, J: Integer;
Ch: Char;
const
FilePrefix = 'file://';
procedure InvalidChar(C: Char);
begin
VS := Str;
Result := $2000 + Ord(C);
end;
begin
Result := $0000;
Str := Trim(S);
// Delete ' on Unix or " on Windows as it is not needed when assuming there's
// one file path per string
I := 1;
repeat
I := {$ifndef WINDOWS} Pos('''', Str) {$ELSE} Pos('"', Str) {$ENDIF};
if I > 0 then
Delete(Str, I, 1);
until I = 0;
//find and delete 'file://' (and any part before) if it is passed as part of
//filename (it happens sometimes in Gnome, i.e. using "open with" context
//menu entry) or if it has been entered as is by the user
I := Pos(FilePrefix, Str);
if I > 0 then
Str := Copy(Str, I + Length(FilePrefix), Length(Str) - I - Length(FilePrefix) - 1);
//replace %xx with the appropriate character used sometimes to pass non
//available keyboards char or spaces. xx must be 2 hexa char
repeat
I := Pos('%', Str);
if I <> 0 then
try //if there's synthax error like non hexa char
Ch := Char(StrToWord(Str[I + 1] + Str[I + 2]));
Delete(Str, I, 3);
Insert(Ch, Str, I);
except
Result := $0100 + I;
end;
//if error leave everything else as is as it's not valid path anyway
until (I = 0) or (Result <> 0);
//the application support the use of environment variable in filenames just
//like Unix command line does, so this extract environment variable when $()
//symbol is found
repeat
I := Pos('$(', Str);
if I <> 0 then
try
J := I;
while Str[J] <> ')' do
Inc(J);
//on windows we use Windows unt that have an incompatible GetEnvironmentVariable
EnvVar := SysUtils.GetEnvironmentVariable(Copy(Str, I + 2, J - I - 2));
if EnvVar <> '' then
begin
Delete(Str, I, I - J);
Insert(EnvVar, Str, I);
end else
Result := $1000 + I;
except
Result := $0200 + I;
end;
until (I = 0) or (Result <> 0);
// remove double / on unix or double \\ on windows
{$warning This is not working when we have network address}
repeat
{$ifdef WINDOWS}
I := Pos('\\', Str);
{$else}
I := Pos('//', Str);
{$endif}
if I = 1 then
I := 0;
if I <> 0 then
if Str[I - 1] <> ':' then //it's looking like an URL so don't erase it
Delete(Str, I, 1);
until I = 0;
// check for remaining invalid char
for I := 0 to 31 do
if (Pos(Char(I), Str) <> 0) then
InvalidChar(Char(I));
if Pos('*', Str) <> 0 then
InvalidChar('*');
if Pos('?' , Str) <> 0 then
InvalidChar('?');
{$IFDEF MSWINDOWS}
if Pos('"', Str) <> 0 then
InvalidChar('"');
{$ELSE}
if Pos('''', Str) <> 0 then
InvalidChar('''');
if Pos(':', Str) <> 0 then
InvalidChar(':');
{$ENDIF}
if Pos('<', Str) <> 0 then
InvalidChar('<');
if Pos('>', Str) <> 0 then
InvalidChar('>');
if Pos('|', Str) <> 0 then
InvalidChar('|');
TmpS := ExtractFileName(Str);
//reserved characters, filename only (others are checked for the full name)
if (Pos('\', TmpS) <> 0) or (Pos('/', TmpS) <> 0) then
Result := $0300; //we should get the pos for low(result)
if Pos(':', TmpS) <> 0 then
Result := $0400; //we should get the pos for low(result)
//reserved filenames (Windows)
{$IFDEF MSWINDOWS}
TmpS := UpCase(ExtractFileExt(TmpS));
if (TmpS = 'CON') or (TmpS = 'PRN') or (TmpS = 'AUX') or (TmpS = 'NUL') or
(TmpS = 'COM1') or (TmpS = 'COM2') or (TmpS = 'COM3') or (TmpS = 'COM4') or
(TmpS = 'COM5') or (TmpS = 'COM6') or (TmpS = 'COM7') or (TmpS = 'COM8') or
(TmpS = 'COM9') or (TmpS = 'LPT1') or (TmpS = 'LPT2') or (TmpS = 'LPT3') or
(TmpS = 'LPT4') or (TmpS = 'LPT5') or (TmpS = 'LPT6') or (TmpS = 'LPT7') or
(TmpS = 'LPT8') or (TmpS = 'LPT9') then
Result := $0500; //pos is not needed as it's the file extention
{$ENDIF}
if Result <> $0000 then
Result := -Result
else
if DirectoryExists(Str) then
begin
{$IFDEF MSWINDOWS}
if Str[Length(Str)] <> '\' then
Str := Str + '\';
{$ELSE}
if Str[Length(Str)] <> '/' then
Str := Str + '/';
{$ENDIF}
Result := $1000;
end else
if FileExists(Str) then
Result := $2000;
VS := Str;
end;
function DirectoryIsEmpty(ADir: string): Boolean;
var
SR: SearchRec;
Mask: string;
begin
{$IFDEF MSWINDOWS}
Mask := '*.*';
{$ELSE}
Mask := '*';
{$ENDIF}
if ADir[Length(ADir)] <> DirectorySeparator then
ADir := ADir + DirectorySeparator;
try
FindFirst(ADir + Mask, faAnyFile, SR);
if DosError = 0 then //first is directory itself (./)
begin
FindNext(SR);
if DosError = 0 then //second is parent directory (../)
begin
FindNext(SR);
Result := DosError <> 0;
end else
Result := False; //this is a file (or damaged directory)
end else
Result := False; //the directory do not exists (or the filesystem is damaged)
finally
FindClose(SR) ;
end;
end;
function RemoveSpecialChar(const S: UnicodeString): string;
var
I: Integer;
Tmp: UnicodeString;
begin
Tmp := S;
for I := 1 to Length(S) do
if not (S[I] in ['0'..'9', '.', '-', '_', 'A'..'Z', 'a'..'z']) then
Tmp[I] := '_'
else
Tmp[I] := S[I];
Result := Tmp;
end;
function CreateLockFile(const AName: string): Boolean;
var
AFile: Text;
Test: string;
begin
Result := True;
try
if FileExists(AName) then
//use SysUtils and not Windows one when compiling with Windows target
if not SysUtils.DeleteFile(AName) then
begin
Result := False;
Exit;
end;
{$I-}
Assign(AFile, AName);
Rewrite(AFile);
Writeln(AFile, GetUserName + '|' + GetComputerName);
Close(AFile);
Reset(AFile);
Readln(AFile, Test);
Close(AFile);
{$I+}
if Test = '' then
begin
ShowMessage('Error 5: unable to write lock file');
//trigger an error here as there was an error writing file
Halt(5);
end;
except
Result := False;
end;
end;
procedure GetLockingInfo(const AName: string; out User, Computer: string);
var
AFile: Text;
I: integer;
AStr: string;
begin
if not FileExists(AName) then
begin
User := '';
Computer := '';
end else
begin
{$I-}
Assign(AFile, AName);
Reset(AFile);
Readln(AFile, AStr);
Close(AFile);
{$I+}
if AStr <> '' then
begin
I := Pos('|', AStr);
if I <> 0 then
begin
User := Copy(AStr, 1, I - 1);
Computer := Copy(AStr, I + 1, Length(AStr) - I);
end else
User := AStr; //this is not suposed to happen
end else
begin
ShowMessage('Error 6: the lock file is on readable');
//trigger an error here as the file was probably unreadable
Halt(6);
end;
end;
end;
end.