Skip to content

Commit 8727762

Browse files
committed
Initial commit
0 parents  commit 8727762

10 files changed

Lines changed: 1744 additions & 0 deletions

File tree

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
ultupdater.exe
2+
*.sqlite*
3+
venv
4+
test
5+
.idea

downloader.go

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"crypto/sha1"
6+
"database/sql"
7+
"encoding/hex"
8+
"fmt"
9+
"fyne.io/fyne/v2/dialog"
10+
"github.com/cavaliergopher/grab/v3"
11+
"path/filepath"
12+
"sync"
13+
"time"
14+
)
15+
16+
type Update struct {
17+
IndexFile *IndexedFile
18+
Progress float64
19+
Done bool
20+
}
21+
22+
type Downloader struct {
23+
RateLimit *int
24+
state *InstallerState
25+
ctx context.Context
26+
cancel context.CancelFunc
27+
client *grab.Client
28+
reqch chan *grab.Request
29+
respch chan *grab.Response
30+
updatech chan *Update
31+
workerWg sync.WaitGroup
32+
responderWg sync.WaitGroup
33+
updaterWg sync.WaitGroup
34+
running bool
35+
started bool
36+
installPath string
37+
}
38+
39+
func NewDownloader(state *InstallerState) *Downloader {
40+
d := &Downloader{
41+
RateLimit: nil,
42+
state: state,
43+
client: grab.NewClient(),
44+
workerWg: sync.WaitGroup{},
45+
responderWg: sync.WaitGroup{},
46+
updaterWg: sync.WaitGroup{},
47+
running: false,
48+
started: false,
49+
}
50+
51+
return d
52+
}
53+
54+
func (d *Downloader) Resume() error {
55+
if d.running {
56+
return nil
57+
}
58+
installPath, err := d.state.folderPath.Get()
59+
if err != nil {
60+
return err
61+
}
62+
d.installPath = installPath
63+
64+
// Reset context
65+
d.ctx, d.cancel = context.WithCancel(context.Background())
66+
67+
// Set up background
68+
d.reqch = make(chan *grab.Request, 10)
69+
d.respch = make(chan *grab.Response, 4)
70+
d.updatech = make(chan *Update, 4)
71+
72+
// Add 4 workers
73+
for i := 0; i < 4; i++ {
74+
d.workerWg.Add(1)
75+
go func() {
76+
defer d.workerWg.Done()
77+
d.client.DoChannel(d.reqch, d.respch)
78+
}()
79+
}
80+
81+
// Add a receiver for responses
82+
d.responderWg.Add(1)
83+
go func() {
84+
for resp := range d.respch {
85+
d.responderWg.Add(1)
86+
// Spin up goroutine for each response
87+
go func(resp *grab.Response) {
88+
defer d.responderWg.Done()
89+
t := time.NewTicker(500 * time.Millisecond)
90+
defer t.Stop()
91+
92+
for {
93+
select {
94+
case <-t.C:
95+
// Update in-progress values here
96+
f := resp.Request.Tag.(*IndexedFile)
97+
d.updatech <- &Update{
98+
IndexFile: f,
99+
Progress: resp.Progress(),
100+
Done: false,
101+
}
102+
case <-resp.Done:
103+
// Done, check for error
104+
err := resp.Err()
105+
if err != nil {
106+
fmt.Println(err.Error())
107+
// Bad download
108+
} else {
109+
// Successful download, notify UI updater
110+
f := resp.Request.Tag.(*IndexedFile)
111+
d.updatech <- &Update{
112+
IndexFile: f,
113+
Progress: 1,
114+
Done: true,
115+
}
116+
}
117+
return
118+
}
119+
}
120+
}(resp)
121+
}
122+
// Channel closed, finish out
123+
d.responderWg.Done()
124+
}()
125+
126+
// Set up UI updater
127+
d.updaterWg.Add(1)
128+
go func() {
129+
defer d.updaterWg.Done()
130+
// Create UI files
131+
uifiles := make([]*UiFile, 4)
132+
for i := 0; i < 4; i++ {
133+
uifiles[i] = &UiFile{
134+
Filepath: "",
135+
Progress: 0,
136+
Done: true,
137+
}
138+
}
139+
140+
for update := range d.updatech {
141+
fmt.Println(fmt.Sprintf("path: %s, progress: %f", update.IndexFile.Filepath, update.Progress))
142+
// Update UI element
143+
updateIdx := -1
144+
for idx, f := range uifiles {
145+
if f.Filepath == update.IndexFile.Filepath {
146+
f.Progress = update.Progress
147+
f.Done = update.Done
148+
updateIdx = idx
149+
break
150+
}
151+
}
152+
if updateIdx == -1 {
153+
// Didn't find existing entry, find an older one to replace
154+
for idx, f := range uifiles {
155+
if f.Done == true {
156+
f.Filepath = update.IndexFile.Filepath
157+
f.Progress = update.Progress
158+
f.Done = update.Done
159+
updateIdx = idx
160+
break
161+
}
162+
}
163+
}
164+
// If updated ui state, update element
165+
if updateIdx != -1 {
166+
if updateIdx == 0 {
167+
_ = d.state.fileTitle1.Set(update.IndexFile.Filepath)
168+
_ = d.state.fileProgress1.Set(update.Progress)
169+
}
170+
if updateIdx == 1 {
171+
_ = d.state.fileTitle2.Set(update.IndexFile.Filepath)
172+
_ = d.state.fileProgress2.Set(update.Progress)
173+
}
174+
if updateIdx == 2 {
175+
_ = d.state.fileTitle3.Set(update.IndexFile.Filepath)
176+
_ = d.state.fileProgress3.Set(update.Progress)
177+
}
178+
if updateIdx == 3 {
179+
_ = d.state.fileTitle4.Set(update.IndexFile.Filepath)
180+
_ = d.state.fileProgress4.Set(update.Progress)
181+
}
182+
183+
}
184+
185+
if update.Done {
186+
// Mark as done
187+
err := d.state.Repo.MarkFileDone(update.IndexFile)
188+
if err != nil {
189+
dialog.NewError(&DatabaseError{err}, d.state.window).Show()
190+
}
191+
192+
// Add new request if still running
193+
go func() {
194+
select {
195+
case <-d.ctx.Done():
196+
{
197+
// Kill request channel since we are sole sender
198+
close(d.reqch)
199+
return
200+
}
201+
default:
202+
{
203+
// Add new request to the queue
204+
f, err := d.state.Repo.GetNextFile()
205+
if err != nil {
206+
if err != sql.ErrNoRows {
207+
dialog.NewError(&DatabaseError{err}, d.state.window).Show()
208+
}
209+
} else {
210+
req, err := d.NewRequest(f)
211+
if err != nil {
212+
dialog.NewError(err, d.state.window).Show()
213+
}
214+
d.reqch <- req
215+
}
216+
}
217+
}
218+
}()
219+
220+
// Update Total Progress bar state
221+
d.state.downloadedSize += update.IndexFile.Size
222+
d.state.downloadedFiles += 1
223+
err = d.state.formatDownloadedSize.Set(FormatBytes(d.state.downloadedSize))
224+
if err != nil {
225+
dialog.NewError(err, d.state.window).Show()
226+
}
227+
progress := float64(d.state.downloadedSize) / float64(d.state.totalSize)
228+
fmt.Println(progress)
229+
err = d.state.progressBarTotal.Set(progress)
230+
if err != nil {
231+
dialog.NewError(err, d.state.window).Show()
232+
}
233+
234+
}
235+
}
236+
}()
237+
238+
// Add initial 10 files
239+
files, err := d.state.Repo.GetNextFileBatch(10)
240+
if err != nil {
241+
return err
242+
}
243+
for _, f := range files {
244+
req, err := d.NewRequest(f)
245+
if err != nil {
246+
return err
247+
}
248+
249+
// Add request to queue
250+
d.reqch <- req
251+
}
252+
d.running = true
253+
d.started = true
254+
255+
return nil
256+
}
257+
258+
func (d *Downloader) Stop() {
259+
if !d.running {
260+
return
261+
}
262+
263+
// Stop new and current requests
264+
d.cancel()
265+
266+
// Wait for all workers to exit
267+
d.workerWg.Wait()
268+
close(d.respch)
269+
270+
// Wait for all responses to finish processing
271+
d.responderWg.Wait()
272+
close(d.updatech)
273+
d.updaterWg.Wait()
274+
275+
d.running = false
276+
}
277+
278+
func (d *Downloader) NewRequest(f *IndexedFile) (*grab.Request, error) {
279+
// Set up request
280+
dest := filepath.Join(d.installPath, f.Filepath)
281+
req, err := grab.NewRequest(dest, fmt.Sprintf("%s/%s", d.state.baseUrl, f.Filepath))
282+
if err != nil {
283+
return nil, err
284+
}
285+
286+
// Add automatic checksumming
287+
sum, err := hex.DecodeString(f.SHA1)
288+
if err != nil {
289+
return nil, err
290+
}
291+
req.SetChecksum(sha1.New(), sum, true)
292+
req.Size = f.Size
293+
req = req.WithContext(d.ctx)
294+
295+
// Add indexed file as tag
296+
req.Tag = f
297+
298+
return req, nil
299+
}

