-
Notifications
You must be signed in to change notification settings - Fork 59
/
Copy pathinstall_prepare.go
258 lines (213 loc) Β· 7.43 KB
/
install_prepare.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
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
package operate
import (
"io"
"github.com/itchio/butler/butlerd"
"github.com/itchio/butler/installer"
"github.com/itchio/butler/installer/bfs"
itchio "github.com/itchio/go-itchio"
"github.com/itchio/httpkit/progress"
"github.com/itchio/wharf/eos"
"github.com/itchio/wharf/eos/option"
"github.com/pkg/errors"
)
type InstallPerformStrategy = int
const (
InstallPerformStrategyNone InstallPerformStrategy = 0
InstallPerformStrategyInstall InstallPerformStrategy = 1
InstallPerformStrategyHeal InstallPerformStrategy = 2
InstallPerformStrategyUpgrade InstallPerformStrategy = 3
)
type InstallPrepareResult struct {
File eos.File
ReceiptIn *bfs.Receipt
Strategy InstallPerformStrategy
}
type InstallTask func(res *InstallPrepareResult) error
func InstallPrepare(oc *OperationContext, meta *MetaSubcontext, isub *InstallSubcontext, allowDownloads bool, task InstallTask) error {
rc := oc.rc
params := meta.Data
consumer := oc.Consumer()
client := rc.Client(params.Access.APIKey)
res := &InstallPrepareResult{}
receiptIn, err := bfs.ReadReceipt(params.InstallFolder)
if err != nil {
receiptIn = nil
consumer.Errorf("Could not read existing receipt: %s", err.Error())
}
if receiptIn == nil {
consumer.Infof("No receipt found.")
}
res.ReceiptIn = receiptIn
istate := isub.Data
if istate.DownloadSessionID == "" {
res, err := client.NewDownloadSession(itchio.NewDownloadSessionParams{
GameID: params.Game.ID,
Credentials: params.Access.Credentials,
})
if err != nil {
return errors.WithStack(err)
}
istate.DownloadSessionID = res.UUID
err = oc.Save(isub)
if err != nil {
return err
}
consumer.Infof("β Starting fresh download session (%s)", istate.DownloadSessionID)
} else {
consumer.Infof("β» Resuming download session (%s)", istate.DownloadSessionID)
}
if receiptIn == nil {
consumer.Infof("β No previous install info (no recorded upload or build)")
} else {
consumer.Infof("β Previously installed:")
LogUpload(consumer, receiptIn.Upload, receiptIn.Build)
}
consumer.Infof("β To be installed:")
LogUpload(consumer, params.Upload, params.Build)
if receiptIn != nil && receiptIn.Upload != nil && receiptIn.Upload.ID == params.Upload.ID {
consumer.Infof("Installing over same upload")
if receiptIn.Build != nil && params.Build != nil {
oldID := receiptIn.Build.ID
newID := params.Build.ID
if newID > oldID {
consumer.Infof("β Upgrading from build %d to %d", oldID, newID)
upgradeRes, err := client.GetBuildUpgradePath(itchio.GetBuildUpgradePathParams{
CurrentBuildID: oldID,
TargetBuildID: newID,
Credentials: params.Access.Credentials,
})
if err != nil {
consumer.Warnf("Could not find upgrade path: %s", err.Error())
consumer.Infof("Falling back to heal...")
res.Strategy = InstallPerformStrategyHeal
return task(res)
}
upgradePath := upgradeRes.UpgradePath
// skip the current build, we're not interested in it
upgradePath.Builds = upgradePath.Builds[1:]
var totalUpgradeSize int64
consumer.Infof("Found upgrade path with %d items: ", len(upgradePath.Builds))
for _, b := range upgradePath.Builds {
f := FindBuildFile(b.Files, itchio.BuildFileTypePatch, itchio.BuildFileSubTypeDefault)
if f == nil {
consumer.Warnf("Whoops, build %d is missing a patch, falling back to heal...", b.ID)
res.Strategy = InstallPerformStrategyHeal
return task(res)
}
{
of := FindBuildFile(b.Files, itchio.BuildFileTypePatch, itchio.BuildFileSubTypeOptimized)
if of != nil {
f = of
}
}
consumer.Infof(" - Build %d (%s)", b.ID, progress.FormatBytes(f.Size))
totalUpgradeSize += f.Size
}
fullUploadSize := params.Upload.Size
var comparative = "smaller than"
if totalUpgradeSize > fullUploadSize {
comparative = "larger than"
}
consumer.Infof("Total upgrade size %s is %s full upload %s",
progress.FormatBytes(totalUpgradeSize),
comparative,
progress.FormatBytes(fullUploadSize),
)
if totalUpgradeSize > fullUploadSize {
consumer.Infof("Heal is less expensive, let's do that")
res.Strategy = InstallPerformStrategyHeal
return task(res)
}
consumer.Infof("Will apply %d patches", len(upgradePath.Builds))
res.Strategy = InstallPerformStrategyUpgrade
if istate.UpgradePath == nil {
istate.UpgradePath = upgradePath
istate.UpgradePathIndex = 0
err = oc.Save(isub)
if err != nil {
return err
}
} else {
consumer.Infof("%d patches already done, letting it resume", istate.UpgradePathIndex)
}
return task(res)
} else if newID < oldID {
consumer.Infof("β Downgrading from build %d to %d", oldID, newID)
res.Strategy = InstallPerformStrategyHeal
return task(res)
}
consumer.Infof("βΊ Re-installing build %d", newID)
res.Strategy = InstallPerformStrategyHeal
return task(res)
}
}
installSourceURL := MakeSourceURL(client, consumer, istate.DownloadSessionID, params, "")
file, err := eos.Open(installSourceURL, option.WithConsumer(consumer))
if err != nil {
return errors.WithStack(err)
}
res.File = file
defer file.Close()
if params.Upload.Storage == itchio.UploadStorageExternal {
consumer.Warnf("Dealing with an external upload (from %s), all bets are off.", params.Upload.Host)
if IsBadExternalHost(params.Upload.Host) {
consumer.Warnf("Host (%s) is known not to work, failing early.", params.Upload.Host)
return errors.WithStack(butlerd.CodeUnsupportedHost)
}
if !allowDownloads {
consumer.Warnf("Can't determine source information at that time")
return nil
}
consumer.Warnf("Forcing download before we check anything else.")
lf, err := doForceLocal(file, oc, meta, isub)
if err != nil {
return errors.WithStack(err)
}
file.Close()
file = lf
res.File = lf
}
if istate.InstallerInfo == nil || istate.InstallerInfo.Type == installer.InstallerTypeUnknown {
consumer.Infof("Determining source information...")
installerInfo, err := installer.GetInstallerInfo(consumer, file)
if err != nil {
return errors.WithStack(err)
}
// sniffing may have read parts of the file, so seek back to beginning
_, err = file.Seek(0, io.SeekStart)
if err != nil {
return errors.WithStack(err)
}
if params.IgnoreInstallers {
switch installerInfo.Type {
case installer.InstallerTypeArchive:
// that's cool
case installer.InstallerTypeNaked:
// that's cool too
default:
consumer.Infof("Asked to ignore installers, forcing (naked) instead of (%s)", installerInfo.Type)
installerInfo.Type = installer.InstallerTypeNaked
}
}
dui, err := AssessDiskUsage(file, receiptIn, params.InstallFolder, installerInfo)
if err != nil {
return errors.WithMessage(err, "assessing disk usage")
}
consumer.Infof("Estimated disk usage (accuracy: %s)", dui.Accuracy)
consumer.Infof(" β %s needed free space", progress.FormatBytes(dui.NeededFreeSpace))
consumer.Infof(" β %s final disk usage", progress.FormatBytes(dui.FinalDiskUsage))
istate.InstallerInfo = installerInfo
err = oc.Save(isub)
if err != nil {
return err
}
} else {
consumer.Infof("Using cached source information")
}
installerInfo := istate.InstallerInfo
if installerInfo.Type == installer.InstallerTypeUnsupported {
consumer.Errorf("Item is packaged in a way that isn't supported, refusing to install")
return errors.WithStack(butlerd.CodeUnsupportedPackaging)
}
return task(res)
}