13b2f5e14d
Removes five unused funcs/vars/fields that golangci-lint had been flagging (anthropic.toolCallDoneEvent, mistral.translateMessages, hook.newError, subprocess.vibeParser.lastAssistantMsgID, tui.cBase), two ineffectual assignments (tui/rendering.go visible-window loop, subprocess stream_test setup), and a stale if/HasPrefix that's now a strings.TrimPrefix. Wires errcheck onto every subprocess / stream lifecycle path so a failed close or shutdown is at least logged rather than silently dropped: - engine/loop.go: stream.Close on both the error and success paths - mcp/manager.go: Shutdown when StartAll partial-fails; Transport close after Initialize failure - mcp/transport.go: stdin.Close + syscall.Kill on graceful-timeout fallback - slm/download.go: Close propagated as a named-return error on the success path; explicitly discarded on the rollback path - slm/classifier.go, slm/manager.go, hook/prompt.go, context/summarize.go, config/write.go, cmd/gnoma/main.go, tool/fs/grep.go: explicit ignores or error logging on Close / Shutdown / WalkDir / Scanln Production-code errcheck and ineffassign are now zero. Remaining golangci-lint output is test-only Close-in-defer noise plus stylistic staticcheck QF suggestions, left alone.
92 lines
2.2 KiB
Go
92 lines
2.2 KiB
Go
package slm
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
)
|
|
|
|
// download fetches url to dst and returns the hex SHA256 and byte count of the download.
|
|
// Partial files are deleted on any failure.
|
|
// progress receives (downloaded, total) bytes; total is -1 if the server doesn't report Content-Length.
|
|
func download(ctx context.Context, url, dst string, progress func(downloaded, total int64)) (sha256hex string, size int64, err error) {
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return "", 0, fmt.Errorf("slm: download: %w", err)
|
|
}
|
|
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return "", 0, fmt.Errorf("slm: download: %w", err)
|
|
}
|
|
defer func() { _ = resp.Body.Close() }()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return "", 0, fmt.Errorf("slm: download: unexpected status %s", resp.Status)
|
|
}
|
|
|
|
f, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0700)
|
|
if err != nil {
|
|
return "", 0, fmt.Errorf("slm: download: create file: %w", err)
|
|
}
|
|
|
|
ok := false
|
|
defer func() {
|
|
closeErr := f.Close()
|
|
if !ok {
|
|
_ = os.Remove(dst)
|
|
return
|
|
}
|
|
// Success path: surface the close error by wiring it into the named return.
|
|
if closeErr != nil && err == nil {
|
|
err = fmt.Errorf("slm: download: close: %w", closeErr)
|
|
}
|
|
}()
|
|
|
|
h := sha256.New()
|
|
pr := &progressReader{r: resp.Body, total: resp.ContentLength, fn: progress}
|
|
n, err := io.Copy(io.MultiWriter(f, h), pr)
|
|
if err != nil {
|
|
return "", 0, fmt.Errorf("slm: download: write: %w", err)
|
|
}
|
|
|
|
ok = true
|
|
return hex.EncodeToString(h.Sum(nil)), n, nil
|
|
}
|
|
|
|
// hashFile computes the SHA256 of the file at path and returns the hex digest and size.
|
|
func hashFile(path string) (sha256hex string, size int64, err error) {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
defer func() { _ = f.Close() }()
|
|
|
|
h := sha256.New()
|
|
n, err := io.Copy(h, f)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
return hex.EncodeToString(h.Sum(nil)), n, nil
|
|
}
|
|
|
|
type progressReader struct {
|
|
r io.Reader
|
|
total int64
|
|
read int64
|
|
fn func(downloaded, total int64)
|
|
}
|
|
|
|
func (p *progressReader) Read(b []byte) (int, error) {
|
|
n, err := p.r.Read(b)
|
|
p.read += int64(n)
|
|
if p.fn != nil {
|
|
p.fn(p.read, p.total)
|
|
}
|
|
return n, err
|
|
}
|