forked from fynelabs/selfupdate
/
updater.go
152 lines (124 loc) · 3.48 KB
/
updater.go
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
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
package selfupdate
import (
"crypto/ed25519"
"errors"
"io"
"sync"
"time"
)
// ErrNotSupported is returned by `Manage` when it is not possible to manage the current application.
var ErrNotSupported = errors.New("operating system not supported")
type Source interface {
Get(*Version) (io.ReadCloser, int64, error)
GetSignature() ([64]byte, error)
LatestVersion() (*Version, error)
}
type Config struct {
Current *Version
Source Source
Schedule Schedule
PublicKey ed25519.PublicKey
TargetPath string
ProgressCallback func(float64, error) // if present will call back with 0.0 at the start, rising through to 1.0 at the end if the progress is known. A negative start number will be sent if size is unknown, any error will pass as is and the process is considered done
RestartConfirmCallback func() bool // if present will ask for user acceptance before restarting app
UpgradeConfirmCallback func(string) bool // if present will ask for user acceptance, it can present the message passed
ExitCallback func(error) // if present will be expected to handle app exit procedure
}
type Schedule struct {
FetchOnStart bool
Interval time.Duration
}
type Version struct {
Number string // if the app knows its version and supports checking metadata
Build int // if the app has a build number this could be compared
Date time.Time // last update, could be mtime
}
type Updater struct {
lock sync.Mutex
conf *Config
}
func (u *Updater) CheckNow(targetpath string) error {
u.lock.Lock()
defer u.lock.Unlock()
v := u.conf.Current
if v == nil {
mtime, err := lastModifiedExecutable()
if err != nil {
return err
}
v = &Version{Date: mtime}
}
latest, err := u.conf.Source.LatestVersion()
if err != nil {
return err
}
if !latest.Date.After(v.Date) {
return nil
}
if ask := u.conf.UpgradeConfirmCallback; ask != nil {
if !ask("New version found") {
return nil
}
}
s, err := u.conf.Source.GetSignature()
if err != nil {
return err
}
r, contentLength, err := u.conf.Source.Get(v)
if err != nil {
return err
}
defer r.Close()
opts := Options{}
opts.Signature = s[:]
opts.PublicKey = u.conf.PublicKey
opts.TargetPath = targetpath
u.conf.TargetPath = targetpath
pr := &progressReader{Reader: r, progressCallback: u.conf.ProgressCallback, contentLength: contentLength}
err = Apply(pr, opts)
if err != nil {
return err
}
if ask := u.conf.RestartConfirmCallback; ask != nil {
if !ask() {
return nil
}
}
return u.Restart()
}
func (u *Updater) Restart() error {
return Restart(u.conf.ExitCallback,u.conf.TargetPath)
}
// Manage sets up an Updater and runs it to manage the current executable.
func Manage(conf *Config,targetpath string) (*Updater, error) {
updater := &Updater{conf: conf}
go func() {
if updater.conf.Schedule.FetchOnStart {
updater.CheckNow(targetpath)
}
if updater.conf.Schedule.Interval != 0 {
for {
time.Sleep(updater.conf.Schedule.Interval)
updater.CheckNow(targetpath)
}
}
}()
// TODO check if we can support the current app!
return updater, nil
}
// ManualUpdate applies a specific update manually instead of managing the update of this app automatically.
func ManualUpdate(s Source, publicKey ed25519.PublicKey) error {
v := &Version{}
r, _, err := s.Get(v)
if err != nil {
return err
}
signature, err := s.GetSignature()
if err != nil {
return err
}
opts := Options{}
opts.Signature = signature[:]
opts.PublicKey = publicKey
return Apply(r, opts)
}