// Copyright (C) 2021 Storj Labs, Inc. // See LICENSE for copying information. package ulfs import ( "context" "io" "sync" "github.com/zeebo/errs" "storj.io/uplink" ) // // read handles // type uplinkMultiReadHandle struct { project *uplink.Project bucket string key string mu sync.Mutex done bool eof bool off int64 info *ObjectInfo } func newUplinkMultiReadHandle(project *uplink.Project, bucket, key string) *uplinkMultiReadHandle { return &uplinkMultiReadHandle{ project: project, bucket: bucket, key: key, } } func (u *uplinkMultiReadHandle) Close() error { u.mu.Lock() defer u.mu.Unlock() u.done = true return nil } func (u *uplinkMultiReadHandle) SetOffset(offset int64) error { u.mu.Lock() defer u.mu.Unlock() if u.done { return errs.New("already closed") } u.off = offset u.eof = false return nil } func (u *uplinkMultiReadHandle) NextPart(ctx context.Context, length int64) (ReadHandle, error) { opts, err := func() (opts *uplink.DownloadOptions, err error) { u.mu.Lock() defer u.mu.Unlock() if u.done { return nil, errs.New("already closed") } else if u.eof { return nil, io.EOF } else if u.info != nil && u.off >= u.info.ContentLength { return nil, io.EOF } opts = &uplink.DownloadOptions{Offset: u.off, Length: length} if u.off < 0 { opts.Length = -1 u.eof = u.off+length > 0 } u.off += length return opts, nil }() if err != nil { return nil, err } dl, err := u.project.DownloadObject(ctx, u.bucket, u.key, opts) if err != nil { return nil, err } u.mu.Lock() defer u.mu.Unlock() if u.info == nil { info := uplinkObjectToObjectInfo(u.bucket, dl.Info()) u.info = &info } if u.off < 0 { if norm := u.off + u.info.ContentLength; norm > 0 { u.off = norm } } return &uplinkReadHandle{ info: u.info, dl: dl, }, nil } func (u *uplinkMultiReadHandle) Info(ctx context.Context) (*ObjectInfo, error) { u.mu.Lock() if u.info != nil { u.mu.Unlock() return u.info, nil } u.mu.Unlock() // TODO(jeff): maybe we want to dedupe concurrent requests? obj, err := u.project.StatObject(ctx, u.bucket, u.key) if err != nil { return nil, err } u.mu.Lock() defer u.mu.Unlock() if u.info == nil { info := uplinkObjectToObjectInfo(u.bucket, obj) u.info = &info } info := *u.info return &info, nil } // uplinkReadHandle implements readHandle for *uplink.Downloads. type uplinkReadHandle struct { info *ObjectInfo dl *uplink.Download } func (u *uplinkReadHandle) Read(p []byte) (int, error) { return u.dl.Read(p) } func (u *uplinkReadHandle) Close() error { return u.dl.Close() } func (u *uplinkReadHandle) Info() ObjectInfo { return *u.info }