lazarus/components/onlinepackagemanager/opkman_updates.pas
balazs fd64c39c73 Opkman: Fix memory leak.
git-svn-id: trunk@59110 -
2018-09-21 07:50:18 +00:00

581 lines
16 KiB
ObjectPascal

{
***************************************************************************
* *
* 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: Balázs Székely
}
unit opkman_updates;
{$mode objfpc}{$H+}
{$INCLUDE opkman_fpcdef.inc}
interface
uses
Classes, SysUtils, fpjson, fpjsonrtti, dateutils,
// LazUtils
Laz2_XMLCfg, LazIDEIntf,
// OpkMan
opkman_serializablepackages, opkman_options, opkman_common,
{$IFDEF MSWINDOWS}
opkman_const,
{$IFDEF FPC311}zipper,{$ELSE}opkman_zip,{$ENDIF}
{$ENDIF}
{$IFDEF FPC311}fphttpclient{$ELSE}opkman_httpclient{$ENDIF}
;
const
OpkVersion = 1;
type
{ TUpdateLazPackages }
TUpdateLazPackages = class(TCollectionItem)
private
FName: String;
FVersion: String;
FForceNotify: Boolean;
FInternalVersion: Integer;
published
property Name: String read FName write FName;
property Version: String read FVersion write FVersion;
property ForceNotify: Boolean read FForceNotify write FForceNotify;
property InternalVersion: Integer read FInternalVersion write FInternalVersion;
end;
{ TUpdatePackageData }
TUpdatePackageData = class(TPersistent)
private
FDownloadZipURL: String;
FDisableInOPM: Boolean;
FName: String;
public
constructor Create;
destructor Destroy; override;
procedure Clear;
published
property Name: String read FName write FName;
property DownloadZipURL: String read FDownloadZipURL write FDownloadZipURL;
property DisableInOPM: Boolean read FDisableInOPM write FDisableInOPM;
end;
{TUpdatePackage}
TUpdatePackage = class(TPersistent)
private
FUpdatePackageData: TUpdatePackageData;
FUpdateLazPackages: TCollection;
FLastError: String;
procedure Clear;
public
constructor Create;
destructor Destroy; override;
function LoadFromJSON(const AJSON: TJSONStringType): Boolean;
function SaveToJSON(var AJSON: TJSONStringType): Boolean;
property LastError: String read FLastError;
published
property UpdatePackageData: TUpdatePackageData read FUpdatePackageData write FUpdatePackageData;
property UpdateLazPackages: TCollection read FUpdateLazPackages write FUpdateLazPackages;
end;
{ TUpdates }
TUpdates = class(TThread)
private
FHTTPClient: TFPHTTPClient;
FUpdatePackage: TUpdatePackage;
FVersion: Integer;
FNeedToBreak: Boolean;
FBusyUpdating: Boolean;
FBusySaving: Boolean;
FOpenSSLAvailable: Boolean;
FOnUpdate: TNotifyEvent;
FTime: QWORD;
FInterval: Cardinal;
FFileName: String;
function GetUpdateInfo(const AURL: String; var AJSON: TJSONStringType): Boolean;
procedure DoOnUpdate;
procedure Load;
procedure Save;
procedure AssignPackageData(AMetaPackage: TMetaPackage);
procedure ResetPackageData(AMetaPackage: TMetaPackage);
procedure CheckForOpenSSL;
procedure CheckForUpdates;
function IsTimeToUpdate: Boolean;
protected
procedure Execute; override;
public
constructor Create(const AFileName: String);
destructor Destroy; override;
procedure StartUpdate;
procedure StopUpdate;
published
property OnUpdate: TNotifyEvent read FOnUpdate write FOnUpdate;
end;
var
Updates: TUpdates = nil;
implementation
{ TUpdatePackage }
procedure TUpdatePackage.Clear;
var
I: Integer;
begin
FUpdatePackageData.Clear;
for I := FUpdateLazPackages.Count - 1 downto 0 do
FUpdateLazPackages.Items[I].Free;
FUpdateLazPackages.Clear;
end;
constructor TUpdatePackage.Create;
begin
FUpdatePackageData := TUpdatePackageData.Create;
FUpdateLazPackages := TCollection.Create(TUpdateLazPackages);
end;
destructor TUpdatePackage.Destroy;
var
I: Integer;
begin
FUpdatePackageData.Free;
for I := FUpdateLazPackages.Count - 1 downto 0 do
FUpdateLazPackages.Items[I].Free;
FUpdateLazPackages.Free;
inherited Destroy;
end;
function TUpdatePackage.LoadFromJSON(const AJSON: TJSONStringType): Boolean;
var
DeStreamer: TJSONDeStreamer;
begin
DeStreamer := TJSONDeStreamer.Create(nil);
try
Clear;
try
DeStreamer.JSONToObject(AJSON, Self);
Result := True;
except
on E: Exception do
begin
FLastError := E.Message;
Result := False;
end;
end;
finally
DeStreamer.Free;
end;
end;
function TUpdatePackage.SaveToJSON(var AJSON: TJSONStringType): Boolean;
var
Streamer: TJSONStreamer;
begin
Result := False;
Streamer := TJSONStreamer.Create(nil);
try
Streamer.Options := Streamer.Options + [jsoUseFormatString];
try
AJSON := Streamer.ObjectToJSONString(Self);
Result := AJSON <> '';
except
on E: Exception do
begin
FLastError := E.Message;
Result := False;
end;
end;
finally
Streamer.Free;
end;
end;
{ TUpdatePackageData }
constructor TUpdatePackageData.Create;
begin
Clear;
end;
destructor TUpdatePackageData.Destroy;
begin
//
inherited Destroy;
end;
procedure TUpdatePackageData.Clear;
begin
FName := '';
FDownloadZipURL := '';
FDisableInOPM := False;
end;
{ TUpdates }
constructor TUpdates.Create(const AFileName: String);
begin
inherited Create(True);
FreeOnTerminate := True;
FFileName := AFileName;
FHTTPClient := TFPHTTPClient.Create(nil);
{$IFDEF FPC311}
FHTTPClient.IOTimeout := Options.ConTimeOut*1000;
{$ENDIF}
if Options.ProxyEnabled then
begin
FHTTPClient.Proxy.Host:= Options.ProxyServer;
FHTTPClient.Proxy.Port:= Options.ProxyPort;
FHTTPClient.Proxy.UserName:= Options.ProxyUser;
FHTTPClient.Proxy.Password:= Options.ProxyPassword;
end;
FUpdatePackage := TUpdatePackage.Create;
end;
destructor TUpdates.Destroy;
begin
FHTTPClient.Free;
FUpdatePackage.Free;
inherited Destroy;
end;
procedure TUpdates.Load;
var
PackageCount: Integer;
LazarusPkgCount: Integer;
I, J: Integer;
Path, SubPath: String;
PackageName: String;
LazarusPkgName: String;
MetaPkg: TMetaPackage;
LazarusPkg: TLazarusPackage;
HasUpdate: Boolean;
FXML: TXMLConfig;
begin
if (not Assigned(SerializablePackages)) or (SerializablePackages.Count = 0) then
Exit;
FXML := TXMLConfig.Create(FFileName);
try
FVersion := FXML.GetValue('Version/Value', 0);
PackageCount := FXML.GetValue('Count/Value', 0);
for I := 0 to PackageCount - 1 do
begin
Path := 'Package' + IntToStr(I) + '/';
PackageName := FXML.GetValue(Path + 'Name', '');
MetaPkg := SerializablePackages.FindMetaPackage(PackageName, fpbPackageName);
if MetaPkg <> nil then
begin
HasUpdate := False;
MetaPkg.DownloadZipURL := FXML.GetValue(Path + 'DownloadZipURL', '');
MetaPkg.DisableInOPM := FXML.GetValue(Path + 'DisableInOPM', False);
MetaPkg.Rating := FXML.GetValue(Path + 'Rating', 0);
LazarusPkgCount := FXML.GetValue(Path + 'Count', 0);
for J := 0 to LazarusPkgCount - 1 do
begin
SubPath := Path + 'PackageFile' + IntToStr(J) + '/';
LazarusPkgName := FXML.GetValue(SubPath + 'Name', '');
LazarusPkg := MetaPkg.FindLazarusPackage(LazarusPkgName);
if LazarusPkg <> nil then
begin
LazarusPkg.UpdateVersion := FXML.GetValue(SubPath + 'UpdateVersion', '');
LazarusPkg.ForceNotify := FXML.GetValue(SubPath + 'ForceNotify', False);
LazarusPkg.InternalVersion := FXML.GetValue(SubPath + 'InternalVersion', 0);;
LazarusPkg.InternalVersionOld := FXML.GetValue(SubPath + 'InternalVersionOld', 0);
LazarusPkg.RefreshHasUpdate;
if not HasUpdate then
HasUpdate := (LazarusPkg.HasUpdate) and (LazarusPkg.InstalledFileVersion < LazarusPkg.UpdateVersion);
end;
end;
MetaPkg.HasUpdate := HasUpdate;
end;
end;
finally
FXML.Free;
end;
if Assigned(FOnUpdate) and (not FNeedToBreak) then
Synchronize(@DoOnUpdate);
end;
procedure TUpdates.Save;
var
I, J: Integer;
Path, SubPath: String;
MetaPkg: TMetaPackage;
LazarusPkg: TLazarusPackage;
FXML: TXMLConfig;
begin
if (not Assigned(SerializablePackages)) or (SerializablePackages.Count = 0) or (FBusySaving) then
Exit;
FBusySaving := True;
FXML := TXMLConfig.CreateClean(FFileName);
try
FXML.SetDeleteValue('Version/Value', OpkVersion, 0);
FXML.SetDeleteValue('Count/Value', SerializablePackages.Count, 0);
for I := 0 to SerializablePackages.Count - 1 do
begin
MetaPkg := SerializablePackages.Items[I];
Path := 'Package' + IntToStr(I) + '/';
FXML.SetDeleteValue(Path + 'Name', MetaPkg.Name, '');
FXML.SetDeleteValue(Path + 'DownloadZipURL', MetaPkg.DownloadZipURL, '');
FXML.SetDeleteValue(Path + 'DisableInOPM', MetaPkg.DisableInOPM, False);
FXML.SetDeleteValue(Path + 'Rating', MetaPkg.Rating, 0);
FXML.SetDeleteValue(Path + 'Count', SerializablePackages.Items[I].LazarusPackages.Count, 0);
for J := 0 to SerializablePackages.Items[I].LazarusPackages.Count - 1 do
begin
SubPath := Path + 'PackageFile' + IntToStr(J) + '/';
LazarusPkg := TLazarusPackage(SerializablePackages.Items[I].LazarusPackages.Items[J]);
FXML.SetDeleteValue(SubPath + 'Name', LazarusPkg.Name, '');
FXML.SetDeleteValue(SubPath + 'UpdateVersion', LazarusPkg.UpdateVersion, '');
FXML.SetDeleteValue(SubPath + 'ForceNotify', LazarusPkg.ForceNotify, False);
FXML.SetDeleteValue(SubPath + 'InternalVersion', LazarusPkg.InternalVersion, 0);
FXML.SetDeleteValue(SubPath + 'InternalVersionOld', LazarusPkg.InternalVersionOld, 0);
end;
end;
FXML.Flush;
finally
FXML.Free;
FBusySaving := False;
end;
end;
procedure TUpdates.AssignPackageData(AMetaPackage: TMetaPackage);
var
I: Integer;
HasUpdate: Boolean;
LazarusPkg: TLazarusPackage;
UpdLazPkgs: TUpdateLazPackages;
begin
if FBusySaving then
Exit;
HasUpdate := False;
AMetaPackage.DownloadZipURL := FUpdatePackage.FUpdatePackageData.DownloadZipURL;
AMetaPackage.DisableInOPM := FUpdatePackage.FUpdatePackageData.DisableInOPM;
for I := 0 to FUpdatePackage.FUpdateLazPackages.Count - 1 do
begin
UpdLazPkgs := TUpdateLazPackages(FUpdatePackage.FUpdateLazPackages.Items[I]);
LazarusPkg := AMetaPackage.FindLazarusPackage(UpdLazPkgs.Name);
if LazarusPkg <> nil then
begin
LazarusPkg.UpdateVersion := UpdLazPkgs.Version;
LazarusPkg.ForceNotify := UpdLazPkgs.ForceNotify;
LazarusPkg.InternalVersion := UpdLazPkgs.InternalVersion;
LazarusPkg.RefreshHasUpdate;
if not HasUpdate then
HasUpdate := (LazarusPkg.HasUpdate) and (LazarusPkg.InstalledFileVersion < LazarusPkg.UpdateVersion);
end;
end;
AMetaPackage.HasUpdate := HasUpdate;
end;
procedure TUpdates.ResetPackageData(AMetaPackage: TMetaPackage);
var
I: Integer;
LazarusPkg: TLazarusPackage;
begin
if FBusySaving then
Exit;
AMetaPackage.DownloadZipURL := '';
AMetaPackage.DisableInOPM := False;
AMetaPackage.HasUpdate := False;
for I := 0 to AMetaPackage.LazarusPackages.Count - 1 do
begin
LazarusPkg := AMetaPackage.FindLazarusPackage(TLazarusPackage(AMetaPackage.LazarusPackages.Items[I]).Name);
if LazarusPkg <> nil then
begin
LazarusPkg.HasUpdate := False;
LazarusPkg.UpdateVersion := '';
LazarusPkg.ForceNotify := False;
LazarusPkg.InternalVersion := 0;
LazarusPkg.InternalVersionOld := 0;
end;
end;
end;
procedure TUpdates.CheckForOpenSSL;
{$IFDEF MSWINDOWS}
var
ParamPath, libeaydll, ssleaydll, ZipFile: String;
UnZipper: TUnZipper;
{$ENDIF}
begin
{$IFDEF MSWINDOWS}
ParamPath := ExtractFilePath(ParamStr(0));
libeaydll := ParamPath + 'libeay32.dll';
ssleaydll := ParamPath + 'ssleay32.dll';
FOpenSSLAvailable := FileExists(libeaydll) and FileExists(ssleaydll);
if not FOpenSSLAvailable then
begin
ZipFile := ParamPath + ExtractFileName(cOpenSSLURL);
try
FHTTPClient.Get(cOpenSSLURL, ZipFile);
except
end;
if FileExists(ZipFile) then
begin
UnZipper := TUnZipper.Create;
try
try
UnZipper.FileName := ZipFile;
UnZipper.Examine;
UnZipper.UnZipAllFiles;
except
end;
finally
UnZipper.Free;
end;
DeleteFile(ZipFile);
FOpenSSLAvailable := FileExists(libeaydll) and FileExists(ssleaydll);
end;
end;
{$ELSE}
FOpenSSLAvailable := True;
{$ENDIF}
end;
function TUpdates.IsTimeToUpdate: Boolean;
begin
Result := Assigned(SerializablePackages) and (FOpenSSLAvailable) and
(not FBusyUpdating) and (not FNeedToBreak);
case Options.CheckForUpdates of
0: Result := MinutesBetween(Now, Options.LastUpdate) >= 2;
1: Result := HoursBetween(Now, Options.LastUpdate) >= 1;
2: Result := DaysBetween(Now, Options.LastUpdate) >= 1;
3: Result := WeeksBetween(Now, Options.LastUpdate) >= 1;
4: Result := MonthsBetween(Now, Options.LastUpdate) >= 1;
5: Result := False;
end;
end;
function TUpdates.GetUpdateInfo(const AURL: String; var AJSON: TJSONStringType): Boolean;
var
URL: String;
Ms: TMemoryStream;
begin
Result := False;
if Trim(AURL) = '' then
Exit;
if Pos('.json', AURL) = 0 then
Exit;
URL := FixProtocol(AURL);
Ms := TMemoryStream.Create;
try
try
FHTTPClient.AllowRedirect := True;
FHTTPClient.HTTPMethod('GET', URL, MS, []);
if FHTTPClient.ResponseStatusCode = 200 then
begin
if Ms.Size > 0 then
begin
MS.Position := 0;
SetLength(AJSON, MS.Size);
MS.Read(Pointer(AJSON)^, Length(AJSON));
Result := Length(AJSON) > 0;
{since the class name has changed form "UpdatePackageFiles" to "UpdateLazPackages",
we have to replace the references in the old JSONs(we don't have access to the files, they are
located at the developers update page.}
if Result then
AJSON := StringReplace(AJSON, 'UpdatePackageFiles', 'UpdateLazPackages', [rfReplaceAll, rfIgnoreCase]);
end;
end;
except
Result := False;
end;
finally
Ms.Free;
end;
end;
procedure TUpdates.DoOnUpdate;
begin
if Assigned(FOnUpdate) then
FOnUpdate(Self);
end;
procedure TUpdates.CheckForUpdates;
var
I: Integer;
JSON: TJSONStringType;
begin
FBusyUpdating := True;
try
Options.LastUpdate := Now;
Options.Changed := True;
for I := 0 to SerializablePackages.Count - 1 do
begin
if FNeedToBreak then
Break;
JSON := '';
if (Assigned(LazarusIDE) and LazarusIDE.IDEIsClosing) then
Break;
if GetUpdateInfo(Trim(SerializablePackages.Items[I].DownloadURL), JSON) then
begin
if FUpdatePackage.LoadFromJSON(JSON) then
AssignPackageData(SerializablePackages.Items[I])
else
ResetPackageData(SerializablePackages.Items[I]);
end
else
ResetPackageData(SerializablePackages.Items[I]);
end;
if Assigned(FOnUpdate) and (not FNeedToBreak) then
Synchronize(@DoOnUpdate);
finally
FBusyUpdating := False;
end;
end;
procedure TUpdates.Execute;
begin
while not Terminated do
begin
Sleep(1);
if (GetTickCount64 - FTime > FInterval) then
begin
FTime := GetTickCount64;
if IsTimeToUpdate then
CheckForUpdates;
end;
if FNeedToBreak then
Break;
end;
end;
procedure TUpdates.StartUpdate;
begin
Load;
CheckForOpenSSL;
FTime := GetTickCount64;
FInterval := 6000;
Start;
end;
procedure TUpdates.StopUpdate;
begin
Save;
FHTTPClient.Terminate;
FNeedToBreak := True;
end;
end.