repos / dbin

📦 Poor man's package manager.
git clone https://github.com/xplshn/dbin.git

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}