cbklib

Check-in [e93e2faf2b]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Update software update with cross-platform support
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:e93e2faf2b15b1f99f31e4ef15436498caf71ff4
User & Date: admin 2015-06-27 00:22:49
Context
2015-06-27
00:26
Minor update to software update check-in: fe57bb3e5b user: admin tags: trunk
00:22
Update software update with cross-platform support check-in: e93e2faf2b user: admin tags: trunk
2015-06-20
02:34
Initial commit check-in: 55ecffde0d user: admin tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to softwareupdate/pkgIndex.tcl.

4
5
6
7
8
9
10
11
# by a "package unknown" script.  It invokes the
# "package ifneeded" command to set up package-related
# information so that packages will be loaded automatically
# in response to "package require" commands.  When this
# script is sourced, the variable $dir must contain the
# full path name of this file's directory.

package ifneeded softwareupdate 1.4 [list source [file join $dir softwareupdate.tcl]]







|
4
5
6
7
8
9
10
11
# by a "package unknown" script.  It invokes the
# "package ifneeded" command to set up package-related
# information so that packages will be loaded automatically
# in response to "package require" commands.  When this
# script is sourced, the variable $dir must contain the
# full path name of this file's directory.

package ifneeded softwareupdate 1.5 [list source [file join $dir softwareupdate.tcl]]

Changes to softwareupdate/softwareupdate.tcl.

1
2
3
4
5
6
7
8
9

10
11
12
13
14
15
16
17
18
19


20



21
22
23
24
25
26
















27
28
29
30
31
32
33
..
37
38
39
40
41
42
43
44
45
46
47
48
49

50
51
52
53
54
55
56
57
58
59
60

61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84








85
86
87
88
89
90
91
..
92
93
94
95
96
97
98




99
100
101












102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185

186
187




188
189
190
191
192
193
194
195

196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222



223
224
225



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242

243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
...
295
296
297
298
299
300
301

302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
...
333
334
335
336
337
338
339

340
341
342
343
344
345
346
347

348
349
350
351
352
353
354
355
356
357
358
359















360
361
362
363
364
365
366
...
368
369
370
371
372
373
374

375
376

377

378
379
380
381
382
383
384
385
386
387
388
389
390
391


















392
393
394
395
396
397

#softwareupdate.tcl  routines to manage spoftware updates

#  Copyright (C) 2014  WordTech Communications LLC

#Proprietary to WordTech Communications LLC. Redistribution prohibited.

package provide softwareupdate 1.4
package require http
package require tdom


namespace eval softwareupdate {

    if {![info exists library]} {
	variable library [file dirname [info script]]
    }


    variable icon
    variable appname


    



    
    proc setIcon {app} {
	
	set iconfile [file join [softwareupdate::findCurrentInstallation] "Contents" "Resources" $app.icns]

	tk::mac::iconBitmap myicon 64 64 -imageFile $iconfile
















    }

