Files
lazarus-ccr/components/lazautoupdate/latest_stable/ulazautoupdate.pas
gbamber 13ee2b0f8f LazAutoUpdate to 0.3.9
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@6801 8e941d3f-bd1b-0410-a28a-d453659cc2b4
2019-01-25 14:40:40 +00:00

2911 lines
99 KiB
ObjectPascal

unit ulazautoupdate;
{
Summary and Copyright
=====================
LazAutoUpdate (c)2015 Gordon Bamber (minesadorada@charcodelvalle.com)
A Lazarus Visual component that enables an update service for Executables.
Web References
==============
Wiki Page: http://wiki.freepascal.org/LazAutoUpdater
Forum thread: http://forum.lazarus.freepascal.org/index.php/topic,25444.0.html
SourceForge project: https://sourceforge.net/projects/lazautoupdate/
Other Credits
=============
VersionSupport: Mike Thompson - mike.cornflake@gmail.com
(added to and modified by minesadorada@charcodelvalle.com)
Windows admin RunAs function: Vincent at freepascal forum
THpttpClient code: GetMem at freepascal forum
License
=======
This library is free software; you can redistribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version with the following modification:
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent modules,and
to copy and distribute the resulting executable under terms of your choice,
provided that you also meet, for each linked independent module, the terms
and conditions of the license of that module. An independent module is a
module which is not derived from or based on this library. If you modify
this library, you may extend this exception to your version of the library,
but you are not obligated to do so. If you do not wish to do so, delete this
exception statement from your version.
This program 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 Library General Public License
for more details.
You should have received a copy of the GNU Library General Public License
along with this library; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
Version Control and History
===========================
Via SVN: https://svn.code.sf.net/p/lazarus-ccr/svn/components/lazautoupdate/
Via SourceForge: https://sourceforge.net/projects/lazautoupdate/
Also see 'Version History' below
}
{$mode objfpc}{$H+}
interface
uses
{$IFDEF UNIX}{$IFDEF UseCThreads}
cthreads,
cmem, // the c memory manager is on some systems much faster for multi-threading
{$ENDIF}{$ENDIF}
Forms, Classes, SysUtils, lazautoupdate_httpclient, strutils, PropEdits,
LazUTF8, FileUtil, LazFileUtils, Dialogs, StdCtrls,
Buttons, DateUtils,{$IFDEF LINUX}process, asyncprocess,{$ENDIF}zipper, LResources,
VersionSupport, inifiles, aboutlazautoupdateunit, uappisrunning, LCLProc,
fileinfo, open_ssl, ushortcut, winpeimagereader {need this for reading exe info}
, elfreader {needed for reading ELF executables}
, machoreader {needed for reading MACH-O executables}
{$IFDEF WINDOWS}, Windows, ShellAPI{$ENDIF}; // Thanks to Windows 10 and 704 error
const
C_SOURCEFORGEURL =
'https://sourceforge.net/projects/%s/files/%s/%s/download';
// [updatepath,projectname,filename]
C_GITHUBFILE_URL = 'https://raw.github.com/%s/%s/%s/%s';
// https://raw.github.com/<username>/<repo>/<branch>/file
// GitHubUserName,GitHubProjectName,updatepath,filename
C_GITHUBFILE_URL_UPDATES = 'https://raw.github.com/%s/%s/%s/%s/%s';
// https://raw.github.com/<username>/<repo>/<branchname>/some_directory/file
// https://raw.github.com/<username>/<repo>/<tagname>/some_directory/file
{
Version History
===============
V0.0.1: Initial alpha
V0.0.2: Added auOther properties
V0.0.3: Limit to download time
auOther code working
V0.0.4: Added Events
V0.0.5: Improved error handling in DownloadNewVersion
Added LastError property
Added VersionCountLimit rpoerty
Added DownloadCountLimit property
V0.0.6: Linux implementation
Property CopyTree added
V0.0.7: Added Sleep(1) to download code
V0.0.8: UpdateToNewVersion now shells AppFileWithPath
V0.0.9: AutoUpdate method added
V0.1.0: Added WaitFor routine to simulate Sleep in Linux
V0.1.1: Tidied up Debugmode
Improved version comparison
V0.1.3: Fixed bug whereby Setting AppVersion property gave incorrect value
V0.1.4: Added public AppVersionNumber property
V0.1.5: Added Scrollbars to the WhatsNew memo
V0.1.6: Added Public ResetAppVersion method
Added Private VersionStringToNumber function
V0.1.7: Added Public SilentUpdate method
Added public properties:
LCLVersion,WidgetSet, FPCVersion,LastCompiled,TargetOS
V0.1.8: Bugfix: Removed Parent Form code if fSilentMode=TRUE
V0.1.9: Added public AppRunningSilentUpdate method
V0.1.10:Added uAppIsRunning unit
Added IsAppActive public function
V0.1.11:SilentInstall method
V0.1.12:Moved ProgramDir references to fAppFileName references
V0.1.13:Bugfix Update
V0.1.14:Added debug messages in DoSilentUpdate method
V0.1.15:DoSilentUpdate: Added code for CopyTree=TRUE/FALSE
Changed some Ansi functions to UTF8
V0.1.16:$IFDEF WINDOWS UpdateToNewVersion kills app if running
V0.1.17:Added public methods CreateLocalLauImportFile and RelocateLauImportFile
V0.1.18:Added public method RemoteUpdateToNewVersion
V0.1.19:Improved VersionStringToNumber
V0.1.20:Bugfix: 'No Build Information Available' -> '0.0.0.0'
V0.1.21:Bugfix: RemoteUpdate killed app too soon
V0.1.22:lauimport file is not re-created if it already exists
V0.1.23:Bugfix: PrettyName in lauimport sometimes contained duplicated OS info
V0.1.24:Bugfix to CreateLocalLauImportFile
More checks on PrettyName
V0.1.25:Changed default: CopyTree = TRUE
V0.1.26:Updated uses clause for FileUtils.
V0.2.0: Rewritten for 2017
V0.2.4: GitHub integration with branches
V0.2.5: IsWindowsAdministrator check added and property to control it
V0.2.6: Enabled GitHub tags (GitHubBranchOrTag property)
V0.2.7: Updates Tray Updater routines
V0.2.8: Changed constants C_UPDATEHMNAME and C_LAUUPDATENAME
V0.2.9: Added CreateLocalLauImportFile in UpdateToNewVersion
V0.3.1: Added SetExecutePermission (LINUX only)
V0.3.2: Bugfix for DoSilentUpdate
V0.3.3: Added event OnUpdate
V0.3.4: Added unit ushortcut (CreateDesktopShortCut) for installers
V0.3.5: Rule #3:There is to be NO v0.3.5.0
V0.3.6: Bugfixed CreateShortCut code
V0.3.7: Added public property Mode=(lauUpdate|lauInstall)
V0.3.7.1: Added (DoSilentUpdate) copy C_UPDATEHMNAME to installed folder
V0.3.7.2: Unix: SetExecutePermissions on installed app
V0.3.8: Shortcut Menu items now created/deleted
}
C_TLazAutoUpdateComponentVersion = '0.3.9.0';
C_TThreadedDownloadComponentVersion = '0.0.3.0';
{
V0.0.1: Initial alpha
V0.0.2: Added fDebugmode to all classes and functions
V0.0.3: Changed to http_client
}
C_OnlineVersionsININame = 'versions.ini'; // User can change
C_UpdatesFolder = 'updates'; // User can change
// Don't change these without some thought..
C_LAUTRayINI = 'lauimport.ini'; // Name syncronises with TrayUpdater App
C_WhatsNewFilename = 'whatsnew.txt';
C_INISection = 'versions';
C_GUIEntry = 'GUI';
C_ModuleEntry = 'Module';
C_MASTER = 'master';
// Compiler mode directives
// (note: nothing for Mac/Darwin)
{$IFDEF WINDOWS}
C_OS = 'win';
{$ELSE}
C_OS = 'linux';
{$ENDIF}
{$IFDEF CPU32}
C_BITNESS = '32';
{$ELSE}
C_BITNESS = '64';
{$ENDIF}
C_PFX = C_OS + C_BITNESS; // Used in file naming
{$IFDEF WINDOWS}
C_UPDATEHMNAME = 'updatehm' + C_PFX + '.exe';
C_LAUUPDATENAME = 'lauupdate' + C_PFX + '.exe';
{$ELSE}
C_UPDATEHMNAME = 'updatehm' + C_PFX;
C_LAUUPDATENAME = 'lauupdate' + C_PFX;
{$ENDIF}
// Windows Constants (unused)
C_RUNONCEKEY = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce';
C_RUNKEY = 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run';
resourcestring
C_ComponentPrettyName = 'Lazarus Auto-Update Component';
C_TempVersionsININame = 'new%s'; // [C_OnlineVersionsININame]
C_Checking = 'Checking for updates...';
C_NoSFTypes = 'Sorry only ProjectType = auSourceForge is supported in this version';
C_Downloading = 'Please wait. Downloading.. ';
C_ConsoleTitle = 'Updating %s'; //[fAppFileName]
C_TakingTooLong =
'Check is taking too long (bad/slow internet connection?). Try again later?';
C_Error500 = 'There is a problem with the Internet connection (error %d) Try again later?';
C_Error404 = 'Cannot find the file at this time. (error %d) Try again later?';
C_UnableToDelete = 'Sorry, unable to delete %s%sPlease delete it manually';
C_OK = 'OK';
C_NotProperFileName = 'This is not a proper file name';
C_WhatsNewInVersion = 'What''s new in version %s';
C_PropIsEmpty = 'Property SFProjectName is empty!';
C_ThreadDownloadCrash = 'ThreadDownloadHTTP Crashed! (NewVersionAvailable)';
C_DownloadedBytes = 'Downloaded %s: %d bytes';
C_UnableToDeleteOld = 'Unable to delete old files in %s';
C_DirectoryProblems = 'Problems with the % directory';
C_ThreadDownloadHTTPCrash = 'ThreadDownloadHTTP Crashed! (NewVersionAvailable)';
C_DownloadSuccess = 'Downloaded new version %s sucessfully.';
C_UnableToDownload = 'Unable to download new version%sReturn code was %d';
C_PleaseWaitProcessing = 'Please wait. Processing....';
C_UpdaterMissing = 'Missing %s';
C_FolderMissing = 'Missing %s folder';
C_NotApplicable = '<not applicable>';
C_ThreadStarted = 'Thread Started';
// C_SourceForgeDownload = 'SourceForge download';
C_CannotLoadFromRemote = 'Cannot load document from remote server';
C_DownloadIsEmpty = 'Downloaded document is empty.';
C_DownloadFailedErrorCode = 'Download failed with error code ';
rsANewVersionS = 'A new version %s is available. Would you like to download it?';
rsVewVersionSH = 'Vew version %s has downloaded. Click OK to update now.';
rsCancelledYou = 'Cancelled. You can download and update to the new version' +
' later.';
rsDownloadFail = 'Download failed. (HTTP Errorcode %d) Try again later';
rsCancelledYou2 = 'Cancelled. You can download the new version later.';
rsThisApplicat = 'This application is up-to-date';
rsOnlyWindowsU = 'Only Windows users whith Administrator status can update ' +
'this application.%sPlease log off, then log on as an administrator (or ' +
'switch users to an administrator account),%sthen try again. This ' +
'restriction is for the safety and security of your Windows system.%' +
'sClick OK to continue';
rsApplicationU = 'Application update';
rsSImportantMe = '%sImportant message from LazAutoUpdate component:%sThere ' +
'is no version information in your project!%sClick [Continue], and/or [' +
'Abort] to quit, and use%sIDE menu item Project/Project Options/Version ' +
'Info%sto add Version Info by clicking the checkbox.';
rsNoBuildInfor = 'No build information available';
type
// Dummy thread to initialise the threading system
tc = class(tthread)
procedure Execute; override;
end;
// This type is currently unused
TProjectType = (auSourceForge, auGitHubReleaseZip, auOther);
// Array of these records used for multiple updates
UpdateListRecord = record
PrettyName: string;
Path: string;
VersionString: string;
VersionNumber: cardinal;
end;
TWorkingMode = (lauUpdate, lauInstall);
TOnPercent = procedure(Sender: TObject; Percent: integer) of object;
TDownloadWrapper = class; // Forward declaration
TShortCutClass = class; // Forward declaration
TDownloadThreadClass = class; // Forward declaration
TDataEvent = procedure(Sender: TObject;
const ContentLength, CurrentPos: int64) of object;
{TLAZAUTOUPDATE}
// Event declarations
TOnNewVersionAvailable = procedure(Sender: TObject; Newer: boolean;
OnlineVersion: string) of object;
TOnDownloaded = procedure(Sender: TObject; ResultCode, BytesDownloaded: integer) of
object;
TOnDebugEvent = procedure(Sender: TObject; lauMethodName, lauMessage: string) of
object;
TOnUpdated = procedure(Sender: TObject; NewVersion, LauMessage: string) of object;
TLazAutoUpdate = class(TAboutLazAutoUpdate)
private
fSourceForgeProjectName: string;
fGitHubProjectName: string;
fGitHubRepositoryName: string;
fGitHubBranchOrTag: string;
fApplicationVersionString: string;
fApplicationVersionQuad: TVersionQuad;
fGuiQuad: TVersionQuad;
fProjectType: TProjectType;
fDownloadWrapper: TDownloadWrapper;
fAppFileName: string;
fComponentVersion: string;
fShowUpdateInCaption: boolean;
fUpdateList: array of UpdateListRecord;
fUpdateListCount: integer;
fUpdatesFolder: string;
fDownloadZipName: string;
fVersionsININame: string;
fParentApplication: TApplication;
fParentForm: TForm;
fGUIOnlineVersion: string;
fShowDialogs: boolean;
fDownloadInprogress: boolean;
fWindowsAdminCheck: boolean;
fShortCutClass: TShortCutClass;
fWorkingMode: TWorkingMode;
{$IFDEF UNIX}
FUpdateHMProcess: TAsyncProcess;
{$ENDIF}
fauOtherSourceURL: string;
fauOtherSourceFilename: string;
WhatsNewForm: TForm;
WhatsNewMemo: TMemo;
cmdClose: TBitBtn;
FOnNewVersionAvailable: TOnNewVersionAvailable;
FOnDownloaded: TOnDownloaded;
fOnDebugEvent: TOnDebugEvent;
fOnUpdated: TOnUpdated;
fLastError: string;
fVersionCountLimit, fDownloadCountLimit: cardinal;
fZipfileName: string;
fCopyTree: boolean;
fDebugMode, fFireDebugEvent: boolean;
fSilentMode: boolean;
fLCLVersion, fWidgetSet, fFPCVersion, fLastCompiled, fTargetOS: string;
// fQuad: TVersionQuad;
fProgVersion: TProgramVersion;
objFileVerInfo: TFileVersionInfo;
fUpdateExe, fUpdateSilentExe: string;
flauOnFileWrite: TOnPercent;
flauOnProgress: TOnPercent;
procedure SetProjectType(AValue: TProjectType);
// projectype=auOther property Sets
procedure SetauOtherSourceFilename(AValue: string);
procedure SetauOtherSourceURL(AValue: string);
procedure SetSourceForgeProjectName(Avalue: string);
procedure SetAppFilename(Avalue: string);
procedure SetApplicationVersionString(Avalue: string);
procedure SetShowDialogs(AValue: boolean);
procedure SetDebugMode(AValue: boolean);
function GetThreadDownloadReturnCode: integer;
function IsOnlineVersionNewer(const sznewINIPath: string): boolean;
function DoSilentUpdate: boolean;
function GetUpdateSilentExe: string;
function GetUpdateExe: string;
protected
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure DebugTest;
{Main functions}
// If NewVersionAvailable then DownloadNewVersion then UpdateToNewVersion
// Returns TRUE if GUIVersion > AppVersion
function NewVersionAvailable: boolean;
// Returns TRUE if successful
function DownloadNewVersion: boolean;
// Returns TRUE if successful. Also creates a C_LAUTRayINI file in the GetAppConfig folder for TrayUpdater
function UpdateToNewVersion: boolean;
// Put in form.activate. Shows <whatsnew.txt> only if in ProgramDirectory then deletes it. Exits otherwise
procedure ShowWhatsNewIfAvailable;
// Checks for new version then shows dialogs to update
function AutoUpdate: boolean;
// No dialogs - what it says on the tin.
function SilentUpdate: boolean;
// Used in SilentUpdate. Shells to local lauupdate(.exe)
function RemoteUpdateToNewVersion: boolean;
// Returns TRUE if EXEName is running under Windows or Linux
function AppIsActive(const ExeName: string): boolean;
// Resets AppVersion property to the ownling application version
procedure ResetAppVersion;
// Create a new lauimport.ini in GetAppConfigDirUTF8 folder
function CreateLocalLauImportFile: boolean;
// If lauimport.ini is found in the app folder, move it to the AppData folder
procedure RelocateLauImportFile;
// Uses properties in TShortCutClass
function MakeShortCut: boolean;
function DeleteShortCut: boolean; // (use fShortCutClass.ShortCutName)
// Download lists (now superceded by CopyTree)
// TODO: Use Indexed properties to handle list access
function AddToUpdateList(APrettyName, APath, AVersionString: string;
AVersionNumber: cardinal): integer;
procedure ClearUpdateList;
property UpdateListCount: integer read fUpdateListCount;
// GUI can use these properties during and after downloads
// NewVersionAvailable sets this. It is the online version
property GUIOnlineVersion: string read fGUIOnlineVersion;
// Set by NewVersionAvailable and DownLoadNewVersion
property ReturnCode: integer read GetThreadDownloadReturnCode;
// Set by NewVersionAvailable and DownLoadNewVersion when running
property DownloadInprogress: boolean read fDownloadInprogress;
// The name of the zipfile in the remote <updates> directory
property DownloadZipName: string read fDownloadZipName;
// The Path + Filename of the app to overwite and then run
property AppFileWithPath: string read fAppFilename write SetAppFilename;
// The version string of the app to be updated. You can set this to '0.0.0.0' for a definite update.
property AppVersion: string read fApplicationVersionString
write SetApplicationVersionString;
// Can be queried
property LastError: string read fLastError;
// Debugging use only
property DebugMode: boolean read fDebugMode write SetDebugMode;
// property AppVersionNumber: integer read fApplicationVersionQuad;
// Info useful for About dialogs
property LCLVersion: string read fLCLVersion;
property WidgetSet: string read fWidgetSet;
property FPCVersion: string read fFPCVersion;
property LastCompiled: string read fLastCompiled;
property TargetOS: string read fTargetOS;
property WindowsAdminCheck: boolean read fWindowsAdminCheck write fWindowsAdminCheck;
{$IFDEF LINUX}
// Used in UpdateToNewVersion
function SetExecutePermission(const AFileName: string; var AErrMsg: string): boolean;
{$ENDIF}
published
// Events
property OnNewVersionAvailable: TOnNewVersionAvailable
read FOnNewVersionAvailable write FOnNewVersionAvailable;
property OnDownloaded: TOnDownloaded read fOnDownloaded write fOnDownloaded;
property OnDebugEvent: TOnDebugEvent read fOnDebugEvent write fOnDebugEvent;
property OnUpdated: TOnUpdated read fOnUpdated write fOnUpdated;
property OnFileWriteProgress: TOnPercent read flauOnFileWrite write flauOnFileWrite;
property OnDownloadProgress: TOnPercent read flauOnProgress write flauOnProgress;
// Embedded class
property ThreadDownload: TDownloadWrapper
read fDownloadWrapper write fDownloadWrapper;
// Set this property before using methods
property SFProjectName: string read fSourceForgeProjectName
write SetSourceForgeProjectName;
// For when fProjectType = auOther
// Fully qualified URL (not including the filename).
property auOtherSourceURL: string read fauOtherSourceURL write SetauOtherSourceURL;
// Just the filename of the file to be downloaded (can be zipped)
property auOtherSourceFilename: string read fauOtherSourceFilename
write SetauOtherSourceFilename;
property ProjectType: TProjectType
read fProjectType write SetProjectType default auSourceForge;
// Version of this component
property AutoUpdateVersion: string read fComponentVersion;
// Zipfile contains a whole directory tree (relative to App Directory)
property CopyTree: boolean read fCopyTree write fCopyTree default True;
// Default is 'updates' *must be the same in SourceForge file section or GitHub Branch subfolder*
property UpdatesFolder: string read fUpdatesFolder write fUpdatesFolder;
// Default=versions.ini File in SourceForge/GitHub /updates folder
property VersionsININame: string read fVersionsININame write fVersionsININame;
// Default is to modify parent form's caption during downloads
property ShowUpdateInCaption: boolean read fShowUpdateInCaption
write fShowUpdateInCaption default False;
// Set to FALSE if you want to handle them in form code
property ShowDialogs: boolean read fShowDialogs write SetShowDialogs default False;
// How many counts to wait until 'Too long' meesage quits out
property VersionCountLimit: cardinal read fVersionCountLimit
write fVersionCountLimit;
// How many counts to wait until 'Too long' meesage quits out
property DownloadCountLimit: cardinal read fDownloadCountLimit
write fDownloadCountLimit;
// Default is application filename.zip
property ZipfileName: string read fZipfileName write fZipfileName;
// Name of Console app
property UpdateExe: string read GetUpdateExe;
// Name of Console app
property UpdateExeSilent: string read GetUpdateSilentExe;
// Main project name/UserName
property GitHubProjectname: string read fGitHubProjectName write fGitHubProjectName;
// Name of your GitHub repository within the project/username
property GitHubRepositoryName: string read fGitHubRepositoryName
write fGitHubRepositoryName;
// Default=master but any branchname or tagname is OK
property GitHubBranchOrTag: string read fGitHubBranchOrTag write fGitHubBranchOrTag;
// Install or Update (default=Update)
property WorkingMode: TworkingMode read fWorkingMode write fWorkingMode;
// Subproperties available
property ShortCut: TShortCutClass read fShortCutClass write fShortCutClass;
end;
TShortCutCategory = (scAudioVideo, scAudio, scDevelopment,
scEducation, scGame, scGraphics, scNetwork, scOffice, scScience, scSettings,
scSystem, scUtility);
// TShortCutCategoryFlags = Set of TShortCutCategory;
TShortCutClass = class(TPersistent)
private
// ShortCut stuff for CreateDesktopShortCut in ushortcut.pas
fShortCutTarget: string;
fShortCutTargetArguments: string;
fShortCutShortcutName: string;
fShortCutIconFileName: string;
fShortCutCategory: TShortCutCategory; // For easier property access
procedure SetShortCutCategoryString(ACategory: TShortCutCategory);
public
fShortCutCategoryString: string;
constructor Create; // Constructor must be public
destructor Destroy; override; // Destructor must be public
property CategoryString: string read fShortCutCategoryString;
published
property Target: string read fShortCutTarget write fShortCutTarget;
property TargetArguments: string read fShortCutTargetArguments
write fShortCutTargetArguments;
property ShortcutName: string read fShortCutShortcutName write fShortCutShortcutName;
property IconFileName: string read fShortCutIconFileName write fShortCutIconFileName;
property Category: TShortCutCategory read fShortCutCategory
write SetShortCutCategoryString;
end;
{TDownloadWrapper }
TDownloadWrapper = class(TPersistent)
private
fURL: string;
fFileName: string;
fReturnCode: integer;
fThreadFinished: boolean;
fDownloadSize: integer;
fUnzipAfter: boolean;
fComponentVersion: string;
fApplicationVersionString: string;
fIsCodeRepository: boolean;
public
fDownloadWrapperPercent: integer;
fWrapperOnFileWrite: TOnPercent;
fDownLoad: TDownloadThreadClass;
fDebugMode: boolean;
fShowDialogs: boolean;
fLastError: string; // Propagated to TLazAutoUpdate
constructor Create;
destructor Destroy; override;
//function GetDownloadWrapperPercent:Integer;
// Starts the thread
function ThreadDownloadHTTP: boolean;
// Called when the thread is done
procedure DownloadTerminiated(Sender: TObject);
// Passed to the thread
property URL: string read fURL write fURL;
// Passed to the thread
property Filename: string read fFileName write fFileName;
// From TLazAutoUpdate
property AppVersion: string read fApplicationVersionString
write fApplicationVersionString;
// From the thread
property ReturnCode: integer read fReturnCode write fReturnCode;
// From DownloadTerminated
property ThreadFinished: boolean read fThreadFinished write fThreadFinished;
// From the thread
property DownloadSize: integer read fDownloadSize write fDownloadSize;
// From TLazAutoUpdate
property UnzipAfter: boolean read fUnzipAfter;
// From TLazAutoUpdate
property IsSourceForge: boolean read fIsCodeRepository;
property OnDownloadWrapperFileWriteProgress: TOnPercent
read fWrapperOnFileWrite write fWrapperOnFileWrite;
property DownloadWrapperPercent: integer
read fDownloadWrapperPercent write fDownloadWrapperPercent;
published
// Version of the underlying thread class
property ThreadDownloadVersion: string read fComponentVersion;
end;
{ TStreamAdapter }
TStreamAdapter = class(TStream)
strict private
fOnProgress: TOnPercent;
fPercent: integer;
fStream: TStream;
public
constructor Create(AStream: TStream; ASize: int64);
destructor Destroy; override;
function Read(var Buffer; Count: longint): longint; override;
function Write(const Buffer; Count: longint): longint; override;
function Seek(Offset: longint; Origin: word): longint; override;
procedure DoProgress(Writing: boolean); virtual;
published
property OnFileWriteProgress: TOnPercent read FOnProgress write FOnProgress;
end;
{TDownloadThreadClass }
TDownloadThreadClass = class(TThread)
private
fdtcOnFileWrite: TOnPercent;
fThreadDataEvent: TDataEvent;
fPercent: integer;
public
fHTTPClient: TFPHTTPClient;
fIsRepositoryURL: boolean; // Propagated from TLazAutoUpdate
fDebugMode: boolean; // propagated from TLazAutoUpdate
fShowDialogs: boolean; // propagated from TLazAutoUpdate
fDownloadSize: integer; // propagated to TDownloadWrapper
fReturnCode: integer; // Propagated to TDownloadWrapper
fLastError: string; // Propagated to TDownloadWrapper
fURL: string;
fFileName: string;
constructor Create(CreateSuspended: boolean);
destructor Destroy; override;
procedure Execute; override; // Starts thread
function GetPercent: integer;
procedure SetPercent(AValue: integer);
function DownloadHTTP(URL, TargetFile: string; var ReturnCode, DownloadSize: integer;
bIsRepositoryURL, DebugMode: boolean): boolean;
procedure DoPercent(Sender: TObject; const ContentLength, CurrentPos: int64);
function GetDownloadFileSize(URL: string; bIsRepositoryURL: boolean): int64;
property Percent: integer read fPercent write fPercent;
property OnThreadFileWriteProgress: TOnPercent
read fdtcOnFileWrite write fdtcOnFileWrite;
property OnThreadDataEvent: TDataEvent read fThreadDataEvent write fThreadDataEvent;
end;
// For the TShortCutClass filename properties (needs propedits unit)
TMyFileNamePropertyEditor = class(TFileNamePropertyEditor)
public
// Override the Edit method for total control
function GetFilter: string; override;
function GetDialogOptions: TOpenOptions; override;
function GetDialogTitle: string; override;
end;
// Non-threaded function
// Function GetDownloadFileSize(URL:String;bIsRepositoryURL:Boolean):Int64;
procedure Register;
implementation
procedure Register;
begin
{$I lazautoupdate_icon.lrs}
RegisterComponents('System', [TLazAutoUpdate]);
// Register the custom property editors for the TShortCutClass filename properties
RegisterPropertyEditor(TypeInfo(string),
TShortCutClass, 'Target', TMyFileNamePropertyEditor);
RegisterPropertyEditor(TypeInfo(string),
TShortCutClass, 'IconFileName', TMyFileNamePropertyEditor);
end;
// Start Property editors for File type properties in TShortCutClass
function TMyFileNamePropertyEditor.GetFilter: string;
begin
{$IFDEF WINDOWS}
Result := 'Windows executable|*.exe|All Files|*.*';
{$ELSE}
{$IFDEF LINUX}
Result := 'Linux executable|*.|All Files|*.*';
{$ELSE}
Result := 'All Files|*.*';
{$ENDIF}
{$ENDIF}
end;
function TMyFileNamePropertyEditor.GetDialogOptions: TOpenOptions;
begin
// To see the full list, drop an OpenDialog onto a form and see the Options property
Result := [ofFileMustExist, ofPathMustExist];
end;
function TMyFileNamePropertyEditor.GetDialogTitle: string;
begin
Result := 'Choose Shortcut Target Filename';
end;
// End Property editors for File type properties in TShortCutClass
// Dummy thread to initialise the threading process
procedure tc.Execute;
begin
end;
procedure WaitFor(const MillisecondDelay: longword);
// Linux - this proc is intentionally thread-blocking
var
ThisSecond: longword;
begin
ThisSecond := MilliSecondOfTheDay(Now);
while MilliSecondOfTheDay(Now) < (ThisSecond + MillisecondDelay) do ;
end;
procedure TShortCutClass.SetShortCutCategoryString(ACategory: TShortCutCategory);
{
FreeDesktop Valid Categories:
TShortCutCategory = (scAudioVideo,scAudio,scDevelopment,
scEducation,scGame,scGraphics,scNetwork,scOffice,scScience,scSettings,
scSystem,scUtility);
}
begin
if ACategory = fShortCutCategory then
exit;
fShortCutCategoryString := 'Unknown';
case ACategory of
scAudioVideo: fShortCutCategoryString := 'AudioVideo';
scAudio: fShortCutCategoryString := 'Audio';
scDevelopment: fShortCutCategoryString := 'Development';
scEducation: fShortCutCategoryString := 'Education';
scGame: fShortCutCategoryString := 'Game';
scGraphics: fShortCutCategoryString := 'Graphics';
scNetwork: fShortCutCategoryString := 'Network';
scOffice: fShortCutCategoryString := 'Office';
scScience: fShortCutCategoryString := 'Science';
scSettings: fShortCutCategoryString := 'Settings';
scSystem: fShortCutCategoryString := 'System';
scUtility: fShortCutCategoryString := 'Utility';
end;
end;
procedure TLazAutoUpdate.DebugTest;
begin
ShowMessage(fApplicationVersionString);
end;
{$IFDEF WINDOWS}
// === START WINDOWS PROCS =====================================================
// This is all about permissions in Windows 10
procedure ShowAdminCheckMessage;
var
sMessage: string;
begin
sMessage := Format(rsOnlyWindowsU, [lineending, lineending, lineending]);
MessageDlg(rsApplicationU, sMessage, mtInformation, [mbOK], 0);
end;
function IsXP: boolean;
var
osVinfo: TOSVERSIONINFO;
begin
ZeroMemory(@osVinfo, SizeOf(osVinfo));
OsVinfo.dwOSVersionInfoSize := SizeOf(TOSVERSIONINFO);
if ((GetVersionEx(osVInfo) = True) and (osVinfo.dwPlatformId =
VER_PLATFORM_WIN32_NT) and (osVinfo.dwMajorVersion = 5) and
(osVinfo.dwMinorVersion = 1)) then
Result := True
else
Result := False;
end;
function IsWindowsAdmin: boolean;
const
SECURITY_NT_AUTHORITY: TSIDIdentifierAuthority =
(Value: (0, 0, 0, 0, 0, 5));
SECURITY_BUILTIN_DOMAIN_RID = $00000020;
DOMAIN_ALIAS_RID_ADMINS = $00000220;
var
hAccessToken: THandle;
ptgGroups: PTokenGroups;
dwInfoBufferSize: DWORD;
psidAdministrators: PSID;
x: integer;
bSuccess: BOOL;
LastError: integer;
begin
if Win32Platform <> VER_PLATFORM_WIN32_NT then
begin
Result := True;
exit;
end;
Result := False;
bSuccess := OpenThreadToken(GetCurrentThread, TOKEN_QUERY, True, hAccessToken);
if not bSuccess then
begin
if GetLastError = ERROR_NO_TOKEN then
bSuccess := OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, hAccessToken);
end;
if bSuccess then
begin
GetMem(ptgGroups, 1024);
bSuccess := GetTokenInformation(hAccessToken, TokenGroups,
ptgGroups, 1024, @dwInfoBufferSize);
LastError := GetLastError;
if not bSuccess then
ShowMessage(format('GetLastError %d', [LastError]));
CloseHandle(hAccessToken);
if bSuccess then
begin
AllocateAndInitializeSid(SECURITY_NT_AUTHORITY, 2,
SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0, psidAdministrators);
{$R-}
for x := 0 to ptgGroups^.GroupCount - 1 do
if EqualSid(psidAdministrators, ptgGroups^.Groups[x].Sid) then
begin
Result := True;
break;
end;
{$R+}
FreeSid(psidAdministrators);
end;
FreeMem(ptgGroups);
end;
end;
function IsWindowsAdminWinXP: boolean; // Currently unused
const
GENERIC_READ = $80000000;
GENERIC_WRITE = $40000000;
GENERIC_EXECUTE = $20000000;
GENERIC_ALL = $10000000;
var
hSC: THandle;
begin
Result := True;
hSC := OpenSCManager(nil, nil, GENERIC_READ or GENERIC_WRITE or GENERIC_EXECUTE);
if (hSC = 0) then
Result := False;
CloseServiceHandle(hSC);
end;
{$ENDIF}
// === END WINDOWS PROCS =======================================================
constructor TShortCutClass.Create;
begin
inherited Create; // TComponent method;
end;
destructor TShortCutClass.Destroy;
begin
inherited Destroy;
end;
constructor TLazAutoUpdate.Create(AOwner: TComponent);
var
sz: string;
begin
inherited Create(AOwner); // TComponent method;
{ initialise threading system }
with tc.Create(False) do
begin
waitfor;
Free;
end;
// Freed in Destroy
fDownloadWrapper := TDownloadWrapper.Create;
// Freed in Destroy
fShortCutClass := TShortCutClass.Create();
fShortCutClass.ShortcutName := 'MyShortcutName';
fShortCutClass.TargetArguments := '';
fShortCutClass.Category := scDevelopment;
fShortCutClass.fShortCutCategoryString := 'Development';
// Leave URL and Filename to be set via properties
fComponentVersion := C_TLazAutoUpdateComponentVersion;
// Unused
ClearUpdateList;
fUpdateListCount := 0;
// Grab the application and form objects from the application
fParentApplication := Tapplication(AOwner.Owner);
fParentForm := TForm(AOwner);
// Set default
fApplicationVersionString := rsNoBuildInfor;
// Get Versioninfo
objFileVerInfo := TFileVersionInfo.Create(fParentApplication);
try
try
objFileVerInfo.Filename := ParamStrUTF8(0);
objFileVerInfo.ReadFileInfo;
fApplicationVersionString := objFileVerInfo.VersionStrings.Values['FileVersion'];
fileinfo.GetProgramVersion(fApplicationVersionQuad);
fileinfo.GetProgramVersion(fProgVersion);
except
// EResNotFound raised if no versioninfo in project
sz := rsSImportantMe;
raise Exception.Createfmt(sz, [LineEnding, LineEnding,
LineEnding, LineEnding, LineEnding]);
FreeAndNil(fDownloadWrapper);
FreeAndNil(fShortCutClass);
Application.Terminate;
// Eat other Exceptions?
end;
finally
objFileVerInfo.Free;
end;
if (fApplicationVersionString = rsNoBuildInfor) then
fApplicationVersionString := '0.0.0.0';
fCopyTree := True; // User can change
// UpdateList: Redundant?
AddToUpdateList('', LazUTF8.ParamStrUTF8(0), GetFileVersion, 0);
fWorkingMode := lauUpdate; // Default
fProjectType := auSourceForge; // User can change
fUpdatesFolder := C_UpdatesFolder; // User can change
fVersionsININame := C_OnlineVersionsININame; // User can change
fShowUpdateInCaption := False; // User can change
fShowDialogs := False; // User can change
fDebugMode := False;
fFireDebugEvent := False;
fSilentMode := False;
// Propagate down
fDownloadWrapper.fDebugmode := fDebugMode;
if ((fProjectType = auSourceForge) or (fProjectType = auGitHubReleaseZip)) then
fDownloadWrapper.fIsCodeRepository := True
else
fDownloadWrapper.fIsCodeRepository := False;
fApplicationVersionQuad := StrToVersionQuad(fApplicationVersionString);
fLastError := C_OK;
fVersionCountLimit := 1000000; // default
fDownloadCountLimit := 10000000; // default
fZipfileName := ''; // assign later
// BE SURE TO CHANGE THE CONSTANTS IF YOU CHANGE THE UPDATE EXE NAME
GetUpdateSilentExe;
GetUpdateExe;
// Assorted versioninfo properties
fLCLVersion := GetLCLVersion;
fWidgetSet := GetWidgetSet;
fFPCVersion := GetCompilerInfo;
fLastCompiled := GetCompiledDate;
fTargetOS := GetOS;
fWindowsAdminCheck := True;
// AboutBox properties
AboutBoxComponentName := Format('Laz Auto-update v%s',
[C_TLazAutoUpdateComponentVersion]);
AboutBoxVersion := C_TLazAutoUpdateComponentVersion;
AboutBoxWidth := 400;
AboutBoxHeight := 450;
sz := 'A component for updating your application' + LineEnding;
sz += 'Designed for projects hosted by SourceForge and GitHub' +
LineEnding + LineEnding;
sz += 'Main methods:' + LineEnding;
sz += 'Procedure AutoUpdate' + LineEnding;
sz += 'Function NewVersionAvailable: Boolean' + LineEnding;
sz += 'Function DownloadNewVersion: Boolean' + LineEnding;
sz += 'Function UpdateToNewVersion: Boolean' + LineEnding;
sz += 'Procedure ShowWhatsNewIfAvailable' + LineEnding;
sz += 'For troubleshooting, set DebugMode=TRUE';
AboutBoxTitle := 'LazAutoUpdate';
AboutBoxDescription := sz;
// AboutBoxBackgroundColor:=clWindow;
//AboutBoxFontName (string)
//AboutBoxFontSize (integer)
AboutBoxAuthorname := 'Gordon Bamber';
//AboutBoxOrganisation (string)
AboutBoxAuthorEmail := 'minesadorada@charcodelvalle.com';
AboutBoxLicenseType := 'MODIFIEDGPL';
end;
destructor TLazAutoUpdate.Destroy;
begin
FreeAndNil(fDownloadWrapper);
FreeAndNil(fShortCutClass);
inherited Destroy;
end;
function TLazAutoUpdate.GetUpdateSilentExe: string;
begin
fUpdateSilentExe := C_LAUUPDATENAME;
if csDesigning in ComponentState then
Result := 'lauupdate'
else
Result := fUpdateSilentExe;
end;
function TLazAutoUpdate.GetUpdateExe: string;
begin
fUpdateExe := C_UPDATEHMNAME;
if csDesigning in ComponentState then
Result := 'updatehm'
else
Result := fUpdateExe;
end;
function TLazAutoUpdate.AppIsActive(const ExeName: string): boolean;
begin
Result := AppIsRunning(ExeName);
end;
procedure TLazAutoUpdate.ResetAppVersion;
begin
fApplicationVersionString := GetFileVersion;
if (fApplicationVersionString = 'No build information available') then
fApplicationVersionString := '0.0.0.0';
fApplicationVersionQuad := StrToVersionQuad(fApplicationVersionString);
end;
procedure TLazAutoUpdate.SetShowDialogs(AValue: boolean);
begin
fShowDialogs := AValue;
if fDownloadWrapper <> nil then
fDownloadWrapper.fShowDialogs := AValue;
end;
procedure TLazAutoUpdate.SetDebugMode(AValue: boolean);
begin
fDebugMode := AValue;
// Fire the OnDebugEvent event handler?
if Assigned(fOndebugEvent) then
fFireDebugEvent := fDebugMode;
if fDownloadWrapper <> nil then
fDownloadWrapper.fDebugMode := AValue;
end;
procedure TLazAutoUpdate.SetauOtherSourceURL(AValue: string);
// Must end in '/'
begin
if ((AValue <> fauOtherSourceURL) and (AValue <> '')) then
begin
if not AnsiEndsStr('/', AValue) then
AValue += '/';
fauOtherSourceURL := AValue;
end;
end;
procedure TLazAutoUpdate.SetauOtherSourceFilename(AValue: string);
begin
if ((AValue <> fauOtherSourceFilename) and (AValue <> '')) then
begin
try
fauOtherSourceFilename := ExtractFileName(AValue);
except
ShowMessage(C_NotProperFileName);
end;
end;
end;
// A couple of public functions for installer apps
function TLazAutoUpdate.MakeShortCut: boolean;
begin
Result := False; // assume failure, look for success
if Assigned(fOndebugEvent) then
fFireDebugEvent := True;
if fFireDebugEvent then
fOndebugEvent(Self, 'MakeShortCut', 'MakeShortCut called');
if fShortCutClass.Target = '' then
fShortCutClass.Target := fAppFilename;
if fFireDebugEvent then
fOndebugEvent(Self, 'MakeShortCut', Format('Target=%s, TargetArguments=%s',
[fShortCutClass.Target, fShortCutClass.TargetArguments]));
if fFireDebugEvent then
fOndebugEvent(Self, 'MakeShortCut',
Format('Shortcut Name=%s, IconFileName=%s',
[fShortCutClass.ShortcutName, fShortCutClass.IconFileName]));
{$IFDEF LINUX}
if fFireDebugEvent then
fOndebugEvent(Self, 'MakeShortCut', Format('Category=%s',
[fShortCutClass.CategoryString]));
{$ENDIF}
Result := CreateDesktopShortCut(fShortCutClass.Target,
fShortCutClass.TargetArguments, fShortCutClass.ShortcutName,
fShortCutClass.IconFileName, fShortCutClass.CategoryString);
fLastError := GetShortCutDebugString;
if fFireDebugEvent then
if Result = True then
fOndebugEvent(Self, 'MakeShortCut', 'MakeShortCut succeded.' +
GetShortCutDebugString)
else
fOndebugEvent(Self, 'MakeShortCut', 'MakeShortCut failed. Error(s): ' +
GetShortCutDebugString);
end;
function TLazAutoUpdate.DeleteShortCut: boolean;
begin
Result := False; // assume failure, look for success
if Assigned(fOndebugEvent) then
fFireDebugEvent := True;
if fFireDebugEvent then
fOndebugEvent(Self, 'DeleteShortCut',
Format('DeleteShortCut called. Shortcut name=%s', [fShortCutClass.ShortcutName]));
if fShortCutClass.ShortcutName = '' then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'DeleteShortCut', 'ShortCut.ShortCutName was empty!');
Exit;
end;
Result := DeleteDesktopShortcut(fShortCutClass.ShortCutName);
if fFireDebugEvent then
if Result = True then
fOndebugEvent(Self, 'MakeShortCut', 'DeleteShortCut succeded.' +
GetShortCutDebugString)
else
fOndebugEvent(Self, 'MakeShortCut', 'DeleteShortCut failed. Error: ' +
GetShortCutDebugString);
end;
procedure TLazAutoUpdate.ShowWhatsNewIfAvailable;
begin
// Should be called on form.activate
// Afer an update, the 'whatsnew.txt' is copied into the application's folder
// This routine shows it, then deletes it
// If it isn't there, then it exits early
// read the VMT once
if Assigned(fOndebugEvent) then
fFireDebugEvent := True;
if not FileExistsUTF8(ProgramDirectory + C_WhatsNewFilename) then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'ShowWhatsNewIfAvailable', 'Unable to locate ' +
C_WhatsNewFilename);
Exit;
end;
// Linux fix
if DirectoryExistsUTF8(C_WhatsNewFilename) then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'ShowWhatsNewIfAvailable', 'Found directory ' +
C_WhatsNewFilename);
if RemoveDirUTF8(C_WhatsNewFilename) then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'ShowWhatsNewIfAvailable', 'Deleted directory ' +
C_WhatsNewFilename);
end;
Exit;
end;
// Create the form, memo and close button
if fParentForm <> nil then
WhatsNewForm := TForm.CreateNew(fParentForm)
else
WhatsNewForm := TForm.CreateNew(fParentApplication);
WhatsNewMemo := TMemo.Create(WhatsNewForm);
cmdClose := TBitBtn.Create(WhatsNewForm);
try // ..finally destroy all
with WhatsNewForm do
begin
Height := 480;
Width := 640;
// BorderStyle:=bsToolWindow;
Caption := Format(C_WhatsNewInVersion, [GetFileVersion]);
DefaultMonitor := dmActiveForm;
// FormStyle:=fsStayOnTop;
Position := poScreenCenter;
ShowInTaskBar := stNever;
end;
with WhatsNewMemo do
begin
Height := WhatsNewForm.Height - 80;
Width := WhatsNewForm.ClientWidth;
ReadOnly := True;
ScrollBars := ssAutoBoth;
WordWrap := True;
Parent := WhatsNewForm;
try
Lines.LoadFromFile(ProgramDirectory + C_WhatsNewFilename);
except
Clear;
Lines.Add('Unable to show whats new');
end;
end;
with cmdClose do
begin
Top := WhatsNewForm.Height - Height - 20;
Left := (WhatsNewForm.Width div 2) - (Width div 2);
Kind := bkClose;
Parent := WhatsNewForm;
end;
// Show the window modally (cmdClose will close it)
WhatsNewForm.ShowModal;
try
// Delete the whatsnew.txt now the user has seen it
if not SysUtils.DeleteFile(ProgramDirectory + C_WhatsNewFilename) then
if fShowDialogs then
ShowMessageFmt(C_UnableToDelete,
[C_WhatsNewFilename, LineEnding]);
except
// Ignore Exceptions
end;
finally
{
cmdClose.Free; // Not needed
WhatsNewMemo.Free; // Not needed
}
FreeAndNil(WhatsNewForm); // Free the form and its minions
end;
end;
function TLazAutoUpdate.SilentUpdate: boolean;
// Part of the tray update system
begin
// read the VMT once
if Assigned(fOndebugEvent) then
fFireDebugEvent := True;
Result := False;
fSilentMode := True;
fShowUpdateInCaption := False;
fShowDialogs := False;
if fFireDebugEvent then
fOndebugEvent(Self, 'SilentUpdate', 'Calling UpdateToNewVersion');
// Use the local lauupdate if available
if FileExistsUTF8(ProgramDirectory + C_LAUUPDATENAME) then
begin
if RemoteUpdateToNewVersion then
// If IsAppRunning=FALSE, then calls DoSilentUpdate
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'SilentUpdate', 'UpdateToNewVersion succeeded');
fSilentMode := False;
Result := True;
end;
end
else
begin
if UpdateToNewVersion then
// If IsAppRunning=FALSE, then calls DoSilentUpdate
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'SilentUpdate', 'UpdateToNewVersion succeeded');
fSilentMode := False;
Result := True;
end;
end;
end;
function TLazAutoUpdate.AutoUpdate: boolean;
// Do-all proc that user can drop into a menu
begin
if Assigned(fOndebugEvent) then
fFireDebugEvent := True;
Result := False;
if fFireDebugEvent then
fOndebugEvent(Self, 'AutoUpdate', 'Calling NewVersionAvailable');
if NewVersionAvailable then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'AutoUpdate', 'NewVersionAvailable succeeded');
if MessageDlg(fParentApplication.Title, Format(rsANewVersionS,
[fGUIOnlineVersion]), mtConfirmation, [mbYes, mbNo], 0, mbYes) = 6 then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'AutoUpdate', 'Calling DownloadNewVersion');
if DownloadNewVersion then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'AutoUpdate', 'DownloadNewVersion suceeded');
if MessageDlg(fParentApplication.Title,
Format(rsVewVersionSH, [fGUIOnlineVersion]), mtConfirmation,
[mbOK, mbCancel], 0, mbOK) = 1 then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'AutoUpdate', 'Calling UpdateToNewVersion');
UpdateToNewVersion;
end
else
MessageDlg(fParentApplication.Title,
rsCancelledYou,
mtInformation, [mbOK], 0);
end
else
MessageDlg(fParentApplication.Title,
Format(rsDownloadFail, [GetThreadDownloadReturnCode]),
mtInformation, [mbOK], 0);
end
else
MessageDlg(fParentApplication.Title,
rsCancelledYou2,
mtInformation, [mbOK], 0);
end
else
begin
MessageDlg(fParentApplication.Title,
rsThisApplicat,
mtInformation, [mbOK], 0);
Result := True;
end;
end;
function TLazAutoUpdate.IsOnlineVersionNewer(const sznewINIPath: string): boolean;
// Compares version contained in szTempXMLPath INI file
// to fApplicationVersionNumber
var
VersionINI: TIniFile;
{
C_INISection = 'versions';
C_GUIEntry ='GUI';
C_ModuleEntry = 'Module';
}
begin
// read the VMT once
if Assigned(fOndebugEvent) then
fFireDebugEvent := True;
Result := False;
VersionINI := TIniFile.Create(sznewINIPath);
try
fGUIOnlineVersion := VersionINI.ReadString(C_INISection, C_GUIEntry, '0.0.0.0');
if not TryStrToVersionQuad(fGUIOnlineVersion, fGuiQuad) then
fGUIQuad := StrToVersionQuad('0.0.0.0');
finally
VersionINI.Free;
end;
if fFireDebugEvent then
fOndebugEvent(Self, 'IsOnlineVersionNewer',
Format('fGUIOnlineVersion=%s, fApplicationVersionString=%s, szTempXMLPath=%s',
[fGUIOnlineVersion, fApplicationVersionString, sznewINIPath]));
// Fetch the 4 (or less) version elements and make into an Integer
// so 1.10 > 1.9.9.9
// iGUIVersion := VersionStringToNumber(fGUIOnlineVersion);
// Test: Is the online version newer?
if NewerVersion(fGUIQuad, fApplicationVersionQuad) then
Result := True;
end;
function TLazAutoUpdate.NewVersionAvailable: boolean;
// Returns TRUE is a new version is available
var
szURL, szTargetPath: string;
cCount: cardinal;
szOldCaption: string;
begin
Result := False;
// read the VMT once
if Assigned(fOndebugEvent) then
fFireDebugEvent := True;
if fZipFileName = '' then
begin
fZipfileName := ChangeFileExt(ExtractFilename(fAppFilename), '.zip');
if fFireDebugEvent then
fOndebugEvent(Self, 'NewVersionAvailable',
Format('Assigning ZipFile name %s', [fZipfileName]));
end;
if fProjectType = auSourceForge then
begin
if fSourceForgeProjectName = '' then
begin
if fShowDialogs then
ShowMessage(C_PropIsEmpty);
if fFireDebugEvent then
fOndebugEvent(Self, 'NewVersionAvailable (auSourceForge)', C_PropIsEmpty);
Exit;
end;
szURL := Format(C_SOURCEFORGEURL, [fSourceForgeProjectName,
fUpdatesFolder, fVersionsININame]);
end;
if fProjectType = auGitHubReleaseZip then
begin
if ((fGitHubProjectName = '') or (fGitHubRepositoryName = '')) then
begin
if fShowDialogs then
ShowMessage(C_PropIsEmpty);
if fFireDebugEvent then
fOndebugEvent(Self, 'NewVersionAvailable (auGitHubReleaseZip)', C_PropIsEmpty);
Exit;
end;
if ((fUpdatesFolder = C_NotApplicable) or (fUpdatesFolder = '')) then
szURL := Format(C_GITHUBFILE_URL, [fGitHubProjectName,
fGitHubRepositoryName, fGitHubBranchOrTag, fVersionsININame])
else
szURL := Format(C_GITHUBFILE_URL_UPDATES,
[fGitHubProjectName, fGitHubRepositoryName, fGitHubBranchOrTag,
fUpdatesFolder, fVersionsININame]);
end;
if fProjectType = auOther then
// fauOtherSourceURL ends with '/'
begin
szURL := fauOtherSourceURL + fVersionsININame;
end;
szTargetPath := AppendPathDelim(ExtractFilePath(fAppFilename)) +
Format(C_TempVersionsININame, [fVersionsININame]);
if fFireDebugEvent then
fOndebugEvent(Self, 'NewVersionAvailable',
Format('URL is %s', [szURL]));
if fFireDebugEvent then
fOndebugEvent(Self, 'NewVersionAvailable',
Format('Target Path %s', [szTargetPath]));
// Delete any old versions
try
if FileExistsUTF8(szTargetPath) then
begin
SysUtils.DeleteFile(szTargetPath);
if fFireDebugEvent then
fOndebugEvent(Self, 'NewVersionAvailable',
Format('Deleted old file %s', [szTargetPath]));
end;
except
if fFireDebugEvent then
fOndebugEvent(Self, 'NewVersionAvailable',
Format('Failed to delete old file %s', [szTargetPath]));
// No error if the delete can't be done
end;
with fDownloadWrapper do
begin
URL := szURL;
Filename := szTargetPath;
if not fSilentMode then
szOldCaption := fParentForm.Caption;
// Initialise fields
ThreadFinished := False;
ReturnCode := 0;
DownloadSize := 0;
fDownloadInprogress := True;
DebugMode := fDebugMode;
if not fSilentMode then
fParentForm.Caption := C_Checking;
CheckForOpenSSL;
// Start the thread
ThreadDownloadHTTP;
if fFireDebugEvent then
fOndebugEvent(Self, 'NewVersionAvailable',
Format('ThreadDownloadHTTP return Code was %d', [fReturnCode]));
if fFireDebugEvent then
fOndebugEvent(Self, 'NewVersionAvailable',
Format('ThreadDownloadHTTP Last Error was %s', [fLastError]));
cCount := 0;
// Update the GUI during the thread
try
while (ThreadFinished = False) do
begin
Inc(cCount);
Sleep(1);
fParentApplication.ProcessMessages;
ThreadSwitch();
{$IFDEF WINDOWS}
if fShowUpdateInCaption then
fParentForm.Caption := Format(C_Checking + ' %d', [cCount])
else
Sleep(10);
{$ENDIF}
fParentApplication.ProcessMessages;
if (cCount > fVersionCountLimit) then
begin
if fShowDialogs then
ShowMessage(C_TakingTooLong);
ThreadFinished := True;
fDownloadSize := 0;
fDownloadInprogress := False;
if not fSilentMode then
fParentForm.Caption := szOldCaption;
Exit;
end;
end;
except
ThreadFinished := True;
fDownloadSize := 0;
fDownloadInprogress := False;
if not fSilentMode then
fParentForm.Caption := szOldCaption;
if fFireDebugEvent then
fOndebugEvent(Self, 'NewVersionAvailable',
C_ThreadDownloadCrash);
Exit;
end;
if fFireDebugEvent then
fOndebugEvent(Self, 'NewVersionAvailable',
Format('After Threadfinished: Return Code was %d', [fReturnCode]));
Sleep(1);
fDownloadInprogress := False;
if fDownloadSize > 0 then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'NewVersionAvailable',
Format('Downloaded %s OK', [szTargetPath]));
fParentApplication.ProcessMessages;
Result := IsOnlineVersionNewer(szTargetPath);
if fFireDebugEvent then
fOndebugEvent(Self, 'NewVersionAvailable',
Format(C_DownloadedBytes, [szTargetPath, fDownloadSize]));
end
else
if fFireDebugEvent then
fOndebugEvent(Self, 'NewVersionAvailable',
Format('DownloadSize was %d', [fDownloadSize]));
end;
if not fSilentMode then
fParentForm.Caption := szOldCaption;
if Assigned(fOnNewVersionAvailable) then
fOnNewVersionAvailable(Self, Result, fGUIOnlineVersion);
end;
function TLazAutoUpdate.DownloadNewVersion: boolean;
// Returns TRUE is download succeeded
// If FALSE. then examine ReturnCode property
var
szURL, szTargetPath, szUpdatesFolder: string;
cCount: cardinal;
szOldCaption: string;
iDownloadedSize: integer;
FileStringList: TStringList;
iCount: integer;
begin
Result := False;
// read the VMT once
if Assigned(fOndebugEvent) then
fFireDebugEvent := True;
// **************
// **************
iDownloadedSize := 0;
if fZipFileName = '' then
begin
fZipfileName := ChangeFileExt(ExtractFilename(fAppFilename), '.zip');
if fFireDebugEvent then
fOndebugEvent(Self, 'DownloadNewVersion',
Format('ZipFilename was empty. Assigned %s', [fZipfileName]));
end;
szTargetPath := fZipfileName;
if fProjectType = auSourceForge then
szURL := Format(C_SOURCEFORGEURL, [fSourceForgeProjectName,
fUpdatesFolder, ExtractFileName(szTargetPath)]);
if fProjectType = auGitHubReleaseZip then
if ((fUpdatesFolder = C_NotApplicable) or (fUpdatesFolder = '')) then
szURL := Format(C_GITHUBFILE_URL, [fGitHubProjectName,
fGitHubRepositoryName, fGitHubBranchOrTag, fZipfileName])
else
szURL := Format(C_GITHUBFILE_URL_UPDATES,
[fGitHubProjectName, fGitHubRepositoryName, fGitHubBranchOrTag,
fUpdatesFolder, fZipfileName]);
if fProjectType = auOther then
// fauOtherSourceURL ends with '/'
begin
szURL := fauOtherSourceURL + fVersionsININame;
end;
szUpdatesFolder := AppendPathDelim(ExtractFilePath(fAppFilename)) + fUpdatesFolder;
if fFireDebugEvent then
fOndebugEvent(Self, 'DownloadNewVersion',
Format('Download parameters: TargetPath=%s%sURL=%s%sUpdatesFolder=%s',
[szTargetPath, LineEnding, szURL, LineEnding, szUpdatesFolder]));
// If updates folder exists, delete previous contents
// If not, then create updates folder
try
if DirPathExists(szUpdatesFolder) then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'DownloadNewVersion',
Format('Files already exist in %s. They will be deleted.',
[szUpdatesFolder]));
FileStringList := TStringList.Create;
try
FileStringList := FindAllFiles(szUpdatesFolder, '*.*', True);
try
for iCount := 0 to FileStringList.Count - 1 do
SysUtils.DeleteFile(FileStringList[iCount]);
except
fLastError := Format(C_UnableToDeleteOld, [szUpdatesFolder]);
if fDebugMode then
ShowMessage(fLastError);
end;
finally
FileStringList.Free;
end;
end
else
begin
if ForceDirectory(szUpdatesFolder) then
if fFireDebugEvent then
fOndebugEvent(Self, 'DownloadNewVersion',
Format('New directory %s was created', [szUpdatesFolder]));
end;
except
if fFireDebugEvent then
fOndebugEvent(Self, 'DownloadNewVersion',
Format(C_DirectoryProblems, [szUpdatesFolder]));
raise Exception.CreateFmt(C_DirectoryProblems, [szUpdatesFolder]);
end;
// Set target to the updates folder
szTargetPath := AppendPathDelim(szUpdatesFolder) + ExtractFileName(szTargetPath);
if fProjectType = auOther then
// fauOtherSourceURL ends with PathDelimiter
begin
szURL := fauOtherSourceURL + fauOtherSourceFilename;
szTargetPath := szUpdatesFolder + PathDelim + fauOtherSourceFilename;
end;
if fFireDebugEvent then
fOndebugEvent(Self, 'DownloadNewVersion',
Format('szURL=%s, szTargetPath=%s', [szURL, szTargetPath]));
// Double-check: Delete any earlier updates?
try
if FileExistsUTF8(szTargetPath) then
begin
SysUtils.DeleteFile(szTargetPath);
if fFireDebugEvent then
fOndebugEvent(Self, 'DownloadNewVersion',
Format('Deleting old file %s', [szTargetPath]));
end;
except
// Ignore exceptions
end;
fDownloadInprogress := True;
CheckForOpenSSL;
if Assigned(OnFileWriteProgress) then
begin
fDownloadWrapper.OnDownloadWrapperFileWriteProgress := OnFileWriteProgress;
if fFireDebugEvent then
fOndebugEvent(Self, 'DownloadNewVersion', 'OnProgess assigned OK');
end;
// Do the download
with fDownloadWrapper do
begin
// Initialise fields
URL := szURL;
Filename := szTargetPath;
ThreadFinished := False;
ReturnCode := 0;
DownloadSize := 0;
fUnzipAfter := True;
DebugMode := fDebugMode;
if not fSilentMode then
szOldCaption := fParentForm.Caption;
if not fSilentMode then
fParentForm.Caption := C_Downloading;
// Start the thread
ThreadDownloadHTTP;
cCount := 0;
begin
// The thread is running....
cCount := 0;
try
while (ThreadFinished = False) do
begin
try
// fDownloadWrapper.fDownLoad.Synchronize(fDownloadWrapper.fDownLoad.GetPercent);
cCount := fDownloadWrapper.fDownLoad.Percent;
//threadswitch;
//fParentApplication.QueueAsyncCall(@fDownloadWrapper.fDownLoad.GetPercent,0);
// Inc(cCount);
//CheckSynchronize();
Sleep(1);
fParentApplication.ProcessMessages;
{$IFDEF WINDOWS}
if fShowUpdateInCaption then
begin
fParentForm.Caption := Format(C_Downloading + ' %d', [cCount]);
fParentApplication.ProcessMessages;
end
else
sleep(10);
fParentApplication.ProcessMessages; // Keep GUI responsive
if (cCount > fDownloadCountLimit) then // Download taking too long?
begin
fDownloadInprogress := False;
if not fSilentMode then
fParentForm.Caption := szOldCaption;
if fFireDebugEvent then
fOndebugEvent(Self, 'DownloadNewVersion', C_TakingTooLong);
ThreadFinished := True;
fDownloadSize := 0;
Exit;
end;
{$ENDIF}
finally
end;
end;
iDownloadedSize := fDownloadSize;
except
if fFireDebugEvent then
fOndebugEvent(Self, 'DownloadNewVersion', C_ThreadDownloadHTTPCrash);
end;
fDownloadInprogress := False;
Sleep(1);
// We have the HTTP return code in MyThreadDownload.ReturnCode. Normal=200
if (ReturnCode <> 0) and (ReturnCode < 400) then
begin
// Success! New version is unzipped and ready in the /updates folder
// Shell into the updater app here
// 1) Closes this app
// 2) Copies the szUpdateFolder/downloadedexe to the .exe
// 3) Restarts this (updated) app (showing C_WhatsNewFilename?)
Result := (fDownloadSize > 0);
if fFireDebugEvent then
begin
fOndebugEvent(Self, 'DownloadNewVersion',
Format(C_DownloadedBytes, [ExtractFilename(szTargetPath),
fDownloadSize]));
fOndebugEvent(Self, 'DownloadNewVersion',
Format(C_DownloadSuccess, [fGUIOnlineVersion]));
end;
end
else
begin
Result := False;
if fFireDebugEvent then
fOndebugEvent(Self, 'DownloadNewVersion',
Format(C_UnableToDownload, [LineEnding, ReturnCode]));
Free;
end;
end;
end;
// Fire the event?
if not fSilentMode then
fParentForm.Caption := C_PleaseWaitProcessing;
if Assigned(fOnDownloaded) then
fOnDownloaded(Self, ReturnCode, iDownloadedSize);
if not fSilentMode then
fParentForm.Caption := szOldCaption;
end;
function UTF8StringReplace(const S, OldPattern, NewPattern: string;
Flags: TReplaceFlags): string;
var
uS, uOld: string;
// Warning! Always changes string to UPPERCASE
begin
if rfIgnoreCase in Flags then
begin
uS := LazUTF8.UTF8UpperCase(S);
uOld := LazUTF8.UTF8UpperCase(OldPattern);
Flags := Flags - [rfIgnoreCase]; //no point uppercasing again
Result := StringReplace(uS, uOld, NewPattern, Flags);
end
else
Result := StringReplace(S, OldPattern, NewPattern, Flags);
end;
function TLazAutoUpdate.CreateLocalLauImportFile: boolean;
// Used in SysTray app
var
LAUTRayINI: TIniFile;
szSection: string;
szSuffix: string;
begin
// read the VMT once
if Assigned(fOndebugEvent) then
fFireDebugEvent := True;
if fFireDebugEvent then
fOndebugEvent(Self, 'CreateLocalLauImportFile', 'CreateLocalLauImportFile called');
//
if (ProgramDirectory + C_LAUTRayINI <> '') then
begin
RelocateLauImportFile;
Result := True;
Exit;
end;
// Make up OS-Bitness suffix
{$IFDEF WINDOWS}
szSuffix := 'win';
{$ELSE}
szSuffix := 'linux';
{$ENDIF}
{$IFDEF CPU64}
szSuffix += '64';
{$ELSE}
szSuffix += '32';
{$ENDIF}
Result := False;
LAUTRayINI := TIniFile.Create(GetAppConfigDirUTF8(False, True) + C_LAUTRayINI);
try
with LAUTRayINI do
begin
if fParentApplication <> nil then
szSection := fParentApplication.Title
else
if fParentForm <> nil then
szSection := fParentForm.Caption
else
szSection := 'My Application';
if ((AnsiContainsText(szSection,
{$I %FPCTARGETOS%}
) = False) and (AnsiContainsText(szSection, szSuffix) = False)) then
szSection += szSuffix;
WriteString(szSection, 'AppPrettyName', szSection);
WriteString(szSection, 'AppPath', ExtractFilename(fAppFilename));
WriteString(szSection, 'INIPath', fVersionsININame);
WriteString(szSection, 'ZipPath', fZipfileName);
WriteString(szSection, 'AppVersion', fApplicationVersionString);
WriteString(szSection, 'SFProjectName', fSourceForgeProjectName);
WriteString(szSection, 'SFUpdatesDirectory', fUpdatesFolder);
WriteString(szSection, 'Location', ExtractFilePath(fAppFilename));
//Suggest a schedule
WriteInteger(szSection, 'IntervalType', 0);
WriteInteger(szSection, 'IntervalDay', 0);
WriteInteger(szSection, 'IntervalDate', 1);
WriteInteger(szSection, 'IntervalHour', 9);
WriteInteger(szSection, 'Update', 0);
WriteString(szSection, 'LastCheckDateTime', '2000-01-01 00-00');
UpdateFile;
Result := True;
end;
finally
FreeAndNil(LAUTRayINI);
end;
end;
procedure TLazAutoUpdate.RelocateLauImportFile;
// If C_LAUTRayINI is found in the App Folder, it is moved to the <LocalAppData>/updatehm folder
var
szSourceLAUTrayPath, szDestLAUTrayPath, szDestLAUTrayDirectory: string;
begin
// read the VMT once
if Assigned(fOndebugEvent) then
fFireDebugEvent := True;
szSourceLAUTrayPath := ExtractFilePath(fAppFilename) + C_LAUTRayINI;
if fFireDebugEvent then
fOndebugEvent(Self, 'RelocateLauImportFile',
Format('Looking for %s.', [szSourceLAUTrayPath]));
if FileExists(szSourceLAUTrayPath) then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'RelocateLauImportFile',
Format('Found %s.', [szSourceLAUTrayPath]));
// Make up local <AppData>/updatehm/C_LAUTRayINI path
szDestLAUTrayPath := GetAppConfigDirUTF8(False, False); // Don't create it yet
{$IFDEF WINDOWS}
szDestLAUTrayPath := StringReplace(szDestLAUTrayPath, Application.Title,
'updatehm' + C_PFX, [rfReplaceAll]);
{$ELSE}
szDestLAUTrayPath := UTF8StringReplace(szDestLAUTrayPath,
Application.Title, 'updatehm' + C_PFX, [rfReplaceAll]);
{$ENDIF}
end
else
Exit; // Nothing to do
// szDestLAUTrayPath := LowerCase(szDestLAUTrayPath);
szDestLAUTrayDirectory := ExtractFilePath(szDestLAUTrayPath);
if DirectoryExistsUTF8(szDestLAUTrayDirectory) then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'RelocateLauImportFile',
Format('%s already exists.', [szDestLAUTrayDirectory]));
end
else
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'RelocateLauImportFile',
Format('%s does not previously exist.', [szDestLAUTrayDirectory]));
if ForceDirectory(szDestLAUTrayDirectory) then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'RelocateLauImportFile',
Format('Created folder %s.', [szDestLAUTrayDirectory]));
end
else
if fFireDebugEvent then
fOndebugEvent(Self, 'RelocateLauImportFile',
Format('Unable to create folder %s.', [szDestLAUTrayDirectory]));
end;
// Don't copy over an existing file
if not FileExists(szDestLAUTrayPath + C_LAUTRayINI) then
begin
// Move C_LAUTRayINI from app folder to local <AppData> folder
if FileUtil.CopyFile(szSourceLAUTrayPath, szDestLAUTrayPath +
C_LAUTRayINI, [cffOverwriteFile]) then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'RelocateLauImportFile',
Format('Relocated %s from %s to %s', [C_LAUTRayINI,
szSourceLAUTrayPath, szDestLAUTrayPath]));
SysUtils.DeleteFile(szSourceLAUTrayPath);
end
else
if fFireDebugEvent then
fOndebugEvent(Self, 'RelocateLauImportFile',
Format('Failed to relocate %s from %s to %s',
[C_LAUTRayINI, szSourceLAUTrayPath, szDestLAUTrayPath]));
end;
end;
function TLazAutoUpdate.DoSilentUpdate: boolean;
// Used in Systray app
// Called from UpdateToNewVersion when the app is not running
// Updates the app, and also copies over and updates C_LAUTRayINI
var
szAppFolder: string;
szLAUTrayAppPath: string;
INI: TINIFile;
SectionStringList: TStrings;
szTempUpdatesFolder: string;
ErrMsg: string;
begin
// fWorkingMode=lauInstall or lauUpdate
Result := False;
// read the VMT once
if Assigned(fOndebugEvent) then
fFireDebugEvent := True;
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate', 'Starting DoSilentUpdate');
if fFireDebugEvent then
if fWorkingMode = lauUpdate then
fOndebugEvent(Self, 'DoSilentUpdate', 'Update mode')
else
fOndebugEvent(Self, 'DoSilentUpdate', 'Install mode');
if fWorkingMode = lauUpdate then
begin
if not FileExists(fAppFilename) then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate',
Format('AppFilename %s is missing. Exiting routine', [fAppFilename]));
Exit;
end;
end;
// uses fUpdatesFolder
szTempUpdatesFolder := AppendPathDelim(ExtractFilePath(fAppFilename) + fUpdatesFolder);
if not DirectoryExistsUTF8(szTempUpdatesFolder) then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate',
Format('Updates folder %s was missing.', [szTempUpdatesFolder]));
if ForceDirectory(szTempUpdatesFolder) then
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate',
Format('Created folder %s.', [szTempUpdatesFolder]));
end;
szAppFolder := AppendPathDelim(ExtractFilePath(fAppFilename));
// Copy over everything from the updates folder
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate',
Format('About to copy from %s to %s', [szTempUpdatesFolder, szAppFolder]));
if fCopyTree then
begin
if CopyDirTree(szTempUpdatesFolder, szAppFolder,
[cffOverwriteFile, cffCreateDestDirectory]) then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate',
Format('CopyTree successful from %s to %s',
[szTempUpdatesFolder, szAppFolder]));
end
else
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate',
Format('Error: CopyTree unsuccessful from %s to %s',
[szTempUpdatesFolder, szAppFolder]));
end
else
begin
// Copy over app
if FileUtil.CopyFile(szTempUpdatesFolder + ExtractFileName(fAppFilename),
szAppFolder + ExtractFileName(fAppFilename), [cffOverwriteFile]) then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate',
Format('Copied app from %s to %s', [szTempUpdatesFolder +
ExtractFileName(fAppFilename), szAppFolder + ExtractFileName(fAppFilename)]));
{$IFDEF LINUX}
if not SetExecutePermission(szAppFolder + ExtractFileName(fAppFilename),
ErrMsg) then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate',
Format('Unable to set permissions for %s because of %s',
[szAppFolder + ExtractFileName(fAppFilename), ErrMsg]));
if fShowDialogs then
ShowMessageFmt('Unable to set permissions for %s because of %s',
[szAppFolder + ExtractFileName(fAppFilename), ErrMsg]);
end;
{$ENDIF}
end
else
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate',
Format('Failed to copy app from %s to %s',
[szTempUpdatesFolder + ExtractFileName(fAppFilename),
szAppFolder + ExtractFileName(fAppFilename)]));
// Copy over WhatsNew
if FileUtil.CopyFile(szTempUpdatesFolder + 'whatsnew.txt',
szAppFolder + 'whatsnew.txt', [cffOverwriteFile]) then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate',
Format('Copied whatsnew.txt from %s to %s',
[szTempUpdatesFolder + 'whatsnew.txt', szAppFolder + 'whatsnew.txt']));
end
else
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate',
Format('Failed to copy whatsnew.txt from %s to %s',
[szTempUpdatesFolder + 'whatsnew.txt', szAppFolder + 'whatsnew.txt']));
end;
if (fWorkingMode = lauInstall) then
if FileExists(C_UPDATEHMNAME) then
begin
if FileUtil.CopyFile(C_UPDATEHMNAME, szAppFolder + C_UPDATEHMNAME) then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate',
Format('Sucessfully copied %s to %s',
[C_UPDATEHMNAME, szAppFolder]));
{$IFDEF LINUX}
if not SetExecutePermission(szAppFolder + C_UPDATEHMNAME, ErrMsg) then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate',
Format('Unable to set permissions for %s because of %s',
[szAppFolder + C_UPDATEHMNAME, ErrMsg]));
if fShowDialogs then
ShowMessageFmt('Unable to set permissions for %s because of %s',
[szAppFolder + C_UPDATEHMNAME, ErrMsg]);
end;
{$ENDIF}
end
else
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate',
Format('Unabled to copy %s to %s', [C_UPDATEHMNAME, szAppFolder]));
end
else
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate', 'Unable to locate ' + C_UPDATEHMNAME);
// Deal with C_LAUTRayINI
// Copied to the global application data folder
// Add entry 'Location'
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate', 'About to process ' +
szTempUpdatesFolder + C_LAUTRayINI);
if FileExists(szTempUpdatesFolder + C_LAUTRayINI) then
begin
szLAUTrayAppPath := GetAppConfigDirUTF8(False, True); // Create it if necessary
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate',
Format('App data directory is %s', [szLAUTrayAppPath]));
// AppDataDirectory/LazAutoUpdater/
szLAUTrayAppPath := UTF8StringReplace(szLAUTrayAppPath, Application.Title,
'updatehm' + C_PFX, [rfIgnoreCase, rfReplaceAll]);
szLAUTrayAppPath := LowerCase(szLAUTrayAppPath);
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate',
Format('App data directory changed to %s', [szLAUTrayAppPath]));
// Now AppDataDirectory/updatehm(+C_PFX)/
try
if ForceDirectory(szLAUTrayAppPath) then
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate',
Format('Made directory %s', [szLAUTrayAppPath]));
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate',
Format('About to copy %s to %s', [szTempUpdatesFolder +
C_LAUTRayINI, szLAUTrayAppPath]));
Fileutil.CopyFile(szTempUpdatesFolder + C_LAUTRayINI, szLAUTrayAppPath +
C_LAUTRayINI, [cffOverWriteFile]);
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate',
Format('Successfully copied %s to %s ',
[C_LAUTRayINI, szLAUTrayAppPath]));
if FileExists(szLAUTrayAppPath + C_LAUTRayINI) then
begin
INI := TINIFile.Create(szLAUTrayAppPath + C_LAUTRayINI);
SectionStringList := TStringList.Create;
try
INI.ReadSections(SectionStringList);
if SectionStringList.Count > 0 then
begin
INI.WriteString(SectionStringList[0], 'Location',
ExtractFilePath(fAppFilename));
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate',
Format('Wrote new entry in section %s. Location=%s',
[SectionStringList[0], ExtractFilePath(fAppFilename)]));
end
else
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate',
'Failed to find a valid section in ' + C_LAUTRayINI);
finally
FreeAndNil(SectionStringList);
FreeAndNil(INI);
end;
Result := True;
end
else
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate',
'Failed to copy ' + C_LAUTRayINI + ' to ' + szLAUTrayAppPath);
except
On E: Exception do
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate',
Format('Could not update %s. Error: %s ', [C_LAUTRayINI, E.Message]));
end;
end;
if fFireDebugEvent then
fOndebugEvent(Self, 'DoSilentUpdate', 'Leaving DoSilentUpdate');
end;
function TLazAutoUpdate.RemoteUpdateToNewVersion: boolean;
// Used in Systray app
// Shells to 'lauupdate' console app in ProgramDirectory to remotely update an app
{$IFDEF WINDOWS}
function RunAsAdmin(const Handle: THandle; const Path, Params: string): boolean;
var
sei: TShellExecuteInfoA;
begin
FillChar(sei, SizeOf(sei), 0);
sei.cbSize := SizeOf(sei);
sei.Wnd := Handle;
sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI;
sei.lpVerb := 'runas';
sei.lpFile := PAnsiChar(Path);
sei.lpParameters := PAnsiChar(Params);
sei.nShow := SW_SHOWNORMAL;
Result := ShellExecuteExA(@sei);
end;
{$ENDIF}
var
cCount: cardinal;
szAppDir, szParams: string;
begin
Result := False;
{$IFDEF WINDOWS}
if fWindowsAdminCheck then
if not IsWindowsAdmin then
begin
ShowAdminCheckMessage;
Exit;
end;
{$ENDIF}
// fWorkingMode=lauInstall or lauUpdate
szAppDir := AppendPathDelim(ExtractFilePath(fAppFilename));
// read the VMT once
if Assigned(fOndebugEvent) then
fFireDebugEvent := True;
if fFireDebugEvent then
fOndebugEvent(Self, 'RemoteUpdateToNewVersion',
'Entering RemoteUpdateToNewVersion');
// Running update using updatehm?
if ((AppIsRunning(ExtractFileName(fAppFilename)) = False) and
(ExtractFileName(fAppFilename) <> ExtractFileName(fparentApplication.EXEname))) then
Result := DoSilentUpdate
else
begin
cCount := 0;
if not FileExists(ProgramDirectory + C_LAUUPDATENAME) then
begin
if fShowDialogs then
ShowMessageFmt(C_UpdaterMissing, [ProgramDirectory + C_LAUUPDATENAME]);
if fFireDebugEvent then
fOndebugEvent(Self, 'RemoteUpdateToNewVersion',
Format(C_UpdaterMissing, [ProgramDirectory + C_LAUUPDATENAME]));
Exit;
end;
if not DirectoryExistsUTF8(szAppDir + fUpdatesFolder) then
begin
if fShowDialogs then
ShowMessageFmt(C_FolderMissing, [szAppDir + fUpdatesFolder]);
if fFireDebugEvent then
fOndebugEvent(Self, 'RemoteUpdateToNewVersion',
Format(C_FolderMissing, [szAppDir + fUpdatesFolder]));
Exit;
end;
{$IFDEF WINDOWS}
szParams := ExtractFileName(fAppFilename);
szParams := szParams + ' ' + fUpdatesFolder;
szParams := szParams + ' ' + C_WhatsNewFilename;
szParams := szParams + ' ' + fParentApplication.Title;
if (fCopyTree = True) then
szParams := szParams + ' copytree';
if fFireDebugEvent then
fOndebugEvent(Self, 'RemoteUpdateToNewVersion',
Format('Executing %s', [ProgramDirectory + C_LAUUPDATENAME]));
RunAsAdmin(fParentForm.Handle, ProgramDirectory + C_LAUUPDATENAME, szParams);
// Check for C_WhatsNewFilename in the app directory in a LOOP
if fFireDebugEvent then
fOndebugEvent(Self, 'RemoteUpdateToNewVersion',
Format('Waiting for %s', [szAppDir + C_WhatsNewFilename]));
while not FileExistsUTF8(szAppDir + C_WhatsNewFilename) do
begin
fParentApplication.ProcessMessages;
Inc(CCount);
if cCount > 10000000 then
Break; // Get out of jail in case updatehm.exe fails to copy file
end;
{$ELSE}
// Update and re-start the app
FUpdateHMProcess := TAsyncProcess.Create(nil);
FUpdateHMProcess.Executable := ProgramDirectory + C_LAUUPDATENAME;
FUpdateHMProcess.CurrentDirectory := ProgramDirectory;
if not fSilentMode then
FUpdateHMProcess.ConsoleTitle :=
Format(C_ConsoleTitle, [fParentApplication.Title]);
FUpdateHMProcess.Parameters.Clear;
FUpdateHMProcess.Parameters.Add(fAppFilename); //Param 1 = EXEname
FUpdateHMProcess.Parameters.Add(fUpdatesFolder); // Param 2 = updates
FUpdateHMProcess.Parameters.Add(C_WhatsNewFilename); // Param 3 = whatsnew.txt
FUpdateHMProcess.Parameters.Add(fParentApplication.Title); // Param 4 = Prettyname
if (fCopyTree = True) then
FUpdateHMProcess.Parameters.Add('copytree');
// Param 5 = Copy the whole of /updates to the App Folder
if fFireDebugEvent then
fOndebugEvent(Self, 'RemoteUpdateToNewVersion',
Format('Executing %s', [ProgramDirectory + C_LAUUPDATENAME]));
try
FUpdateHMProcess.Execute;
// Check for C_WhatsNewFilename in the app directory in a LOOP
if fFireDebugEvent then
fOndebugEvent(Self, 'RemoteUpdateToNewVersion',
Format('Waiting for %s', [szAppDir + C_WhatsNewFilename]));
while not FileExists(szAppDir + C_WhatsNewFilename) do
begin
fParentApplication.ProcessMessages;
Inc(CCount);
if cCount > 10000000 then
Break; // Get out of jail in case updatehm.exe fails to copy file
end;
finally
FUpdateHMProcess.Free;
end;
{$ENDIF}
// remotely shut down the app?
if fSilentMode then
begin
if AppIsRunning(ExtractFileName(fAppFilename)) then
KillApp(ExtractFileName(fAppFilename));
if fFireDebugEvent then
fOndebugEvent(Self, 'RemoteUpdateToNewVersion',
Format('Killing %s ready for update', [fAppFilename]));
end;
if not fSilentMode then
fParentForm.Close;
if fFireDebugEvent then
fOndebugEvent(Self, 'RemoteUpdateToNewVersion',
'Success');
Result := True;
end;
end;
{$IFDEF LINUX}
function TLazAutoUpdate.SetExecutePermission(const AFileName: string;
var AErrMsg: string): boolean;
var
SL: TStringList;
Process: TProcess;
begin
Result := False;
Process := TProcess.Create(nil);
try
Process.Executable := '/bin/chmod';
Process.Parameters.Add('+X');
Process.Parameters.Add(AFileName);
Process.Options := Process.Options + [poWaitOnExit, poUsePipes];
Process.Execute;
SL := TStringList.Create;
try
SL.LoadFromStream(Process.Stderr);
AErrMsg := Trim(SL.Text);
Result := Trim(AErrMsg) = '';
finally
SL.Free;
end;
finally
Process.Free;
end;
end;
{$ENDIF}
{
procedure CheckPermissions;
var
ErrMsg: String;
begin
if SetExecutePermission('/minesadorada/developer/updates/consoleupdater', ErrMsg) then
MessageDlg('Permission successfully set.', mtInformation, [mbOk], 0)
else
MessageDlg('Cannot set permission. Error message: ' + ErrMsg, mtError, [mbOk], 0);
end;
}
function TLazAutoUpdate.UpdateToNewVersion: boolean;
// Shells to updater console
// Requires admin user in Win 10
{$IFDEF WINDOWS}
function RunAsAdmin(const Handle: THandle; const Path, Params: string): boolean;
var
sei: TShellExecuteInfoA;
begin
FillChar(sei, SizeOf(sei), 0);
sei.cbSize := SizeOf(sei);
sei.Wnd := Handle;
sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI;
sei.lpVerb := 'runas';
sei.lpFile := PAnsiChar(Path);
sei.lpParameters := PAnsiChar(Params);
sei.nShow := SW_SHOWNORMAL;
Result := ShellExecuteExA(@sei);
end;
{$ENDIF}
var
cCount: cardinal;
szAppDir: string;
szParams: string;
{$IFDEF LINUX}
ErrMsg: string;
{$ENDIF}
begin
Result := False;
{$IFDEF WINDOWS}
if fWindowsAdminCheck then
if not IsWindowsAdmin then
begin
ShowAdminCheckMessage;
Exit;
end;
{$ENDIF}
szAppDir := AppendPathDelim(ExtractFilePath(fAppFilename));
// read the VMT once
if Assigned(fOndebugEvent) then
fFireDebugEvent := True;
if fFireDebugEvent then
fOndebugEvent(Self, 'UpdateToNewVersion',
'Entering UpdateToNewVersion');
// Running update using updatehm?
if not AppIsRunning(ExtractFileName(fAppFilename)) then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'UpdateToNewVersion', 'Doing SilentUpdate');
Result := DoSilentUpdate;
end
else
begin
// Start Regular update
cCount := 0;
if not FileExists(szAppDir + C_UPDATEHMNAME) then
begin
if fShowDialogs then
ShowMessageFmt(C_UpdaterMissing, [szAppDir + C_UPDATEHMNAME]);
if fFireDebugEvent then
fOndebugEvent(Self, 'UpdateToNewVersion',
Format(C_UpdaterMissing, [szAppDir + C_UPDATEHMNAME]));
Exit;
end;
{$IFDEF LINUX}
if not SetExecutePermission(szAppDir + C_UPDATEHMNAME, ErrMsg) then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'UpdateToNewVersion',
Format('Unable to set permissions for %s because of %s',
[szAppDir + fUpdatesFolder, ErrMsg]));
if fShowDialogs then
ShowMessageFmt('Unable to set permissions for %s because of %s',
[szAppDir + fUpdatesFolder, ErrMsg]);
Result := False;
Exit;
end;
{$ENDIF}
if not DirectoryExistsUTF8(szAppDir + fUpdatesFolder) then
begin
if fShowDialogs then
ShowMessageFmt(C_FolderMissing, [szAppDir + fUpdatesFolder]);
if fFireDebugEvent then
fOndebugEvent(Self, 'UpdateToNewVersion',
Format(C_FolderMissing, [szAppDir + fUpdatesFolder]));
Exit;
end;
// remotely shut down the app?
if fSilentMode then
begin
if AppIsRunning(ExtractFileName(fAppFilename)) then
KillApp(ExtractFileName(fAppFilename));
if fFireDebugEvent then
fOndebugEvent(Self, 'UpdateToNewVersion',
Format('Killing %s ready for update', [fAppFilename]));
end;
{$IFDEF WINDOWS}
szParams := ExtractFileName(fAppFilename);
szParams := szParams + ' ' + fUpdatesFolder;
szParams := szParams + ' ' + C_WhatsNewFilename;
szParams := szParams + ' ' + fParentApplication.Title;
if (fCopyTree = True) then
szParams := szParams + ' copytree';
fOndebugEvent(Self, 'UpdateToNewVersion',
Format('Executing %s', [szAppDir + C_UPDATEHMNAME]));
if RunAsAdmin(fParentForm.Handle, szAppDir + C_UPDATEHMNAME, szParams) then
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'UpdateToNewVersion', 'RunAsAdmin succeeded');
end
else
begin
if fFireDebugEvent then
fOndebugEvent(Self, 'UpdateToNewVersion', 'RunAsAdmin failed');
end;
// Check for C_WhatsNewFilename in the app directory in a LOOP
if fFireDebugEvent then
fOndebugEvent(Self, 'UpdateToNewVersion',
Format('Waiting for %s', [szAppDir + C_WhatsNewFilename]));
while not FileExistsUTF8(szAppDir + C_WhatsNewFilename) do
begin
fParentApplication.ProcessMessages;
Inc(CCount);
if cCount > 10000000 then
Break; // Get out of jail in case updatehm.exe fails to copy file
end;
{$ELSE}
// Update and re-start the app
FUpdateHMProcess := TAsyncProcess.Create(nil);
try
// FUpdateHMProcess.Executable := AppendPathDelim(GetAppConfigDir(false)) + C_UPDATEHMNAME;
FUpdateHMProcess.Executable := szAppDir + C_UPDATEHMNAME;
// FUpdateHMProcess.CurrentDirectory := AppendPathDelim(GetAppConfigDir(false));
FUpdateHMProcess.CurrentDirectory := szAppDir;
if not fSilentMode then
FUpdateHMProcess.ConsoleTitle :=
Format(C_ConsoleTitle, [fParentApplication.Title]);
FUpdateHMProcess.Parameters.Clear;
FUpdateHMProcess.Parameters.Add(ExtractFileName(fAppFilename)); //Param 1 = EXEname
FUpdateHMProcess.Parameters.Add(fUpdatesFolder); // Param 2 = updates
FUpdateHMProcess.Parameters.Add(C_WhatsNewFilename); // Param 3 = whatsnew.txt
FUpdateHMProcess.Parameters.Add(fParentApplication.Title); // Param 4 = Prettyname
if (fCopyTree = True) then
FUpdateHMProcess.Parameters.Add('copytree');
// Param 5 = Copy the whole of /updates to the App Folder
if fFireDebugEvent then
fOndebugEvent(Self, 'UpdateToNewVersion',
Format('Executing %s', [szAppDir + C_UPDATEHMNAME]));
try
FUpdateHMProcess.Execute;
except
raise Exception.CreateFmt(
'Error %d: Run this application in Administrator mode or turn off UAC',
[GetLastOSError]);
end;
// Check for C_WhatsNewFilename in the app directory in a LOOP
if fFireDebugEvent then
fOndebugEvent(Self, 'UpdateToNewVersion',
Format('Waiting for %s', [szAppDir + C_WhatsNewFilename]));
while not FileExists(szAppDir + C_WhatsNewFilename) do
begin
fParentApplication.ProcessMessages;
Inc(CCount);
if cCount > 100000 then
begin
// Fire the OnUpdated event
if Assigned(fOnUpdated) then
begin
fOnUpdated(Self, fGUIOnlineVersion, 'Unsuccessful update');
Application.ProcessMessages;
Sleep(100);
end;
Break; // Get out of jail in case updatehm.exe fails to copy file
end;
end;
finally
FUpdateHMProcess.Free;
end;
{$ENDIF}
CreateLocalLauImportFile; // Creates a new import file in GetAppConfigDirUTF8
// Fire the OnUpdated event
if Assigned(fOnUpdated) then
begin
fOnUpdated(Self, fGUIOnlineVersion, 'Successful update');
Application.ProcessMessages;
Sleep(100);
end;
if fFireDebugEvent then
fOndebugEvent(Self, 'UpdateToNewVersion',
'Success');
if not fSilentMode then
fParentForm.Close;
Result := True;
end;
end;
procedure TLazAutoUpdate.ClearUpdateList;
// Unused
begin
Setlength(fUpdateList, 0);
end;
function TLazAutoUpdate.AddToUpdateList(APrettyName, APath, AVersionString: string;
AVersionNumber: cardinal): integer;
// Unused
var
iLast: integer;
TheRec: UpdateListRecord;
begin
Setlength(fUpdateList, 0);
iLast := High(fUpdateList);
if (iLast = -1) then
iLast := 0; // For when array is empty
if (iLast = 1) then
Exit; // TEMP: Only one entry allowed
try
Inc(iLast);
Setlength(fUpdateList, iLast);
with TheRec do
begin
PrettyName := APrettyName;
Path := APath;
VersionString := AVersionString;
VersionNumber := AVersionNumber;
end;
fUpdateList[iLast - 1] := TheRec; // Remember array is zero-based
finally
Result := High(fUpdateList); // 0 = one element
fUpdateListCount := Result + 1; // 1 = one element
end;
if (Result = 0) then
begin
fAppFilename := fUpdateList[iLast - 1].Path;
fDownloadZipName := ChangeFileExt(fAppFilename, '.zip');
fZipfileName := fDownloadZipName;
fApplicationVersionString := fUpdateList[iLast - 1].VersionString;
end;
end;
function TLazAutoUpdate.GetThreadDownloadReturnCode: integer;
begin
Result := 0;
if ThreadDownload.ThreadFinished then
Result := fDownloadWrapper.fReturnCode;
end;
procedure TLazAutoUpdate.SetProjectType(AValue: TProjectType);
// Set properties in a context-sensitive way
begin
if (AValue <> fProjectType) then
fProjectType := AValue;
if fProjectType = auOther then
begin
fSourceForgeProjectName := C_NotApplicable;
fGitHubRepositoryName := C_NotApplicable;
fGitHubProjectName := C_NotApplicable;
fGitHubBranchOrTag := C_NotApplicable;
fauOtherSourceFilename := '';
fauOtherSourceURL := '';
end;
if fProjectType = auSourceForge then
begin
fUpdatesFolder := C_UpdatesFolder;
fSourceForgeProjectName := '';
fauOtherSourceFilename := C_NotApplicable;
fauOtherSourceURL := C_NotApplicable;
fGitHubRepositoryName := C_NotApplicable;
fGitHubProjectName := C_NotApplicable;
fGitHubBranchOrTag := C_NotApplicable;
end;
if fProjectType = auGitHubReleaseZip then
begin
fZipFileName := ChangeFileExt(fVersionsININame, '.zip');
fUpdatesFolder := C_UpdatesFolder;
fSourceForgeProjectName := C_NotApplicable;
fauOtherSourceFilename := C_NotApplicable;
fauOtherSourceURL := C_NotApplicable;
fGitHubBranchOrTag := C_MASTER;
fGitHubRepositoryName := '';
fGitHubProjectName := '';
fUpdatesFolder := C_NotApplicable;
end;
end;
procedure TLazAutoUpdate.SetSourceForgeProjectName(Avalue: string);
// Ensure lowercase
begin
fSourceForgeProjectName := LowerCase(AValue);
end;
procedure TLazAutoUpdate.SetAppFilename(Avalue: string);
// Guess a default value
begin
fAppFilename := AValue;
// Set a default value?
if (fDownloadZipName = '') then
fDownloadZipName := ChangeFileExt(ExtractFilename(fAppFilename), '.zip');
fDownloadWrapper.Filename := fUpdatesFolder + PathDelim + fDownloadZipName;
end;
procedure TLazAutoUpdate.SetApplicationVersionString(Avalue: string);
begin
if AValue = '' then
Exit;
fApplicationVersionString := AValue;
fApplicationVersionQuad := StrToVersionQuad(fApplicationVersionString);
end;
// Threaded version
// ================
// Var bDownloadIsPresent:Boolean;
// MyTheadDownload:TDownloadWrapper;
// Begin
// MyTheadDownload:=TDownloadWrapper.Create(sourceforgedownloadURL,Localfilepath);
// {
// Note the Localfilepath MUST be specified, and can be a different filename and path
// than the filename specified in the sourceforgedownloadURL
// }
// bDownloadIsPresent:=MyTheadDownload.ThreadDownloadHTTP;
// MyTheadDownload.UnzipAfter:=FALSE; // True *by default* if targetfile is a zip file
// If NOT bDownloadIsPresent then Exit; {BailOut ->}
// WHILE NOT MyThreadDownload.ThreadFinished do
// begin
// {.. show the user it is downloading in the background}
// Application.ProcessMessages; // <- Very Important; else the app will freeze!
// end;
// {File has now downloaded OK to Localfilepath (and is optionally unzipped)}
// MyTheadDownload.Free;
// End;
{ TDownloadThreadClass }
constructor TDownloadThreadClass.Create(CreateSuspended: boolean);
begin
inherited Create(CreateSuspended);
fReturnCode := 0; // Failure code
fDownloadSize := 0;
FreeOnTerminate := True;
fLastError := C_OK;
end;
destructor TDownloadThreadClass.Destroy;
begin
FreeAndNil(fHTTPClient);
inherited Destroy;
end;
procedure TDownloadThreadClass.DoPercent(Sender: TObject;
const ContentLength, CurrentPos: int64);
begin
fPercent := integer((Trunc((CurrentPos / ContentLength) * 100)));
end;
procedure TDownloadThreadClass.Execute;
begin
fHTTPClient := TFPHTTPClient.Create(nil);
// OnThreadDataEvent:=fHTTPClient.OnDataReceived;
// fHTTPClient.OnDataReceived:=@DoPercent;
// Start the download procedure
fDownloadSize := GetDownloadFileSize(fURL, fIsRepositoryURL);
if (fDownloadSize > 0) then
begin
fDownloadSize := 0;
DownloadHTTP(fURL, fFileName, fReturnCode, fDownloadSize,
fIsRepositoryURL, fDebugMode);
end
else
fLastError := 'Zero Size';
end;
constructor TDownloadWrapper.Create();
// Called from LazAutoUpdate.Create
begin
inherited Create;
fThreadFinished := False;
fAppLicationVersionString := '0.0.1';
fComponentVersion := C_TThreadedDownloadComponentVersion;
fLastError := C_OK;
// Create the thread (suspended)
fdownload := TDownloadThreadClass.Create(True);
fdownload.FreeOnTerminate := True;
fdownload.OnTerminate := @DownloadTerminiated;
end;
destructor TDownloadWrapper.Destroy;
begin
inherited Destroy;
end;
{ TDownloadWrapper }
function TDownloadWrapper.ThreadDownloadHTTP: boolean;
begin
if (CompareFileExt(ExtractFilename(fFileName), 'zip', False) = 0) then
fUnzipAfter := True
else
fUnzipAfter := False;
if Assigned(OnDownloadWrapperFileWriteProgress) then
fdownload.OnThreadFileWriteProgress := OnDownloadWrapperFileWriteProgress;
fdownload.fIsRepositoryURL := fIsCodeRepository;
fdownload.fDebugMode := fDebugMode;
fdownload.fLastError := fLastError;
fdownload.fURL := URL;
fdownload.fFileName := FileName;
fdownload.fDownloadSize := 0;
fdownload.start;
Result := True;
end;
procedure TDownloadWrapper.DownloadTerminiated(Sender: TObject);
// Unzips all files ready for updatehmxxx to copy them over
var
UnZipper: TUnZipper;
begin
FreeAndNil(fDownload.fHTTPClient);
fReturnCode := fdownload.fReturnCode;
fDownloadSize := fdownload.fDownloadSize;
fLastError := fdownload.fLastError;
fThreadFinished := True;
if fReturnCode = 401 then
Exit;
if (FileExists(fFileName) = True) and
(CompareFileExt(fFileName, '.zip', False) = 0) then
if fUnzipAfter then
begin
UnZipper := TUnZipper.Create;
try
UnZipper.FileName := fFileName;
UnZipper.OutputPath := ExtractFileDir(fFileName);
UnZipper.Examine;
UnZipper.UnZipAllFiles;
if FileExists(fFileName) then
SysUtils.DeleteFile(fFileName);
finally
UnZipper.Free;
end;
end;
end;
{ End of class members}
function TDownloadThreadClass.GetPercent: integer;
begin
Result := fPercent;
end;
procedure TDownloadThreadClass.SetPercent(AValue: integer);
begin
fPercent := AValue;
end;
function TDownloadThreadClass.GetDownloadFileSize(URL: string;
bIsRepositoryURL: boolean): int64;
var
VSize: int64 = 0;
I: integer;
S: string;
begin
try
if bIsRepositoryURL then
fHTTPClient.AllowRedirect := True;
try
fHTTPClient.HTTPMethod('HEAD', URL, nil, [200]);
except
Result := 0;
end;
for I := 0 to pred(fHTTPClient.ResponseHeaders.Count) do
begin
S := UpperCase(fHTTPClient.ResponseHeaders[I]);
if Pos('CONTENT-LENGTH:', S) > 0 then
begin
VSize := StrToIntDef(Copy(S, Pos(':', S) + 1, Length(S)), 0);
Result := VSize;
Break;
end;
end;
except
Result := 0;
end;
end;
function TDownloadThreadClass.DownloadHTTP(URL, TargetFile: string;
var ReturnCode, DownloadSize: integer; bIsRepositoryURL, Debugmode: boolean): boolean;
// Download file; retry if necessary.
// Deals with https download links
var
// HTTPClient: TFPHTTPClient;
vStream: TStreamAdapter;
vSize: int64;
begin
Result := False;
vSize := GetDownloadFileSize(URL, bIsRepositoryURL);
if (vSize = 0) then
Exit;
if bIsRepositoryURL then
begin
fHTTPClient.AllowRedirect := True;
end;
// ReturnCode may not be useful, but it's provided here
try
try
// Try to get the file
// HTTPClient.Get(URL, TargetFile);
vStream := TStreamAdapter.Create(TFileStream.Create(TargetFile, fmCreate), VSize);
if Assigned(OnThreadFileWriteProgress) then
vStream.OnFileWriteProgress := OnThreadFileWriteProgress;
fHTTPClient.OnDataReceived := @DoPercent;
fHTTPClient.HTTPMethod('GET', Url, vStream, [200]);
ReturnCode := fHTTPClient.ResponseStatusCode;
DownloadSize := Filesize(TargetFile);
Result := True;
except
// We don't care for the reason for this error; the download failed.
if FileExists(TargetFile) then
SysUtils.DeleteFile(TargetFile);
ReturnCode := 401;
DownloadSize := 0;
Result := False;
end;
finally
vStream.Free;
end;
end;
constructor TStreamAdapter.Create(AStream: TStream; ASize: int64);
begin
inherited Create;
FStream := AStream;
fStream.Size := ASize;
fStream.Position := 0;
end;
destructor TStreamAdapter.Destroy;
begin
FStream.Free;
inherited Destroy;
end;
function TStreamAdapter.Read(var Buffer; Count: longint): longint;
begin
Result := FStream.Read(Buffer, Count);
DoProgress(False);
end;
function TStreamAdapter.Write(const Buffer; Count: longint): longint;
begin
Result := FStream.Write(Buffer, Count);
DoProgress(True);
end;
function TStreamAdapter.Seek(Offset: longint; Origin: word): longint;
begin
Result := FStream.Seek(Offset, Origin);
end;
procedure TStreamAdapter.DoProgress(Writing: boolean);
begin
fPercent := Trunc((FStream.Position) / (FStream.Size) * 100);
//WriteLn(FStream.Size);
if Assigned(OnFileWriteProgress) then
begin
OnFileWriteProgress(self, FPercent);
end;
end;
end.