add spooldb client and look up spool location alongside weight

This commit is contained in:
Ian Gulliver
2026-05-31 13:50:37 -07:00
parent d3e9cc8e54
commit 2b4d2134b4
5 changed files with 378 additions and 2 deletions
+77 -2
View File
@@ -2,6 +2,7 @@ package main
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"flag"
@@ -22,12 +23,14 @@ import (
"github.com/makiuchi-d/gozxing"
"github.com/makiuchi-d/gozxing/qrcode"
xdraw "golang.org/x/image/draw"
"spoolweight/spooldb"
)
type result struct {
Image string `json:"image"`
SpoolID string `json:"spool_id,omitempty"`
URL string `json:"url,omitempty"`
Location string `json:"location,omitempty"`
Weight *float64 `json:"weight,omitempty"`
Unit string `json:"unit,omitempty"`
Confidence *float64 `json:"confidence,omitempty"`
@@ -61,9 +64,12 @@ func main() {
log.Printf("auth: %s", auth.name)
}
locator := &spoolLocator{}
defer locator.close()
results := make([]result, 0, flag.NArg())
for _, path := range flag.Args() {
results = append(results, processImage(path, auth))
results = append(results, processImage(path, auth, locator))
}
enc := json.NewEncoder(os.Stdout)
@@ -71,9 +77,66 @@ func main() {
enc.Encode(results)
}
// spoolLocator lazily logs into spooldb on first use, so its (slow) browser
// login overlaps with the first image's weight read rather than blocking it.
// Location lookup is best-effort: any failure is logged and the location is
// simply omitted from the output.
type spoolLocator struct {
once sync.Once
client *spooldb.Client
err error
}
func (s *spoolLocator) login() (*spooldb.Client, error) {
s.once.Do(func() {
user, pass := os.Getenv("SPOOLDB_USER"), os.Getenv("SPOOLDB_PASS")
if user == "" || pass == "" {
s.err = fmt.Errorf("SPOOLDB_USER/SPOOLDB_PASS not set")
return
}
c, err := spooldb.New()
if err != nil {
s.err = err
return
}
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
defer cancel()
if err := c.Login(ctx, user, pass); err != nil {
c.Close()
s.err = fmt.Errorf("login: %w", err)
return
}
s.client = c
})
return s.client, s.err
}
// location returns the spool's storage location, or "" if it can't be looked up.
func (s *spoolLocator) location(spoolID string) string {
client, err := s.login()
if err != nil {
log.Printf("spooldb: skipping location lookup: %v", err)
return ""
}
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
loc, err := client.SpoolLocation(ctx, spoolID)
if err != nil {
log.Printf("spooldb: location lookup for %s: %v", spoolID, err)
return ""
}
return loc
}
func (s *spoolLocator) close() {
if s.client != nil {
s.client.Close()
}
}
// processImage reads one photo, capturing any failure in the result's Error
// field so a single bad image doesn't abort the whole batch.
func processImage(path string, auth authInfo) result {
func processImage(path string, auth authInfo, locator *spoolLocator) result {
r := result{Image: path}
img, err := loadImage(path)
@@ -94,7 +157,19 @@ func processImage(path string, auth authInfo) result {
r.SpoolID = spoolID(url)
r.URL = url
// Look up the spool's location concurrently with the (slower) weight read.
var loc string
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
loc = locator.location(r.SpoolID)
}()
w, err := readWeight(img, auth)
wg.Wait()
r.Location = loc
if err != nil {
r.Error = fmt.Sprintf("read weight: %v", err)
return r