    proc setAppName {name} {

	variable appname
	set appname $name
	return $appname
................................................................................

    #check version of installed software
    proc checkVersion {app version} {
	variable appversion
	variable appname
	variable currentversion
	variable versionnumber
	variable appcast
	variable sparkledata
	variable changedata

	softwareupdate::setIcon $app
	softwareupdate::checkingForUpdates


	set appcast http://www.codebykevin.com/[string tolower $appname].xml
	http::config -useragent "$appname Update Check"

	set xml [http::data [http::geturl $appcast]]

	puts $xml

	if [catch {http::geturl $appcast} msg] {
	    puts "error: $msg"
	    tk_messageBox -icon info -title "" -message "Update Error!" -detail "An error occurred in retrieving update information.\nPlease try again later."

	    return
	} 

	dom parse $xml sparkledata
	set versionnumber [string trim [$sparkledata selectNodes -namespaces [list x "http://www.andymatuschak.org/xml-namespaces/sparkle"] {string(//enclosure/@x:version)}] .0]

	set changedata [$sparkledata selectNodes {string(//description/text())}]

	set minOS [$sparkledata selectNodes -namespaces [list x "http://www.andymatuschak.org/xml-namespaces/sparkle"] {string(//x:minimumSystemVersion)}]

	set hostOS [exec sw_vers -productVersion]

	if {![package vsatisfies $hostOS $minOS]} {
	    tk_messageBox -icon warning -message "Error!" -detail "$appname is not supported on Mac OS X $hostOS. The minimum supported OS version is $minOS." 
	    return
	}
	
	if {[expr $currentversion < $versionnumber]} {
	    softwareupdate::updatePitch
	} else {
	    softwareupdate::upToDate $appname $currentversion
	}
    }










    #define the current version of the software
    proc setVersion {app number} {
	variable currentversion
	variable appname
	set currentversion $number
	set appname $app
................................................................................
	
    }


    #get the current installation path
    proc findCurrentInstallation {} {
	variable currentinstall




	set approot [info nameofexecutable]
	set apppath [split $approot /]
	set currentinstall [join [lrange $apppath 0 [lsearch $apppath "*.app"]] / ]












	return $currentinstall

    }

    #prompt user to update: for Perl, which chokes on XML parsing from Tcl
    proc updatePitchPerl {apptitle appnumber currentapp changelog} {
	variable appname
	variable myicon
	variable changedata
	variable currentversion
	variable versionnumber
	variable changelist

	catch {destroy .updateprogress}

	catch {destroy .update}


	toplevel .update
	wm title .update "Software Update"

	wm withdraw .update

	frame .update.f -bg gray95
	pack .update.f -fill both -expand yes

	frame .update.f.top -bg gray95
	pack .update.f.top -fill both -expand yes

	label .update.f.top.i -bitmap myicon -bg gray95 -relief flat -highlightthickness 0
	pack .update.f.top.i -side left -fill both -expand yes

	frame .update.f.top.r -bg gray95
	pack .update.f.top.r -side right -fill both -expand yes

	label .update.f.top.r.title -text "A new version of $apptitle is available!" -font {-weight bold} -bg gray95 -relief flat -highlightthickness 0
	pack .update.f.top.r.title -fill both -expand yes -side top

	label .update.f.top.r.msg -text "$apptitle $appnumber is available--you have $currentapp. Would you like to download it now?" -bg gray95 -relief flat -highlightthickness 0
	pack .update.f.top.r.msg -fill both -expand yes -side top

	label .update.f.top.r.release -text "Release Notes:" -font {-weight bold} -relief flat -highlightthickness 0 -bg gray95 
	pack .update.f.top.r.release -side top -fill both -expand yes

	text .update.f.top.r.text -font TkDefaultFont
	pack .update.f.top.r.text -side top -fill both -expand yes

	ttk::frame .update.f.top.r.bottom -padding 5
	pack .update.f.top.r.bottom -side bottom -fill both -expand yes

	ttk::button .update.f.top.r.bottom.skip -text "Skip This Version" -command {destroy .update}

	ttk::button .update.f.top.r.bottom.install -text "Install Update" -default active -command softwareupdate::installUpdate
	
	pack .update.f.top.r.bottom.install .update.f.top.r.bottom.skip  -side right  -fill both -expand yes

	set changetext [split $changelog *]

	set changetext [lrange $changetext 1 end]

	foreach item $changetext {
	    
	    .update.f.top.r.text insert end "* $item\n"

	}

	.update.f.top.r.text configure -state disabled

	wm resizable .update 0 0
	wm deiconify .update
	raise .update
	wm transient .update .

    }

    #prompt user to update
    proc updatePitch {} {
	variable appname
	variable myicon
	variable changedata
	variable currentversion
	variable versionnumber

	catch {destroy .updateprogress}


	catch {destroy .update}




	
	toplevel .update
	wm title .update "Software Update"

	wm withdraw .update

	frame .update.f -bg gray95
	pack .update.f -fill both -expand yes


	frame .update.f.top -bg gray95
	pack .update.f.top -fill both -expand yes

	label .update.f.top.i -bitmap myicon -bg gray95 -relief flat -highlightthickness 0
	pack .update.f.top.i -side left -fill both -expand yes

	frame .update.f.top.r -bg gray95
	pack .update.f.top.r -side right -fill both -expand yes

	label .update.f.top.r.title -text "A new version of $appname is available!" -font {-weight bold} -bg gray95 -relief flat -highlightthickness 0
	pack .update.f.top.r.title -fill both -expand yes -side top

	label .update.f.top.r.msg -text "$appname $versionnumber is available--you have $currentversion. Would you like to download it now?" -font {-size 10} -bg gray95 -relief flat -highlightthickness 0
	pack .update.f.top.r.msg -fill both -expand yes -side top

	label .update.f.top.r.release -text "Release Notes:" -font {-size 10 -weight bold} -relief flat -highlightthickness 0 -bg gray95 
	pack .update.f.top.r.release -side top -fill both -expand yes

	text .update.f.top.r.text -font TkDefaultFont
	pack .update.f.top.r.text -side top -fill both -expand yes

	ttk::frame .update.f.top.r.bottom -padding 5
	pack .update.f.top.r.bottom -side bottom -fill both -expand yes

	ttk::button .update.f.top.r.bottom.skip -text "Skip This Version" -command {destroy .update}




	ttk::button .update.f.top.r.bottom.install -text "Install Update" -default active -command softwareupdate::installUpdate
	
	pack .update.f.top.r.bottom.install .update.f.top.r.bottom.skip  -side right  -fill both -expand yes




	.update.f.top.r.text insert end $changedata

	.update.f.top.r.text configure -state disabled

	wm resizable .update 0 0
	wm deiconify .update
	raise .update
	wm transient .update .

    }


    #"busy" dialog
    proc checkingForUpdates {} {

	variable appname


	catch {destroy .updateprogress}

	toplevel .updateprogress 
	wm title .updateprogress "Updating $appname"

	wm withdraw .updateprogress
	update idletasks

	ttk::frame .updateprogress.f -padding 5
	pack .updateprogress.f -fill both -expand yes

	label .updateprogress.f.l -bg gray95 -bitmap myicon -bd 0 -relief flat -highlightthickness 0
	pack .updateprogress.f.l -side left -fill both -expand yes

	frame .updateprogress.f.r -bg gray95 -bd 0 -highlightcolor gray95
	pack .updateprogress.f.r -side right -fill both -expand yes
	
	
	ttk::label .updateprogress.f.r.t -text "Checking for updates..." -padding 5
	pack .updateprogress.f.r.t -side top -fill both -expand yes

	ttk::frame .updateprogress.f.r.f -padding 5
................................................................................
    }

    #show progress of installation
    proc progressDialog {} {

	variable appname
	variable status


	catch {destroy .downloadprogress}

	toplevel .downloadprogress
	wm title .downloadprogress "Updating $appname"

	wm transient .downloadprogress .

	label .downloadprogress.label -bitmap myicon   -anchor w -bg gray95 -highlightthickness 0

	pack .downloadprogress.label -side left -fill both -expand yes


	ttk::frame .downloadprogress.frame -padding 5
	pack .downloadprogress.frame -side right -fill both -expand yes

................................................................................
    }

    #download and install the update
    proc installUpdate {} {
	variable currentinstall
	variable status
	variable appname


	catch {destroy .update}

	softwareupdate::findCurrentInstallation

	variable appname
	variable status



	softwareupdate::progressDialog
	set status "Downloading update for $appname"
	#use curl because http module seems broken especially when called from Perl; also use /tmp because Perl doesn't like env(TMPDIR)
	catch {exec curl http://www.codebykevin.com/updates/[list $appname].tgz >/tmp/$appname.tgz}
	update
	after 1000
	cd /tmp
	set status "Unpacking update for $appname"
	update
	after 1000
	catch {exec tar xvfz [list $appname].tgz}















	set status "Ready to install"
	.downloadprogress.frame.bar configure -mode determinate
	.downloadprogress.frame.bar configure -value 100
	.downloadprogress.frame.bar stop
	destroy .downloadprogress.frame.b 
	pack [ttk::button .downloadprogress.frame.b -text "Install and Relaunch" -command softwareupdate::launchUpdate] -side right -fill both -expand no
	return
................................................................................


    #launch the update
    proc launchUpdate {}  {

	variable currentinstall
	variable appname


	catch {destroy .downloadprogress}



	if {[catch {exec codesign -v /tmp/$appname.app} msg]} {
	    bgerror $msg
	    tk_messageBox -icon warning -message "Error!" -detail "An error occurred in the installation of $appname. Please try again later."
	    return
	} else {

	    file rename -force $currentinstall [file join /Users [exec whoami] .Trash [file tail $currentinstall]]

	    file rename -force /tmp/$appname.app $currentinstall

	    exec $currentinstall/Contents/MacOS/$appname &

	    exit
	}



















    }

    namespace export *

}



|



|

<
>










>
>

>
>
>
|
<
|
<
|
<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







<
<
<

<
<
>

|


<
<
<
<
|

<
>



|
|
<
<
<
<
<
<
<
<
<
<
<
<
|


|


|
>
>
>
>
>
>
>
>







 







>
>
>
>
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>




<
<
<
<
<
<
<
<

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


<
<
<
<

<
>

<
>
>
>
>
|
<
<

<

<
<
>

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
>
>
>
|
|
<
>
>
>
|
<
|
<
|
<
<
<
<
|
|

<




>












|


|







 







>








|







 







>





|
|
|
>

<
|
<
|
|
|
|
|
|
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
|
<
>

>
|
|
|
|
|

|

|

|

|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>






>
1
2
3
4
5
6
7
8

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

27

28

29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
..
55
56
57
58
59
60
61



62


63
64
65
66
67




68
69

70
71
72
73
74
75












76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
..
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127








128































































129
130




131

132
133

134
135
136
137
138


139

140


141
142


























143
144
145
146
147

148
149
150
151

152

153




154
155
156

157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
...
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
...
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270

271

272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
...
303
304
305
306
307
308
309
310
311

312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
#softwareupdate.tcl  routines to manage spoftware updates

#  Copyright (C) 2015  WordTech Communications LLC

#Proprietary to WordTech Communications LLC. Redistribution prohibited.

package provide softwareupdate 1.5
package require http



namespace eval softwareupdate {

    if {![info exists library]} {
	variable library [file dirname [info script]]
    }


    variable icon
    variable appname
    variable tmpdir
    variable currentinstall
    
    switch [tk windowingsystem] {
	"aqua" {
	    set tmpdir $::env(TMPDIR)


	}

	

	"win32" {
	    set tmpdir $::env(TMP)
	}
	
	"x11" {
	    set tmpdir $::env(TMP)
	}
    }
    
    
    
    proc setIcon {appicon} {
	
	variable icon
	set icon $appicon
	return $icon
    }

    proc setAppName {name} {

	variable appname
	set appname $name
	return $appname
................................................................................

    #check version of installed software
    proc checkVersion {app version} {
	variable appversion
	variable appname
	variable currentversion
	variable versionnumber






	set appname $app

	set versionurl http://www.codebykevin.com/version.tcl
	http::config -useragent "$appname Update Check"





	if [catch {http::geturl $versionurl} msg] {
	    puts "error: $msg"

	    tk_messageBox -icon warning -title "Unable to Connect to Server" -message "Unable to Connect to Server" -detail "Unable to connect to www.codebykevin.com to check for updates. Please make sure you are connected to the Internet." -parent .
	    return
	} 

	array set appversion [http::data [http::geturl $versionurl]]
	set versionnumber $appversion([string tolower $appname])












	if {$currentversion < $versionnumber} {
	    softwareupdate::updatePitch
	} else {
	    softwareupdate::upToDate
	}
    }
    
    #define the current version of the software
    proc setVersion {app number} {
	variable currentversion
	variable appname
	set currentversion $number
	set appname $app
	
    }

    #define the current version of the software
    proc setVersion {app number} {
	variable currentversion
	variable appname
	set currentversion $number
	set appname $app
................................................................................
	
    }


    #get the current installation path
    proc findCurrentInstallation {} {
	variable currentinstall
	variable appname 
	switch [tk windowingsystem] {

	    "aqua" {
		set approot [info nameofexecutable]
		set apppath [split $approot /]
		set currentinstall [join [lrange $apppath 0 [lsearch $apppath "*.app"]] / ]
	    }
	    
	    "win32" {
		set currentinstall [file join C {Program Files} $appname]
		
	    }
	    
	    "x11" {
		return
	    }
	}
	
	return $currentinstall

    }









































































    proc updatePitch {} {
	variable appname






	set changeurl http://www.codebykevin.com/$appname-changes.tcl


        if [catch {http::geturl $changeurl} msg] {
	    puts "error: $msg"
	    tk_messageBox -icon warning -title "Unable to Connect to Server" -message "Unable to Connect to Server" -detail "Unable to connect to www.codebykevin.com to check for updates. Please make sure you are connected to the Internet." -parent .
	    return
	} 







	set changelist [http::data [http::geturl $changeurl]]



























	set updateanswer [tk_messageBox -title "Update" -icon info -message "Update Available" -detail "A new version ($softwareupdate::versionnumber) of $appname is available.\n\nThis new version features the following updates and changes:\n\n$changelist\n\nWould you like to install it? " -type yesno -parent .]
	switch -- $updateanswer {
	    yes {
		softwareupdate::installUpdate
	    }

	    no {
		destroy $updateanswer
		return
	    }

	    

	}




    }



    #"busy" dialog
    proc checkingForUpdates {} {

	variable appname
	variable icon

	catch {destroy .updateprogress}

	toplevel .updateprogress 
	wm title .updateprogress "Updating $appname"

	wm withdraw .updateprogress
	update idletasks

	ttk::frame .updateprogress.f -padding 5
	pack .updateprogress.f -fill both -expand yes

	ttk::label .updateprogress.f.l -bg gray95 -image $icon 
	pack .updateprogress.f.l -side left -fill both -expand yes

	ttk::frame .updateprogress.f.r 
	pack .updateprogress.f.r -side right -fill both -expand yes
	
	
	ttk::label .updateprogress.f.r.t -text "Checking for updates..." -padding 5
	pack .updateprogress.f.r.t -side top -fill both -expand yes

	ttk::frame .updateprogress.f.r.f -padding 5
................................................................................
    }

    #show progress of installation
    proc progressDialog {} {

	variable appname
	variable status
	variable icon

	catch {destroy .downloadprogress}

	toplevel .downloadprogress
	wm title .downloadprogress "Updating $appname"

	wm transient .downloadprogress .

	ttk::label .downloadprogress.label -image $icon   -anchor w 

	pack .downloadprogress.label -side left -fill both -expand yes


	ttk::frame .downloadprogress.frame -padding 5
	pack .downloadprogress.frame -side right -fill both -expand yes

................................................................................
    }

    #download and install the update
    proc installUpdate {} {
	variable currentinstall
	variable status
	variable appname
	variable tmpdir

	catch {destroy .update}

	softwareupdate::findCurrentInstallation

	softwareupdate::progressDialog
	set status "Downloading update for $appname"
	
	switch [tk windowingsystem] {


	    "aqua" {

		http::geturl http://www.codebykevin.com/updates/[list $appname].tgz -channel [open $tmpdir/[list $appname].tgz w] 
		update
		after 1000
		cd $tmpdir
		set status "Unpacking update for $appname"
		update
		after 1000
		catch {exec tar xvfz [list $appname].tgz}
	    }
	    
	    "win32" {
		
		http::geturl http://www.codebykevin.com/updates/[list $appname].exe -channel [open $tmpdir/[list $appname].exe w] 

	    }
	    
	    "x11" {
		tk_messageBox -icon info -parent . -message "Please ask the maintainer of $appname on your platform to prepare a release of the latest version."
		return
	    }
	}
	
	
	set status "Ready to install"
	.downloadprogress.frame.bar configure -mode determinate
	.downloadprogress.frame.bar configure -value 100
	.downloadprogress.frame.bar stop
	destroy .downloadprogress.frame.b 
	pack [ttk::button .downloadprogress.frame.b -text "Install and Relaunch" -command softwareupdate::launchUpdate] -side right -fill both -expand no
	return
................................................................................


    #launch the update
    proc launchUpdate {}  {

	variable currentinstall
	variable appname
	variable tmpdir
	

	switch [tk windowingsystem] {

	    "aqua" {
		if {[catch {exec codesign -v $tmpdir/$appname.app} msg]} {
		    bgerror $msg
		    tk_messageBox -icon warning -message "Error!" -detail "An error occurred in the installation of $appname. Please try again later."
		    return
		} else {

		    file rename -force $currentinstall [file join /Users [exec whoami] .Trash [file tail $currentinstall]]

		    file rename -force /tmp/$appname.app $currentinstall

		    exec $currentinstall/Contents/MacOS/$appname &

		    exit
		}
	    }
	    
	    "win32" {
		
		file rename -force $currentinstall/[list $appname].exe $currentinstall/[list $appname].exe~
		file copy $tmpdir/[list $appname].exe $currentinstall/[list $appname.exe]
		exec $currentinstall/[list $appname].exe &
	    }
	    
	    "x11" {
		tk_messageBox -icon info -parent . -message "Please ask the maintainer of $appname on your platform to prepare a release of the latest version."
		return
	    }
	}

	catch {destroy .downloadprogress}

	

    }

    namespace export *

}