Platforms to show: All Mac Windows Linux Cross-Platform
/MacExtras/Sparkle/UpdaterKit/Updater Example
You find this example project in your Plugins Download as a Xojo project file within the examples folder: /MacExtras/Sparkle/UpdaterKit/Updater Example
This example is the version from Wed, 31th Oct 2023.
Project "Updater Example.xojo_binary_project"
Class App Inherits Application
Const kEditClear = "&Löschen"
Const kFileQuit = "Beenden"
Const kFileQuitShortcut = ""
EventHandler Sub Open()
' RegisterPlugins
'const url = "https://www.monkeybreadsoftware.de/UpdaterTest/appcast-redirect-test.xml"
const url = "https://www.monkeybreadsoftware.de/UpdaterTest/appcast.xml"
UpdaterEngine.Init url
End EventHandler
End Class
Class MainWindow Inherits Window
Control PushButton1 Inherits PushButton
ControlInstance PushButton1 Inherits PushButton
EventHandler Sub Action()
UpdaterEngine.CheckForUpdates
End EventHandler
End Control
Control PushButton2 Inherits PushButton
ControlInstance PushButton2 Inherits PushButton
EventHandler Sub Action()
UpdaterEngine.TestCases
End EventHandler
End Control
Control PushButton3 Inherits PushButton
ControlInstance PushButton3 Inherits PushButton
EventHandler Sub Action()
if UpdaterEngine.HasInternetConnection then
MsgBox "Internet connection found."
else
MsgBox "No Internet connection found."
end if
End EventHandler
End Control
Control PushButton4 Inherits PushButton
ControlInstance PushButton4 Inherits PushButton
EventHandler Sub Action()
Dim sock As New HTTPSecureSocket
UpdaterEngine.SetSocketProxy sock
if sock.HTTPProxyAddress="" then
MsgBox "No proxy"
else
MsgBox "Proxy: "+sock.HTTPProxyAddress+":"+str(sock.HTTPProxyPort)
end if
End EventHandler
End Control
Control PushButton5 Inherits PushButton
ControlInstance PushButton5 Inherits PushButton
EventHandler Sub Action()
UpdaterEngine.Reset
End EventHandler
End Control
Control StaticText1 Inherits Label
ControlInstance StaticText1 Inherits Label
End Control
Control StaticText2 Inherits Label
ControlInstance StaticText2 Inherits Label
End Control
Control PushButton6 Inherits PushButton
ControlInstance PushButton6 Inherits PushButton
EventHandler Sub Action()
MsgBox str(UpdaterEngine.automaticallyCheckForUpdates)
End EventHandler
End Control
Control StaticText3 Inherits Label
ControlInstance StaticText3 Inherits Label
End Control
Control PushButton7 Inherits PushButton
ControlInstance PushButton7 Inherits PushButton
EventHandler Sub Action()
UpdaterEngine.automaticallyCheckForUpdates = true
End EventHandler
End Control
Control PushButton8 Inherits PushButton
ControlInstance PushButton8 Inherits PushButton
EventHandler Sub Action()
UpdaterEngine.automaticallyCheckForUpdates = false
End EventHandler
End Control
Control CheckBox1 Inherits CheckBox
ControlInstance CheckBox1 Inherits CheckBox
EventHandler Sub Action()
UpdaterEngine.TestWindowsOnMac = me.Value
End EventHandler
End Control
Note "About"
Written by Christian Schmitz
Copyright 2010 by Christian Schmitz Software GmbH
End Class
MenuBar MenuBar1
MenuItem FileMenu = "&Ablage"
MenuItem FileQuit = "#App.kFileQuit"
MenuItem EditMenu = "&Bearbeiten"
MenuItem EditUndo = "&Rückgängig"
MenuItem UntitledMenu1 = "-"
MenuItem EditCut = "&Ausschneiden"
MenuItem EditCopy = "&Kopieren"
MenuItem EditPaste = "&Einfügen"
MenuItem EditClear = "#App.kEditClear"
MenuItem UntitledMenu0 = "-"
MenuItem EditSelectAll = "&Alles auswählen"
End MenuBar
Module UpdaterEngine
ComputedProperty Protected LastUpdateVersion As string
Sub Set()
SavePrefValue "LastUpdateVersion", Value
End Set
Sub Get()
return LoadPrefValue("LastUpdateVersion")
End Get
End ComputedProperty
ComputedProperty Private SecondLaunchFlag As Boolean
Sub Set()
if value then
SavePrefValue "SecondLaunchFlag", "true"
else
SavePrefValue "SecondLaunchFlag", "false"
end if
End Set
Sub Get()
// if there is a value set, we are on the second run
dim s as string = LoadPrefValue("SecondLaunchFlag")
Return s="true"
End Get
End ComputedProperty
ComputedProperty automaticallyCheckForUpdates As Boolean
Sub Set()
#if TargetMachO
dim updater as SUUpdaterMBS = updaterRef
if updater<>nil then
updater.automaticallyChecksForUpdates = value
end if
#endif
if value then
SavePrefValue "automaticallyChecksForUpdates", "true"
else
SavePrefValue "automaticallyChecksForUpdates", "false"
end if
End Set
Sub Get()
#if TargetMachO
dim updater as SUUpdaterMBS = updaterRef
if updater<>nil then
Return updater.automaticallyChecksForUpdates
end if
#endif
Return LoadPrefValue("automaticallyChecksForUpdates")<>"false" // if not set, it'll be true
End Get
End ComputedProperty
Const UseCURL = true
Const kCheckSumNodeName = "MD5Win"
Const kFailedToFindUpdate = "An error occurred in retrieving update information. Please try again later."
Const kLanguage = "?"
Const kLengthNodeName = "lengthWin"
Const kNoInternetConnection = "No Internet connection available."
Const kURLNodeName = "urlWin"
Const kYouhavethecurrentversion = "You have the current version of %."
Protected Sub CURLConfigure(c as Variant)
#If UseCURL
dim curl as CURLSMBS = c
// configure curl here:
curl.OptionVerbose = true
curl.OptionFollowLocation = true
curl.OptionFreshConnect = true
curl.OptionMaxRedirs = 3
curl.OptionConnectionTimeout = 10
#Else
#Pragma unused c
#endif
End Sub
Protected Sub CURLMultiTimerAction(t as timer)
#Pragma Unused t
#if UseCURL then
dim multi as CURLSMultiMBS = CURLMulti
if multi <> nil then
multi.Perform
end if
#endif
End Sub
Protected Sub CURLRun(c as Variant)
// schedule CURL object
#if UseCURL then
dim curl as CURLSMBS = c
dim multi as CURLSMultiMBS = CURLMulti
if multi = nil then
// initialize
multi = new CURLSMultiMBS
CurlMultiTimer = new timer
AddHandler CurlMultiTimer.Action, AddressOf CurlMultiTimerAction
CurlMultiTimer.Period = 20
CurlMultiTimer.Mode = 2
// store reference for later
CURLMulti = multi
end if
// schedule transfer
if not multi.AddCURL(curl) then
Break // trouble?
end if
#Else
#Pragma unused c
#endif
End Sub
Protected Sub CURLSearchUpdate(updatesocket as UpdateURLConnection)
#Pragma unused updatesocket
#If UseCURL Then
// query update via CURL
dim UpdateCURL as new CURLSMBS
UpdateCURL.OptionURL = URL
UpdateCURL.CollectOutputData = true
UpdateCURL.CollectDebugData = true
UpdateCURL.CollectHeaderData = true
AddHandler UpdateCURL.Finished, AddressOf CURLSearchUpdateFinished
// copy proxy setting, or set yourself here:
Dim httpSock As New HTTPSocket
If httpSock.HTTPProxyAddress <> "" Then
If httpSock.HTTPProxyPort > 0 Then
UpdateCURL.OptionProxy = httpSock.HTTPProxyAddress+":"+Str(httpSock.HTTPProxyPort)
else
UpdateCURL.OptionProxy = httpSock.HTTPProxyAddress
end if
end if
CURLConfigure UpdateCURL
CURLRun UpdateCURL
CURLForSearchUpdate = UpdateCURL
#EndIf
End Sub
Protected Sub CURLSearchUpdateFinished(c as object, ResultCode as integer)
#if UseCURL then
dim curl as CURLSMBS = CURLSMBS(c)
CURLForSearchUpdate = nil
dim h as integer = curl.GetInfoResponseCode
if ResultCode <> 0 or h <> 200 then
// problem?
dim DebugMessages as string = curl.DebugData
// fix encoding if none is known
if DebugMessages.Encoding = nil then
if encodings.UTF8.IsValidData(DebugMessages) then
DebugMessages = DefineEncoding(DebugMessages, encodings.UTF8)
else
DebugMessages = DefineEncoding(DebugMessages, encodings.ISOLatin1)
end if
end if
DebugMessages = ReplaceLineEndings(DebugMessages, EndOfLine)
// write log file
try
dim f as FolderItem = SpecialFolder.Preferences.Child("Updater.log")
dim t as TextOutputStream = TextOutputStream.Create(f)
t.Write DebugMessages
catch io as IOException
// ignore write error
end try
break // check debug messages
Return
end if
// normal okay
UpdaterEngine.GotXML curl.OutputData
#Else
#Pragma unused c
#Pragma unused ResultCode
#EndIf
End Sub
Protected Sub CheckForUpdates()
// Call for manual update search
#if TargetMachO then
dim updater as SUUpdaterMBS = updaterRef
if updater <> Nil then
// ask Sparkle to check for updates
updater.checkForUpdates
Return
end if
#endif
// search for updates with our own engine
if TestWindowsOnMac or TargetWin32 then
SearchUpdate true
end if
End Sub
Private Sub CheckUpdateMac()
// Loads sparkle framework for Mac and checks for updates in background
#If TargetMachO Then
// download Sparkle from their website:
// https://sparkle-project.org
// Place Sparkle Framework in the MacOS folder
dim ExecutableFile as FolderItem = app.ExecutableFile
dim MacOSFolder as FolderItem = ExecutableFile.Parent
dim ContentsFolder as FolderItem = MacOSFolder.Parent
dim FrameworksFolder as FolderItem = ContentsFolder.Child("Frameworks")
Dim SparkleFramework As FolderItem = FrameworksFolder.Child("Sparkle.framework")
if SparkleFramework <> Nil and SparkleFramework.Exists then
if SUUpdaterMBS.LoadFramework(SparkleFramework) then
// We loaded the Sparkle Framework
dim Updater as new SUUpdaterMBS
updaterRef = Updater
if not DebugBuild then // updater can't work well in IDE without plist keys
Updater.automaticallyDownloadsUpdates = true
Updater.automaticallyChecksForUpdates = true
if updater.automaticallyChecksForUpdates then
Updater.checkForUpdatesInBackground
end if
end if
end if
end if
#endif
Exception n as runtimeexception
// ignore all exceptions here
End Sub
Protected Sub DisplayError(s as string)
MsgBox kFailedToFindUpdate+EndOfLine+EndOfLine+s
End Sub
Private Function FindChild(x as XmlNode, name as string) As XmlNode
// Finds a child node in an xml node with the given name
dim c as integer = x.ChildCount-1
for i as integer = 0 to c
dim n as XmlNode = x.Child(i)
if n <> Nil and n.LocalName = name then
Return n
end if
next
End Function
Private Function FindDescriptionURL(x as XmlNode) As string
// Finds a child node in an xml node with the given name
dim FirstURL as string
dim c as integer = x.ChildCount-1
for i as integer = 0 to c
dim n as XmlNode = x.Child(i)
if n <> Nil and n.LocalName = "releaseNotesLink" then
dim a as string = n.GetAttribute("xml:lang")
if len(a)=0 then
Return GetTextChild(n)
else
if a = kLanguage then
Return GetTextChild(n)
elseif len(FirstURL)=0 then
// if the language is not availble on the server, we use the first one in list.
FirstURL = GetTextChild(n)
end if
end if
end if
next
End Function
Private Function FindDomainName(url as string) As string
// Returns domain name of a given URL
// e.g. https://www.macsw.de/something gives www.macsw.de
dim p1 as integer = instr(url, "//")
if p1>0 then
dim p2 as integer = instr(p1+2, url, "/")
if p2 = 0 then
return mid(url, p1+2)
else
Return mid(url, p1+2, p2-p1-2)
end if
end if
End Function
Private Sub FindProxyInNetworkService(sock as HTTPSecureSocket, dico as CFObjectMBS, s as SystemConfigurationMBS, p as SCPreferencesMBS)
// Internal method to find the proxy settings from Mac OS X SystemConfiguration database in a given service
if dico isa CFDictionaryMBS then
dim cd as CFDictionaryMBS=CFDictionaryMBS(dico)
// get the link path
dim o as CFObjectMBS=cd.Value(s.kSCResvLink)
if o isa CFStringMBS then
dim cs as CFStringMBS=CFStringMBS(o)
// search the Network Services linked
cd=p.GetPathValue(cs)
// get its proxy values
o=cd.Value(s.kSCEntNetProxies)
if o isa CFDictionaryMBS then
cd=CFDictionaryMBS(o)
dim HTTPEnable as Boolean
// Check HTTPEnable:
o=cd.Value(s.kSCPropNetProxiesHTTPEnable)
if o isa CFBooleanMBS then
dim b as CFBooleanMBS=CFBooleanMBS(o)
HTTPEnable = b.Value
end if
// for unknown reason it is a CFNumber for the proxy settings!?
if o isa CFNumberMBS then
dim n as CFNumberMBS=CFNumberMBS(o)
HTTPEnable = n.integerValue <> 0
end if
if HTTPEnable then
dim HTTPPort as integer
o=cd.Value(s.kSCPropNetProxiesHTTPPort)
if o isa CFNumberMBS then
dim b as CFNumberMBS = CFNumberMBS(o)
HTTPPort=b.integerValue
end if
dim HTTPProxy as string
o=cd.Value(s.kSCPropNetProxiesHTTPProxy)
if o isa CFStringMBS then
dim b as CFStringMBS=CFStringMBS(o)
HTTPProxy=b.Str
end if
if len(HTTPProxy)>0 then
sock.HTTPProxyAddress = HTTPProxy
end if
if HTTPPort>0 then
sock.HTTPProxyPort = HTTPPort
end if
end if
end if
end if
end if
End Sub
Private Function GetTextChild(x as XmlNode) As string
dim c as integer = x.ChildCount-1
for i as integer = 0 to c
dim n as XmlNode = x.Child(i)
if n isa XmlTextNode then
Return n.Value
end if
next
End Function
Protected Function GetUpdater() As Variant
// updater is stored here in a variant to avoid linking in the Cocoa plugins on Windows
Return updaterRef
End Function
Protected Sub GotXML(content as string)
#pragma BreakOnExceptions off
FoundOlderVersion = false
FoundNewerVersion = false
FoundCurrentVersion = false
dim x as XmlDocument
try
x = new XmlDocument(content)
catch
break // xml wrong?
MsgBox kFailedToFindUpdate
Return
end try
if WalkXML(x) then
Return // shows update dialog
end if
Exception r as RuntimeException
break // some exception in xml walking
Finally
if Manually then
if FoundNewerVersion then
// we found an update and the update window is shown
// or found ignored update so we do nothing
elseif FoundCurrentVersion then
// we found the version which is running
MsgBox Replace(kYouhavethecurrentversion, "%", appname)
elseif FoundOlderVersion then
// user have a newer version, maybe a beta, so we show dialog that no newer code
MsgBox Replace(kYouhavethecurrentversion, "%", appname)
else
// we failed to find the update
MsgBox kFailedToFindUpdate
end if
end if
End Sub
Protected Function HasInternetConnection() As Boolean
Dim s as string = DNSNameToAddressMBS(domainName)
if len(s) > 0 then
Return true
end if
End Function
Protected Sub Init(u as string, ApplicationName as string = "")
url = u
regkeyname = MakeRegkeyname(u)
domainName = FindDomainName(u)
appname = ApplicationName
if len(appname)=0 then // no name provided
appname = app.ExecutableFile.Name
appname = Replace(appname, ".exe", "")
appname = Replace(appname, ".app", "")
appname = Replace(appname, ".debug", "")
end if
if DebugBuild then
Return // no update checking on debugging
end if
CheckUpdateMac
if SecondLaunchFlag and updaterref=Nil then
if automaticallyCheckForUpdates then
SearchUpdate false
end if
end if
SecondLaunchFlag = true
End Sub
Protected Function LoadPrefValue(key as string) As string
#if TargetMacOS then
dim p as CFPreferencesMBS
dim o as CFObjectMBS
dim s as CFStringMBS
p=new CFPreferencesMBS
o=p.CopyAppValue(NewCFStringMBS(key),p.kCFPreferencesCurrentApplication)
if o isa CFStringMBS then
s=CFStringMBS(o)
Return s
end if
#elseif TargetWin32 then
dim SoftwareFolder as new registryitem("HKEY_CURRENT_USER\Software")
dim DataFolder as RegistryItem = SoftwareFolder.AddFolder(regkeyname)
return DataFolder.Value(key)
#endif
End Function
Private Function MakeRegkeyname(url as string) As string
url = ReplaceAll(url, "/", "-")
url = ReplaceAll(url, ":", "")
Return url
End Function
Protected Sub Reset()
LastUpdateVersion = ""
SecondLaunchFlag = false
automaticallyCheckForUpdates = true
End Sub
Protected Sub SavePrefValue(key as string, value as string)
#if TargetMacOS then
dim p as new CFPreferencesMBS
p.SetAppValue NewCFStringMBS(key), NewCFStringMBS(value), p.kCFPreferencesCurrentApplication
call p.AppSynchronize(p.kCFPreferencesCurrentApplication)
#elseif TargetWin32 then
dim SoftwareFolder as new registryitem("HKEY_CURRENT_USER\Software")
dim DataFolder as RegistryItem = SoftwareFolder.AddFolder(regkeyname)
DataFolder.Value(key)=value
#endif
End Sub
Protected Sub SearchUpdate(manuell as Boolean)
// Searches for updates
'if DebugBuild then Return // disable in debugging
Manually = manuell
if Manually then
LastUpdateVersion = "" // reset update counter
end if
if HasInternetConnection then
updatesocket=New UpdateURLConnection
'UpdaterEngine.SetSocketProxy updatesocket
#if UseCURL then
CURLSearchUpdate updatesocket
#Else
// query update server via HTTPSocket. Can be changed to HTTPSecureSocket if needed
'updatesocket.SetRequestHeader "Accept", ""
'updatesocket.SetRequestHeader "Host", ""
'updatesocket.SetRequestHeader "Accept-Language", ""
'updatesocket.SetRequestHeader "Accept-Encoding", "gzip, deflate"
'updatesocket.SetRequestHeader "Connection", "keep-alive"
updatesocket.Send("GET", URL)
#endif
else
if manuell then
MsgBox kNoInternetConnection
end if
end if
End Sub
Protected Sub SetSocketProxy(h as HTTPSecureSocket)
// Sets the proxy properties of a httpsocket
#if TargetWin32 then
SetSocketProxyWindows h
#endif
#if TargetMachO then
SetSocketProxyMac h
#endif
End Sub
Private Sub SetSocketProxyMac(h as HTTPSecureSocket)
// Internal method to load the proxy settings from Mac OS X SystemConfiguration database
#if TargetMachO then
dim s as new SystemConfigurationMBS
dim p as new SCPreferencesMBS
if p.Create(NewCFStringMBS("TestRB"),Nil) then
// currentset is a CFString
// and can be used as key in the Network Services.
dim o as CFObjectMBS = p.GetValue(s.kSCPrefCurrentSet)
dim currentset as CFStringMBS = CFStringMBS(o)
'CFShowMBS currentset
// Find the value for this path and get the dictionary
dim cd as CFDictionaryMBS=p.GetPathValue(currentset)
'CFShowMBS cd
// get the network stuff in this dictionary
o=cd.Value(NewCFStringMBS("Network"))
if o isa CFDictionaryMBS then
cd=CFDictionaryMBS(o)
'CFShowMBS cd
// get the list of services
o=cd.Value(NewCFStringMBS("Service"))
if o isa CFDictionaryMBS then
cd=CFDictionaryMBS(o)
'CFShowMBS cd
// now we try each of them
dim cdl as CFDictionaryListMBS=cd.List
dim c as integer=cdl.Count-1
for i as integer=0 to c
FindProxyInNetworkService(h, cdl.Value(i),s,p)
next
end if
end if
end if
#endif
Exception r as runtimeexception
End Sub
Private Sub SetSocketProxyWindows(h as HTTPSecureSocket)
// Internal method to load the proxy settings from Windows
#if TargetWin32 then
dim w as new WindowsProxyMBS
if w.UsingProxy then
dim s as string = w.Proxy
if len(s)>0 then
dim c as integer = CountFields(s, " ")
if c = 1 then
// only one proxy
SetSocketProxyWindows h, s
else
// we have several, pick one
dim items(-1) as string = split(s, " ")
for each item as string in items
dim p as integer = instr(item, "=")
dim protocol as string = left(item, p-1)
dim proxy as string = mid(item, p+1)
if protocol = "http" then
SetSocketProxyWindows h, proxy
end if
next
end if
end if
end if
#else
#pragma Unused h
#endif
End Sub
Private Sub SetSocketProxyWindows(sock as HTTPSecureSocket, proxy as string)
// Internal method to parse the proxy settings from Windows and set them on the socket
#if TargetWin32 then
dim nameString as string = NthField(proxy, ":", 1)
dim portString as string = NthField(proxy, ":", 2)
dim port as integer = val(portString)
if nameString <> "" then
sock.HTTPProxyAddress = nameString
end if
if port>0 then
sock.HTTPProxyPort = port
end if
#else
#pragma Unused sock
#pragma Unused proxy
#endif
End Sub
Protected Sub TestCases()
// Some test cases to verify some of the core functions do work
dim lines(-1) as string
dim s as string
s = findDomainName("https://www.test.com/test/file")
if s <> "www.test.com" then
lines.Append "findDomainName 1 failed: "+s
end if
s = findDomainName("https://www.test.com/")
if s <> "www.test.com" then
lines.Append "findDomainName 2 failed: "+s
end if
s = findDomainName("https://www.test.com")
if s <> "www.test.com" then
lines.Append "findDomainName 3 failed: "+s
end if
if VersionIsNewer("1","2")=false then
lines.Append "VersionIsNewer 1 failed"
end if
if VersionIsNewer("2","1")=true then
lines.Append "VersionIsNewer 2 failed"
end if
if VersionIsNewer("1","1.2")=false then
lines.Append "VersionIsNewer 3 failed"
end if
if VersionIsNewer("1.2","1")=true then
lines.Append "VersionIsNewer 4 failed"
end if
if VersionIsNewer("1.1","1.2")=false then
lines.Append "VersionIsNewer 5 failed"
end if
if VersionIsNewer("1.2","1.1")=true then
lines.Append "VersionIsNewer 6 failed"
end if
if VersionIsNewer("1.1","1.1.1")=false then
lines.Append "VersionIsNewer 7 failed"
end if
if VersionIsNewer("1.2","1.1.1")=true then
lines.Append "VersionIsNewer 8 failed"
end if
if VersionIsNewer("1.1.1","1.1.2")=false then
lines.Append "VersionIsNewer 9 failed"
end if
if VersionIsNewer("1.1.2","1.1.1")=true then
lines.Append "VersionIsNewer 10 failed"
end if
if VersionIsNewer("1.1.1r1","1.1.1r2")=false then
lines.Append "VersionIsNewer 11 failed"
end if
if VersionIsNewer("1.1.1r2","1.1.1r1")=true then
lines.Append "VersionIsNewer 12 failed"
end if
If VersionIsNewer("2.0.1b12", "2.0.2")=False Then
lines.Append "VersionIsNewer 13 failed"
End If
If VersionIsNewer("2.0.1.12", "2.0.2")=False Then
lines.Append "VersionIsNewer 14 failed"
End If
if VersionIsNewer("1.21","1.22.5")=false then
lines.Append "VersionIsNewer 15 failed"
end if
if VersionIsNewer("1.21.1.3","1.25")=false then
lines.Append "VersionIsNewer 16 failed"
end if
if VersionIsNewer("1.22.5","1.21")=true then
lines.Append "VersionIsNewer 17 failed"
end if
if VersionIsNewer("1.25","1.21.1.3")=true then
lines.Append "VersionIsNewer 18 failed"
end if
SavePrefValue "test", "1"
if LoadPrefValue("test") <> "1" then
lines.Append "Preferences failed."
end if
if UBound(lines)=-1 then
MsgBox "OK"
else
MsgBox Join(lines,EndOfLine)
end if
End Sub
Protected Function VersionIsNewer(oldVersion as string, newVersion as string) As Boolean
// Function to compare two version strings
// version strings in name: 2, 3.4, 4.5.6, 7.8.9r10
dim o(-1) as integer // array for old version number
dim n(-1) as integer // array for new version number
// Parse old version into an array
dim oo as integer
dim ho as Boolean = false // has digit?
for i as integer = 1 to len(oldVersion)
dim x as string = mid(oldVersion, i, 1)
if asc(x) >= 48 and asc(x) <= 57 then // a digit
oo = oo * 10 + (asc(x) - 48)
ho = true
else
if ho then
o.Append oo
end if
ho = false
oo = 0
end if
next
if ho then
o.Append oo
end if
// Parse new version into an array
dim nn as integer
dim hn as Boolean = false // has digit?
for i as integer = 1 to len(newVersion)
dim x as string = mid(newVersion, i, 1)
if asc(x) >= 48 and asc(x) <= 57 then // a digit
nn = nn * 10 + (asc(x) - 48)
hn = true
else
if hn then
n.Append nn
end if
hn = false
nn = 0
end if
next
if hn then
n.Append nn
end if
// Now compare both arrays
dim uo as integer = UBound(o)
dim un as integer = UBound(n)
dim u as integer = uo // get maximum ubound of both arrays
if un > uo then u = un
// we add zeros on the end to fill version numbers
while UBound(o)<u
o.Append 0
wend
while UBound(n)<u
n.Append 0
wend
// compare each part
for i as integer = 0 to u
if n(i) > o(i) then // newer?
Return true
elseif n(i) < o(i) then // older?
Return false
end if
next
// equal
if un>uo then // newer version has longer version string. 1.2.3 is newer than 1.2
Return true
end if
End Function
Private Function WalkChannel(x as XmlNode) As Boolean
// Now check all item nodes
dim c as integer = x.ChildCount-1
for i as integer = 0 to c
dim n as XmlNode = x.Child(i)
if n <> Nil and n.LocalName="item" then
if walkItem(n) then
Return true
end if
end if
next
End Function
Private Function WalkRSS(x as XmlNode) As Boolean
// go over the RSS feed and check all channels for updates
dim c as integer = x.ChildCount-1
for i as integer = 0 to c
dim n as XmlNode = x.Child(i)
if n <> Nil and n.LocalName="channel" then
if walkChannel(n) then
Return true
end if
end if
next
End Function
Private Function WalkXML(x as XmlNode) As Boolean
// find the RSS items in the xml tree
dim c as integer = x.ChildCount-1
for i as integer = 0 to c
dim n as XmlNode = x.Child(i)
if n <> Nil and n.LocalName="rss" then
if walkRSS(n) then
Return true
end if
end if
next
End Function
Private Function walkItem(x as XmlNode) As Boolean
// Check an item node
dim enclosureNode as XmlNode = FindChild(x, "enclosure")
// dim DescriptionNode as XmlNode = FindChild(x, "description")
// dim pubDateNode as XmlNode = FindChild(x, "pubDate")
// dim TitleNode as XmlNode = FindChild(x, "title")
// dim pubDate as string = GetTextChild(pubDateNode)
// dim Title as string = GetTextChild(TitleNode)
dim Description as string = FindDescriptionURL(x)
#if Target32Bit then
const bits = "32"
#elseif Target64Bit then
const bits = "64"
#else
break //?
#endif
dim URL as string = enclosureNode.GetAttribute(kURLNodeName+Bits)
Dim version As String = enclosureNode.GetAttribute("sparkle:shortVersionString")
if version = "" then // alternative node name
version = enclosureNode.GetAttribute("sparkle:version")
End If
// dim dsaSignature as string = enclosureNode.GetAttribute("sparkle:dsaSignature")
Dim length As String = enclosureNode.GetAttribute(kLengthNodeName+Bits)
// dim type as string = enclosureNode.GetAttribute("type")
Dim checksum As String = enclosureNode.GetAttribute(kCheckSumNodeName+Bits)
// todo: add check for date
if DebugBuild then
System.DebugLog "New Version: "+Version+", LastUpdateVersion: "+LastUpdateVersion+", My Version: "+app.ShortVersion
System.DebugLog "URL: "+url
System.DebugLog "length: "+length
System.DebugLog "checksum: "+checksum
end if
if app.ShortVersion = Version then // same version
FoundCurrentVersion = true
// version newer?
elseif VersionIsNewer(app.ShortVersion, Version) then
// you can overwrite display version number with this xml key:
dim shortVersion as string = enclosureNode.GetAttribute("sparkle:shortVersionString")
If shortVersion<>"" then version = shortVersion
FoundNewerVersion = true
LastUpdateVersion = Version
// Show GUI for installing update
UpdateWindow.Init Version, Description, URL, val(length), checksum, appname
Return true
else
// Well, we didn't find the same version and we didn't find a newer version so our app must be newer than what is in the XML file
FoundOlderVersion = true
end if
Exception n as NilObjectException
End Function
Note "About"
Written by Christian Schmitz
Copyright 2010 by Christian Schmitz Software GmbH
Note "Copyright"
© 2015 by Christian Schmitz, Monkeybread Software
This is part of the MBS Xojo Updater Kit
https://www.monkeybreadsoftware.de/xojo/
Property Protected CURLForSearchUpdate As Variant
Property Protected CURLMulti As Variant
Property Protected CURLMultiTimer As timer
Property Private FoundCurrentVersion As Boolean
Property Private FoundNewerVersion As Boolean
Property Private FoundOlderVersion As Boolean
Property Private Manually As Boolean
Property Protected TestWindowsOnMac As Boolean = false
Property Private URL As string
Property Private appname As string
Property Protected domainName As string
Property Private regkeyname As string
Property Private updaterRef As Variant
Property Private updatesocket As UpdateURLConnection
End Module
Class UpdateWindow Inherits Window
Const kCancel = "Cancel"
Const kDownloadingDots = "Downloading..."
Const kFailedToDownloadFile = "Failed to download file. We got less bytes than we should."
Const kFailedToDownloadFile2 = "Failed to download file. The file does not match the checksum."
Const kFailedToLaunchUpdater = "Failed to launch the updater. Error %"
Const kFailedToWriteUpdater = "Failed to write the updater to disc. No update was installed. "
Const kInstallUpdate = "Install Update"
Const kNetworkError = "Download failed with a network error."
Const kNewUpdateAvailable = "A new version of % is available!"
Const kReleaseNotesLabel = "Release notes:"
Const kRemindMeLater = "Remind Me Later"
Const kSkipThisVersion = "Skip This Version"
Const kUpdateWindowTitle = "Software Update"
Const kXIsAvailable = "%1 %2 is now available—you have %3. Would you like to download it now?"
Control Info2 Inherits Label
ControlInstance Info2 Inherits Label
End Control
Control web Inherits HTMLViewer
ControlInstance web Inherits HTMLViewer
End Control
Control InstallButton Inherits PushButton
ControlInstance InstallButton Inherits PushButton
EventHandler Sub Action()
// Start update download
me.Enabled=false
me.Caption=kDownloadingDots
InstallButton.Visible = false
LaterButton.Visible = false
SkipButton.Visible = false
bar.Visible=True
// we use a 3 second timer to make sure the user does not press cancel directly
CancelButton.Enabled = false
CancelButton.Visible = true
EnableCancelButtonTimer.Mode = EnableCancelButtonTimer.ModeSingle
UpdaterEngine.SetSocketProxy sock
#If UpdaterEngine.UseCURL Then
// delegate to CURL for download
CURLStartDownload
#Else
// ask it to use TLS 1.2
sock.SSLConnectionType = SSLSocket.SSLConnectionTypes.TLSv12
// download the URL now
sock.Get DownloadURL
#endif
End EventHandler
End Control
Control sock Inherits HTTPSecureSocket
ControlInstance sock Inherits HTTPSecureSocket
EventHandler Sub Error(code as integer)
// a socket or network error
SockError code
End EventHandler
EventHandler Sub PageReceived(url as string, httpStatus as integer, headers as internetHeaders, content as string)
if HTTPStatus >= 300 and httpStatus <= 399 then
// redirect
RedirectCount = RedirectCount + 1
if RedirectCount >= 5 then
UpdaterEngine.DisplayError "Too many redirects on download."
Return
end if
if headers<>Nil then
// get new URL
dim u as string = headers.Value("Location")
if len(u)>0 then
// and start a new download
me.Get u
Return
else
UpdaterEngine.DisplayError "No new location in redirect."
Return
end if
end if
end if
// Process downloaded file
SockPageReceived content
#pragma Unused url
End EventHandler
EventHandler Sub ReceiveProgress(bytesReceived as integer, totalBytes as integer, newData as string)
if ControlCount = 0 then
// window already closed
Return
end if
// update progress bar
bar.Value=100.0*bytesReceived/totalBytes
#pragma Unused newdata
End EventHandler
End Control
Control bar Inherits ProgressBar
ControlInstance bar Inherits ProgressBar
EventHandler Sub Open()
me.top = SkipButton.top
End EventHandler
End Control
Control LaterButton Inherits PushButton
ControlInstance LaterButton Inherits PushButton
EventHandler Sub Action()
// User cancelled, so close socket and window
UpdaterEngine.LastUpdateVersion = "" // reset so we are asked next time
sock.Close
close
End EventHandler
End Control
Control SkipButton Inherits PushButton
ControlInstance SkipButton Inherits PushButton
EventHandler Sub Action()
// User cancelled, so close socket and window
sock.Close
close
End EventHandler
End Control
Control CancelButton Inherits PushButton
ControlInstance CancelButton Inherits PushButton
EventHandler Sub Action()
// User cancelled, so close socket and window
sock.Close
close
End EventHandler
EventHandler Sub Open()
me.top = SkipButton.top
End EventHandler
End Control
Control ReleaseNotesLabel Inherits Label
ControlInstance ReleaseNotesLabel Inherits Label
End Control
Control Info1 Inherits Label
ControlInstance Info1 Inherits Label
End Control
Control EnableCancelButtonTimer Inherits Timer
ControlInstance EnableCancelButtonTimer Inherits Timer
EventHandler Sub Action()
CancelButton.Enabled = true
End EventHandler
End Control
Control Warning Inherits Label
ControlInstance Warning Inherits Label
EventHandler Sub Open()
Me.Visible = (DebugBuild and TargetMacOS)
End EventHandler
End Control
EventHandler Sub Close()
#if UpdaterEngine.UseCURL then
if CURLForDownload <> nil then
dim c as CURLSMBS = CURLForDownload
c.Cancel = true
end if
#endif
End EventHandler
Protected Sub CURLDownloadFinished(c as object, ResultCode as Integer)
// remove this function if you don't use CURL!
#if UpdaterEngine.UseCURL then
dim curl as CURLSMBS = CURLSMBS(c)
CURLForDownload = nil
dim h as integer = curl.GetInfoResponseCode
if ResultCode <> 0 or h <> 200 then
// problem?
dim DebugMessages as string = curl.DebugData
// fix encoding if none is known
if DebugMessages.Encoding = nil then
if encodings.UTF8.IsValidData(DebugMessages) then
DebugMessages = DefineEncoding(DebugMessages, encodings.UTF8)
else
DebugMessages = DefineEncoding(DebugMessages, encodings.ISOLatin1)
end if
end if
DebugMessages = ReplaceLineEndings(DebugMessages, EndOfLine)
// write log file
try
dim f as FolderItem = SpecialFolder.Preferences.Child("Updater.log")
dim t as TextOutputStream = TextOutputStream.Create(f)
t.Write DebugMessages
catch io as IOException
// ignore write error
end try
break // check debug messages
if ControlCount = 0 then
// window already closed
Return
end if
dim msg as string
UpdaterEngine.LastUpdateVersion = "" // retry on next run
Select case ResultCode
case CURLSMBS.kError_COULDNT_CONNECT
msg = "Failed to connect to server."
case CURLSMBS.kError_COULDNT_RESOLVE_HOST
msg = "Failed to find IP for update server."
case CURLSMBS.kError_COULDNT_RESOLVE_PROXY
msg = "Failed to find IP for proxy server."
case CURLSMBS.kError_ABORTED_BY_CALLBACK
Return // ignore, as cancelled by software
case CURLSMBS.kError_OUT_OF_MEMORY
msg = "Out of memory."
end Select
select case h
case 404
msg = "File not found on server."
case 500
msg = "Internal Server Error."
case 503
msg = "Service Unavailable."
end Select
MsgBox kNetworkError+EndOfLine+EndOfLine+"Result: "+str(ResultCode)+" Response: "+str(h)+EndOfLine+msg
Return
end if
if ControlCount = 0 then
// window already closed
Return
end if
// normal okay
SockPageReceived curl.OutputData
#Else
#Pragma unused c
#Pragma unused ResultCode
#EndIf
End Sub
Protected Function CURLProgress(c as object, dltotal as Int64, dlnow as Int64, ultotal as Int64, ulnow as Int64, percent as double) As Boolean
#Pragma unused c
#Pragma unused dltotal
#Pragma unused ultotal
#Pragma unused dlnow
#Pragma unused ulnow
#Pragma unused percent
#If UpdaterEngine.UseCURL Then
if ControlCount = 0 then
// window already closed
Return true
end if
// update progress bar
bar.Value = percent
#EndIf
End Function
Protected Sub CURLStartDownload()
#if UpdaterEngine.UseCURL then
// query update file via CURL
dim UpdateCURL as new CURLSMBS
UpdateCURL.OptionURL = DownloadURL
UpdateCURL.CollectOutputData = true
UpdateCURL.CollectDebugData = true
UpdateCURL.CollectHeaderData = true
AddHandler UpdateCURL.Finished, AddressOf CURLDownloadFinished
AddHandler UpdateCURL.Progress, AddressOf CURLProgress
// copy proxy setting, or set yourself here:
if sock.HTTPProxyAddress <> "" then
if sock.HTTPProxyPort > 0 then
UpdateCURL.OptionProxy = sock.HTTPProxyAddress+":"+str(sock.HTTPProxyPort)
else
UpdateCURL.OptionProxy = sock.HTTPProxyAddress
end if
end if
UpdaterEngine.CURLConfigure UpdateCURL
UpdaterEngine.CURLRun UpdateCURL
CURLForDownload = UpdateCURL
#endif
End Sub
Private Function FindTempFile() As FolderItem
// Find a random name for the installer file
dim n as integer = 0
dim f as FolderItem = SpecialFolder.Temporary.Child("install.exe")
while f=Nil or f.Exists
n = n + 1
f = SpecialFolder.Temporary.Child("install"+str(n)+".exe")
wend
Return f
End Function
Sub Init(Version as string, DescriptionURL as string, theDownloadURL as string, theLength as integer, theChecksum as string, appname as string)
// initialize the dialog and load the description webpage
Title = kUpdateWindowTitle
DownloadURL = theDownloadURL
DownloadLength = theLength
DownloadCheckSum = theChecksum
Info1.text = Replace(kNewUpdateAvailable, "%", appname)
Info2.text = Replace(Replace(Replace(kXIsAvailable, "%3", app.ShortVersion), "%2", Version), "%1", appname)
web.LoadURL DescriptionURL
show
End Sub
Private Sub SockError(code as integer)
// We got a socket error:
if ControlCount = 0 then
// window already closed
Return
end if
UpdaterEngine.LastUpdateVersion = "" // retry on next run
// display some message text. We don't localize it, but we can show it at least for our support hotline
dim msg as string
select case code
case 0
Msg = ": No error."
case 100
Msg = ": There was an error opening and initializing the drivers."
case 102
Msg = ": This code means that you lost your connection."
case 103
Msg = ": Unable to resolve the address that was specified."
case 107
Msg = ": This error means that the port you specified is invalid."
case 108
Msg = ": This error indicates that your application has run out of memory."
end Select
MsgBox kNetworkError+EndOfLine+EndOfLine+str(code)+msg
End Sub
Private Sub SockPageReceived(content as string)
// We got a file from the webserver and now we check whether it is okay, write it to a file and launch it.
// Check Length of file
dim Length as integer = lenb(content)
if DebugBuild then
System.DebugLog "Got length: "+str(length)+" "+str(DownloadLength)
end if
if Length <> DownloadLength then
MsgBox kFailedToDownloadFile
Return
end if
// Check MD5 Checksum if we have one
if len(DownloadCheckSum)>0 then
dim checksum as string = MD5StringMBS(content)
if DebugBuild then
System.DebugLog "Got checksum: "+str(checksum)+" "+str(DownloadCheckSum)
end if
if checksum <> DownloadCheckSum then
MsgBox kFailedToDownloadFile2
Return
end if
end if
// todo: signature check like in Sparkle?
// Now write installer to disc
dim f as FolderItem = FindTempFile
dim b as BinaryStream
try
b = BinaryStream.Create(f, true)
catch i as IOException
UpdaterEngine.LastUpdateVersion = "" // retry on next run
MsgBox kFailedToWriteUpdater
Return
end try
b.Write content
b.Close
// launch it
f.Launch(true)
// Check error from launching
dim e as integer = f.LastErrorCode
if e <> 0 then
MsgBox Replace(kFailedToLaunchUpdater,"%",str(e))
else
quit
end if
End Sub
Note "Copyright"
© 2015 by Christian Schmitz, Monkeybread Software
This is part of the MBS Xojo Updater Kit
https://www.monkeybreadsoftware.de/xojo/
Property Private CURLForDownload As Variant
Property Private DownloadCheckSum As string
Property Private DownloadLength As Integer
Property Private DownloadURL As string
Property Protected RedirectCount As Integer
End Class
Class UpdateURLConnection Inherits URLConnection
EventHandler Sub ContentReceived(URL As String, HTTPStatus As Integer, content As String)
If HTTPStatus >= 300 And httpStatus <= 399 Then
// redirect
RedirectCount = RedirectCount + 1
If RedirectCount >= 5 Then
UpdaterEngine.DisplayError "Too many redirects."
Return
End If
// get new URL
Dim u As String = ResponseHeader("Location")
If Len(u)>0 Then
// and start a new download
self.send("GET", u)
Return
End If
End If
// normal okay
UpdaterEngine.GotXML content
#Pragma Unused url
End EventHandler
Note "Copyright"
© 2015 by Christian Schmitz, Monkeybread Software
This is part of the MBS Xojo Updater Kit
https://www.monkeybreadsoftware.de/xojo/
Property Protected RedirectCount As Integer
End Class
ExternalFile Info
End ExternalFile
End Project
The items on this page are in the following plugins: MBS MacExtras Plugin.