lib/torrent/storage/originstorage/torrent.go (84 lines of code) (raw):
// Copyright (c) 2016-2019 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package originstorage
import (
"errors"
"fmt"
"github.com/uber/kraken/core"
"github.com/uber/kraken/lib/store"
"github.com/uber/kraken/lib/torrent/storage"
"github.com/uber/kraken/lib/torrent/storage/piecereader"
"github.com/willf/bitset"
"go.uber.org/atomic"
)
// Torrent errors.
var (
ErrReadOnly = errors.New("Read-only torrent is being written to")
)
// Torrent is a read-only storage.Torrent. It allows concurrent reads on all
// pieces.
type Torrent struct {
metaInfo *core.MetaInfo
cas *store.CAStore
numComplete *atomic.Int32
}
// NewTorrent creates a new Torrent.
func NewTorrent(cas *store.CAStore, mi *core.MetaInfo) (*Torrent, error) {
return &Torrent{
cas: cas,
metaInfo: mi,
numComplete: atomic.NewInt32(int32(mi.NumPieces())),
}, nil
}
// Digest returns the digest of the target blob.
func (t *Torrent) Digest() core.Digest {
return t.metaInfo.Digest()
}
// Stat returns the TorrentInfo for t.
func (t *Torrent) Stat() *storage.TorrentInfo {
return storage.NewTorrentInfo(t.metaInfo, t.Bitfield())
}
// InfoHash returns the torrent metainfo hash.
func (t *Torrent) InfoHash() core.InfoHash {
return t.metaInfo.InfoHash()
}
// NumPieces returns the number of pieces in the torrent.
func (t *Torrent) NumPieces() int {
return t.metaInfo.NumPieces()
}
// Length returns the length of the target file.
func (t *Torrent) Length() int64 {
return t.metaInfo.Length()
}
// PieceLength returns the length of piece pi.
func (t *Torrent) PieceLength(pi int) int64 {
return t.metaInfo.GetPieceLength(pi)
}
// MaxPieceLength returns the longest piece length of the torrent.
func (t *Torrent) MaxPieceLength() int64 {
return t.PieceLength(0)
}
// Complete is always true.
func (t *Torrent) Complete() bool {
return true
}
// BytesDownloaded always returns the total number of bytes.
func (t *Torrent) BytesDownloaded() int64 {
return t.metaInfo.Length()
}
// WritePiece returns error, since Torrent is read-only.
func (t *Torrent) WritePiece(src storage.PieceReader, pi int) error {
return ErrReadOnly
}
// Bitfield always returns a completed bitfield.
func (t *Torrent) Bitfield() *bitset.BitSet {
return bitset.New(uint(t.NumPieces())).Complement()
}
func (t *Torrent) String() string {
downloaded := int(float64(t.BytesDownloaded()) / float64(t.metaInfo.Length()) * 100)
return fmt.Sprintf("torrent(hash=%s, downloaded=%d%%)", t.InfoHash().Hex(), downloaded)
}
type opener struct {
torrent *Torrent
}
func (o *opener) Open() (store.FileReader, error) {
return o.torrent.cas.GetCacheFileReader(o.torrent.Digest().Hex())
}
// GetPieceReader returns a reader for piece pi.
func (t *Torrent) GetPieceReader(pi int) (storage.PieceReader, error) {
if pi >= t.NumPieces() {
return nil, fmt.Errorf("invalid piece index %d: num pieces = %d", pi, t.NumPieces())
}
return piecereader.NewFileReader(t.getFileOffset(pi), t.PieceLength(pi), &opener{t}), nil
}
// HasPiece returns if piece pi is complete.
// For Torrent it's always true.
func (t *Torrent) HasPiece(pi int) bool {
return true
}
// MissingPieces always returns empty list.
func (t *Torrent) MissingPieces() []int {
return []int{}
}
// getFileOffset calculates the offset in the torrent file given piece index.
// Assumes pi is a valid piece index.
func (t *Torrent) getFileOffset(pi int) int64 {
return t.metaInfo.PieceLength() * int64(pi)
}