diff --git a/cmd/image/oci.go b/cmd/image/oci.go index 488c6a3..2cd6141 100644 --- a/cmd/image/oci.go +++ b/cmd/image/oci.go @@ -17,6 +17,8 @@ import ( "oras.land/oras-go/v2/registry/remote" "oras.land/oras-go/v2/registry/remote/auth" "oras.land/oras-go/v2/registry/remote/credentials" + + "github.com/cocoonstack/cocoon/utils" ) // pullConns is the number of parallel HTTP Range connections used to fetch a blob; ghcr throttles a @@ -100,17 +102,9 @@ func rangeSupported(ctx context.Context, client *auth.Client, url string) bool { // fetchParallel downloads [0,size) in pullConns chunks concurrently, each writing at its offset. func fetchParallel(ctx context.Context, client *auth.Client, url string, size int64, f *os.File) error { - chunk := (size + pullConns - 1) / pullConns g, gctx := errgroup.WithContext(ctx) - for i := range int64(pullConns) { - start := i * chunk - if start >= size { - break - } - end := start + chunk - 1 - if end >= size { - end = size - 1 - } + for _, r := range utils.SplitRanges(size, pullConns) { + start, end := r[0], r[1] g.Go(func() error { return fetchRange(gctx, client, url, start, end, f) }) } return g.Wait() @@ -127,12 +121,7 @@ func fetchRange(ctx context.Context, client *auth.Client, url string, start, end return err } defer func() { _ = resp.Body.Close() }() - if resp.StatusCode != http.StatusPartialContent { - return fmt.Errorf("range %d-%d: unexpected status %s", start, end, resp.Status) - } - // each chunk owns a disjoint region, so concurrent WriteAt via the offset writer never overlaps - _, err = io.Copy(io.NewOffsetWriter(f, start), resp.Body) - return err + return utils.CopyRangeBody(resp, io.NewOffsetWriter(f, start), start, end) } // fetchSingle streams the blob in one connection (fallback when Range isn't supported). diff --git a/go.mod b/go.mod index c4b4b63..87601fe 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/cocoonstack/cocoon-macos go 1.26.4 require ( - github.com/cocoonstack/cocoon v0.4.5 + github.com/cocoonstack/cocoon v0.4.6-0.20260704025747-d9fa4dfb5105 github.com/docker/go-units v0.5.0 github.com/opencontainers/image-spec v1.1.1 github.com/projecteru2/core v0.0.0-20241016125006-ff909eefe04c diff --git a/go.sum b/go.sum index 679c7af..9557d2d 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZe github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= -github.com/cocoonstack/cocoon v0.4.5 h1:FoLgZhXdfKFvTJcF1ijb6A3C4n22NDOtwT2FDsoJbgM= -github.com/cocoonstack/cocoon v0.4.5/go.mod h1:LOFTBWiRXYsZJtNIF6UctDO0gThKCaG5+NOQ+ahGgv8= +github.com/cocoonstack/cocoon v0.4.6-0.20260704025747-d9fa4dfb5105 h1:kY2QEEK+RiJflgTGRfvRZJvN4X87d36dkrneyOZiAMI= +github.com/cocoonstack/cocoon v0.4.6-0.20260704025747-d9fa4dfb5105/go.mod h1:Rl/SAzj1RbyL8XJaIyWiHdT96yK2D1e0xCr8flVqIcA= github.com/containernetworking/cni v1.3.0 h1:v6EpN8RznAZj9765HhXQrtXgX+ECGebEYEmnuFjskwo= github.com/containernetworking/cni v1.3.0/go.mod h1:Bs8glZjjFfGPHMw6hQu82RUgEPNGEaBb9KS5KtNMnJ4= github.com/containernetworking/plugins v1.9.0 h1:Mg3SXBdRGkdXyFC4lcwr6u2ZB2SDeL6LC3U+QrEANuQ= diff --git a/home/home.go b/home/home.go index 161acb4..3641ea0 100644 --- a/home/home.go +++ b/home/home.go @@ -43,7 +43,7 @@ func VMDir(cmd *cobra.Command, name string) string { // command context alongside it. func OpenStore(cmd *cobra.Command) (context.Context, *cloudimg.CloudImg, error) { ctx := cliutil.CommandContext(cmd) - s, err := cloudimg.New(ctx, Dir(cmd)) + s, err := cloudimg.New(ctx, Dir(cmd), 0) // 0 = cloudimg's default pull connections if err != nil { return ctx, nil, fmt.Errorf("init cloudimg store: %w", err) }