mirror of
https://github.com/samuelncui/acp.git
synced 2026-11-08 04:21:16 +00:00
Replaces the streaming request/result API with caller-owned items and consolidates the content options, following the Job execution performance design. - `Item`, `BatchSource` and `Run` replace `StreamRequest`, `StreamResult`, `StreamSource`, `StreamSink` and `RunStream`. Every accepted item receives exactly one terminal callback from a single goroutine and without doing I/O; an item ACP cannot process reports `Failed`, and a completion carries one outcome per requested target in request order with its error identity preserved. A graceful stop stops feeding, finishes the items in flight and reports the rest, and a hard stop abandons per-item accounting. - One `HashPolicy` replaces `WithHash`, `WithSignatureCache` and `ForceRehash`. A stored hash is read whenever the policy uses the cache, reused only where the policy allows it, and refreshed only when it differs. - A source-only `WithReadMode` selects buffered (the default) or mapped reads, and buffered sources open with `O_NOATIME` where the platform has it. `LinearDevice` keeps request ordering, append targets and abort-on-error but no longer selects the read mode. - `Overwrite` becomes a target-device option, `WithReadBuffer` sets the read buffer, and `SelectFiles` returns the entries the command line copies, replacing `AccurateJob`, `WildcardJob` and the walk options. - Copy consumers share one reference-counted chunk buffer each from a common pool, and the per-item write goroutines finish before the result channel closes, so no finished item can be dropped. The command line tools, the documentation and the test suite move with the API, including semantic tests for the callback contract, the stop and drain paths, the hash policy matrix and the read modes.
92 lines
2.8 KiB
Go
92 lines
2.8 KiB
Go
package acp
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io/fs"
|
|
"time"
|
|
)
|
|
|
|
// Item is one unit of work owned by the caller.
|
|
//
|
|
// The caller implements Item over whatever state it owns, so the item is the handle for
|
|
// its own result and no id lookup or payload plumbing is needed.
|
|
type Item interface {
|
|
// Source returns the exact path to read.
|
|
Source() string
|
|
|
|
// Targets returns the destinations of a copy. An empty slice hashes the source
|
|
// without writing it anywhere.
|
|
Targets() []string
|
|
|
|
// Completed reports the item's terminal outcome. ACP calls it exactly once for
|
|
// every accepted item, from a single goroutine, and never waits for I/O on it.
|
|
Completed(*Result)
|
|
|
|
// Failed reports an item ACP could not process at all. It also reports items that
|
|
// were accepted but abandoned by a graceful stop, with the stopping error.
|
|
Failed(error)
|
|
}
|
|
|
|
// BatchSource supplies items serially. Next returns io.EOF when the input ends, and
|
|
// must be cheap: ACP calls it from the loop that feeds the read buffer.
|
|
type BatchSource interface {
|
|
Next(context.Context) ([]Item, error)
|
|
}
|
|
|
|
// Result reports the content facts of one completed item and one outcome per requested
|
|
// target. An item whose every target failed is still a completed item, which is how
|
|
// "not written" is distinguished from "could not be processed".
|
|
type Result struct {
|
|
Source string
|
|
|
|
Size int64
|
|
Mode fs.FileMode
|
|
ModTime time.Time
|
|
WriteTime time.Time
|
|
|
|
SHA256 []byte
|
|
SignatureCacheHit bool
|
|
|
|
// Targets holds one outcome per requested target, in request order.
|
|
Targets []TargetResult
|
|
}
|
|
|
|
// TargetResult is the outcome of one requested target.
|
|
type TargetResult struct {
|
|
Path string
|
|
Size int64
|
|
WriteTime time.Time
|
|
|
|
// Err is nil when the target was written and verified. Target failures keep their
|
|
// error identity, so errors.Is(err, ErrTargetNoSpace) identifies an exhausted
|
|
// medium.
|
|
Err error
|
|
}
|
|
|
|
// Run copies caller-owned items until the batch source ends or the pipeline fails.
|
|
//
|
|
// A graceful stop (the caller cancels the context) stops feeding items, finishes the
|
|
// items already in flight, reports every accepted item through its terminal callback,
|
|
// and returns the stopping error.
|
|
func Run(ctx context.Context, source BatchSource, opts ...Option) error {
|
|
if source == nil {
|
|
return fmt.Errorf("run failed, batch source is nil")
|
|
}
|
|
|
|
copyer, err := New(ctx, append(opts, withBatchSource(source))...)
|
|
if err != nil {
|
|
return fmt.Errorf("run failed, %w", err)
|
|
}
|
|
if err := copyer.WaitErr(); err != nil {
|
|
return fmt.Errorf("run failed, %w", err)
|
|
}
|
|
|
|
// The pipeline drained, so a cancelled caller learns why it stopped even when the
|
|
// batch source ended its input instead of reporting the cancelation itself.
|
|
if err := ctx.Err(); err != nil {
|
|
return fmt.Errorf("run stopped, %w", err)
|
|
}
|
|
return nil
|
|
}
|