Skip to content

Commit 6b09a76

Browse files
authored
Add download progress bar (#10)
Part of #4
1 parent a1816fd commit 6b09a76

File tree

14 files changed

+275
-146
lines changed

14 files changed

+275
-146
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ go.work.sum
2424

2525
build/*
2626

27+
*.log

Taskfile.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ tasks:
2727

2828
build:
2929
desc: Build binary
30-
cmd: go build -o ./build/hihapu ./cmd/...
30+
cmd: go build -o ./build/hihapu ./cmd/main.go

app/app.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,27 @@ func New(configPath string) (*Application, error) {
5353
app.WithRemote(remote)
5454
app.WithFiles(new(local.Files))
5555

56-
app.logger = slog.Default()
56+
app.logger = initLogger()
5757

5858
return app, nil
5959
}
6060

61+
// todo: rewrite
62+
func initLogger() *slog.Logger {
63+
logFile, err := os.Create("hipapu.log")
64+
if err != nil {
65+
return nil
66+
}
67+
68+
fileHandler := slog.NewTextHandler(logFile, &slog.HandlerOptions{Level: slog.LevelDebug})
69+
70+
logger := slog.New(fileHandler)
71+
72+
logger.Info("Log started")
73+
74+
return logger
75+
}
76+
6177
type cfg interface {
6278
// Add adds installation to the list.
6379
Add(installation config.Installation) error

app/app_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func TestAppWorkflow(t *testing.T) {
8282
}},
8383
}, nil)
8484
mockRemote.
85-
On("DownloadFile", ctx, expectecdDownloadURL, mock.AnythingOfType("*os.File")).
85+
On("DownloadFile", ctx, expectecdDownloadURL, mock.Anything).
8686
Return(nil)
8787

8888
apk.WithRemote(mockRemote)

app/sync.go

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import (
55
"context"
66
"errors"
77
"fmt"
8+
"io"
89
"os"
910
"path/filepath"
1011
"strings"
1112
"time"
1213

1314
"github.com/outcatcher/hipapu/internal/config"
15+
"github.com/schollz/progressbar/v3"
1416
)
1517

1618
// Public sync errors. Self-explanatory.
@@ -19,7 +21,7 @@ var (
1921
ErrMissingAsset = errors.New("no asset with given name found")
2022
)
2123

22-
// Synchronize downloads all new releases replacing local files.
24+
// Synchronize runs synchronization of all new releases replacing local files reporting the progress.
2325
func (a *Application) Synchronize(ctx context.Context) error {
2426
installations := a.config.GetInstallations()
2527

@@ -31,7 +33,10 @@ func (a *Application) Synchronize(ctx context.Context) error {
3133

3234
var errs error
3335

34-
for _, installation := range installations {
36+
for i, installation := range installations {
37+
// todo: output is a CLI interaction, needs to be moved out to cmd somehow
38+
fmt.Printf("Synchronizing installation #%d of %d\n", i+1, len(installations))
39+
3540
// todo: parrallelize
3641
if err := a.syncInstallation(ctx, installation); err != nil {
3742
errs = errors.Join(errs, err)
@@ -44,7 +49,9 @@ func (a *Application) Synchronize(ctx context.Context) error {
4449
}
4550

4651
//nolint:cyclop,funlen // rewriting makes it less readable
47-
func (a *Application) syncInstallation(ctx context.Context, installation config.Installation) error {
52+
func (a *Application) syncInstallation(
53+
ctx context.Context, installation config.Installation,
54+
) error {
4855
file, err := a.files.GetFileInfo(installation.LocalPath)
4956
if err != nil {
5057
return fmt.Errorf("failed to get file info: %w", err)
@@ -53,7 +60,12 @@ func (a *Application) syncInstallation(ctx context.Context, installation config.
5360
urlParts := strings.Split(installation.RepoURL, "/")
5461
owner, repo := urlParts[len(urlParts)-2], urlParts[len(urlParts)-1]
5562

56-
a.log().Info("Starting sync of installation", "owner", owner, "repo", repo, "local path", installation.LocalPath)
63+
a.log().InfoContext(ctx,
64+
"Starting sync of installation",
65+
"owner", owner,
66+
"repo", repo,
67+
"local path", installation.LocalPath,
68+
)
5769

5870
release, err := a.remote.GetLatestRelease(ctx, owner, repo)
5971
if err != nil {
@@ -96,7 +108,20 @@ func (a *Application) syncInstallation(ctx context.Context, installation config.
96108

97109
a.log().Info("Download started", "download URL", downloadURL, "total size, MiB", totalSize/1024/1024) //nolint:mnd
98110

99-
if err := a.remote.DownloadFile(ctx, downloadURL, tmpFile); err != nil {
111+
// todo: progress bar is a CLI interaction, needs to be moved out to cmd somehow
112+
bar := progressbar.DefaultBytes(-1, "Downloading to"+tmpFilePath)
113+
bar.ChangeMax(totalSize)
114+
115+
// cleanup on cancel
116+
go func() {
117+
<-ctx.Done()
118+
119+
_ = tmpFile.Close()
120+
}()
121+
122+
writer := io.MultiWriter(bar, tmpFile)
123+
124+
if err := a.remote.DownloadFile(ctx, downloadURL, writer); err != nil {
100125
return fmt.Errorf("failed to dowload to tmp file: %w", err)
101126
}
102127

cmd/actions.go

Lines changed: 0 additions & 64 deletions
This file was deleted.

cmd/handlers/actions.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright (C) 2025 Anton Kachurin
2+
package handlers
3+
4+
// DefaultCommandName - name of default command.
5+
const DefaultCommandName = commandNameList
6+
7+
// ActionHandlers handle CLI actions.
8+
type ActionHandlers struct {
9+
filePath, repoPath, configPath string
10+
}

cmd/handlers/add.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright (C) 2025 Anton Kachurin
2+
package handlers
3+
4+
import (
5+
"context"
6+
"fmt"
7+
8+
"github.com/outcatcher/hipapu/app"
9+
"github.com/urfave/cli/v3"
10+
)
11+
12+
const commandNameAdd = "add"
13+
14+
// AddCommand handle 'add' subcommand.
15+
func (h *ActionHandlers) AddCommand() *cli.Command {
16+
return &cli.Command{
17+
Name: commandNameAdd,
18+
Usage: "Adds package to the watchlist",
19+
Flags: []cli.Flag{
20+
&cli.StringFlag{
21+
Name: "path",
22+
Usage: "(required) File to watch. Will be created if doesn't exist.",
23+
Required: true,
24+
Destination: &h.filePath,
25+
Aliases: []string{"p"},
26+
TakesFile: true,
27+
OnlyOnce: true,
28+
},
29+
&cli.StringFlag{
30+
Name: "repo",
31+
Usage: "(required) Repo to watch. Must exist.",
32+
Required: true,
33+
Destination: &h.repoPath,
34+
Aliases: []string{"r"},
35+
TakesFile: true,
36+
OnlyOnce: true,
37+
},
38+
},
39+
EnableShellCompletion: true,
40+
Action: h.addHandler,
41+
Suggest: true,
42+
}
43+
}
44+
45+
func (h *ActionHandlers) addHandler(context.Context, *cli.Command) error {
46+
application, err := app.New(h.configPath)
47+
if err != nil {
48+
return fmt.Errorf("failed to start app: %w", err)
49+
}
50+
51+
if err := application.Add(h.repoPath, h.filePath); err != nil {
52+
return fmt.Errorf("error during installation addition: %w", err)
53+
}
54+
55+
println("Added!")
56+
57+
return nil
58+
}

cmd/handlers/config.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package handlers
2+
3+
import (
4+
"os/user"
5+
6+
"github.com/urfave/cli/v3"
7+
)
8+
9+
// ConfigFlag - handle '--config' flag.
10+
func (h *ActionHandlers) ConfigFlag() *cli.StringFlag {
11+
return &cli.StringFlag{
12+
Name: "config",
13+
Usage: "Configuration file path",
14+
Sources: cli.ValueSourceChain{},
15+
Required: false,
16+
Value: defaultConfigPath(),
17+
Destination: &h.configPath,
18+
Aliases: []string{"c"},
19+
TakesFile: true,
20+
OnlyOnce: true,
21+
}
22+
}
23+
24+
func defaultConfigPath() string {
25+
basePath := "."
26+
27+
usr, _ := user.Current()
28+
if usr != nil {
29+
basePath = usr.HomeDir
30+
}
31+
32+
return basePath + "/.config/hipapu/config.json"
33+
}

cmd/handlers/list.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (C) 2025 Anton Kachurin
2+
package handlers
3+
4+
import (
5+
"context"
6+
"fmt"
7+
8+
"github.com/outcatcher/hipapu/app"
9+
"github.com/urfave/cli/v3"
10+
)
11+
12+
const commandNameList = "list"
13+
14+
// ListCommand handle 'list' subcommand.
15+
func (h *ActionHandlers) ListCommand() *cli.Command {
16+
return &cli.Command{
17+
Name: commandNameList,
18+
Usage: "List existing installations",
19+
EnableShellCompletion: true,
20+
Action: h.list,
21+
Suggest: true,
22+
}
23+
}
24+
25+
func (h *ActionHandlers) list(context.Context, *cli.Command) error {
26+
application, err := app.New(h.configPath)
27+
if err != nil {
28+
return fmt.Errorf("failed to start app: %w", err)
29+
}
30+
31+
installations := application.List()
32+
33+
fmt.Println("Installations:")
34+
35+
for i, installation := range installations {
36+
fmt.Printf(" %d) %s <---> %s\n", i+1, installation.RepoURL, installation.LocalPath)
37+
}
38+
39+
return nil
40+
}

0 commit comments

Comments
 (0)