1 {
2  /***************************************************************************
3                             lpkcache.pas
4                             ------------
5 
6 
7  ***************************************************************************/
8 
9  ***************************************************************************
10  *                                                                         *
11  *   This source is free software; you can redistribute it and/or modify   *
12  *   it under the terms of the GNU General Public License as published by  *
13  *   the Free Software Foundation; either version 2 of the License, or     *
14  *   (at your option) any later version.                                   *
15  *                                                                         *
16  *   This code is distributed in the hope that it will be useful, but      *
17  *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
18  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
19  *   General Public License for more details.                              *
20  *                                                                         *
21  *   A copy of the GNU General Public License is available on the World    *
22  *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
23  *   obtain it by writing to the Free Software Foundation,                 *
24  *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
25  *                                                                         *
26  ***************************************************************************
27 
28   Author: Mattias Gaertner
29 
30   Abstract:
31     Multithreaded scanner for lpk files to gather information about all
32     available lpk files.
33 
34   Why this unit is needed:
35     The *loaded* packages are handled by the PackageGraph (unit packagesystem).
36     The IDE remembers all lpk files (file name and version) of the users
37     disk in $(PrimaryConfigPath)/packagefiles.xml.
38     The lpk files are often scattered on the disk, might be outdated,
39     broken, wrong version or on slow network shares, so scanning them is
40     expensive. That's why this is done in another thread.
41 
42   Usage:
43     LPKInfoCache.StartLPKReaderWithAllAvailable;
44     or LPKInfoCache.StartLPKReader(ListOfLPKFiles)
45 
46 }
47 unit LPKCache;
48 
49 {$mode objfpc}{$H+}
50 
51 interface
52 
53 uses
54   Classes, SysUtils, Laz_AVL_Tree,
55   // LazUtils
56   LazFileUtils, Laz2_XMLCfg, LazLoggerBase, LazTracer, LazMethodList,
57   // IdeIntf
58   PackageDependencyIntf, PackageIntf, PackageLinkIntf,
59   // IDE
60   EnvironmentOpts, PackageLinks, PackageDefs, PackageSystem;
61 
62 type
63   TLPKInfoState = (
64     lpkiNotParsed,
65     lpkiParsing,
66     lpkiParsedError,
67     lpkiParsed
68     );
69 
70   { TLPKInfo }
71 
72   TLPKInfo = class
73   public
74     ID: TLazPackageID; // name and version
75     LPKFilename: string;
76     InLazSrc: boolean; // lpk is in lazarus source directory
77     Installed: TPackageInstallType;
78     Base: boolean; // is base package, can not be uninstalled
79 
80     LPKParsed: TLPKInfoState;
81     LPKError: string;
82 
83     // the below is only valid if TLPKInfoState=lpkiParsed
84     Author: string;
85     Description: string;
86     License: string;
87     PkgType: TLazPackageType; // design, runtime
88 
89     procedure Assign(Source: TObject);
90     constructor Create(TheID: TLazPackageID);
91     destructor Destroy; override;
92   end;
93 
94   TLPKInfoCache = class;
95 
96   { TIPSLPKReader }
97 
98   TIPSLPKReader = class(TThread)
99   protected
100     procedure SynChangePkgVersion;
101     procedure SynQueueEmpty;
102     procedure Log(Msg: string);
103     procedure Execute; override;
104   public
105     Cache: TLPKInfoCache;
106     NewVersion: TPkgVersion;
107     Info: TLPKInfo; // currently processed info
108     Abort: boolean;
109     FilenameQueue: TStrings; // list of file names to parse by the lpkreader thread
110     destructor Destroy; override;
111   end;
112 
113   TLPKInfoCacheEvent = (
114     liceOnBeforeVersionChange,
115     liceOnAfterVersionChange,
116     liceOnQueueEmpty
117     );
118   TOnLPKInfoBeforeVersionChange = procedure(PkgInfo: TLPKInfo; NewID: TPkgVersion) of object;
119   TOnLPKInfoAfterVersionChange = procedure(PkgInfo: TLPKInfo; OldID: string) of object;
120 
121   { TLPKInfoCache }
122 
123   TLPKInfoCache = class
124   private
125     FCritSec: TRTLCriticalSection;
126     FLPKReader: TIPSLPKReader;
127     fLPKByFilename: TAvlTree; // tree of TLPKInfo sorted for LPKFilename
128     fLPKByID: TAvlTree; // tree of TLPKInfo sorted for ID
129     fEvents: array[TLPKInfoCacheEvent] of TMethodList;
130     fAvailableFiles: TStrings; // used by OnIterateAvailablePackages
131     procedure QueueEmpty;
132     procedure OnIterateAvailablePackages(APackage: TLazPackageID);
133   public
134     constructor Create;
135     destructor Destroy; override;
136 
137     // call by main thread only
138     procedure StartLPKReaderWithAllAvailable;
139     procedure StartLPKReader(Filenames: TStrings);
140     procedure EndLPKReader;
141     procedure ParseLPKInfoInMainThread(Info: TLPKInfo);
142     procedure AddOnBeforeVersionChange(const OnBefore: TOnLPKInfoBeforeVersionChange;
143       AsLast: boolean = true);
144     procedure RemoveOnBeforeVersionChange(const OnBefore: TOnLPKInfoBeforeVersionChange);
145     procedure AddOnAfterVersionChange(const OnAfter: TOnLPKInfoAfterVersionChange;
146       AsLast: boolean = true);
147     procedure RemoveOnAfterVersionChange(const OnAfter: TOnLPKInfoAfterVersionChange);
148     procedure AddOnQueueEmpty(const OnEmpty: TNotifyEvent; AsLast: boolean = true);
149     procedure RemoveOnQueueEmpty(const OnEmpty: TNotifyEvent);
150     procedure ChangePkgVersion(PkgInfo: TLPKInfo; NewVersion: TPkgVersion);
151 
152     // requires critical section
153     procedure EnterCritSection;
154     procedure LeaveCritSection;
FindPkgInfoWithFilenamenull155     function FindPkgInfoWithFilename(aFilename: string): TLPKInfo; // requires crit sec
FindPkgInfoWithIDnull156     function FindPkgInfoWithID(PkgID: TLazPackageID): TLPKInfo; // requires crit sec
FindPkgInfoWithIDAsStringnull157     function FindPkgInfoWithIDAsString(PkgID: string): TLPKInfo; // requires crit sec
158     property LPKByFilename: TAvlTree read fLPKByFilename; // tree of TLPKInfo sorted for LPKFilename
159     property LPKByID: TAvlTree read fLPKByID; // tree of TLPKInfo sorted for ID
160 
161     // thread safe
IsValidLPKFilenamenull162     function IsValidLPKFilename(LPKFilename: string): boolean;
163     procedure ParseLPK(LPKFilename: string;
164       out ErrorMsg, Author, License, Description: string;
165       out PkgType: TLazPackageType;
166       var Version: TPkgVersion); // called by main and helper thread
167     procedure ParseLPKInfo(Info: TLPKInfo; var NewVersion: TPkgVersion);
168   end;
169 
170 var
171   LPKInfoCache: TLPKInfoCache = nil; // set by main.pp
172 
CompareIPSPkgInfosnull173 function CompareIPSPkgInfos(PkgInfo1, PkgInfo2: Pointer): integer;
ComparePkgIDWithIPSPkgInfonull174 function ComparePkgIDWithIPSPkgInfo(PkgID, PkgInfo: Pointer): integer;
CompareIPSPkgInfosWithFilenamenull175 function CompareIPSPkgInfosWithFilename(PkgInfo1, PkgInfo2: Pointer): integer;
CompareFilenameWithIPSPkgInfonull176 function CompareFilenameWithIPSPkgInfo(Filename, PkgInfo: Pointer): integer;
177 
178 implementation
179 
CompareIPSPkgInfosnull180 function CompareIPSPkgInfos(PkgInfo1, PkgInfo2: Pointer): integer;
181 var
182   Info1: TLPKInfo absolute PkgInfo1;
183   Info2: TLPKInfo absolute PkgInfo2;
184 begin
185   Result:=CompareLazPackageIDNames(Info1.ID,Info2.ID);
186 end;
187 
ComparePkgIDWithIPSPkgInfonull188 function ComparePkgIDWithIPSPkgInfo(PkgID, PkgInfo: Pointer): integer;
189 var
190   ID: TLazPackageID absolute PkgID;
191   Info: TLPKInfo absolute PkgInfo;
192 begin
193   Result:=CompareLazPackageIDNames(ID,Info.ID);
194 end;
195 
CompareIPSPkgInfosWithFilenamenull196 function CompareIPSPkgInfosWithFilename(PkgInfo1, PkgInfo2: Pointer): integer;
197 var
198   Info1: TLPKInfo absolute PkgInfo1;
199   Info2: TLPKInfo absolute PkgInfo2;
200 begin
201   Result:=CompareFilenames(Info1.LPKFilename,Info2.LPKFilename);
202 end;
203 
CompareFilenameWithIPSPkgInfonull204 function CompareFilenameWithIPSPkgInfo(Filename, PkgInfo: Pointer): integer;
205 var
206   Info: TLPKInfo absolute PkgInfo;
207 begin
208   Result:=CompareFilenames(AnsiString(Filename),Info.LPKFilename);
209 end;
210 
211 { TLPKInfoCache }
212 
213 procedure TLPKInfoCache.StartLPKReader(Filenames: TStrings);
214 var
215   i: Integer;
216   CurFilename: String;
217   Info: TLPKInfo;
218   ID: TLazPackageID;
219   NeedsStart: Boolean;
220   Pkg: TLazPackage;
221 begin
222   if (Filenames=nil) or (Filenames.Count=0) then begin
223     QueueEmpty;
224     exit;
225   end;
226   NeedsStart:=false;
227   EnterCritSection;
228   try
229     for i:=Filenames.Count-1 downto 0 do
230     begin
231       CurFilename:=Filenames[i];
232       if not IsValidLPKFilename(CurFilename) then continue;
233       Info:=FindPkgInfoWithFilename(CurFilename);
234       if Info<>nil then begin
235         // info is known
236         if Info.LPKParsed<>lpkiNotParsed then continue;
237       end else begin
238         // new info
239         ID:=TLazPackageID.Create;
240         ID.Name:=ExtractFileNameOnly(CurFilename);
241         Info:=TLPKInfo.Create(ID);
242         Info.LPKFilename:=CurFilename;
243         Info.InLazSrc:=FileIsInPath(Info.LPKFilename,
244                                   EnvironmentOptions.GetParsedLazarusDirectory);
245         Info.Base:=Info.InLazSrc and PackageGraph.IsStaticBasePackage(Info.ID.Name);
246         Pkg:=PackageGraph.FindPackageWithFilename(Info.LPKFilename);
247         if Pkg<>nil then
248           Info.Installed:=Pkg.Installed;
249         fLPKByFilename.Add(Info);
250         fLPKByID.Add(Info);
251       end;
252       if FLPKReader=nil then begin
253         // create thread
254         FLPKReader:=TIPSLPKReader.Create(true);
255         FLPKReader.Cache:=Self;
256         FLPKReader.FreeOnTerminate:=true;
257         FLPKReader.FilenameQueue:=TStringList.Create;
258       end;
259       FLPKReader.FilenameQueue.Add(Info.LPKFilename);
260       NeedsStart:=true;
261     end;
262   finally
263     LeaveCritSection;
264   end;
265 
266   if NeedsStart then
267     FLPKReader.Start
268   else
269     QueueEmpty;
270 end;
271 
272 procedure TLPKInfoCache.EndLPKReader;
273 var
274   i: Integer;
275 begin
276   EnterCritSection;
277   try
278     if FLPKReader=nil then exit;
279     FLPKReader.Abort:=true;
280   finally
281     LeaveCritSection;
282   end;
283   i:=0;
284   while FLPKReader<>nil do begin
285     Sleep(10);
286     inc(i,10);
287     if i>=1000 then begin
288       debugln(['TLPKInfoCache.EndLPKReader still waiting for lpk reader to end ...']);
289       i:=0;
290     end;
291   end;
292 end;
293 
294 procedure TLPKInfoCache.ParseLPKInfoInMainThread(Info: TLPKInfo);
295 var
296   NewVersion: TPkgVersion;
297 begin
298   NewVersion:=nil;
299   try
300     ParseLPKInfo(Info,NewVersion);
301     ChangePkgVersion(Info,NewVersion);
302   finally
303     NewVersion.Free;
304   end;
305 end;
306 
TLPKInfoCache.IsValidLPKFilenamenull307 function TLPKInfoCache.IsValidLPKFilename(LPKFilename: string): boolean;
308 var
309   PkgName: String;
310 begin
311   Result:=false;
312   if not FilenameIsAbsolute(LPKFilename) then exit;
313   if not FilenameExtIs(LPKFilename,'.lpk') then exit;
314   PkgName:=ExtractFileNameOnly(LPKFilename);
315   if not IsValidPkgName(PkgName) then exit;
316   Result:=true;
317 end;
318 
319 procedure TLPKInfoCache.AddOnBeforeVersionChange(
320   const OnBefore: TOnLPKInfoBeforeVersionChange; AsLast: boolean);
321 begin
322   fEvents[liceOnBeforeVersionChange].Add(TMethod(OnBefore),AsLast);
323 end;
324 
325 procedure TLPKInfoCache.RemoveOnBeforeVersionChange(
326   const OnBefore: TOnLPKInfoBeforeVersionChange);
327 begin
328   fEvents[liceOnBeforeVersionChange].Remove(TMethod(OnBefore));
329 end;
330 
331 procedure TLPKInfoCache.AddOnAfterVersionChange(
332   const OnAfter: TOnLPKInfoAfterVersionChange; AsLast: boolean);
333 begin
334   fEvents[liceOnAfterVersionChange].Add(TMethod(OnAfter),AsLast);
335 end;
336 
337 procedure TLPKInfoCache.RemoveOnAfterVersionChange(
338   const OnAfter: TOnLPKInfoAfterVersionChange);
339 begin
340   fEvents[liceOnAfterVersionChange].Remove(TMethod(OnAfter));
341 end;
342 
343 procedure TLPKInfoCache.AddOnQueueEmpty(const OnEmpty: TNotifyEvent;
344   AsLast: boolean);
345 begin
346   fEvents[liceOnQueueEmpty].Add(TMethod(OnEmpty),AsLast);
347 end;
348 
349 procedure TLPKInfoCache.RemoveOnQueueEmpty(const OnEmpty: TNotifyEvent);
350 begin
351   fEvents[liceOnQueueEmpty].Remove(TMethod(OnEmpty));
352 end;
353 
354 procedure TLPKInfoCache.OnIterateAvailablePackages(APackage: TLazPackageID);
355 begin
356   if APackage is TLazPackage then
357     fAvailableFiles.Add(TLazPackage(APackage).Filename)
358   else if APackage is TLazPackageLink then begin
359     {if (OPMInterface<>nil) and (TLazPackageLink(APackage).Origin=ploOnline) and
360         (not OPMInterface.IsPackageAvailable(TLazPackageLink(APackage), 2)) then
361       fAvailableFiles.Add(TLazPackageLink(APackage).OPMFileName)
362     else}
363       fAvailableFiles.Add(TLazPackageLink(APackage).LPKFilename);
364   end;
365 end;
366 
367 procedure TLPKInfoCache.QueueEmpty;
368 begin
369   fEvents[liceOnQueueEmpty].CallNotifyEvents(Self);
370 end;
371 
372 procedure TLPKInfoCache.ChangePkgVersion(PkgInfo: TLPKInfo;
373   NewVersion: TPkgVersion);
374 var
375   OldID: String;
376   i: Integer;
377 begin
378   if PkgInfo.ID.Version.Compare(NewVersion)=0 then exit;
379   // notify before
380   i:=fEvents[liceOnBeforeVersionChange].Count;
381   while fEvents[liceOnBeforeVersionChange].NextDownIndex(i) do
382     TOnLPKInfoBeforeVersionChange(fEvents[liceOnBeforeVersionChange].Items[i])(PkgInfo,NewVersion);
383   // change
384   fLPKByID.Remove(PkgInfo);
385   OldID:=PkgInfo.ID.IDAsString;
386   PkgInfo.ID.Version.Assign(NewVersion);
387   fLPKByID.Add(PkgInfo);
388   // notify after
389   i:=fEvents[liceOnAfterVersionChange].Count;
390   while fEvents[liceOnAfterVersionChange].NextDownIndex(i) do
391     TOnLPKInfoAfterVersionChange(fEvents[liceOnAfterVersionChange].Items[i])(PkgInfo,OldID);
392 end;
393 
FindPkgInfoWithFilenamenull394 function TLPKInfoCache.FindPkgInfoWithFilename(aFilename: string): TLPKInfo;
395 var
396   Node: TAvlTreeNode;
397 begin
398   Node:=fLPKByFilename.FindKey(Pointer(aFilename),@CompareFilenameWithIPSPkgInfo);
399   if Node<>nil then
400     Result:=TLPKInfo(Node.Data)
401   else
402     Result:=nil;
403 end;
404 
TLPKInfoCache.FindPkgInfoWithIDnull405 function TLPKInfoCache.FindPkgInfoWithID(PkgID: TLazPackageID): TLPKInfo;
406 var
407   Node: TAvlTreeNode;
408 begin
409   Node:=fLPKByID.FindKey(Pointer(PkgID),@ComparePkgIDWithIPSPkgInfo);
410   if Node<>nil then
411     Result:=TLPKInfo(Node.Data)
412   else
413     Result:=nil;
414 end;
415 
TLPKInfoCache.FindPkgInfoWithIDAsStringnull416 function TLPKInfoCache.FindPkgInfoWithIDAsString(PkgID: string): TLPKInfo;
417 var
418   ID: TLazPackageID;
419 begin
420   Result:=nil;
421   ID:=TLazPackageID.Create;
422   try
423     if not ID.StringToID(PkgID) then exit;
424     Result:=FindPkgInfoWithID(ID);
425   finally
426     ID.Free;
427   end;
428 end;
429 
430 procedure TLPKInfoCache.ParseLPK(LPKFilename: string; out ErrorMsg, Author,
431   License, Description: string; out PkgType: TLazPackageType;
432   var Version: TPkgVersion);
433 var
434   Path: String;
435   XMLConfig: TXMLConfig;
436   FileVersion: Integer;
437 begin
438   ErrorMsg:='';
439   Author:='';
440   License:='';
441   Description:='';
442   PkgType:=lptRunAndDesignTime;
443   Version.Clear;
444   if FilenameIsAbsolute(LPKFilename) and FileExistsUTF8(LPKFilename) then begin
445     // load the package file
446     try
447       XMLConfig:=TXMLConfig.Create(LPKFilename);
448       try
449         Path:='Package/';
450         FileVersion:=XMLConfig.GetValue(Path+'Version',0);
451         Author:=XMLConfig.GetValue(Path+'Author/Value','');
452         Description:=XMLConfig.GetValue(Path+'Description/Value','');
453         License:=XMLConfig.GetValue(Path+'License/Value','');
454         PkgType:=LazPackageTypeIdentToType(XMLConfig.GetValue(Path+'Type/Value',
455                                                 LazPackageTypeIdents[lptRunTime]));
456         PkgVersionLoadFromXMLConfig(Version,XMLConfig,Path+'Version/',FileVersion);
457       finally
458         XMLConfig.Free;
459       end;
460     except
461       on E: Exception do begin
462         ErrorMsg:='file="'+LPKFilename+'": '+E.Message;
463         DebugLn('TLPKInfoCache.ParseLPK ERROR: '+ErrorMsg);
464       end;
465     end;
466   end else begin
467     ErrorMsg:='file not found "'+LPKFilename+'"';
468   end;
469 end;
470 
471 procedure TLPKInfoCache.ParseLPKInfo(Info: TLPKInfo;
472   var NewVersion: TPkgVersion);
473 // if not done, parse the lpk and update LPKError, LPKParsed,
474 // Author, Description, License, PkgType
475 // Version is not changed, but returned in NewVersion
476 var
477   ErrorMsg: string;
478   Author: string;
479   License: string;
480   Description: string;
481   PkgType: TLazPackageType;
482 begin
483   // check if alread parsed
484   EnterCritSection;
485   try
486     if NewVersion=nil then begin
487       NewVersion:=TPkgVersion.Create;
488       NewVersion.Assign(Info.ID.Version);
489     end;
490     if Info.LPKParsed<>lpkiNotParsed then exit;
491     Info.LPKParsed:=lpkiParsing;
492   finally
493     LeaveCritSection;
494   end;
495 
496   // parse
497   ParseLPK(Info.LPKFilename,ErrorMsg,Author,License,Description,PkgType,NewVersion);
498 
499   // change info
500   // Note: the version is not changed
501   EnterCritSection;
502   try
503     if Info.LPKParsed<>lpkiParsing then exit;
504     if ErrorMsg<>'' then begin
505       Info.LPKError:=ErrorMsg;
506       Info.LPKParsed:=lpkiParsedError;
507     end else begin
508       Info.LPKError:='';
509       Info.LPKParsed:=lpkiParsed;
510       Info.Author:=Author;
511       Info.Description:=Description;
512       Info.License:=License;
513       Info.PkgType:=PkgType;
514     end;
515   finally
516     LeaveCritSection;
517   end;
518 end;
519 
520 constructor TLPKInfoCache.Create;
521 var
522   e: TLPKInfoCacheEvent;
523 begin
524   InitCriticalSection(FCritSec);
525   fLPKByFilename:=TAvlTree.Create(@CompareIPSPkgInfosWithFilename);
526   fLPKByID:=TAvlTree.Create(@CompareIPSPkgInfos);
527   for e:=Low(TLPKInfoCacheEvent) to high(TLPKInfoCacheEvent) do
528     fEvents[e]:=TMethodList.Create;
529 end;
530 
531 destructor TLPKInfoCache.Destroy;
532 var
533   e: TLPKInfoCacheEvent;
534 begin
535   EndLPKReader;
536   FreeAndNil(fLPKByID);
537   fLPKByFilename.FreeAndClear;
538   FreeAndNil(fLPKByFilename);
539   for e:=Low(TLPKInfoCacheEvent) to high(TLPKInfoCacheEvent) do
540     FreeAndNil(fEvents[e]);
541   inherited Destroy;
542   DoneCriticalsection(FCritSec);
543 end;
544 
545 procedure TLPKInfoCache.StartLPKReaderWithAllAvailable;
546 begin
547   fAvailableFiles:=TStringList.Create;
548   try
549     PackageGraph.IteratePackages(fpfSearchAllExisting,@OnIterateAvailablePackages);
550     StartLPKReader(fAvailableFiles);
551   finally
552     FreeAndNil(fAvailableFiles);
553   end;
554 end;
555 
556 procedure TLPKInfoCache.EnterCritSection;
557 begin
558   EnterCriticalsection(FCritSec);
559 end;
560 
561 procedure TLPKInfoCache.LeaveCritSection;
562 begin
563   LeaveCriticalsection(FCritSec);
564 end;
565 
566 { TIPSLPKReader }
567 
568 procedure TIPSLPKReader.Execute;
569 begin
570   try
571     while not Abort do begin
572       // get next lpk to parse
573       Cache.EnterCritSection;
574       try
575         Info:=nil;
576         while FilenameQueue.Count>0 do begin
577           Info:=Cache.FindPkgInfoWithFilename(FilenameQueue[FilenameQueue.Count-1]);
578           FilenameQueue.Delete(FilenameQueue.Count-1);
579           if Info=nil then continue;
580           if Info.LPKParsed=lpkiNotParsed then
581             break
582           else
583             Info:=nil;
584         end;
585         if Info=nil then break;
586       finally
587         Cache.LeaveCritSection;
588       end;
589       Cache.ParseLPKInfo(Info,NewVersion);
590       if NewVersion.Compare(Info.ID.Version)<>0 then begin
591         Synchronize(@SynChangePkgVersion);
592       end;
593       Info:=nil;
594     end;
595   except
596     on E: Exception do begin
597       Log('ERROR: TIPSLPKReader.Execute: '+E.Message);
598     end;
599   end;
600 
601   Synchronize(@SynQueueEmpty);
602 
603   Cache.EnterCritSection;
604   try
605     Cache.FLPKReader:=nil;
606   finally
607     Cache.LeaveCritSection;
608   end;
609 end;
610 
611 procedure TIPSLPKReader.SynChangePkgVersion;
612 begin
613   Cache.ChangePkgVersion(Info,NewVersion);
614 end;
615 
616 procedure TIPSLPKReader.SynQueueEmpty;
617 begin
618   Cache.QueueEmpty;
619 end;
620 
621 procedure TIPSLPKReader.Log(Msg: string);
622 begin
623   debugln(['TIPSLPKReader.Log: ',Msg]);
624 end;
625 
626 destructor TIPSLPKReader.Destroy;
627 begin
628   FreeAndNil(FilenameQueue);
629   FreeAndNil(NewVersion);
630   inherited Destroy;
631 end;
632 
633 { TLPKInfo }
634 
635 constructor TLPKInfo.Create(TheID: TLazPackageID);
636 begin
637   ID:=TheID;
638 end;
639 
640 procedure TLPKInfo.Assign(Source: TObject);
641 var
642   SrcInfo: TLPKInfo;
643   SrcID: TLazPackageID;
644 begin
645   if Source is TLPKInfo then
646   begin
647     SrcInfo:=TLPKInfo(Source);
648     PkgType:=SrcInfo.PkgType;
649     LPKParsed:=SrcInfo.LPKParsed;
650     LPKFilename:=SrcInfo.LPKFilename;
651     LPKError:=SrcInfo.LPKError;
652     License:=SrcInfo.License;
653     Installed:=SrcInfo.Installed;
654     InLazSrc:=SrcInfo.InLazSrc;
655     ID.AssignID(SrcInfo.ID);
656     Description:=SrcInfo.Description;
657     Base:=SrcInfo.Base;
658     Author:=SrcInfo.Author;
659   end else if Source is TLazPackageID then begin
660     SrcID:=TLazPackageID(Source);
661     ID.AssignID(SrcID);
662   end else
663     RaiseGDBException('');
664 end;
665 
666 destructor TLPKInfo.Destroy;
667 begin
668   FreeAndNil(ID);
669   inherited Destroy;
670 end;
671 
672 end.
673 
674