lazarus/packager/packagelinks.pas

1367 lines
46 KiB
ObjectPascal

{
/***************************************************************************
packagelinks.pas
----------------
***************************************************************************/
***************************************************************************
* *
* This source 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 2 of the License, or *
* (at your option) any later version. *
* *
* This code 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. *
* *
* A copy of the GNU General Public License is available on the World *
* Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also *
* obtain it by writing to the Free Software Foundation, *
* Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA. *
* *
***************************************************************************
Author: Mattias Gaertner
Abstract:
Package links helps the IDE to find package filenames by name.
If you are searching for the dialog to see the package links: pkglinksdlg.pas
}
unit PackageLinks;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Laz_AVL_Tree,
// LCL
Forms,
// LazUtils
Laz2_XMLCfg, LazFileCache, LazFileUtils, FileUtil, LazUtilities, LazTracer,
AvgLvlTree,
// Codetools
FileProcs, CodeToolManager,
// BuildIntf
PackageDependencyIntf, PackageLinkIntf, PackageIntf, MacroIntf,
// IDE
IDEProcs, EnvironmentOpts, LazConf, IDECmdLine, PackageDefs;
const
PkgLinksFileVersion = 3;
{ 3: changed "LastUsed" from day to seconds, so that last used lpk is loaded
after IDE restart }
type
TLazPackageLink = class(TPackageLink)
private
FAutoCheckExists: boolean;
fReferenceCount: integer;
public
constructor Create; override;
destructor Destroy; override;
function GetEffectiveFilename: string; override;
procedure Reference; override;
procedure Release; override;
public
property AutoCheckExists: boolean read FAutoCheckExists write FAutoCheckExists;
end;
{ TLazPackageLinks }
TLazPackageLinks = class;
TPkgLinksState = (
plsUserLinksNeedUpdate,
plsGlobalLinksNeedUpdate
);
TPkgLinksStates = set of TPkgLinksState;
TLazPackageLinks = class(TPackageLinks)
private
// tree of global TPackageLink sorted for ID
FGlobalLinks: TAvlTree;
// tree of online TPackageLink sorted for ID
FOnlineLinks: TAvlTree;
// tree of user TPackageLink sorted for ID
FUserLinksSortID: TAvlTree;
// tree of user TPackageLink sorted for Filename and FileDate
FUserLinksSortFile: TAvlTree;
//
FQueueSaveUserLinks: boolean;
FChangeStamp: integer;
FSavedChangeStamp: integer;
fUpdateLock: integer;
FStates: TPkgLinksStates;
function AddUserLinkSub(APackage: TIDEPackage; const PkgFilename,
PkgName: string): TPackageLink;
function FindLeftMostNode(LinkTree: TAvlTree; const PkgName: string): TAvlTreeNode;
function FindLinkWithPkgNameInTree(LinkTree: TAvlTree;
const PkgName: string; IgnoreFiles: TFilenameToStringTree): TLazPackageLink;
function FindLinkWithDependencyInTree(LinkTree: TAvlTree;
Dependency: TPkgDependencyID; IgnoreFiles: TFilenameToStringTree): TLazPackageLink;
function FindLinkWithPackageIDInTree(LinkTree: TAvlTree;
APackageID: TLazPackageID): TLazPackageLink;
function FindLinkWithLPKFilenameInTree(LinkTree: TAvlTree;
const PkgName, LPKFilename: string): TLazPackageLink;
function GetModified: boolean;
procedure IteratePackagesInTree(MustExist: boolean; LinkTree: TAvlTree;
Event: TIteratePackagesEvent);
procedure SetModified(const AValue: boolean);
procedure SetQueueSaveUserLinks(AValue: boolean);
procedure OnAsyncSaveUserLinks({%H-}Data: PtrInt);
function GetNewerLink(Link1, Link2: TLazPackageLink): TLazPackageLink;
function GetNewestLink(Link1, Link2, Link3: TLazPackageLink): TLazPackageLink;
public
UserLinkLoadTime: longint;
UserLinkLoadTimeValid: boolean;
constructor Create;
destructor Destroy; override;
procedure Clear;
function GetUserLinkFile(WithPath: boolean = true): string;
function GetGlobalLinkDirectory: string;
procedure UpdateGlobalLinks; // reloads the lpl files, keeping LastUsed dates
procedure UpdateUserLinks; // reloads user links and global LastUsed dates
procedure UpdateAll;
procedure RemoveOldUserLinks;
procedure BeginUpdate;
procedure EndUpdate;
function IsUpdating: boolean;
procedure SaveUserLinks(Immediately: boolean = false); override;
function NeedSaveUserLinks(const ConfigFilename: string): boolean;
procedure WriteLinkTree(LinkTree: TAvlTree);
procedure IncreaseChangeStamp;
// Methods defined as interface in TLazPackageLinks.
function FindLinkWithPkgName(const PkgName: string): TPackageLink; override;
function FindLinkWithPkgNameWithIgnore(const PkgName: string;
IgnoreFiles: TFilenameToStringTree): TPackageLink;
function FindLinkWithDependency(Dependency: TPkgDependencyID): TPackageLink; override;
function FindLinkWithDependencyWithIgnore(Dependency: TPkgDependencyID;
IgnoreFiles: TFilenameToStringTree): TPackageLink;
function FindLinkWithPackageID(APackageID: TLazPackageID): TPackageLink; override;
function FindLinkWithFilename(const PkgName, LPKFilename: string): TPackageLink; override;
procedure IteratePackages(MustExist: boolean; Event: TIteratePackagesEvent;
Origins: TPkgLinkOrigins = AllPkgLinkOrigins); override;
function AddOnlineLink(const PkgFilename, PkgName, PkgURL: string): TPackageLink; override;
function AddUserLink(APackage: TIDEPackage): TPackageLink; override;
function AddUserLink(const PkgFilename, PkgName: string): TPackageLink; override;
procedure RemoveUserLink(Link: TPackageLink); override;
procedure RemoveUserLinks(APackageID: TLazPackageID); override;
procedure ClearOnlineLinks; override;
public
property Modified: boolean read GetModified write SetModified;
property ChangeStamp: integer read FChangeStamp;
property QueueSaveUserLinks: boolean read FQueueSaveUserLinks write SetQueueSaveUserLinks;
end;
var
LazPackageLinks: TLazPackageLinks = nil; // set by the PkgBoss
function ComparePackageLinks(Data1, Data2: Pointer): integer;
implementation
function ComparePackageIDAndLink(Key, Data: Pointer): integer;
var
Link: TLazPackageLink;
PkgID: TLazPackageID;
begin
if Key=nil then
Result:=-1
else begin
PkgID:=TLazPackageID(Key);
Link:=TLazPackageLink(Data);
Result:=PkgID.Compare(Link);
end;
end;
function ComparePkgNameAndLink(Key, Data: Pointer): integer;
var
PkgName: String;
Link: TLazPackageLink;
begin
if Key=nil then
Result:=-1
else begin
PkgName:=AnsiString(Key);
Link:=TLazPackageLink(Data);
Result:=CompareText(PkgName,Link.Name);
end;
end;
function ComparePackageLinks(Data1, Data2: Pointer): integer;
var
Link1: TLazPackageLink absolute Data1;
Link2: TLazPackageLink absolute Data2;
begin
Result:=Link1.Compare(Link2);
end;
function CompareLinksForFilename(Data1, Data2: Pointer): integer;
var
Link1: TLazPackageLink absolute Data1;
Link2: TLazPackageLink absolute Data2;
begin
Result:=CompareFilenames(Link1.LPKFilename,Link2.LPKFilename);
end;
function CompareLinksForFilenameAndFileAge(Data1, Data2: Pointer): integer;
var
Link1: TLazPackageLink absolute Data1;
Link2: TLazPackageLink absolute Data2;
begin
// first compare filenames
Result:=CompareFilenames(Link1.LPKFilename,Link2.LPKFilename);
if Result<>0 then exit;
// then compare file date
if Link1.LPKFileDateValid then begin
if Link2.LPKFileDateValid then begin
if Link1.LPKFileDate>Link2.LPKFileDate then
Result:=1
else if Link1.LPKFileDate<Link2.LPKFileDate then
Result:=-1;
end else begin
Result:=1;
end;
end else begin
if Link2.LPKFileDateValid then begin
Result:=-1;
end;
end;
if Result<>0 then exit;
// finally compare version and name
Result:=Link1.Compare(Link2);
end;
function dbgs(Origin: TPkgLinkOrigin): string;
begin
WriteStr(Result, Origin);
Delete(Result, 1, 3); // Remove the 'plo' prefix.
{ case Origin of
ploGlobal: Result:='Global';
ploOnline: Result:='Online';
ploUser: Result:='User';
else Result:='?';
end; }
end;
{ TLazPackageLink }
constructor TLazPackageLink.Create;
begin
inherited Create;
FAutoCheckExists:=true;
end;
destructor TLazPackageLink.Destroy;
begin
//debugln('TPackageLink.Destroy ',IDAsString,' ',dbgs(Pointer(Self)));
//if Origin=ploGlobal then RaiseGDBException('');
inherited Destroy;
end;
function TLazPackageLink.GetEffectiveFilename: string;
begin
Result:=LPKFilename;
if (Result<>'') and not FilenameIsAbsolute(Result) then
Result:=TrimFilename(EnvironmentOptions.GetParsedLazarusDirectory+PathDelim+Result);
end;
procedure TLazPackageLink.Reference;
begin
inc(fReferenceCount);
end;
procedure TLazPackageLink.Release;
begin
if fReferenceCount<=0 then RaiseGDBException('');
dec(fReferenceCount);
if fReferenceCount=0 then Free;
end;
{ TLazPackageLinks }
procedure TLazPackageLinks.OnAsyncSaveUserLinks(Data: PtrInt);
begin
SaveUserLinks(true);
end;
function TLazPackageLinks.GetNewerLink(Link1, Link2: TLazPackageLink): TLazPackageLink;
begin
if Link1=nil then
Result:=Link2
else if Link2=nil then
Result:=Link1
else if Link1.LastUsed>Link2.LastUsed then
Result:=Link1
else
Result:=Link2;
{DbgOut('TPackageLinks.GetNewerLink ');
if Link1<>nil then
DbgOut(' Link1=',Link1.IDAsString,'=',DateToCfgStr(Link1.LastUsed,DateTimeAsCfgStrFormat))
else
DbgOut(' Link1=nil');
if Link2<>nil then
DbgOut(' Link2=',Link2.IDAsString,'=',DateToCfgStr(Link2.LastUsed,DateTimeAsCfgStrFormat))
else
DbgOut(' Link2=nil');
if Result<>nil then
DbgOut(' Result=',Result.IDAsString,'=',DateToCfgStr(Result.LastUsed,DateTimeAsCfgStrFormat))
else
DbgOut(' Result=nil');
debugln;}
end;
function TLazPackageLinks.GetNewestLink(Link1, Link2, Link3: TLazPackageLink): TLazPackageLink;
begin
Result := GetNewerLink(GetNewerLink(Link1, Link2), Link3);
end;
function TLazPackageLinks.FindLeftMostNode(LinkTree: TAvlTree;
const PkgName: string): TAvlTreeNode;
// find left most link with PkgName
begin
Assert(PkgName<>'', 'TLazPackageLinks.FindLeftMostNode: PkgName is empty.');
//Result:=nil;
//if PkgName='' then exit;
Result:=LinkTree.FindLeftMostKey(PChar(PkgName),@ComparePkgNameAndLink);
end;
constructor TLazPackageLinks.Create;
begin
UserLinkLoadTimeValid:=false;
FGlobalLinks:=TAvlTree.Create(@ComparePackageLinks);
FOnlineLinks:=TAvlTree.Create(@ComparePackageLinks);
FUserLinksSortID:=TAvlTree.Create(@ComparePackageLinks);
FUserLinksSortFile:=TAvlTree.Create(@CompareLinksForFilenameAndFileAge);
FSavedChangeStamp:=CTInvalidChangeStamp;
FChangeStamp:=CTInvalidChangeStamp;
end;
destructor TLazPackageLinks.Destroy;
begin
Clear;
FreeAndNil(FUserLinksSortFile);
FreeAndNil(FUserLinksSortID);
FreeAndNil(FOnlineLinks);
FreeAndNil(FGlobalLinks);
inherited Destroy;
end;
procedure TLazPackageLinks.Clear;
begin
QueueSaveUserLinks:=false;
FGlobalLinks.FreeAndClear;
FOnlineLinks.FreeAndClear;
FUserLinksSortID.FreeAndClear;
FUserLinksSortFile.Clear;
FStates:=[plsUserLinksNeedUpdate,plsGlobalLinksNeedUpdate];
end;
function TLazPackageLinks.GetUserLinkFile(WithPath: boolean): string;
begin
Result:='packagefiles.xml';
if WithPath then
Result:=AppendPathDelim(GetPrimaryConfigPath)+Result;
end;
function TLazPackageLinks.GetGlobalLinkDirectory: string;
begin
Result:=AppendPathDelim(EnvironmentOptions.GetParsedLazarusDirectory)
+'packager'+PathDelim+'globallinks'+PathDelim;
end;
procedure TLazPackageLinks.UpdateGlobalLinks;
function ParseFilename(const Filename: string;
out PkgName: string; PkgVersion: TPkgVersion): boolean;
// checks if filename has the form
// <identifier>-<version>.lpl
var
StartPos: Integer;
i: Integer;
EndPos: Integer;
ints: array[1..4] of integer;
begin
Result:=false;
PkgName:='';
if not FilenameExtIs(Filename,'lpl',true) then exit;
StartPos:=1;
// parse identifier
if (StartPos>length(Filename))
or (not (Filename[StartPos] in ['a'..'z','A'..'Z'])) then exit;
inc(StartPos);
while (StartPos<=length(Filename))
and (Filename[StartPos] in ['.','a'..'z','A'..'Z','_','0'..'9']) do
inc(StartPos);
PkgName:=lowercase(copy(Filename,1,StartPos-1));
// parse -
if (StartPos>length(Filename)) or (Filename[StartPos]<>'-') then exit;
inc(StartPos);
// parse version (1-4 times 'int.')
for i:=Low(ints) to High(ints) do ints[i]:=0;
i:=Low(ints);
while i<=High(ints) do begin
// parse int
EndPos:=StartPos;
while (EndPos<=length(Filename))
and (Filename[EndPos] in ['0'..'9']) do inc(EndPos);
ints[i]:=StrToIntDef(copy(Filename,StartPos,EndPos-StartPos),-1);
if (ints[i]<0) or (ints[i]>99999) then exit;
StartPos:=EndPos;
// parse .
if (StartPos>length(Filename)) or (Filename[StartPos]<>'.') then exit;
if StartPos=length(Filename)-length('lpl') then break;
inc(StartPos);
inc(i);
end;
PkgVersion.Major:=ints[1];
PkgVersion.Minor:=ints[2];
PkgVersion.Release:=ints[3];
PkgVersion.Build:=ints[4];
Result:=true;
end;
var
GlobalLinksDir: String;
NewPkgName: string;
PkgVersion: TPkgVersion;
CurPkgLink, OldPkgLink, OtherPkgLink: TLazPackageLink;
sl: TStringList;
LPLFilename: String;
LPKFilename, LazDir: string;
Files: TStrings;
i: Integer;
OldNode, OtherNode: TAvlTreeNode;
UnmappedGlobalLinks, MappedGlobalLinks: TAvlTree;
begin
if fUpdateLock>0 then begin
Include(FStates,plsGlobalLinksNeedUpdate);
exit;
end;
Exclude(FStates,plsGlobalLinksNeedUpdate);
{$IFDEF VerboseGlobalPkgLinks}
debugln(['TPackageLinks.UpdateGlobalLinks START']);
{$ENDIF}
UnmappedGlobalLinks:=FGlobalLinks;
FGlobalLinks:=TAvlTree.Create(@ComparePackageLinks);
MappedGlobalLinks:=TAvlTree.Create(@ComparePackageLinks);
Files:=TStringList.Create;
PkgVersion:=TPkgVersion.Create;
try
GlobalLinksDir:=GetGlobalLinkDirectory;
CodeToolBoss.DirectoryCachePool.GetListing(GlobalLinksDir,Files,false);
LazDir:=EnvironmentOptions.GetParsedLazarusDirectory;
for i:=0 to Files.Count-1 do begin
LPLFilename:=GlobalLinksDir+Files[i];
if not FilenameExtIs(LPLFilename,'lpl',true) then continue;
if (not ParseFilename(Files[i],NewPkgName,PkgVersion))
then begin
DebugLn('Warning: (lazarus) suspicious pkg link file found (name): ',LPLFilename);
continue;
end;
LPKFilename:='';
sl:=TStringList.Create;
try
sl.LoadFromFile(LPLFilename);
if sl.Count<=0 then begin
DebugLn('Warning: (lazarus) pkg link file is empty: ',LPLFilename);
continue;
end;
LPKFilename:=GetForcedPathDelims(sl[0]);
except
on E: Exception do begin
DebugLn('Warning: (lazarus) unable to read pkg link file: ',LPLFilename,' : ',E.Message);
end;
end;
sl.Free;
if LPKFilename='' then begin
debugln(['Warning: (lazarus) TPackageLinks.UpdateGlobalLinks lpl file has empty first line: ',LPLFilename]);
continue;
end;
//debugln(['TPackageLinks.UpdateGlobalLinks NewFilename="',LPKFilename,'"']);
CurPkgLink:=TLazPackageLink.Create;
CurPkgLink.Reference;
CurPkgLink.Origin:=ploGlobal;
CurPkgLink.LPLFilename:=LPLFilename;
CurPkgLink.LPLFileDate:=FileAgeCached(LPLFilename);
CurPkgLink.Name:=NewPkgName;
CurPkgLink.Version.Assign(PkgVersion);
IDEMacros.SubstituteMacros(LPKFilename);
//debugln(['TPackageLinks.UpdateGlobalLinks EnvironmentOptions.LazarusDirectory=',LazDir]);
LPKFilename:=TrimFilename(LPKFilename);
if (FileIsInDirectory(LPKFilename,LazDir)) then
LPKFilename:=CreateRelativePath(LPKFilename,LazDir);
CurPkgLink.LPKFilename:=LPKFilename;
//debugln('TPackageLinks.UpdateGlobalLinks PkgName="',CurPkgLink.Name,'" ',
// ' PkgVersion=',CurPkgLink.Version.AsString,
// ' Filename="',CurPkgLink.LPKFilename,'"',
// ' MakeSense=',dbgs(CurPkgLink.IsMakingSense));
if CurPkgLink.IsMakingSense then begin
OldNode:=UnmappedGlobalLinks.Find(CurPkgLink);
if OldNode<>nil then begin
// keep LastUsed date for global link
OldPkgLink:=TLazPackageLink(OldNode.Data);
CurPkgLink.LastUsed:=OldPkgLink.LastUsed;
UnmappedGlobalLinks.Delete(OldNode);
MappedGlobalLinks.Add(OldPkgLink);
//if CompareText(OldPkgLink.Name,'lclbase')=0 then
// debugln(['TPackageLinks.UpdateGlobalLinks keeping LastUsed of '+OldPkgLink.Name,' ',DateToCfgStr(OldPkgLink.LastUsed,DateTimeAsCfgStrFormat)]);
end;
FGlobalLinks.Add(CurPkgLink);
end else begin
debugln('Warning: (lazarus) TPackageLinks.UpdateGlobalLinks Invalid lpl "',LPLFilename,'"'
,' PkgName="',CurPkgLink.Name,'" '
,' PkgVersion=',CurPkgLink.Version.AsString
,' Filename="',CurPkgLink.LPKFilename,'"');
CurPkgLink.Release;
end;
end;
// map unmapped global links (e.g. a package version changed)
// Note: When the IDE knows several versions of a lpk it loads the last one
// used (i.e. highest LastUsed date). When the version of the lpk
// increased on disk (e.g. svn update or user installed a new Lazarus
// version) the LastUsed date must be moved to the new lpk.
OldNode:=UnmappedGlobalLinks.FindLowest;
while OldNode<>nil do begin
OldPkgLink:=TLazPackageLink(OldNode.Data);
// this old lpl was not found in the new lpl files
//debugln(['TPackageLinks.UpdateGlobalLinks formerly used lpl '+OldPkgLink.IDAsString+' not found in new lpl directory -> searching new lpl ...']);
OldNode:=UnmappedGlobalLinks.FindSuccessor(OldNode);
OtherNode:=FindLeftMostNode(FGlobalLinks,OldPkgLink.Name);
while OtherNode<>nil do begin
OtherPkgLink:=TLazPackageLink(OtherNode.Data);
if CompareText(OtherPkgLink.Name,OldPkgLink.Name)<>0 then break;
OtherNode:=FGlobalLinks.FindSuccessor(OtherNode);
if MappedGlobalLinks.Find(OtherPkgLink)<>nil then continue;
// found a new lpl for the old lpl
if not UnmappedGlobalLinks.Remove(OldPkgLink) then
debugln(['TPackageLinks.UpdateGlobalLinks inconsistency UnmappedGlobalLinks.Remove']);
MappedGlobalLinks.Add(OldPkgLink);
if OtherPkgLink.LastUsed<OldPkgLink.LastUsed then begin
debugln(['Hint: (lazarus) [TPackageLinks.UpdateGlobalLinks] using LastUsed date of '+OldPkgLink.IDAsString+' for new '+OtherPkgLink.IDAsString+' in '+OtherPkgLink.LPKFilename]);
OtherPkgLink.LastUsed:=OldPkgLink.LastUsed;
end;
break;
end;
end;
//WriteLinkTree(FGlobalLinks);
finally
Files.Free;
PkgVersion.Free;
UnmappedGlobalLinks.FreeAndClear;
UnmappedGlobalLinks.Free;
MappedGlobalLinks.FreeAndClear;
MappedGlobalLinks.Free;
end;
end;
procedure TLazPackageLinks.UpdateUserLinks;
var
ConfigFilename: String;
Path: String;
XMLConfig: TXMLConfig;
LinkCount: Integer;
i: Integer;
NewPkgLink: TLazPackageLink;
ItemPath: String;
FileVersion: LongInt;
LastUsedFormat: String;
OtherNode, ANode: TAvlTreeNode;
OtherLink: TLazPackageLink;
UnmappedGlobalLinks, MappedGlobalLinks: TAvlTree;
begin
if fUpdateLock>0 then begin
Include(FStates,plsUserLinksNeedUpdate);
exit;
end;
Exclude(FStates,plsUserLinksNeedUpdate);
// check if file has changed
ConfigFilename:=GetUserLinkFile;
if UserLinkLoadTimeValid and FileExistsCached(ConfigFilename)
and (FileAgeCached(ConfigFilename)=UserLinkLoadTime) then
exit;
// copy system default if needed
CopySecondaryConfigFile(GetUserLinkFile(false));
FUserLinksSortID.FreeAndClear;
FUserLinksSortFile.Clear;
IncreaseChangeStamp;
FileVersion:=PkgLinksFileVersion;
XMLConfig:=nil;
try
XMLConfig:=TXMLConfig.Create(ConfigFilename);
// load user links
Path:='UserPkgLinks/';
FileVersion:=XMLConfig.GetValue(Path+'Version',0);
LinkCount:=XMLConfig.GetValue(Path+'Count',0);
if FileVersion<3 then
LastUsedFormat:=DateAsCfgStrFormat
else
LastUsedFormat:=DateTimeAsCfgStrFormat;
for i:=1 to LinkCount do begin
ItemPath:=Path+'Item'+IntToStr(i)+'/';
NewPkgLink:=TLazPackageLink.Create;
NewPkgLink.Reference;
NewPkgLink.Origin:=ploUser;
NewPkgLink.Name:=XMLConfig.GetValue(ItemPath+'Name/Value','');
PkgVersionLoadFromXMLConfig(NewPkgLink.Version,XMLConfig,ItemPath+'Version/',
FileVersion);
NewPkgLink.LPKFilename:=XMLConfig.GetValue(ItemPath+'Filename/Value','');
NewPkgLink.AutoCheckExists:=
XMLConfig.GetValue(ItemPath+'AutoCheckExists/Value',true);
NewPkgLink.LPKFileDateValid:=
XMLConfig.GetValue(ItemPath+'FileDateValid/Value',false);
if NewPkgLink.LPKFileDateValid then begin
NewPkgLink.LPKFileDateValid:=
CfgStrToDate(XMLConfig.GetValue(ItemPath+'FileDate/Value',''),
NewPkgLink.FFileDate);
end;
if not CfgStrToDate(XMLConfig.GetValue(ItemPath+'LastUsed/Value',''),
NewPkgLink.FLastUsed,LastUsedFormat)
then
NewPkgLink.FLastUsed := 0;
//if CompareText(NewPkgLink.Name,'lclbase')=0 then
// debugln(['TPackageLinks.UpdateUserLinks ',NewPkgLink.IDAsString,' ',DateToCfgStr(NewPkgLink.LastUsed,DateTimeAsCfgStrFormat)]);
if not NewPkgLink.IsMakingSense then begin
debugln(['Warning: (lazarus) TPackageLinks.UpdateUserLinks invalid link: ',NewPkgLink.IDAsString]);
NewPkgLink.Release;
continue;
end;
OtherNode:=FUserLinksSortFile.FindKey(NewPkgLink,@CompareLinksForFilename);
if OtherNode<>nil then begin
// a link to the same file
OtherLink:=TLazPackageLink(OtherNode.Data);
if ConsoleVerbosity>0 then
debugln(['Warning: (lazarus) TPackageLinks.UpdateUserLinks two links for file: ',NewPkgLink.LPKFilename,' A=',OtherLink.IDAsString,' B=',NewPkgLink.IDAsString]);
if OtherLink.LastUsed<NewPkgLink.LastUsed then begin
if ConsoleVerbosity>0 then
debugln(['Warning: (lazarus) TPackageLinks.UpdateUserLinks ignoring older link ',OtherLink.IDAsString]);
FUserLinksSortID.RemovePointer(OtherLink);
FUserLinksSortFile.Delete(OtherNode);
OtherLink.Release;
end else begin
if ConsoleVerbosity>0 then
debugln(['Warning: (lazarus) TPackageLinks.UpdateUserLinks ignoring older link ',NewPkgLink.IDAsString]);
NewPkgLink.Release;
continue;
end;
end;
FUserLinksSortID.Add(NewPkgLink);
FUserLinksSortFile.Add(NewPkgLink);
end;
// load LastUsed dates of global links
Path:='GlobalPkgLinks/';
LinkCount:=XMLConfig.GetValue(Path+'Count',0);
UnmappedGlobalLinks:=TAvlTree.Create(@ComparePackageLinks);
MappedGlobalLinks:=TAvlTree.Create(@ComparePackageLinks);
try
for i:=1 to LinkCount do begin
ItemPath:=Path+'Item'+IntToStr(i)+'/';
NewPkgLink:=TLazPackageLink.Create; // create temporary TLazPackageLink
if not CfgStrToDate(XMLConfig.GetValue(ItemPath+'LastUsed/Value',''),
NewPkgLink.FLastUsed,LastUsedFormat)
then begin
debugln(['Hint: (lazarus) [TPackageLinks.UpdateUserLinks] ignoring invalid entry '+ItemPath]);
NewPkgLink.Free;
continue;
end;
NewPkgLink.Name:=XMLConfig.GetValue(ItemPath+'Name/Value','');
PkgVersionLoadFromXMLConfig(NewPkgLink.Version,XMLConfig,ItemPath+'Version/',
FileVersion);
if not IsValidPkgName(NewPkgLink.Name) then begin
debugln(['Hint: (lazarus) [TPackageLinks.UpdateUserLinks] ignoring invalid global link LastUsed of '+NewPkgLink.IDAsString]);
NewPkgLink.Free;
continue;
end;
//if CompareText(NewPkgLink.Name,'lclbase')=0 then
// debugln(['TPackageLinks.UpdateUserLinks ',NewPkgLink.IDAsString,' LastUsed=',DateToCfgStr(NewPkgLink.LastUsed,DateTimeAsCfgStrFormat)]);
OtherNode:=FGlobalLinks.Find(NewPkgLink);
if OtherNode<>nil then begin
// global link (.lpl) still exists -> load LastUsed date
OtherLink:=TLazPackageLink(OtherNode.Data);
MappedGlobalLinks.Add(NewPkgLink);
if OtherLink.LastUsed<NewPkgLink.LastUsed then
OtherLink.LastUsed:=NewPkgLink.LastUsed;
//if CompareText(OtherLink.Name,'lclbase')=0 then
// debugln(['TPackageLinks.UpdateUserLinks updating LastUsed of '+OtherLink.Name,' ',DateToCfgStr(OtherLink.LastUsed,DateTimeAsCfgStrFormat)]);
continue;
end;
// this global link does not exist (e.g. the version has changed)
// => check after all data was loaded
if UnmappedGlobalLinks.Find(NewPkgLink)<>nil then
NewPkgLink.Free
else
UnmappedGlobalLinks.Add(NewPkgLink);
end;
// map unmapped global links to new global links
// Note: When the IDE knows several versions of a lpk it loads the last one
// used (i.e. highest LastUsed date). When the version of the lpk
// increased on disk (e.g. svn update or user installed a new Lazarus
// version) the LastUsed date must be moved to the new lpk.
ANode:=UnmappedGlobalLinks.FindLowest;
while ANode<>nil do begin
NewPkgLink:=TLazPackageLink(ANode.Data);
//debugln(['TPackageLinks.UpdateUserLinks LastUsed date of '+NewPkgLink.IDAsString+' has no lpl file -> searching a new lpl file ...']);
ANode:=UnmappedGlobalLinks.FindSuccessor(ANode);
// check all global links with same pkg name
OtherNode:=FindLeftMostNode(FGlobalLinks,NewPkgLink.Name);
while (OtherNode<>nil) do begin
OtherLink:=TLazPackageLink(OtherNode.Data);
if CompareText(OtherLink.Name,NewPkgLink.Name)<>0 then break;
OtherNode:=FGlobalLinks.FindSuccessor(OtherNode);
if MappedGlobalLinks.Find(OtherLink)<>nil then
continue;// this lpl LastUsed date was already set
// this lpl LastUsed date was not yet set => set it
UnmappedGlobalLinks.Remove(NewPkgLink);
MappedGlobalLinks.Add(NewPkgLink);
if OtherLink.LastUsed<NewPkgLink.LastUsed then begin
debugln(['Hint: (lazarus) [TPackageLinks.UpdateUserLinks] using LastUsed date of old '+NewPkgLink.IDAsString+' for '+OtherLink.IDAsString+' in '+OtherLink.LPKFilename]);
OtherLink.LastUsed:=NewPkgLink.LastUsed;
end;
break;
end;
end;
finally
MappedGlobalLinks.FreeAndClear;
MappedGlobalLinks.Free;
UnmappedGlobalLinks.FreeAndClear;
UnmappedGlobalLinks.Free;
end;
XMLConfig.Modified:=false;
XMLConfig.Free;
UserLinkLoadTime:=FileAgeCached(ConfigFilename);
UserLinkLoadTimeValid:=true;
except
on E: Exception do begin
DebugLn('Note: (lazarus) unable to read ',ConfigFilename,' ',E.Message);
exit;
end;
end;
RemoveOldUserLinks;
Modified:=FileVersion<>PkgLinksFileVersion;
end;
procedure TLazPackageLinks.UpdateAll;
begin
UpdateGlobalLinks;
UpdateUserLinks;
end;
procedure TLazPackageLinks.RemoveOldUserLinks;
// search for links pointing to the same file but older version
var
ANode: TAvlTreeNode;
NextNode: TAvlTreeNode;
OldPkgLink: TLazPackageLink;
NewPkgLink: TLazPackageLink;
begin
// sort UserLinks for filename
ANode:=FUserLinksSortFile.FindLowest;
while ANode<>nil do begin
NextNode:=FUserLinksSortFile.FindSuccessor(ANode);
if NextNode=nil then break;
OldPkgLink:=TLazPackageLink(ANode.Data);
NewPkgLink:=TLazPackageLink(NextNode.Data);
if CompareFilenames(OldPkgLink.GetEffectiveFilename,
NewPkgLink.GetEffectiveFilename)=0
then begin
// two links to the same file -> delete the older
//debugln('TPackageLinks.RemoveOldUserLinks',
// ' Newer=',NewPkgLink.IDAsString,'=',dbgs(Pointer(NewPkgLink)),
// ' Older=',OldPkgLink.IDAsString,'=',dbgs(Pointer(OldPkgLink)));
FUserLinksSortID.RemovePointer(OldPkgLink);
FUserLinksSortFile.RemovePointer(OldPkgLink);
OldPkgLink.Release;
end;
ANode:=NextNode;
end;
end;
procedure TLazPackageLinks.BeginUpdate;
begin
inc(fUpdateLock);
end;
procedure TLazPackageLinks.EndUpdate;
begin
if fUpdateLock<=0 then RaiseGDBException('TPackageLinks.EndUpdate');
dec(fUpdateLock);
if (plsGlobalLinksNeedUpdate in FStates) then UpdateGlobalLinks;
if (plsUserLinksNeedUpdate in FStates) then UpdateUserLinks;
end;
function TLazPackageLinks.IsUpdating: boolean;
begin
Result:=fUpdateLock>0;
end;
procedure TLazPackageLinks.SaveUserLinks(Immediately: boolean);
var
ConfigFilename: String;
Path: String;
CurPkgLink: TLazPackageLink;
XMLConfig: TXMLConfig;
ANode: TAvlTreeNode;
ItemPath: String;
i: Integer;
LazSrcDir: String;
AFilename: String;
begin
//debugln(['TPackageLinks.SaveUserLinks ']);
if (FUserLinksSortFile=nil) or (FUserLinksSortFile.Count=0) then exit;
ConfigFilename:=GetUserLinkFile;
// check if file needs saving
if not NeedSaveUserLinks(ConfigFilename) then exit;
if ConsoleVerbosity>1 then
DebugLn(['Hint: (lazarus) TPackageLinks.SaveUserLinks saving ... ',ConfigFilename,' Modified=',Modified,' UserLinkLoadTimeValid=',UserLinkLoadTimeValid,' ',FileAgeUTF8(ConfigFilename)=UserLinkLoadTime,' Immediately=',Immediately]);
if Immediately then begin
QueueSaveUserLinks:=false;
end else begin
QueueSaveUserLinks:=true;
exit;
end;
LazSrcDir:=EnvironmentOptions.GetParsedLazarusDirectory;
XMLConfig:=nil;
try
XMLConfig:=TXMLConfig.CreateClean(ConfigFilename);
// store user links
Path:='UserPkgLinks/';
XMLConfig.SetValue(Path+'Version',PkgLinksFileVersion);
ANode:=FUserLinksSortID.FindLowest;
i:=0;
while ANode<>nil do begin
CurPkgLink:=TLazPackageLink(ANode.Data);
ANode:=FUserLinksSortID.FindSuccessor(ANode);
inc(i);
ItemPath:=Path+'Item'+IntToStr(i)+'/';
XMLConfig.SetDeleteValue(ItemPath+'Name/Value',CurPkgLink.Name,'');
//debugln(['TPackageLinks.SaveUserLinks ',CurPkgLink.Name,' ',dbgs(Pointer(CurPkgLink))]);
PkgVersionSaveToXMLConfig(CurPkgLink.Version,XMLConfig,ItemPath+'Version/');
// save package files in lazarus directory relative
AFilename:=CurPkgLink.LPKFilename;
if (LazSrcDir<>'') and FileIsInPath(AFilename,LazSrcDir) then begin
AFilename:=CreateRelativePath(AFilename,LazSrcDir);
//DebugLn(['TPackageLinks.SaveUserLinks ',AFilename]);
end;
XMLConfig.SetDeleteValue(ItemPath+'Filename/Value',AFilename,'');
XMLConfig.SetDeleteValue(ItemPath+'LastUsed/Value',
DateToCfgStr(CurPkgLink.LastUsed,DateTimeAsCfgStrFormat),'');
end;
XMLConfig.SetDeleteValue(Path+'Count',i,0);
// store LastUsed dates of global links
Path:='GlobalPkgLinks/';
XMLConfig.SetValue(Path+'Version',PkgLinksFileVersion);
i:=0;
ANode:=FGlobalLinks.FindLowest;
while ANode<>nil do begin
CurPkgLink:=TLazPackageLink(ANode.Data);
ANode:=FGlobalLinks.FindSuccessor(ANode);
if CurPkgLink.LastUsed<=0 then continue;
inc(i);
ItemPath:=Path+'Item'+IntToStr(i)+'/';
XMLConfig.SetDeleteValue(ItemPath+'Name/Value',CurPkgLink.Name,'');
PkgVersionSaveToXMLConfig(CurPkgLink.Version,XMLConfig,ItemPath+'Version/');
XMLConfig.SetDeleteValue(ItemPath+'LastUsed/Value',
DateToCfgStr(CurPkgLink.LastUsed,DateTimeAsCfgStrFormat),'');
end;
XMLConfig.SetDeleteValue(Path+'Count',i,0);
InvalidateFileStateCache(ConfigFilename);
XMLConfig.Flush;
XMLConfig.Free;
UserLinkLoadTime:=FileAgeCached(ConfigFilename);
UserLinkLoadTimeValid:=true;
except
on E: Exception do begin
DebugLn('Note: (lazarus) unable to read ',ConfigFilename,' ',E.Message);
exit;
end;
end;
Modified:=false;
end;
function TLazPackageLinks.NeedSaveUserLinks(const ConfigFilename: string): boolean;
begin
Result:=Modified
or (not UserLinkLoadTimeValid)
or (not FileExistsCached(ConfigFilename))
or (FileAgeCached(ConfigFilename)<>UserLinkLoadTime);
end;
procedure TLazPackageLinks.WriteLinkTree(LinkTree: TAvlTree);
var
ANode: TAvlTreeNode;
Link: TLazPackageLink;
begin
if LinkTree=nil then exit;
ANode:=LinkTree.FindLowest;
while ANode<>nil do begin
Link:=TLazPackageLink(ANode.Data);
debugln(' ',Link.IDAsString);
ANode:=LinkTree.FindSuccessor(ANode);
end;
end;
function TLazPackageLinks.FindLinkWithPkgNameInTree(LinkTree: TAvlTree;
const PkgName: string; IgnoreFiles: TFilenameToStringTree): TLazPackageLink;
// find left most link with PkgName
var
CurNode: TAvlTreeNode;
Link: TLazPackageLink;
begin
Result:=nil;
if PkgName='' then exit;
CurNode:=FindLeftMostNode(LinkTree,PkgName);
while CurNode<>nil do begin
Link:=TLazPackageLink(CurNode.Data);
if (CompareText(PkgName,Link.Name)=0)
and ((IgnoreFiles=nil) or (not IgnoreFiles.Contains(Link.GetEffectiveFilename)))
then begin
if Result=nil then
Result:=Link
else begin
// there are two packages fitting
if ((Link.LastUsed>Result.LastUsed)
or ((Abs(Link.LastUsed-Result.LastUsed)<1/86400)
and (Link.Version.Compare(Result.Version)>0)))
and FileExistsCached(Link.GetEffectiveFilename) then
Result:=Link; // this one is better
end;
end;
CurNode:=LinkTree.FindSuccessor(CurNode);
if CurNode=nil then break;
if CompareText(TLazPackageLink(CurNode.Data).Name,PkgName)<>0
then
break;
end;
end;
function TLazPackageLinks.FindLinkWithDependencyInTree(LinkTree: TAvlTree;
Dependency: TPkgDependencyID; IgnoreFiles: TFilenameToStringTree): TLazPackageLink;
var
Link: TLazPackageLink;
CurNode: TAvlTreeNode;
{$IFDEF VerbosePkgLinkSameName}
Node1: TAvlTreeNode;
{$ENDIF}
begin
Result:=nil;
if (Dependency=nil) or (not Dependency.IsMakingSense) then begin
DebugLn(['Warning: (lazarus) TPackageLinks.FindLinkWithDependencyInTree Dependency makes no sense']);
exit;
end;
{$IFDEF VerbosePkgLinkSameName}
if CompareText(Dependency.PackageName,'tstver')=0 then
debugln(['TPackageLinks.FindLinkWithDependencyInTree START ',Dependency.AsString(true)]);
{$ENDIF}
// if there are several fitting the description, use the last used
// and highest version
CurNode:=FindLeftMostNode(LinkTree,Dependency.PackageName);
{$IFDEF VerbosePkgLinkSameName}
if CompareText(Dependency.PackageName,'tstver')=0 then begin
Node1:=CurNode.Precessor;
if Node1<>nil then
debugln(['TPackageLinks.FindLinkWithDependencyInTree Precessor=',TPackageLink(Node1.Data).IDAsString]);
Node1:=CurNode.Successor;
if Node1<>nil then
debugln(['TPackageLinks.FindLinkWithDependencyInTree Successor=',TPackageLink(Node1.Data).IDAsString]);
end;
{$ENDIF}
while CurNode<>nil do begin
Link:=TLazPackageLink(CurNode.Data);
{$IFDEF VerbosePkgLinkSameName}
if CompareText(Dependency.PackageName,'tstver')=0 then
debugln(['TPackageLinks.FindLinkWithDependencyInTree Link=',Link.IDAsString]);
{$ENDIF}
if Dependency.IsCompatible(Link.Version)
and ((IgnoreFiles=nil) or (not IgnoreFiles.Contains(Link.GetEffectiveFilename)))
then begin
if Result=nil then
Result:=Link
else begin
{$IFDEF VerbosePkgLinkSameName}
if CompareText(Dependency.PackageName,'tstver')=0 then
debugln(['TPackageLinks.FindLinkWithDependencyInTree Link=',Link.IDAsString,' LastUsed=',DateTimeToStr(Link.LastUsed),' Result=',Result.IDAsString,' LastUsed=',DateTimeToStr(Result.LastUsed)]);
{$ENDIF}
// there are two packages fitting
if ((Link.LastUsed>Result.LastUsed)
or ((Abs(Link.LastUsed-Result.LastUsed)<1/86400)
and (Link.Version.Compare(Result.Version)>0)))
and FileExistsCached(Link.GetEffectiveFilename) then
Result:=Link; // this one is better
end;
end;
CurNode:=LinkTree.FindSuccessor(CurNode);
if CurNode=nil then break;
if CompareText(TLazPackageLink(CurNode.Data).Name,Dependency.PackageName)<>0
then
break;
end;
end;
function TLazPackageLinks.FindLinkWithPackageIDInTree(LinkTree: TAvlTree;
APackageID: TLazPackageID): TLazPackageLink;
var
ANode: TAvlTreeNode;
begin
ANode:=LinkTree.FindKey(APackageID,@ComparePackageIDAndLink);
if ANode<>nil then
Result:=TLazPackageLink(ANode.Data)
else
Result:=nil;
end;
function TLazPackageLinks.FindLinkWithLPKFilenameInTree(LinkTree: TAvlTree;
const PkgName, LPKFilename: string): TLazPackageLink;
var
CurNode: TAvlTreeNode;
begin
CurNode:=FindLeftMostNode(LinkTree,PkgName);
while CurNode<>nil do begin
Result:=TLazPackageLink(CurNode.Data);
if CompareText(PkgName,Result.Name)<>0 then break;
// Treat URLs and filenames differently.
if Result.Origin = ploOnline then begin
if LPKFilename = Result.LPKFilename then exit;
end
else begin
if CompareFilenames(Result.GetEffectiveFilename,LPKFilename)=0 then exit;
end;
CurNode:=LinkTree.FindSuccessor(CurNode);
end;
Result:=nil;
end;
function TLazPackageLinks.GetModified: boolean;
begin
Result:=FSavedChangeStamp<>FChangeStamp;
end;
procedure TLazPackageLinks.IteratePackagesInTree(MustExist: boolean;
LinkTree: TAvlTree; Event: TIteratePackagesEvent);
var
ANode: TAvlTreeNode;
PkgLink: TLazPackageLink;
AFilename: String;
begin
ANode:=LinkTree.FindLowest;
while ANode<>nil do begin
PkgLink:=TLazPackageLink(ANode.Data);
//debugln('TPackageLinks.IteratePackagesInTree PkgLink.Filename=',PkgLink.LPKFilename);
AFilename:=PkgLink.GetEffectiveFilename;
if (not MustExist) or FileExistsUTF8(AFilename) then
Event(PkgLink);
ANode:=LinkTree.FindSuccessor(ANode);
end;
end;
procedure TLazPackageLinks.SetModified(const AValue: boolean);
begin
if Modified=AValue then exit;
if not AValue then
FSavedChangeStamp:=FChangeStamp
else
IncreaseChangeStamp;
end;
procedure TLazPackageLinks.SetQueueSaveUserLinks(AValue: boolean);
begin
if FQueueSaveUserLinks=AValue then Exit;
FQueueSaveUserLinks:=AValue;
if Application=nil then exit;
if FQueueSaveUserLinks then
Application.QueueAsyncCall(@OnAsyncSaveUserLinks,0)
else
Application.RemoveAsyncCalls(Self);
end;
function TLazPackageLinks.FindLinkWithPkgName(const PkgName: string): TPackageLink;
begin
Result := FindLinkWithPkgNameWithIgnore(PkgName, Nil);
end;
function TLazPackageLinks.FindLinkWithPkgNameWithIgnore(const PkgName: string;
IgnoreFiles: TFilenameToStringTree): TPackageLink;
var
UserLink, OnlineLink, GlobalLink: TLazPackageLink;
begin
UserLink:=FindLinkWithPkgNameInTree(FUserLinksSortID,PkgName,IgnoreFiles);
OnlineLink:=FindLinkWithPkgNameInTree(FOnlineLinks,PkgName,IgnoreFiles);
GlobalLink:=FindLinkWithPkgNameInTree(FGlobalLinks,PkgName,IgnoreFiles);
Result:=GetNewestLink(UserLink, OnlineLink, GlobalLink);
end;
function TLazPackageLinks.FindLinkWithDependency(Dependency: TPkgDependencyID): TPackageLink;
begin
Result := FindLinkWithDependencyWithIgnore(Dependency, Nil);
end;
function TLazPackageLinks.FindLinkWithDependencyWithIgnore(Dependency: TPkgDependencyID;
IgnoreFiles: TFilenameToStringTree): TPackageLink;
var
UserLink, OnlineLink, GlobalLink: TLazPackageLink;
begin
UserLink:=FindLinkWithDependencyInTree(FUserLinksSortID,Dependency,IgnoreFiles);
OnlineLink:=FindLinkWithDependencyInTree(FOnlineLinks,Dependency,IgnoreFiles);
GlobalLink:=FindLinkWithDependencyInTree(FGlobalLinks,Dependency,IgnoreFiles);
Result:=GetNewestLink(UserLink, OnlineLink, GlobalLink);
end;
function TLazPackageLinks.FindLinkWithPackageID(APackageID: TLazPackageID): TPackageLink;
var
UserLink, OnlineLink, GlobalLink: TLazPackageLink;
begin
UserLink:=FindLinkWithPackageIDInTree(FUserLinksSortID,APackageID);
OnlineLink:=FindLinkWithPackageIDInTree(FOnlineLinks,APackageID);
GlobalLink:=FindLinkWithPackageIDInTree(FGlobalLinks,APackageID);
Result:=GetNewestLink(UserLink, OnlineLink, GlobalLink);
end;
function TLazPackageLinks.FindLinkWithFilename(const PkgName, LPKFilename: string): TPackageLink;
var
UserLink, OnlineLink, GlobalLink: TLazPackageLink;
begin
UserLink :=FindLinkWithLPKFilenameInTree(FUserLinksSortID,PkgName,LPKFilename);
OnlineLink:=FindLinkWithLPKFilenameInTree(FOnlineLinks,PkgName,LPKFilename);
GlobalLink:=FindLinkWithLPKFilenameInTree(FGlobalLinks,PkgName,LPKFilename);
Result:=GetNewestLink(UserLink, OnlineLink, GlobalLink);
end;
procedure TLazPackageLinks.IteratePackages(MustExist: boolean;
Event: TIteratePackagesEvent; Origins: TPkgLinkOrigins);
begin
if ploUser in Origins then
IteratePackagesInTree(MustExist,FUserLinksSortID,Event);
//online packages are always virtual(meaning: the lpk does not exist localy)==> MustExist is false
if ploOnline in Origins then
IteratePackagesInTree(False,FOnlineLinks,Event);
if ploGlobal in Origins then
IteratePackagesInTree(MustExist,FGlobalLinks,Event);
end;
function TLazPackageLinks.AddOnlineLink(const PkgFilename, PkgName,
PkgURL: string): TPackageLink;
begin
//DebugLn(['TLazPackageLinks.AddOnlineLink: PkgFilename=', PkgFilename, ', PkgName=', PkgName]);
Result := FindLinkWithFilename(PkgName, PkgFilename);
if Assigned(Result) then
begin
if Result.LPKUrl = PkgURL then
begin
Result := nil;
Exit;
end;
end;
Result := TLazPackageLink.Create;
Result.Reference;
if IsValidPkgName(PkgName) then
begin
Result.Name := PkgName;
Result.LPKUrl := PkgURL;
Result.Origin := ploOnline;
Result.OPMFileName := PkgFilename;
FOnlineLinks.Add(Result);
end
else begin
Result.Release;
Result := nil;
end;
end;
function TLazPackageLinks.AddUserLinkSub(APackage: TIDEPackage;
const PkgFilename, PkgName: string): TPackageLink;
begin
Result:=TLazPackageLink.Create;
Result.Reference;
if Assigned(APackage) then
Result.AssignID(APackage)
else
Result.Name:=PkgName;
Result.LPKFilename:=PkgFilename;
if Result.IsMakingSense then
begin
FUserLinksSortID.Add(Result);
FUserLinksSortFile.Add(Result);
IncreaseChangeStamp;
Result.LastUsed:=Now;
end
else begin
Result.Release;
Result:=nil;
end;
end;
function TLazPackageLinks.AddUserLink(APackage: TIDEPackage): TPackageLink;
var
OldLink: TPackageLink;
begin
//DebugLn(['Hint: TLazPackageLinks.AddUserLink: APackage=', APackage.Filename]);
BeginUpdate;
try
// check if link already exists
OldLink:=FindLinkWithPackageID(APackage);
if (OldLink<>nil) then begin
// link exists -> check if it is already the right value
if (OldLink.Compare(APackage)=0)
and (OldLink.GetEffectiveFilename=APackage.Filename) then begin
Result:=OldLink;
Result.LastUsed:=Now;
IncreaseChangeStamp;
exit;
end;
RemoveUserLinks(APackage);
end;
// add user link
Result := AddUserLinkSub(APackage, APackage.Filename, '');
finally
EndUpdate;
end;
end;
function TLazPackageLinks.AddUserLink(const PkgFilename, PkgName: string): TPackageLink;
var
LPK: TXMLConfig;
PkgVersion: TPkgVersion;
begin
//DebugLn(['Hint: TLazPackageLinks.AddUserLink: PkgFilename=', PkgFilename, ', PkgName=', PkgName]);
PkgVersion:=TPkgVersion.Create;
LPK:=nil;
BeginUpdate;
try
// load version
LPK:=LoadXMLConfigViaCodeBuffer(PkgFilename);
if LPK<>nil then
PkgVersionLoadFromXMLConfig(PkgVersion,LPK);
// check if link already exists
Result:=FindLinkWithFilename(PkgName,PkgFilename);
if Assigned(Result) then
begin
Result.LastUsed:=Now;
if LPK<>nil then
Result.Version.Assign(PkgVersion);
exit;
end;
// add user link
Result := AddUserLinkSub(Nil, PkgFilename, PkgName);
if Assigned(Result) and Assigned(LPK) then
Result.Version.Assign(PkgVersion);
finally
EndUpdate;
PkgVersion.Free;
LPK.Free;
end;
end;
procedure TLazPackageLinks.RemoveUserLink(Link: TPackageLink);
var
ANode: TAvlTreeNode;
begin
BeginUpdate;
try
// remove from user links
ANode:=FUserLinksSortFile.FindPointer(Link);
if ANode<>nil then begin
FUserLinksSortID.RemovePointer(Link);
FUserLinksSortFile.RemovePointer(Link);
Link.Release;
IncreaseChangeStamp;
end;
finally
EndUpdate;
end;
end;
procedure TLazPackageLinks.ClearOnlineLinks;
var
Link: TPackageLink;
Node: TAVLTreeNode;
begin
BeginUpdate;
try
for Node in FOnlineLinks do begin
Link := TPackageLink(Node.Data);
Link.Release;
IncreaseChangeStamp;
end;
FOnlineLinks.Clear;
finally
EndUpdate;
end;
end;
procedure TLazPackageLinks.RemoveUserLinks(APackageID: TLazPackageID);
var
ANode: TAvlTreeNode;
OldLink: TLazPackageLink;
begin
BeginUpdate;
try
// remove from user links
repeat
ANode:=FUserLinksSortID.FindKey(APackageID,@ComparePackageIDAndLink);
if ANode=nil then exit;
OldLink:=TLazPackageLink(ANode.Data);
FUserLinksSortID.Delete(ANode);
FUserLinksSortFile.RemovePointer(OldLink);
OldLink.Release;
IncreaseChangeStamp;
until false;
finally
EndUpdate;
end;
end;
procedure TLazPackageLinks.IncreaseChangeStamp;
begin
CTIncreaseChangeStamp(FChangeStamp);
end;
initialization
LazPackageLinks:=nil;
end.