go.mod

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
module ultupdater
2+
3+
go 1.20
4+
5+
require (
6+
fyne.io/fyne/v2 v2.3.5
7+
github.com/cavaliergopher/grab/v3 v3.0.1
8+
github.com/mattn/go-sqlite3 v1.14.17
9+
)
10+
11+
require (
12+
fyne.io/systray v1.10.1-0.20230602210930-b6a2d6ca2a7b // indirect
13+
github.com/davecgh/go-spew v1.1.1 // indirect
14+
github.com/fredbi/uri v0.1.0 // indirect
15+
github.com/fsnotify/fsnotify v1.5.4 // indirect
16+
github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect
17+
github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 // indirect
18+
github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect
19+
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect
20+
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect
21+
github.com/go-text/typesetting v0.0.0-20230405155246-bf9c697c6e16 // indirect
22+
github.com/godbus/dbus/v5 v5.1.0 // indirect
23+
github.com/goki/freetype v0.0.0-20220119013949-7a161fd3728c // indirect
24+
github.com/gopherjs/gopherjs v1.17.2 // indirect
25+
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
26+
github.com/pmezard/go-difflib v1.0.0 // indirect
27+
github.com/srwiley/oksvg v0.0.0-20220731023508-a61f04f16b76 // indirect
28+
github.com/srwiley/rasterx v0.0.0-20210519020934-456a8d69b780 // indirect
29+
github.com/stretchr/testify v1.8.0 // indirect
30+
github.com/tevino/abool v1.2.0 // indirect
31+
github.com/yuin/goldmark v1.4.13 // indirect
32+
golang.org/x/image v0.3.0 // indirect
33+
golang.org/x/mobile v0.0.0-20211207041440-4e6c2922fdee // indirect
34+
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
35+
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
36+
golang.org/x/text v0.6.0 // indirect
37+
gopkg.in/yaml.v3 v3.0.1 // indirect
38+
honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 // indirect
39+
)

0 commit comments

Comments
 (0)