1
+ package main
2
+
3
+ import (
4
+ "strings"
5
+ "encoding/json"
6
+ "github.com/jmoiron/jsonq"
7
+ "github.com/sqweek/dialog"
8
+ "github.com/gonutz/w32/v2"
9
+ "io/ioutil"
10
+ "net/http"
11
+ "time"
12
+ "regexp"
13
+ "os/exec"
14
+ "errors"
15
+ "io"
16
+ "os"
17
+ )
18
+
19
+ const LATEST_RELEASE_URL = "https://api.github.com/repos/iaincollins/icarus/releases/latest"
20
+
21
+ type Release struct {
22
+ productVersion string
23
+ downloadUrl string
24
+ }
25
+
26
+ func CheckForUpdate () {
27
+ currentProductVersion := GetCurrentAppVersion ()
28
+ release , releaseErr := GetLatestRelease (LATEST_RELEASE_URL )
29
+ if releaseErr != nil {
30
+ return
31
+ }
32
+
33
+ // If we are already running the latest release, do nothing
34
+ if (currentProductVersion == release .productVersion ) {
35
+ return
36
+ }
37
+
38
+ ok := dialog .Message ("%s" , "A new version of ICARUS Terminal is available.\n \n Would you like to download the update?" ).Title ("New version available" ).YesNo ()
39
+ if (ok ) {
40
+ downloadUrl := release .downloadUrl // In future may redirect to webpage instead
41
+ exec .Command ("rundll32" , "url.dll,FileProtocolHandler" , downloadUrl ).Start ()
42
+
43
+ // This is disabled as we can't actually run the current installer this way
44
+ // because it requires escalated privilages. This could be addressed by
45
+ // using a different type of installer.
46
+ /*
47
+ downloadedFile, downloadErr := DownloadRelease(release)
48
+ if downloadErr != nil {
49
+ fmt.Println("Error downloading update", downloadErr.Error())
50
+ }
51
+
52
+ installerCmdInstance := exec.Command(downloadedFile)
53
+ installerCmdErr := installerCmdInstance.Start()
54
+ if installerCmdErr != nil {
55
+ fmt.Println("Error installing update", installerCmdErr.Error())
56
+ }
57
+ */
58
+ }
59
+
60
+ return
61
+ }
62
+
63
+ func GetCurrentAppVersion () string {
64
+ const path = "ICARUS Terminal.exe"
65
+
66
+ size := w32 .GetFileVersionInfoSize (path )
67
+ if size <= 0 {
68
+ panic ("GetFileVersionInfoSize failed" )
69
+ }
70
+
71
+ info := make ([]byte , size )
72
+ ok := w32 .GetFileVersionInfo (path , info )
73
+ if ! ok {
74
+ panic ("GetFileVersionInfo failed" )
75
+ }
76
+
77
+ /*
78
+ fixed, ok := w32.VerQueryValueRoot(info)
79
+ if !ok {
80
+ panic("VerQueryValueRoot failed")
81
+ }
82
+ version := fixed.FileVersion()
83
+ fileVersion := fmt.Sprintf(
84
+ "%d.%d.%d.%d",
85
+ version&0xFFFF000000000000>>48,
86
+ version&0x0000FFFF00000000>>32,
87
+ version&0x00000000FFFF0000>>16,
88
+ version&0x000000000000FFFF>>0,
89
+ )
90
+ */
91
+
92
+ translations , ok := w32 .VerQueryValueTranslations (info )
93
+ if ! ok {
94
+ panic ("VerQueryValueTranslations failed" )
95
+ }
96
+ if len (translations ) == 0 {
97
+ panic ("no translation found" )
98
+ }
99
+ t := translations [0 ]
100
+
101
+ productVersion , ok := w32 .VerQueryValueString (info , t , w32 .ProductVersion )
102
+ if ! ok {
103
+ panic ("cannot get product version" )
104
+ }
105
+
106
+ // Convert from version with build number (0.0.0.0) to semver version (0.0.0)
107
+ productVersion = regexp .MustCompile (`(\.[^\.]+)$` ).ReplaceAllString (productVersion , `` )
108
+
109
+ return productVersion
110
+ }
111
+
112
+ func GetLatestRelease (releasesUrl string ) (Release , error ) {
113
+ release := Release {}
114
+
115
+ httpClient := http.Client {Timeout : time .Second * 5 }
116
+
117
+ req , reqErr := http .NewRequest (http .MethodGet , releasesUrl , nil )
118
+ if reqErr != nil {
119
+ return release , reqErr
120
+ }
121
+
122
+ res , getErr := httpClient .Do (req )
123
+ if getErr != nil {
124
+ return release , getErr
125
+ }
126
+
127
+ if res .Body != nil {
128
+ defer res .Body .Close ()
129
+ }
130
+
131
+ body , readErr := ioutil .ReadAll (res .Body )
132
+ if readErr != nil {
133
+ return release , readErr
134
+ }
135
+
136
+ // Hackery to convert the response into JSON that jsonq can parse
137
+ jsonObjectAsString := string (body )
138
+ // jsonObjectAsString = regexp.MustCompile(`^\[`).ReplaceAllString(jsonObjectAsString, `{"releases":[`)
139
+ // jsonObjectAsString = regexp.MustCompile(`\]$`).ReplaceAllString(jsonObjectAsString, `]}`)
140
+
141
+ // Use jsonq to access JSON
142
+ data := map [string ]interface {}{}
143
+ dec := json .NewDecoder (strings .NewReader (jsonObjectAsString ))
144
+ dec .Decode (& data )
145
+ jq := jsonq .NewQuery (data )
146
+
147
+ // Get properties from from JSON
148
+ // tag, _ := jq.String("releases", "0", "tag_name")
149
+ // productVersion := regexp.MustCompile(`^v`).ReplaceAllString(tag, ``)
150
+ // downloadUrl, _ := jq.String("releases", "0", "assets", "0", "browser_download_url")
151
+ tag , _ := jq .String ("tag_name" )
152
+ productVersion := regexp .MustCompile (`^v` ).ReplaceAllString (tag , `` ) // Converts tag (v0.0.0) to semver version (0.0.0) for easier comparion
153
+ downloadUrl , _ := jq .String ("assets" , "0" , "browser_download_url" )
154
+
155
+ if (downloadUrl == "" ) {
156
+ return release , errors .New ("Could not get download URL" )
157
+ }
158
+
159
+ release .productVersion = productVersion
160
+ release .downloadUrl = downloadUrl
161
+
162
+ return release , nil
163
+ }
164
+
165
+ func DownloadRelease (release Release ) (string , error ) {
166
+ pathToDownloadedFile := "ICARUS Update.exe"
167
+
168
+ // Get file to download
169
+ resp , err := http .Get (release .downloadUrl )
170
+ if err != nil {
171
+ return "" , err
172
+ }
173
+ defer resp .Body .Close ()
174
+
175
+ // Create file
176
+ out , err := os .Create (pathToDownloadedFile )
177
+ if err != nil {
178
+ return "" , err
179
+ }
180
+ defer out .Close ()
181
+
182
+ // Write to file
183
+ _ , err = io .Copy (out , resp .Body )
184
+
185
+ return pathToDownloadedFile , nil
186
+ }
0 commit comments