// Package containerimageregistryclient is the container-image-registry seat's client (novox/hq ADR 0627 §1, §2; ADR 0611, design 51 // *Publishing*): it publishes a file in parts to the seat's holder. `publish` opens the transfer naming every part, // the holder answers the parts it lacks, and only those travel by `publish-part`, in chunks a bus message carries; a // cut transfer goes on from what the holder answers when the same transfer is opened again. The sending in parts is // mesh-sdk's seat-neutral runtime (parts); this client binds it to this seat's verbs and types alone. The call is the // caller's own (a bundle's stdio.Ask, a process's call of a seat verb), so it holds no transport. package containerimageregistryclient import ( "context" "encoding/base64" "encoding/json" "fmt" seat "git.novox.be/novox/mesh-contracts/go/seats/containerimageregistry" "git.novox.be/novox/mesh-sdk/go/parts" ) // Call asks one verb of the seat, `seat:.`, with a body, and answers its JSON. type Call func(key string, body any) (json.RawMessage, error) // Sent is what a Publish did: the transfer, whether the holder held its digest already, the parts it lacked and were // sent, and the chunks they took. type Sent struct { Transfer string parts.Sent } // Publish opens (or opens again) the transfer in on the seat and sends the parts the holder answers it lacks, each in // chunks of at most parts.MaxChunk, in sequence, the last marked. in.Parts is written from parts. A part whose bytes // do not hash to its digest is refused before anything of it is sent; a part the holder does not name is never read. // Its outcome, published or refused, is the holder's event, which the caller waits for. func Publish(ctx context.Context, call Call, in seat.PublishInput, ps []parts.Part) (Sent, error) { open := func(_ context.Context, ps []parts.Part) (parts.Opened, error) { in.Parts = make([]seat.Part, 0, len(ps)) for _, p := range ps { in.Parts = append(in.Parts, seat.Part{Digest: p.Digest, Size: p.Size}) } raw, err := call("seat:"+seat.Seat+"."+seat.VerbPublish, in) if err != nil { return parts.Opened{}, fmt.Errorf("%s's publish of %s: %w", seat.Seat, in.Name, err) } var a seat.PublishAnswer if err := json.Unmarshal(raw, &a); err != nil { return parts.Opened{}, fmt.Errorf("%s's answer to publish: %w", seat.Seat, err) } return parts.Opened{Held: a.Held, Lacks: a.Lacks}, nil } chunk := func(_ context.Context, part string, sequence int, last bool, data []byte) error { raw, err := call("seat:"+seat.Seat+"."+seat.VerbPublishPart, seat.PublishPartInput{Transfer: in.Transfer, Part: part, Sequence: int64(sequence), Last: last, Data: base64.StdEncoding.EncodeToString(data)}) if err != nil { return fmt.Errorf("%s's publish-part: %w", seat.Seat, err) } var a seat.PublishPartAnswer if err := json.Unmarshal(raw, &a); err != nil { return fmt.Errorf("%s's answer to publish-part: %w", seat.Seat, err) } return nil } sent, err := parts.Send(ctx, open, chunk, ps) return Sent{Transfer: in.Transfer, Sent: sent}, err }