Skip to content

Commit 009bddc

Browse files
committed
Empty dirs support
1 parent fd532c4 commit 009bddc

5 files changed

Lines changed: 101 additions & 42 deletions

File tree

downloader.go

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ package main
22

33
import (
44
"context"
5-
"crypto/sha1"
65
"database/sql"
6+
"encoding/binary"
77
"encoding/hex"
88
"fmt"
99
"fyne.io/fyne/v2/dialog"
1010
"github.com/cavaliergopher/grab/v3"
11+
"hash/crc32"
12+
"os"
1113
"path/filepath"
1214
"sync"
1315
"time"
16+
"unsafe"
1417
)
1518

1619
type Update struct {
@@ -157,6 +160,34 @@ func (d *Downloader) Resume() error {
157160
d.responderWg.Done()
158161
}()
159162

163+
// Set up empty dirs handler
164+
165+
// Attach to any wait group, it's independent anyway
166+
d.responderWg.Add(1)
167+
go func() {
168+
defer d.responderWg.Done()
169+
// Loop finding empty dirs until done or context ends
170+
for {
171+
select {
172+
case <-d.ctx.Done():
173+
return
174+
default:
175+
// Get next empty dir
176+
dir, err := d.state.Repo.GetNextEmptyDir()
177+
if err != nil {
178+
dialog.NewError(&DatabaseError{err}, d.state.window).Show()
179+
return
180+
}
181+
dest := filepath.Join(d.installPath, dir)
182+
err = os.MkdirAll(dest, os.ModePerm)
183+
if err != nil {
184+
dialog.NewError(&FatalDownloadFailure{err}, d.state.window).Show()
185+
return
186+
}
187+
}
188+
}
189+
}()
190+
160191
// Set up UI updater
161192
d.updaterWg.Add(1)
162193
go func() {
@@ -428,11 +459,11 @@ func (d *Downloader) NewRequest(f *IndexedFile) (*grab.Request, error) {
428459
}
429460

430461
// Add automatic checksumming
431-
sum, err := hex.DecodeString(f.SHA1)
462+
sum, err := hex.DecodeString(fmt.Sprintf("%08x", f.CRC32))
432463
if err != nil {
433464
return nil, err
434465
}
435-
req.SetChecksum(sha1.New(), sum, true)
466+
req.SetChecksum(crc32.NewIEEE(), sum, true)
436467
req.Size = f.Size
437468
req = req.WithContext(d.ctx)
438469
req.BufferSize = d.bufferSize
@@ -445,3 +476,9 @@ func (d *Downloader) NewRequest(f *IndexedFile) (*grab.Request, error) {
445476

446477
return req, nil
447478
}
479+
480+
func intToBytes(n int) []byte {
481+
bytes := make([]byte, unsafe.Sizeof(n)) // Assuming int is 4 bytes on your platform
482+
binary.BigEndian.PutUint32(bytes, uint32(n))
483+
return bytes
484+
}

index.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from tqdm import tqdm
33
import os
44
import sys
5-
import hashlib
5+
import zlib
66
import posixpath
77
import sqlite3
88

@@ -23,13 +23,13 @@ def win_path(path):
2323

2424

2525
def hash(file, bufsize=2 ** 16):
26-
h = hashlib.new('sha1')
26+
crc32 = 0
2727
with open(file, 'rb') as f:
2828
buf = f.read(bufsize)
2929
while len(buf) > 0:
30-
h.update(buf)
30+
crc32 = zlib.crc32(buf, crc32)
3131
buf = f.read(bufsize)
32-
return h.hexdigest()
32+
return crc32
3333

3434

3535
def index(path, name, base_url, db_file):
@@ -38,12 +38,12 @@ def index(path, name, base_url, db_file):
3838
os.remove(db_file)
3939

4040
# Create Database
41-
conn = sqlite3.connect(db_file)
41+
conn = sqlite3.connect(":memory:")
4242
cur = conn.cursor()
4343

4444
# Set up database
4545
insert_empty_dir_query = "INSERT INTO empty_dirs (path) VALUES (?)"
46-
insert_file_query = "INSERT INTO files (path, size, sha1) VALUES (?, ?, ?)"
46+
insert_file_query = "INSERT INTO files (path, size, crc32) VALUES (?, ?, ?)"
4747
overview_schema = """
4848
CREATE TABLE overview (
4949
name TEXT PRIMARY KEY,
@@ -56,7 +56,7 @@ def index(path, name, base_url, db_file):
5656
CREATE TABLE files (
5757
path TEXT PRIMARY KEY,
5858
size INTEGER,
59-
sha1 INTEGER,
59+
crc32 INTEGER,
6060
taken INTEGER DEFAULT false,
6161
done INTEGER DEFAULT false
6262
);
@@ -68,7 +68,7 @@ def index(path, name, base_url, db_file):
6868
);
6969
"""
7070
index_statement = """
71-
CREATE INDEX sha_idx ON files (sha1);
71+
CREATE INDEX crc_idx ON files (crc32);
7272
"""
7373
index2_statement = """
7474
CREATE INDEX done_idx ON files (done, taken);
@@ -95,15 +95,27 @@ def index(path, name, base_url, db_file):
9595
size = os.path.getsize(f)
9696
entries.append((x, size, hash(f)))
9797
pbar.update(1)
98-
if len(entries) > 100:
98+
if len(entries) > 5000:
9999
cur.executemany(insert_file_query, entries)
100+
conn.commit()
100101
entries.clear()
102+
if len(entries) > 0:
103+
cur.executemany(insert_file_query, entries)
104+
conn.commit()
105+
entries.clear()
101106
# Get overview info
102107
cur.execute("SELECT SUM(size), COUNT(*) FROM files;")
103108
res = cur.fetchone()
104109
cur.execute("INSERT INTO overview (name, total_size, total_files, base_url) VALUES (?,?,?,?);",
105110
(name, res[0], res[1], base_url))
106111
conn.commit()
112+
113+
print("Saving Database...")
114+
with sqlite3.connect(db_file) as disk_db:
115+
disk_db.executescript("".join(conn.iterdump()))
116+
117+
print("Done!")
118+
107119
return
108120

109121

main.go

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -172,19 +172,22 @@ func setupLayout(w fyne.Window, state *InstallerState) *fyne.Container {
172172

173173
func mainLayout(w fyne.Window, state *InstallerState) *fyne.Container {
174174
pathHeaderLabel := widget.NewLabel("Install Path: ")
175+
pathHeaderLabel.TextStyle = fyne.TextStyle{Bold: true}
175176
pathLabel := widget.NewLabelWithData(state.folderPath)
176177
pathContainer := container.New(layout.NewHBoxLayout(),
177178
pathHeaderLabel,
178179
pathLabel)
179180

180181
sourceHeaderLabel := widget.NewLabel("Source: ")
182+
sourceHeaderLabel.TextStyle = fyne.TextStyle{Bold: true}
181183
sourceLabel := widget.NewLabel(state.baseUrl)
182184
sourceContainer := container.New(layout.NewHBoxLayout(),
183185
sourceHeaderLabel,
184186
sourceLabel)
185187

186188
// Create active file bars
187189
fileLabel := widget.NewLabel("File: ")
190+
fileLabel.TextStyle = fyne.TextStyle{Bold: true}
188191
fileHeader1 := widget.NewLabelWithData(state.fileTitle1)
189192
fileHeader1.Alignment = fyne.TextAlignLeading
190193
fileHeader1.Wrapping = fyne.TextTruncate
@@ -212,7 +215,6 @@ func mainLayout(w fyne.Window, state *InstallerState) *fyne.Container {
212215
progressBarTotal := widget.NewProgressBarWithData(state.progressBarTotal)
213216
totalLabel := canvas.NewText("Total Progress...", color.White)
214217

215-
rateLimitLabel := widget.NewLabel("Download Speed Limit:")
216218
rateLimitCurrentLabel := widget.NewLabelWithData(state.formatRateLimit)
217219
rateLimitEntry := widget.NewEntryWithData(state.rateLimitEntry)
218220
rateLimitSet := widget.NewButton("Set (KB/s)", func() {
@@ -260,11 +262,7 @@ func mainLayout(w fyne.Window, state *InstallerState) *fyne.Container {
260262
}
261263
})
262264

263-
rateLimContainer := container.New(layout.NewVBoxLayout(),
264-
container.New(layout.NewHBoxLayout(),
265-
rateLimitLabel,
266-
rateLimitCurrentLabel),
267-
container.NewBorder(nil, nil, nil, rateLimitSet, rateLimitEntry))
265+
rateLimContainer := container.NewBorder(nil, nil, nil, rateLimitSet, rateLimitEntry)
268266

269267
// Create buttons
270268
button1 := widget.NewButton("Start", func() {
@@ -306,8 +304,7 @@ func mainLayout(w fyne.Window, state *InstallerState) *fyne.Container {
306304
// Create a row with buttons
307305
buttonsRow := container.NewHBox(button1, button2, button3)
308306

309-
// Create 3 additional labels
310-
downloadedHeaderLabel := widget.NewLabel("Downloaded:")
307+
// Create stats labels
311308
downloadedLabel := widget.NewLabelWithData(state.formatDownloadedSize)
312309
downloadedLabel.Alignment = fyne.TextAlignLeading
313310
downloadedLabel.TextStyle = fyne.TextStyle{Monospace: true}
@@ -319,23 +316,30 @@ func mainLayout(w fyne.Window, state *InstallerState) *fyne.Container {
319316
widget.NewLabel("/"),
320317
totalSizeLabel)
321318

322-
speedHeaderLabel := widget.NewLabel("Average Speed:")
323-
speedHeaderLabel.Alignment = fyne.TextAlignLeading
324319
speedLabel := widget.NewLabelWithData(state.formatDownloadSpeed)
325320
speedLabel.Alignment = fyne.TextAlignLeading
326321
speedLabel.TextStyle = fyne.TextStyle{Monospace: true}
327322

323+
statsForm := &widget.Form{
324+
Items: []*widget.FormItem{
325+
{Text: "Downloaded:", Widget: downloadedContainer},
326+
{Text: "Average Speed:", Widget: speedLabel},
327+
{Text: "Download Speed Limit:", Widget: rateLimitCurrentLabel},
328+
},
329+
}
330+
328331
leftMainContent := container.New(layout.NewVBoxLayout(),
329332
pathContainer,
330-
sourceContainer,
331333
totalLabel,
332334
progressBarTotal,
335+
statsForm,
333336
layout.NewSpacer(),
334337
rateLimContainer,
335338
buttonsRow,
336339
)
337340

338341
rightMainContent := container.New(layout.NewVBoxLayout(),
342+
sourceContainer,
339343
fileContainer1,
340344
fileProgressBar1,
341345
fileContainer2,
@@ -347,21 +351,8 @@ func mainLayout(w fyne.Window, state *InstallerState) *fyne.Container {
347351

348352
mainContent := container.NewBorder(nil, nil, leftMainContent, nil, rightMainContent)
349353

350-
line := canvas.NewLine(color.Gray{Y: 0x55})
351-
line.StrokeWidth = 2
352-
353-
leftSideContent := container.New(layout.NewHBoxLayout(),
354-
container.New(layout.NewVBoxLayout(),
355-
layout.NewSpacer(),
356-
downloadedHeaderLabel,
357-
downloadedContainer,
358-
speedHeaderLabel,
359-
speedLabel),
360-
line,
361-
)
362-
363354
// Combine the grid layout and sidebar label in a horizontal box
364-
mainLayout := container.NewBorder(topBarLayout("install"), nil, leftSideContent, nil, mainContent)
355+
mainLayout := container.NewBorder(topBarLayout("install"), nil, nil, nil, mainContent)
365356

366357
return mainLayout
367358
}
@@ -461,7 +452,7 @@ func FormatBytes(size int64) string {
461452

462453
var unitIndex int
463454
floatSize := float64(size)
464-
for unitIndex = 0; unitIndex < len(units); unitIndex++ {
455+
for unitIndex = 0; unitIndex < len(units)-1; unitIndex++ {
465456
if floatSize < 1024.0 {
466457
break
467458
}

repo.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,20 @@ func (repo *SqliteRepo) MarkFileDone(file *IndexedFile) error {
5252
return err
5353
}
5454

55+
func (repo *SqliteRepo) GetNextEmptyDir() (string, error) {
56+
var d string
57+
err := repo.db.QueryRow(`UPDATE empty_dirs SET done = true
58+
WHERE rowid = (
59+
SELECT MIN(rowid)
60+
FROM empty_dirs
61+
WHERE done = false
62+
) RETURNING path`).Scan(&d)
63+
if err != nil {
64+
return "", err
65+
}
66+
return d, err
67+
}
68+
5569
func (repo *SqliteRepo) GetNextFile() (*IndexedFile, error) {
5670
var f IndexedFile
5771
f.RetryCount = 0
@@ -60,15 +74,16 @@ func (repo *SqliteRepo) GetNextFile() (*IndexedFile, error) {
6074
SELECT MIN(rowid)
6175
FROM files
6276
WHERE done = false AND taken = false
63-
) RETURNING path, size, sha1`).Scan(&f.Filepath, &f.Size, &f.SHA1)
77+
) RETURNING path, size, crc32`).Scan(&f.Filepath, &f.Size, &f.CRC32)
6478
if err != nil {
6579
return nil, err
6680
}
6781
return &f, nil
6882
}
6983

84+
// GetNextFileBatch Cannot be safely executed in parallel
7085
func (repo *SqliteRepo) GetNextFileBatch(limit int64) ([]*IndexedFile, error) {
71-
rows, err := repo.db.Query("SELECT path, size, sha1 FROM files WHERE done = false AND taken = false LIMIT ?", limit)
86+
rows, err := repo.db.Query("SELECT path, size, crc32 FROM files WHERE done = false AND taken = false LIMIT ?", limit)
7287
if err != nil {
7388
return nil, err
7489
}
@@ -78,7 +93,7 @@ func (repo *SqliteRepo) GetNextFileBatch(limit int64) ([]*IndexedFile, error) {
7893
for rows.Next() {
7994
var f IndexedFile
8095
f.RetryCount = 0
81-
err = rows.Scan(&f.Filepath, &f.Size, &f.SHA1)
96+
err = rows.Scan(&f.Filepath, &f.Size, &f.CRC32)
8297
if err != nil {
8398
return nil, err
8499
}
@@ -106,6 +121,10 @@ func (repo *SqliteRepo) ResetDownloadState() error {
106121
return err
107122
}
108123
_, err = repo.db.Exec("UPDATE files SET taken = false")
124+
if err != nil {
125+
return err
126+
}
127+
_, err = repo.db.Exec("UPDATE empty_dirs SET done = false")
109128
return err
110129
}
111130

types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ type UiFile struct {
1818
type IndexedFile struct {
1919
Filepath string `json:"path"`
2020
Size int64 `json:"size"`
21-
SHA1 string `json:"sha1"`
21+
CRC32 int `json:"crc32"`
2222
RetryCount int
2323
}
2424

0 commit comments

Comments
 (0)