{ /*************************************************************************** pkgmanager.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 . 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: Dialog showing the package links of the IDE package systems. } unit PkgLinksDlg; {$mode objfpc}{$H+} interface uses Classes, SysUtils, AVL_Tree, // LCL Forms, Controls, StdCtrls, Buttons, Grids, ExtCtrls, ComCtrls, Menus, Dialogs, // Codetools FileProcs, // LazUtils LazUTF8, LazFileUtils, LazFileCache, // IdeIntf IdeIntfStrConsts, PackageLinkIntf, PackageIntf, IDEDialogs, // IDE LazarusIDEStrConsts, PackageLinks, LPKCache; type { TPkgLinkInfo } TPkgLinkInfo = class(TLazPackageLink) private FEffectiveFilename: string; FIsValid: boolean; FLPKInfo: TLPKInfo; FVisible: boolean; public constructor Create; override; destructor Destroy; override; procedure Assign(Source: TPersistent); override; property Origin; property LPKUrl; property LPKInfo: TLPKInfo read FLPKInfo; property Visible: boolean read FVisible write FVisible; property IsValid: boolean read FIsValid write FIsValid; property EffectiveFilename: string read FEffectiveFilename write FEffectiveFilename; property OPMFileDate; end; { TPackageLinksDialog } TPackageLinksDialog = class(TForm) BtnPanel: TPanel; CloseBitBtn: TBitBtn; DeleteSelectedButton: TButton; FilterEdit: TEdit; LPKFileValidCheckBox: TCheckBox; LPKFileInvalidCheckBox: TCheckBox; LPKParsingTimer: TTimer; CopyCellToClipboardMenuItem: TMenuItem; GridPopupMenu: TPopupMenu; ShowGlobalLinksCheckBox: TCheckBox; ShowOnlineLinksCheckBox: TCheckBox; ShowUserLinksCheckBox: TCheckBox; ScopeGroupBox: TGroupBox; PkgStringGrid: TStringGrid; ProgressBar1: TProgressBar; UpdateGlobalLinksButton: TButton; procedure CopyCellToClipboardMenuItemClick(Sender: TObject); procedure DeleteSelectedButtonClick(Sender: TObject); procedure FilterEditChange(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure LPKFileValidCheckBoxChange(Sender: TObject); procedure LPKFileInvalidCheckBoxChange(Sender: TObject); procedure LPKParsingTimerTimer(Sender: TObject); procedure OnAllLPKParsed(Sender: TObject); procedure ShowLinksCheckBoxChange(Sender: TObject); procedure ShowOnlineLinksCheckBoxChange(Sender: TObject); procedure UpdateGlobalLinksButtonClick(Sender: TObject); private FCountGlobalLinks: integer; FCountLPKValid: integer; FCountLPKInvalid: integer; FCountOnlineLinks: Integer; FCountUserLinks: Integer; FLinks: TAVLTree; // tree of TPkgLinkInfo sorted for name and version FCollectingOrigin: TPkgLinkOrigin; procedure RescanGlobalLinks; procedure UpdateFacets; procedure UpdatePackageList; procedure ClearLinks; procedure IteratePackages(APackage: TLazPackageID); function GetLinkAtRow(Row: integer): TPkgLinkInfo; function GetLinkWithEffectiveFilename(Filename: string; Origins: TPkgLinkOrigins): TPkgLinkInfo; procedure PackageListAvailable(Sender: TObject); public property CountLPKValid: integer read FCountLPKValid; property CountLPKInvalid: integer read FCountLPKInvalid; property CountOnlineLinks: Integer read FCountOnlineLinks; property CountUserLinks: Integer read FCountUserLinks; property CountGlobalLinks: integer read FCountGlobalLinks; end; function ShowPackageLinks: TModalResult; implementation {$R *.lfm} function ShowPackageLinks: TModalResult; var PackageLinksDialog: TPackageLinksDialog; begin PackageLinksDialog:=TPackageLinksDialog.Create(nil); try Result:=PackageLinksDialog.ShowModal; finally PackageLinksDialog.Free; end; end; { TPackageLinksDialog } procedure TPackageLinksDialog.FormCreate(Sender: TObject); begin Caption:=lisPLDPackageLinks; ScopeGroupBox.Caption:=dlgSearchScope; CopyCellToClipboardMenuItem.Caption:=srkmecCopy; DeleteSelectedButton.Caption:=lrsPLDDeleteSelected; UpdateGlobalLinksButton.Caption:=lrsRescanLplFiles; CloseBitBtn.Caption:=lisClose; FilterEdit.TextHint:=lisCEFilter; ProgressBar1.Style:=pbstMarquee; ProgressBar1.Visible:=true; LPKInfoCache.AddOnQueueEmpty(@OnAllLPKParsed); LPKInfoCache.StartLPKReaderWithAllAvailable; if Assigned(OPMInterface) then ShowOnlineLinksCheckBox.Checked := OPMInterface.IsPackageListLoaded else ShowOnlineLinksCheckBox.Enabled := False; // Set handler after setting Checked. ShowOnlineLinksCheckBox.OnChange := @ShowOnlineLinksCheckBoxChange; UpdatePackageList; if OPMInterface <> nil then OPMInterface.AddPackageListNotification(@PackageListAvailable); end; procedure TPackageLinksDialog.FormDestroy(Sender: TObject); begin if OPMInterface <> nil then OPMInterface.RemovePackageListNotification(@PackageListAvailable); LPKInfoCache.EndLPKReader; LPKInfoCache.RemoveOnQueueEmpty(@OnAllLPKParsed); ClearLinks; end; procedure TPackageLinksDialog.FilterEditChange(Sender: TObject); begin UpdatePackageList; end; procedure TPackageLinksDialog.CopyCellToClipboardMenuItemClick(Sender: TObject); begin PkgStringGrid.CopyToClipboard(true); end; procedure TPackageLinksDialog.DeleteSelectedButtonClick(Sender: TObject); var i: Integer; Link: TPkgLinkInfo; ErrMsg: String; HasOnline: Boolean; begin ErrMsg:=''; HasOnline:=false; for i:=1 to PkgStringGrid.RowCount-1 do begin if PkgStringGrid.Cells[0,i]=PkgStringGrid.Columns[0].ValueChecked then begin Link:=GetLinkAtRow(i); if Link=nil then begin HasOnline:=true; continue; end; case Link.Origin of ploGlobal: begin // delete lpl file if FileExistsCached(Link.LPLFilename) then begin if not DeleteFileUTF8(Link.LPLFilename) then ErrMsg+=Format(lrsPLDUnableToDeleteFile, [Link.LPLFilename])+LineEnding; end; end; ploOnline: HasOnline:=true; ploUser: LazPackageLinks.RemoveUserLinks(Link); // delete user link end; end; end; RescanGlobalLinks; UpdatePackageList; LazPackageLinks.SaveUserLinks; if ErrMsg<>'' then begin IDEMessageDialog(dlgMsgWinColorUrgentError, lisPLDSomePackagesCannotBeDeleted+':'+LineEnding +ErrMsg,mtError,[mbOk]); end else if HasOnline then begin IDEMessageDialog(dlgMsgWinColorUrgentError, lisPLDOnlinePackagesCannotBeDeleted, mtError, [ mbOk]); end; if HasOnline then; end; procedure TPackageLinksDialog.LPKFileValidCheckBoxChange(Sender: TObject); begin UpdatePackageList; end; procedure TPackageLinksDialog.LPKFileInvalidCheckBoxChange(Sender: TObject); begin UpdatePackageList; end; procedure TPackageLinksDialog.LPKParsingTimerTimer(Sender: TObject); begin UpdatePackageList; end; procedure TPackageLinksDialog.OnAllLPKParsed(Sender: TObject); begin LPKParsingTimer.Enabled:=false; ProgressBar1.Visible:=false; ProgressBar1.Style:=pbstNormal; UpdatePackageList; end; procedure TPackageLinksDialog.ShowLinksCheckBoxChange(Sender: TObject); begin UpdatePackageList; end; procedure TPackageLinksDialog.ShowOnlineLinksCheckBoxChange(Sender: TObject); begin Assert(Assigned(OPMInterface), 'TPackageLinksDialog: OPMInterface=Nil.'); if (Sender as TCheckBox).Checked and not OPMInterface.IsPackageListLoaded then OPMInterface.GetPackageList // ListBox will be updated later by an event. else UpdatePackageList; end; procedure TPackageLinksDialog.UpdateGlobalLinksButtonClick(Sender: TObject); begin RescanGlobalLinks; UpdatePackageList; end; procedure TPackageLinksDialog.UpdatePackageList; var FilterCase: TCaption; FilterLo: String; function HasFilterText(Link: TPkgLinkInfo): boolean; begin Result:=true; if Pos(FilterLo,UTF8LowerCase(Link.Name))>0 then exit; if Pos(FilterLo,UTF8LowerCase(Link.Version.AsString))>0 then exit; if FilenamesCaseSensitive then {%H-}begin if Pos(FilterCase,Link.EffectiveFilename)>0 then exit; end else {%H-}begin if Pos(FilterLo,UTF8LowerCase(Link.EffectiveFilename))>0 then exit; end; Result:=false; end; var Node: TAvlTreeNode; Link: TPkgLinkInfo; i: Integer; OriginStr: String; Info: TLPKInfo; NextNode: TAvlTreeNode; s: String; begin // collect links ClearLinks; if FLinks=nil then FLinks:=TAVLTree.Create(@ComparePackageLinks); if ShowGlobalLinksCheckBox.Checked then begin FCollectingOrigin:=ploGlobal; LazPackageLinks.IteratePackages(false,@IteratePackages,[ploGlobal]); end; if ShowOnlineLinksCheckBox.Checked then begin FCollectingOrigin:=ploOnline; LazPackageLinks.IteratePackages(false,@IteratePackages,[ploOnline]); end; if ShowUserLinksCheckBox.Checked then begin FCollectingOrigin:=ploUser; LazPackageLinks.IteratePackages(false,@IteratePackages,[ploUser]); end; // query additional information from lpk files LPKInfoCache.EnterCritSection; try FCountLPKValid:=0; FCountLPKInvalid:=0; FCountGlobalLinks:=0; FCountUserLinks:=0; FCountOnlineLinks:=0; Node:=FLinks.FindLowest; FilterCase:=FilterEdit.Text; FilterLo:=UTF8LowerCase(FilterCase); while Node<>nil do begin Link:=TPkgLinkInfo(Node.Data); Link.Visible:=true; NextNode:=Node.Successor; Link.EffectiveFilename:=Link.GetEffectiveFilename; Info:=LPKInfoCache.FindPkgInfoWithFilename(Link.EffectiveFilename); // filter for Validity if Link.Visible then begin Link.IsValid:=true; if Info<>nil then begin Link.LPKInfo.Assign(Info); if Link.LPKInfo.LPKParsed=lpkiParsedError then Link.IsValid:=false; end else Link.IsValid:=false; if Link.IsValid then begin if not LPKFileValidCheckBox.Checked then Link.Visible:=false; end else begin if not LPKFileInvalidCheckBox.Checked then Link.Visible:=false; end; end; if Link.Visible and (FilterCase<>'') then begin // filter for text Link.Visible:=HasFilterText(Link); end; if Link.Visible then begin // this link is shown => increase facet counters if Link.IsValid then inc(FCountLPKValid) else inc(FCountLPKInvalid); case Link.Origin of ploGlobal: inc(FCountGlobalLinks); ploOnline: inc(FCountOnlineLinks); ploUser : inc(FCountUserLinks); end; end else begin // delete link Link.Free; FLinks.Delete(Node); end; Node:=NextNode; end; finally LPKInfoCache.LeaveCritSection; end; // fill/update grid PkgStringGrid.RowCount:=FLinks.Count+1; PkgStringGrid.Columns[0].Title.Caption:=lisMenuSelect; PkgStringGrid.Columns[1].Title.Caption:=lisName; PkgStringGrid.Columns[2].Title.Caption:=lisVersion; PkgStringGrid.Columns[3].Title.Caption:=lisGroup; PkgStringGrid.Columns[4].Title.Caption:=lisOIPState; PkgStringGrid.Columns[5].Title.Caption:=lisA2PFilename2; PkgStringGrid.Columns[6].Title.Caption:=lisLastOpened; i:=1; Node:=FLinks.FindLowest; while Node<>nil do begin Link:=TPkgLinkInfo(Node.Data); Node:=Node.Successor; PkgStringGrid.Cells[0,i]:=PkgStringGrid.Columns[0].ValueUnchecked; PkgStringGrid.Cells[1,i]:=Link.Name; PkgStringGrid.Cells[2,i]:=Link.Version.AsString; case Link.Origin of ploGlobal: OriginStr:=lisPLDGlobal; ploOnline: OriginStr:=lisPLDOnline; ploUser : OriginStr:=lisPLDUser; end; PkgStringGrid.Cells[3,i]:=OriginStr; if Link.Origin = ploOnline then s:=lrsPLDValid else begin if Link.IsValid then s:=lrsPLDValid else if (Info<>nil) and (Info.LPKError<>'') then s:=Info.LPKError else s:=lrsPLDInvalid; end; PkgStringGrid.Cells[4,i]:=s; if Link.Origin = ploOnline then PkgStringGrid.Cells[5,i]:=Link.LPKUrl else PkgStringGrid.Cells[5,i]:=Link.EffectiveFilename; if Link.Origin = ploOnline then PkgStringGrid.Cells[6,i]:= FormatDateTime('YYYY/MM/DD hh:mm:ss', Link.OPMFileDate) else begin if Link.LastUsed=0 then PkgStringGrid.Cells[6,i]:= lisNever else PkgStringGrid.Cells[6,i]:= FormatDateTime('YYYY/MM/DD hh:mm:ss', Link.LastUsed); end; inc(i); end; PkgStringGrid.AutoAdjustColumns; if PkgStringGrid.SortColumn>=0 then PkgStringGrid.SortColRow(true,PkgStringGrid.SortColumn); UpdateFacets; end; procedure TPackageLinksDialog.UpdateFacets; begin ShowGlobalLinksCheckBox.Caption:=lisPLDShowGlobalLinksIn +LazPackageLinks.GetGlobalLinkDirectory+'*.lpl' +' ('+IntToStr(CountGlobalLinks)+')'; ShowOnlineLinksCheckBox.Caption:=lisPLDShowOnlineLinks +' ('+IntToStr(CountOnlineLinks)+')'; ShowUserLinksCheckBox.Caption:=lisPLDShowUserLinksIn +LazPackageLinks.GetUserLinkFile +' ('+IntToStr(CountUserLinks)+')'; LPKFileValidCheckBox.Caption:=Format(lrsPLDLpkFileValid, [IntToStr(CountLPKValid)]); LPKFileInvalidCheckBox.Caption:=Format(lrsPLDLpkFileInvalid, [IntToStr(CountLPKInvalid)]); end; procedure TPackageLinksDialog.RescanGlobalLinks; begin LazPackageLinks.UpdateGlobalLinks; end; procedure TPackageLinksDialog.ClearLinks; begin if FLinks<>nil then begin FLinks.FreeAndClear; FreeAndNil(FLinks); end; end; procedure TPackageLinksDialog.IteratePackages(APackage: TLazPackageID); var NewLink: TPkgLinkInfo; begin NewLink:=TPkgLinkInfo.Create; NewLink.Assign(APackage); NewLink.Origin:=FCollectingOrigin; FLinks.Add(NewLink); end; function TPackageLinksDialog.GetLinkAtRow(Row: integer): TPkgLinkInfo; var Origin: TPkgLinkOrigin; EffectiveFilename, S: String; begin Result:=nil; if (Row<1) or (Row>=PkgStringGrid.RowCount) then exit; EffectiveFilename:=PkgStringGrid.Cells[5,Row]; S := PkgStringGrid.Cells[3,Row]; if S = lisPLDGlobal then Origin:=ploGlobal else if S = lisPLDOnline then Origin:=ploOnline else // lisPLDUser Origin:=ploUser; Result:=GetLinkWithEffectiveFilename(EffectiveFilename,[Origin]); end; function TPackageLinksDialog.GetLinkWithEffectiveFilename(Filename: string; Origins: TPkgLinkOrigins): TPkgLinkInfo; var Node: TAvlTreeNode; begin Node:=FLinks.FindLowest; while Node<>nil do begin Result:=TPkgLinkInfo(Node.Data); if (Result.Origin in Origins) and (Result.EffectiveFilename=Filename) then exit; Node:=Node.Successor; end; Result:=nil; end; procedure TPackageLinksDialog.PackageListAvailable(Sender: TObject); begin DebugLn('TPackageLinksDialog.PackageListAvailable.'); UpdatePackageList; end; { TPkgLinkInfo } constructor TPkgLinkInfo.Create; begin inherited Create; FLPKInfo:=TLPKInfo.Create(TLazPackageID.Create); end; destructor TPkgLinkInfo.Destroy; begin FreeAndNil(FLPKInfo); inherited Destroy; end; procedure TPkgLinkInfo.Assign(Source: TPersistent); var Link: TLazPackageLink; begin if Source is TLazPackageID then begin AssignID(TLazPackageID(Source)); LPKInfo.Assign(Source); if Source is TLazPackageLink then begin Link:=TLazPackageLink(Source); Origin:=Link.Origin; LPKUrl := Link.LPKUrl; LPKFilename:=Link.LPKFilename; LPLFilename:=Link.LPLFilename; AutoCheckExists:=Link.AutoCheckExists; LPKFileDateValid:=Link.LPKFileDateValid; LPKFileDate:=Link.LPKFileDate; LastUsed:=Link.LastUsed; OPMFileDate:=Link.OPMFileDate; end; end else inherited Assign(Source); end; end.