xplshn
·
2025-08-13
update.go
Go
1package main
2
3import (
4 "context"
5 "fmt"
6 "path/filepath"
7 "strings"
8 "sync"
9 "sync/atomic"
10
11 "github.com/urfave/cli/v3"
12 "github.com/zeebo/errs"
13)
14
15var (
16 errUpdateFailed = errs.Class("update failed")
17)
18
19func updateCommand() *cli.Command {
20 return &cli.Command{
21 Name: "update",
22 Usage: "Update binaries, by checking their b3sum[:256] against the repo's",
23 Action: func(_ context.Context, c *cli.Command) error {
24 config, err := loadConfig()
25 if err != nil {
26 return errUpdateFailed.Wrap(err)
27 }
28 uRepoIndex, err := fetchRepoIndex(config)
29 if err != nil {
30 return errUpdateFailed.Wrap(err)
31 }
32 return update(config, arrStringToArrBinaryEntry(c.Args().Slice()), uRepoIndex)
33 },
34 }
35}
36
37func update(config *config, programsToUpdate []binaryEntry, uRepoIndex []binaryEntry) error {
38 var (
39 skipped, updated, errors uint32
40 checked uint32
41 errorMessages string
42 errorMessagesMutex sync.Mutex
43 padding = " "
44 )
45
46 programsToUpdate, err := validateProgramsFrom(config, programsToUpdate, uRepoIndex)
47 if err != nil {
48 return errUpdateFailed.Wrap(err)
49 }
50
51 toBeChecked := uint32(len(programsToUpdate))
52
53 var progressMutex sync.Mutex
54 var wg sync.WaitGroup
55
56 var outdatedPrograms []binaryEntry
57
58 installDir := config.InstallDir
59 for _, program := range programsToUpdate {
60 wg.Add(1)
61
62 go func(program binaryEntry) {
63 defer wg.Done()
64
65 installPath := filepath.Join(installDir, filepath.Base(program.Name))
66 trackedBEntry, err := readEmbeddedBEntry(installPath)
67 if err != nil {
68 return
69 }
70
71 if !fileExists(installPath) {
72 progressMutex.Lock()
73 atomic.AddUint32(&checked, 1)
74 atomic.AddUint32(&skipped, 1)
75 if verbosityLevel >= normalVerbosity {
76 truncatePrintf(false, "\033[2K\r<%d/%d> %s | Warning: Tried to update a non-existent program %s. Skipping.", atomic.LoadUint32(&checked), toBeChecked, padding, parseBinaryEntry(trackedBEntry, false))
77 }
78 progressMutex.Unlock()
79 return
80 }
81
82 localB3sum, err := calculateChecksum(installPath)
83 if err != nil {
84 progressMutex.Lock()
85 atomic.AddUint32(&checked, 1)
86 atomic.AddUint32(&skipped, 1)
87 if verbosityLevel >= normalVerbosity {
88 truncatePrintf(false, "\033[2K\r<%d/%d> %s | Warning: Failed to get B3sum for %s. Skipping.", atomic.LoadUint32(&checked), toBeChecked, padding, parseBinaryEntry(trackedBEntry, false))
89 }
90 progressMutex.Unlock()
91 return
92 }
93
94 binInfo, err := getBinaryInfo(config, program, uRepoIndex)
95 if err != nil {
96 progressMutex.Lock()
97 atomic.AddUint32(&checked, 1)
98 atomic.AddUint32(&skipped, 1)
99 if verbosityLevel >= normalVerbosity {
100 truncatePrintf(false, "\033[2K\r<%d/%d> %s | Warning: Failed to get metadata for %s. Skipping.", atomic.LoadUint32(&checked), toBeChecked, padding, parseBinaryEntry(trackedBEntry, false))
101 }
102 progressMutex.Unlock()
103 return
104 }
105
106 if binInfo.Bsum == "" {
107 progressMutex.Lock()
108 atomic.AddUint32(&checked, 1)
109 atomic.AddUint32(&skipped, 1)
110 if verbosityLevel >= normalVerbosity {
111 truncatePrintf(false, "\033[2K\r<%d/%d> %s | Skipping %s because the B3sum field is null.", atomic.LoadUint32(&checked), toBeChecked, padding, parseBinaryEntry(trackedBEntry, false))
112 }
113 progressMutex.Unlock()
114 return
115 }
116
117 if localB3sum != binInfo.Bsum {
118 progressMutex.Lock()
119 atomic.AddUint32(&checked, 1)
120 atomic.AddUint32(&updated, 1)
121 if verbosityLevel >= normalVerbosity {
122 truncatePrintf(false, "\033[2K\r<%d/%d> %s | %s is outdated and will be updated.", atomic.LoadUint32(&checked), toBeChecked, padding, parseBinaryEntry(trackedBEntry, false))
123 }
124 errorMessagesMutex.Lock()
125 outdatedPrograms = append(outdatedPrograms, program)
126 errorMessagesMutex.Unlock()
127 progressMutex.Unlock()
128 } else {
129 progressMutex.Lock()
130 atomic.AddUint32(&checked, 1)
131 if verbosityLevel >= normalVerbosity {
132 truncatePrintf(false, "\033[2K\r<%d/%d> %s | No updates available for %s.", atomic.LoadUint32(&checked), toBeChecked, padding, parseBinaryEntry(trackedBEntry, false))
133 }
134 progressMutex.Unlock()
135 }
136 }(program)
137 }
138
139 wg.Wait()
140
141 if len(outdatedPrograms) > 0 {
142 fmt.Print("\033[2K\r")
143 err := installBinaries(context.Background(), config, outdatedPrograms, uRepoIndex)
144 if err != nil {
145 atomic.AddUint32(&errors, 1)
146 }
147
148 var stillOutdated []string
149 for _, program := range outdatedPrograms {
150 installPath := filepath.Join(config.InstallDir, filepath.Base(program.Name))
151 localB3sum, sumErr := calculateChecksum(installPath)
152 repoBEntry, infoErr := getBinaryInfo(config, program, uRepoIndex)
153 if sumErr != nil || infoErr != nil || localB3sum != repoBEntry.Bsum {
154 stillOutdated = append(stillOutdated, program.Name)
155 }
156 }
157
158 if len(stillOutdated) > 0 && verbosityLevel >= silentVerbosityWithErrors {
159 fmt.Printf("Failed to update programs: %s\n", strings.Join(stillOutdated, ", "))
160 }
161 }
162
163 finalCounts := fmt.Sprintf("\033[2K\rSkipped: %d\tUpdated: %d\tChecked: %d", atomic.LoadUint32(&skipped), atomic.LoadUint32(&updated), uint32(int(atomic.LoadUint32(&checked))))
164 if errors > 0 && verbosityLevel >= silentVerbosityWithErrors {
165 finalCounts += fmt.Sprintf("\tErrors: %d", atomic.LoadUint32(&errors))
166 }
167
168 if verbosityLevel >= normalVerbosity || (errors > 0 && verbosityLevel >= silentVerbosityWithErrors) {
169 fmt.Print(finalCounts)
170 for _, errorMsg := range strings.Split(errorMessages, "\n") {
171 fmt.Println(strings.TrimSpace(errorMsg))
172 }
173 }
174
175 return nil
176}