package torrent
import (
- "github.com/anacrolix/missinggo/pubsub"
+ "strconv"
+ "strings"
+
+ "github.com/anacrolix/chansync/events"
+ "github.com/anacrolix/missinggo/v2/pubsub"
+ "github.com/anacrolix/sync"
"github.com/anacrolix/torrent/metainfo"
)
-// The torrent's infohash. This is fixed and cannot change. It uniquely
-// identifies a torrent.
+// The Torrent's infohash. This is fixed and cannot change. It uniquely identifies a torrent.
func (t *Torrent) InfoHash() metainfo.Hash {
return t.infoHash
}
-// Closed when the info (.Info()) for the torrent has become available. Using
-// features of Torrent that require the info before it is available will have
-// undefined behaviour.
-func (t *Torrent) GotInfo() <-chan struct{} {
- return t.gotMetainfo
+// Returns a channel that is closed when the info (.Info()) for the torrent has become available.
+func (t *Torrent) GotInfo() events.Done {
+ return t.gotMetainfoC
}
// Returns the metainfo info dictionary, or nil if it's not yet available.
-func (t *Torrent) Info() *metainfo.InfoEx {
- return t.info
+func (t *Torrent) Info() (info *metainfo.Info) {
+ t.nameMu.RLock()
+ info = t.info
+ t.nameMu.RUnlock()
+ return
+}
+
+// Returns a Reader bound to the torrent's data. All read calls block until the data requested is
+// actually available. Note that you probably want to ensure the Torrent Info is available first.
+func (t *Torrent) NewReader() Reader {
+ return t.newReader(0, t.length())
}
-// Returns a Reader bound to the torrent's data. All read calls block until
-// the data requested is actually available.
-func (t *Torrent) NewReader() (ret *Reader) {
- ret = &Reader{
- t: t,
- readahead: 5 * 1024 * 1024,
+func (t *Torrent) newReader(offset, length int64) Reader {
+ r := reader{
+ mu: t.cl.locker(),
+ t: t,
+ offset: offset,
+ length: length,
+ }
+ r.readaheadFunc = defaultReadaheadFunc
+ t.addReader(&r)
+ return &r
+}
+
+type PieceStateRuns []PieceStateRun
+
+func (me PieceStateRuns) String() (s string) {
+ if len(me) > 0 {
+ var sb strings.Builder
+ sb.WriteString(me[0].String())
+ for i := 1; i < len(me); i += 1 {
+ sb.WriteByte(' ')
+ sb.WriteString(me[i].String())
+ }
+ return sb.String()
}
- t.addReader(ret)
return
}
-// Returns the state of pieces of the torrent. They are grouped into runs of
-// same state. The sum of the state run lengths is the number of pieces
-// in the torrent.
-func (t *Torrent) PieceStateRuns() []PieceStateRun {
- t.cl.mu.Lock()
- defer t.cl.mu.Unlock()
- return t.pieceStateRuns()
+// Returns the state of pieces of the torrent. They are grouped into runs of same state. The sum of
+// the state run-lengths is the number of pieces in the torrent.
+func (t *Torrent) PieceStateRuns() (runs PieceStateRuns) {
+ t.cl.rLock()
+ runs = t.pieceStateRuns()
+ t.cl.rUnlock()
+ return
}
-func (t *Torrent) PieceState(piece int) PieceState {
- t.cl.mu.Lock()
- defer t.cl.mu.Unlock()
- return t.pieceState(piece)
+func (t *Torrent) PieceState(piece pieceIndex) (ps PieceState) {
+ t.cl.rLock()
+ ps = t.pieceState(piece)
+ t.cl.rUnlock()
+ return
}
// The number of pieces in the torrent. This requires that the info has been
// obtained first.
-func (t *Torrent) NumPieces() int {
+func (t *Torrent) NumPieces() pieceIndex {
return t.numPieces()
}
-// Drop the torrent from the client, and close it.
+// Get missing bytes count for specific piece.
+func (t *Torrent) PieceBytesMissing(piece int) int64 {
+ t.cl.rLock()
+ defer t.cl.rUnlock()
+
+ return int64(t.pieces[piece].bytesLeft())
+}
+
+// Drop the torrent from the client, and close it. It's always safe to do
+// this. No data corruption can, or should occur to either the torrent's data,
+// or connected peers.
func (t *Torrent) Drop() {
- t.cl.mu.Lock()
- t.cl.dropTorrent(t.infoHash)
- t.cl.mu.Unlock()
+ var wg sync.WaitGroup
+ defer wg.Wait()
+ t.cl.lock()
+ defer t.cl.unlock()
+ t.cl.dropTorrent(t.infoHash, &wg)
}
-// Number of bytes of the entire torrent we have completed.
+// Number of bytes of the entire torrent we have completed. This is the sum of
+// completed pieces, and dirtied chunks of incomplete pieces. Do not use this
+// for download rate, as it can go down when pieces are lost or fail checks.
+// Sample Torrent.Stats.DataBytesRead for actual file data download rate.
func (t *Torrent) BytesCompleted() int64 {
- t.cl.mu.RLock()
- defer t.cl.mu.RUnlock()
+ t.cl.rLock()
+ defer t.cl.rUnlock()
return t.bytesCompleted()
}
// The subscription emits as (int) the index of pieces as their state changes.
// A state change is when the PieceState for a piece alters in value.
-func (t *Torrent) SubscribePieceStateChanges() *pubsub.Subscription {
+func (t *Torrent) SubscribePieceStateChanges() *pubsub.Subscription[PieceStateChange] {
return t.pieceStateChanges.Subscribe()
}
// Returns true if the torrent is currently being seeded. This occurs when the
// client is willing to upload without wanting anything in return.
-func (t *Torrent) Seeding() bool {
- t.cl.mu.Lock()
- defer t.cl.mu.Unlock()
- return t.cl.seeding(t)
+func (t *Torrent) Seeding() (ret bool) {
+ t.cl.rLock()
+ ret = t.seeding()
+ t.cl.rUnlock()
+ return
}
-// Clobbers the torrent display name. The display name is used as the torrent
-// name if the metainfo is not available.
+// Clobbers the torrent display name if metainfo is unavailable.
+// The display name is used as the torrent name while the metainfo is unavailable.
func (t *Torrent) SetDisplayName(dn string) {
- t.cl.mu.Lock()
- defer t.cl.mu.Unlock()
- t.setDisplayName(dn)
+ t.nameMu.Lock()
+ if !t.haveInfo() {
+ t.displayName = dn
+ }
+ t.nameMu.Unlock()
}
// The current working name for the torrent. Either the name in the info dict,
// or a display name given such as by the dn value in a magnet link, or "".
func (t *Torrent) Name() string {
- t.cl.mu.Lock()
- defer t.cl.mu.Unlock()
return t.name()
}
+// The completed length of all the torrent data, in all its files. This is
+// derived from the torrent info, when it is available.
func (t *Torrent) Length() int64 {
- select {
- case <-t.GotInfo():
- return t.length
- default:
- return -1
- }
+ return t._length.Value
}
// Returns a run-time generated metainfo for the torrent that includes the
// info bytes and announce-list as currently known to the client.
-func (t *Torrent) Metainfo() *metainfo.MetaInfo {
- t.cl.mu.Lock()
- defer t.cl.mu.Unlock()
- return t.metainfo()
+func (t *Torrent) Metainfo() metainfo.MetaInfo {
+ t.cl.rLock()
+ defer t.cl.rUnlock()
+ return t.newMetaInfo()
}
-func (t *Torrent) addReader(r *Reader) {
- t.cl.mu.Lock()
- defer t.cl.mu.Unlock()
+func (t *Torrent) addReader(r *reader) {
+ t.cl.lock()
+ defer t.cl.unlock()
if t.readers == nil {
- t.readers = make(map[*Reader]struct{})
+ t.readers = make(map[*reader]struct{})
}
t.readers[r] = struct{}{}
- t.readersChanged()
+ r.posChanged()
}
-func (t *Torrent) deleteReader(r *Reader) {
- t.cl.mu.Lock()
- defer t.cl.mu.Unlock()
+func (t *Torrent) deleteReader(r *reader) {
delete(t.readers, r)
t.readersChanged()
}
-func (t *Torrent) DownloadPieces(begin, end int) {
- t.cl.mu.Lock()
- defer t.cl.mu.Unlock()
- t.pendPieceRange(begin, end)
+// Raise the priorities of pieces in the range [begin, end) to at least Normal
+// priority. Piece indexes are not the same as bytes. Requires that the info
+// has been obtained, see Torrent.Info and Torrent.GotInfo.
+func (t *Torrent) DownloadPieces(begin, end pieceIndex) {
+ t.cl.lock()
+ t.downloadPiecesLocked(begin, end)
+ t.cl.unlock()
+}
+
+func (t *Torrent) downloadPiecesLocked(begin, end pieceIndex) {
+ for i := begin; i < end; i++ {
+ if t.pieces[i].priority.Raise(PiecePriorityNormal) {
+ t.updatePiecePriority(i, "Torrent.DownloadPieces")
+ }
+ }
+}
+
+func (t *Torrent) CancelPieces(begin, end pieceIndex) {
+ t.cl.lock()
+ t.cancelPiecesLocked(begin, end, "Torrent.CancelPieces")
+ t.cl.unlock()
}
-func (t *Torrent) CancelPieces(begin, end int) {
- t.cl.mu.Lock()
- defer t.cl.mu.Unlock()
- t.unpendPieceRange(begin, end)
+func (t *Torrent) cancelPiecesLocked(begin, end pieceIndex, reason string) {
+ for i := begin; i < end; i++ {
+ p := &t.pieces[i]
+ if p.priority == PiecePriorityNone {
+ continue
+ }
+ p.priority = PiecePriorityNone
+ t.updatePiecePriority(i, reason)
+ }
+}
+
+func (t *Torrent) initFiles() {
+ var offset int64
+ t.files = new([]*File)
+ for _, fi := range t.info.UpvertedFiles() {
+ *t.files = append(*t.files, &File{
+ t,
+ strings.Join(append([]string{t.info.BestName()}, fi.BestPath()...), "/"),
+ offset,
+ fi.Length,
+ fi,
+ fi.DisplayPath(t.info),
+ PiecePriorityNone,
+ })
+ offset += fi.Length
+ }
+}
+
+// Returns handles to the files in the torrent. This requires that the Info is
+// available first.
+func (t *Torrent) Files() []*File {
+ return *t.files
+}
+
+func (t *Torrent) AddPeers(pp []PeerInfo) (n int) {
+ t.cl.lock()
+ n = t.addPeers(pp)
+ t.cl.unlock()
+ return
+}
+
+// Marks the entire torrent for download. Requires the info first, see
+// GotInfo. Sets piece priorities for historical reasons.
+func (t *Torrent) DownloadAll() {
+ t.DownloadPieces(0, t.numPieces())
+}
+
+func (t *Torrent) String() string {
+ s := t.name()
+ if s == "" {
+ return t.infoHash.HexString()
+ } else {
+ return strconv.Quote(s)
+ }
+}
+
+func (t *Torrent) AddTrackers(announceList [][]string) {
+ t.cl.lock()
+ defer t.cl.unlock()
+ t.addTrackers(announceList)
+}
+
+func (t *Torrent) Piece(i pieceIndex) *Piece {
+ return t.piece(i)
+}
+
+func (t *Torrent) PeerConns() []*PeerConn {
+ t.cl.rLock()
+ defer t.cl.rUnlock()
+ ret := make([]*PeerConn, 0, len(t.conns))
+ for c := range t.conns {
+ ret = append(ret, c)
+ }
+ return ret
}