{ ******************************************************************************** SPackGui (common files) Copyright (C) 2013 Geoffray Levasseur . Copyright (C) http://www.geoffray-levasseur.org/ http://0.tuxfamilly.org/ This software is governed by the CeCILL license under French law and abiding by the rules of distribution of free software. You can use, modify and/ or redistribute the software under the terms of the CeCILL license as circulated by CEA, CNRS and INRIA at the following URL "http://www.cecill.info". As a counterpart to the access to the source code and rights to copy, modify and redistribute granted by the license, users are provided only with a limited warranty and the software's author, the holder of the economic rights, and the successive licensors have only limited liability. In this respect, the user's attention is drawn to the risks associated with loading, using, modifying and/or developing or reproducing the software by the user in light of its specific status of free software, that may mean that it is complicated to manipulate, and that also therefore means that it is reserved for developers and experienced professionals having in-depth computer knowledge. Users are therefore encouraged to load and test the software's suitability as regards their requirements in conditions enabling the security of their systems and/or data to be ensured and, more generally, to use and operate it in the same conditions as regards security. The fact that you are presently reading this means that you have had knowledge of the CeCILL license and that you accept its terms. ******************************************************************************** Description: some common vars and functions ******************************************************************************** } unit uCommon; {$include defines.inc} interface uses Classes, IniFiles, Forms; type TCharSet = set of Char; const //for test we don't touch to real system files {$IFDEF TEST} //you need to change everything before the ELSE compiler directive //accordingly to your own test path sDefaultLogFileName = '/share/src/pascal/0linux/log/spackgui.log'; sLockFile = '/share/src/pascal/0linux/log/spackgui.lock'; sIconBaseDir = '/share/src/pascal/0linux/icons'; sRootUserName = 'fatalerrors'; //when testing we want to make change even if non root sDefaultSpackPkgDir = '/share/src/pascal/0linux/testpack'; sDefaultConfDir = '/share/src/pascal/0linux/testconf'; sDefaultRepoIndexDir = sDefaultConfDir; sDefaultDwlDir = '/share/src/pascal/0linux/packages'; sRepoConfFile = 'depots.conf'; sMainConfFile = 'spackgui.conf'; {$ELSE} sDefaultLogFileName = {$LOCALSTATEDIR}'/log/spackgui.log'; sLockFile = {$LOCALSTATEDIR}'/lock/spackgui.lock'; sIconBaseDir = {$PREFIX}'/share/spackgui/icons'; sRootUserName = 'root'; sDefaultSpackPkgDir = {$LOCALSTATEDIR}'/lib/spack'; sDefaultConfDir = {$SYSCONFDIR}'/spackgui'; sDefaultRepoIndexDir = sDefaultConfDir; sDefaultDwlDir = {$LOCALSTATEDIR}'/cache/spack'; sRepoConfFile = 'depots.conf'; sMainConfFile = 'spackgui.conf'; {$ENDIF} sDefaultInstallPackage = 'spackadd %s'; sDefaultReinstallPackage = 'spackadd -f %s'; sDefaultRemovePackage = 'spackrm %s'; sDefaultUpdatePackage = 'spackadd %s'; AnsiLineFeed = AnsiChar(#10); AnsiCarriageReturn = AnsiChar(#13); AnsiCrLf = AnsiString(#13#10); iDefaultProxyPort = 3128; RegExprOperator: TCharSet = ['&', '|', '#']; var sConfDir: string; //place where configuration files will be stored sSpackPkgDir: string; //installed packages repository sRepoIndexDir: string; //place where repositories metadata are stored sDwlDir: string; //place where packages are downloaded before install sDefaultRepo: string; //default repo used for upgrades bChangeAllowed: Boolean; //global saying if change are allowed or not bUnitsAreDecimal: Boolean; //user may want decimal cThousandSep: Char; //thousand separator (#0 means none) cDecimalSep: Char; //decimal separator bNoColors: Boolean; //colorize listview or not bShowGrig: Boolean; //show a grid in listview or not sInstallPackage: string; //command to install a package sReinstallPackage: string; //command to reinstall an installed package sRemovePackage: string; //command to remove an installed package sUpdatePackage: string; //command to update an installed package tLockFile: Text; //pointer to lockfile iniMain: TIniFile; //main configuration file object iniRepo: TIniFile; //repositories configuration file object bReadOnly: Boolean; //when non-root we are read-only slRepoList: TStringList; //list of non installed packages repository slDeprecated: TStringList; //list of deprecated packages sArch: string; //used architecture //proxy settings sHttpProxyAddress: string; iHttpProxyPort: Integer; sHttpProxyUser: string; sHttpProxyPass: string; sFtpProxyAddress: string; iFtpProxyPort: Integer; sFtpProxyUser: string; sFtpProxyPass: string; //write a string list in an ini file, the TInifile must be initialized procedure IniWriteStrings(IniFile: TIniFile; const Section, Ident: string; Str: TStrings); //read a string list in an ini file, the TInifile must be initialized procedure IniReadStrings(IniFile: TIniFile; const Section, Ident: string; Str: TStrings); // SPack use that format: pkgname-version-arch-build{,.spack} procedure DecomposePackageName(const S: ansistring; out Name, Version: ansistring; out Build: Byte; out Arch: ansistring); // Extract package name a full spack format string function ExtractPackageName(const S: ansistring): ansistring; // Format numbers for file size display (with units) // the base unit is Byte but spack have KB as base unit function DispSize(const Size: integer): string; //convert strings generated by the previous function to LongInt function SizeStrToInt(const SzStr: string): LongInt; // Get Major, minor and release number (sometimes there's letters so we keep that // string)... Exotic version numbering gets remainings information in rem var procedure DecomposeVersion(const V: string; out Maj, Min, Rel, Rem: string); // If V1 > V2 result is 1, 0 if equal and -1 if V1 < V2 function CompareVersion(const V1, V2: string): ShortInt; // Load/save window geometry in main ini file procedure LoadWindowGeometry(Form: TForm); procedure SaveWindowGeometry(Form: TForm); // Load/save repository configuration informations procedure LoadRepoSettings; procedure SaveRepoSettings; // Initialize config dir and inifiles procedure InitConf; // Get address of a repository from it's name function GetRepoAddress(const RepoName: string): string; implementation uses SysUtils, uStrings, uUtils, uDebug, Controls; procedure IniWriteStrings(IniFile: TIniFile; const Section, Ident: string; Str: TStrings); var I: integer; OldCount: Integer; begin //old number of line as needed OldCount := IniFile.ReadInteger(Section, Ident + rsConfNameCount, 0); if OldCount > Str.Count then //more lines in old config ? for I := Str.Count to OldCount do IniFile.DeleteKey(Section, Ident + IntToStr(I)); //erase old keys IniFile.WriteInteger(Section, Ident + rsConfNameCount, Str.Count); //new count for I := 0 to Str.Count - 1 do IniFile.WriteString(Section, Ident + IntToStr(I), Str[I]); //write each line end; procedure IniReadStrings(IniFile: TIniFile; const Section, Ident: string; Str: TStrings); var I, N: Integer; begin Str.Clear; N := IniFile.ReadInteger(Section, Ident + rsConfNameCount, 0); for I := 0 to N - 1 do Str.Add(IniFile.ReadString(Section, Ident + IntToStr(I), '')); end; procedure DecomposePackageName(const S: ansistring; out Name, Version: ansistring; out Build: Byte; out Arch: ansistring); var I, J: integer; Tmp: string; begin I := Length(S); while S[I] <> '-' do Dec(I); Tmp := Copy(S, I + 1, Length(S) - I + 1); if Trim(Tmp) <> '' then Build := StrToInt(Tmp) else Build := 0; Dec(I); J := I; while S[I] <> '-' do Dec(I); Arch := Copy(S, I + 1, J - I); Dec(I); J := I; while S[I] <> '-' do Dec(I); Version := Copy(S, I + 1, J - I); Name := Copy(S, 1, I - 1); //the following is unneeded as we shouldn't have '-' in versions //-------------------------------------------------------------- //I := Length(Name); //while (I > 1) and (Name[I] <> '-') do //still some '-' in the name ? // Dec(I); //if I > 1 then //name or version contains '-': try to analyse this correctly... // if (Name[I + 1] in ['0'..'9']) and (Name[Length(Name)] in ['0'..'9']) then // begin // Tmp := Copy(Name, I + 1, Length(Name) - I + 1); // Version := Tmp + '-' + Version; // Name := Copy(S, 1, I - 1); // end; end; function ExtractPackageName(const S: ansistring): ansistring; var I, P: integer; Tmp: string; begin if UpperCase(ExtractFileExt(S)) = rsSpackExt then Tmp := RemoveFileExt(S) else Tmp := S; P := Pos(' ', S); if P <> 0 then begin Tmp := Copy(Tmp, P + 1, Length(Tmp) - P); P := Pos(' ', Tmp); Tmp := Copy(Tmp, 1, P - 1); end; I := Length(Tmp); //skip build while Tmp[I] <> '-' do Dec(I); Dec(I); //skip arch while Tmp[I] <> '-' do Dec(I); Dec(I); //skip version while Tmp[I] <> '-' do Dec(I); Result := Copy(S, 1, I - 1); end; function DispSize(const Size: LongInt): string; var Diviser, I, N: Integer; Unt: string; begin if bUnitsAreDecimal then Diviser := 1000 else Diviser := 1024; // we go up to GB as bigger is probably not needed for now in that kind of app if Size > (Diviser * 10) then //if true at least 10KB if (Size div Diviser) > (Diviser * 10) then //10MB if ((Size div Diviser) div Diviser) > (Diviser * 10) then //10GB begin Result := IntToStr(((Size div Diviser) div Diviser) div Diviser); if ((Size div Diviser) div Diviser) mod Diviser <> 0 then Result := Result + cDecimalSep + Copy(IntToStr(((Size div Diviser) div Diviser) mod Diviser), 1, 2); if bUnitsAreDecimal then Unt := rsGB else Unt := rsGiB; end else begin Result := IntToStr((Size div Diviser) div Diviser); if (Size div Diviser) mod Diviser <> 0 then Result := Result + cDecimalSep + Copy(IntToStr((Size div Diviser) mod Diviser), 1, 2); if bUnitsAreDecimal then Unt := rsMB else Unt := rsMiB; end else begin Result := IntToStr(Size div Diviser); if Size mod Diviser <> 0 then Result := Result + cDecimalSep + Copy(IntToStr(Size mod Diviser), 1, 2); if bUnitsAreDecimal then Unt := rsKB else Unt := rsKiB; end else begin Unt := rsByte; Result := IntToStr(Size); end; if (((Length(Result) > 3) and (Pos(cDecimalSep, Result) = 0)) or ((Pos(cDecimalSep, Result) <> 0) and (Length(Result) > 5))) and (cThousandSep <> #0) then begin N := Pos(cDecimalSep, Result) - 1; if N <= 0 then N := Length(Result); I := 0; while N >= 1 do begin if (I <> 0) and (I mod 3 = 0) then Insert(cThousandSep, Result, N + 1); Inc(I); Dec(N); end; end; Result := Result + ' ' + Unt; end; function SizeStrToInt(const SzStr: string): LongInt; var Mul: Integer; Val: Single; S: string; begin S := Trim(SzStr); if S = '' then begin Result := 0; Exit; end; Mul := 1; if Pos(rsKiB, S) > 1 then begin Mul := 1024; S := Copy(S, 1, Length(S) - 4); end else if Pos(rsKB, S) > 1 then begin Mul := 1000; S := Copy(S, 1, Length(S) - 3); end else if Pos(rsMiB, S) > 1 then begin Mul := 1024 * 1024; S := Copy(S, 1, Length(S) - 4); end else if Pos(rsMB, S) > 1 then begin Mul := 1000 * 1000; S := Copy(S, 1, Length(S) - 3); end else if Pos(rsGiB, S) > 1 then begin Mul := 1024 * 1024 * 1024; S := Copy(S, 1, Length(S) - 4); end else if Pos(rsGB, S) > 1 then begin Mul := 1000 * 1000 * 1000; S := Copy(S, 1, Length(S) - 3); end else if Pos(rsByte, S) > 1 then S := Copy(S, 1, Length(S) - 2); while Pos(cThousandSep, S) <> 0 do Delete(S, Pos(cThousandSep, S), 1); if Pos(cDecimalSep, S) <> 0 then S[Pos(cDecimalSep, S)] := '.'; try Val := StrToFloat(S); except PrintLnDbg(Format('E Error while converting %s with intermediate %s to float', [SzStr, S])); Result := 0; Exit; end; try Result := Round(Val * Mul); except PrintLnDbg(Format('E Error while converting %s: float is to big', [SzStr, S])); Result := 0; end; end; procedure DecomposeVersion(const V: string; out Maj, Min, Rel, Rem: string); var I: integer; Tmp: string; begin Rem := ''; Tmp := ''; I := Pos('.', V); if (I = 0) then I := Pos('_', V); if I > 0 then begin Maj := Copy(V, 1, I - 1); Tmp := Copy(V, I + 1, Length(V) - I); if Length(V) = 0 then begin Min := ''; Rel := ''; Rem := ''; Exit; end; I := Pos('.', Tmp); if (I = 0) then I := Pos('_', Tmp); if I > 0 then begin Min := Copy(Tmp, 1, I - 1); Tmp := Copy(Tmp, I + 1, Length(Tmp) - I); if Length(Tmp) = 0 then begin Rel := ''; Rem := ''; Exit; end; I := Pos('.', Tmp); if (I = 0) then I := Pos('_', Tmp); if I > 0 then begin Rel := Copy(Tmp, 1, I - 1); Tmp := Copy(Tmp, I + 1, Length(Tmp) - I); if Length(Tmp) = 0 then Exit; Rem := Tmp; end else Rel := Tmp; end else Min := Tmp; end else Maj := V; end; function CompareVersion(const V1, V2: string): ShortInt; var Maj1, Maj2: string; Min1, Min2: string; Rel1, Rel2: string; Rem1, Rem2: string; begin DecomposeVersion(V1, Maj1, Min1, Rel1, Rem1); DecomposeVersion(V2, Maj2, Min2, Rel2, Rem2); Result := 0; //test remainning informations if Rem1 > Rem2 then Result := 1 else if Rem1 < Rem2 then Result := -1; //test release (overide previous test) if Rel1 > Rel2 then Result := 1 else if Rel1 < Rel2 then Result := -1; //test minor if Min1 > Min2 then Result := 1 else if Min1 < Min2 then Result := -1; //test major if Maj1 > Maj2 then Result := 1 else if Maj1 < Maj2 then Result := -1; end; procedure LoadWindowGeometry(Form: TForm); begin Form.Left := iniMain.ReadInteger(Form.Name, rsConfNameLeft, Form.Left); Form.Top := iniMain.ReadInteger(Form.Name, rsConfNameTop, Form.Top); if Form.BorderStyle <> bsDialog then begin Form.Width := iniMain.ReadInteger(Form.Name, rsConfNameWidth, Form.Width); Form.Height := iniMain.ReadInteger(Form.Name, rsConfNameHeight, Form.Height); if iniMain.ReadBool(Form.Name, rsConfNameMaximized, False) then Form.WindowState := wsMaximized; end; end; procedure SaveWindowGeometry(Form: TForm); var Maxi: Boolean; begin iniMain.WriteInteger(Form.Name, rsConfNameLeft, Form.Left); iniMain.WriteInteger(Form.Name, rsConfNameTop, Form.Top); if Form.BorderStyle <> bsDialog then begin iniMain.WriteInteger(Form.Name, rsConfNameWidth, Form.Width); iniMain.WriteInteger(Form.Name, rsConfNameHeight, Form.Height); Maxi := Form.WindowState = wsMaximized; iniMain.WriteBool(Form.Name, rsConfNameMaximized, Maxi); end; end; procedure InitConf; var UseHome: Boolean; ProxyTmp, Port: string; begin bReadOnly := False; sConfDir := sDefaultConfDir; if not DirectoryExists(sConfDir) then if DirectoryIsReadOnly(ExtractFileDir(sConfDir)) then UseHome := True else MkDir(sConfDir) else if DirectoryIsReadOnly(sConfDir) then UseHome := True; if UseHome then if not DirectoryIsReadOnly(GetHomePath) then begin sConfDir := GetHomePath + rsFreeDesktopConfig + '/' + rsHomeConfDir; //freedestop compliant if not DirectoryExists(sConfDir) then begin if not DirectoryExists(GetHomePath + rsFreeDesktopConfig) then MkDir(GetHomePath + rsFreeDesktopConfig); MkDir(sConfDir); end end else bReadOnly := True; iniMain := TIniFile.Create(sConfDir + '/' + sMainConfFile); PrintLnDbg(Format(rsInfoInitConfFile, [iniMain.FileName]), vlLow); iniRepo := TIniFile.Create(sConfDir + '/' + sRepoConfFile); PrintLnDbg(Format(rsInfoInitConfFile, [iniRepo.FileName]), vlLow); case GetUsedCPU of cpu386: sArch := 'i686'; cpuX86_64: sArch := 'x86_64'; cpuArm: sArch := 'arm'; end; //environment settings sInstallPackage := iniMain.ReadString(rsConfSectionCommand, rsConfNameInstallCommand, sDefaultInstallPackage); sReinstallPackage := iniMain.ReadString(rsConfSectionCommand, rsConfNameReinstallCommand, sDefaultReinstallPackage); sRemovePackage := iniMain.ReadString(rsConfSectionCommand, rsConfNameRemoveCommand, sDefaultRemovePackage); sUpdatePackage := iniMain.ReadString(rsConfSectionCommand, rsConfNameUpdateCommand, sDefaultUpdatePackage); sSpackPkgDir := iniMain.ReadString(rsConfSectionPaths, rsConfNameInstPkgPath, sDefaultSpackPkgDir); sDwlDir := iniMain.ReadString(rsConfSectionPaths, rsConfNamePkgDownloadPath, sDefaultDwlDir); sRepoIndexDir := iniMain.ReadString(rsConfSectionPaths, rsConfNameRepoDownloadPath, sDefaultRepoIndexDir); ProxyTmp := iniMain.ReadString(rsConfSectionProxy, rsConfNameHttpProxy, ''); GetProxyInfo(ProxyTmp, sHttpProxyUser, sHttpProxyPass, sHttpProxyAddress, Port); try if Trim(Port) <> '' then iHttpProxyPort := StrToInt(Port); finally //do nothing end; ProxyTmp := iniMain.ReadString(rsConfSectionProxy, rsConfNameFtpProxy, ''); GetProxyInfo(ProxyTmp, sFtpProxyUser, sFtpProxyPass, sFtpProxyAddress, Port); try if Trim(Port) <> '' then iFtpProxyPort := StrToInt(Port); finally //do nothing end; //display settings try cThousandSep := Chr(iniMain.ReadInteger(rsConfSectionDisplay, rsConfNameThousandsSep, Ord(' '))); except cThousandSep := ' '; end; try cDecimalSep := Chr(iniMain.ReadInteger(rsConfSectionDisplay, rsConfNameDecimalSep, Ord(','))); except cDecimalSep := ','; end; bUnitsAreDecimal := iniMain.ReadBool(rsConfSectionDisplay, rsConfNameDecimalUnits, False); bShowGrig := iniMain.ReadBool(rsConfSectionDisplay, rsConfNameShowGrid, False); bNoColors := iniMain.ReadBool(rsConfSectionDisplay, rsConfNameNoColors, False); end; procedure LoadRepoSettings; var I: Integer; begin PrintLnDbg(rsInfoLoadingRepoSettings, vlLow); if iniRepo = nil then Exit; //exception goes here if slRepoList = nil then slRepoList := TStringList.Create else slRepoList.Clear; iniRepo.ReadSections(slRepoList); for I := 0 to slRepoList.Count - 1 do begin if iniRepo.ReadBool(slRepoList[I], rsConfNameDefault, False) then sDefaultRepo := slRepoList[I]; slRepoList[I] := slRepoList[I] + '=' + iniRepo.ReadString(slRepoList[I], rsConfNameAddress, ''); end; PrintLnDbg(Format(rsInfoDefaultRepo, [sDefaultRepo]), vlLow); end; procedure SaveRepoSettings; var I, N: Integer; Name, Address: string; begin if bReadOnly then Exit; //this should not happen: trigger an exception when conception is over if (slRepoList = nil) or (iniRepo = nil) then Exit; //should not happen: an exception goes here for I := 0 to slRepoList.Count - 1 do begin N := Pos('=', slRepoList[I]); if N = 0 then Continue; //show an error message Name := Trim(Copy(slRepoList[I], 1, N - 1)); Address := Trim(Copy(slRepoList[I], N + 1, Length(slRepoList[I]) - N)); iniRepo.WriteString(Name, rsConfNameAddress, Address); iniRepo.WriteBool(Name, rsConfNameDefault, Name = sDefaultRepo); end; iniRepo.UpdateFile; end; function GetRepoAddress(const RepoName: string): string; var I: Integer; begin for I := 0 to slRepoList.Count - 1 do if Pos(RepoName, slRepoList[I]) = 1 then Result := Copy(slRepoList[I], Pos('=', slRepoList[I]) + 1, Length(slRepoList[I]) - Pos('=', slRepoList[I]) + 1); end; end.