X-Git-Url: http://www.git.stargrave.org/?a=blobdiff_plain;f=t.go;h=45856cc7acbeaa5988074f74435d078a49111a57;hb=HEAD;hp=23e00c002def6a12c97224a4056a99c01bdf11b9;hpb=54b538d3222578d7691f6222b4c5b1b4da93484c;p=btrtrc.git diff --git a/t.go b/t.go index 23e00c00..45856cc7 100644 --- a/t.go +++ b/t.go @@ -1,156 +1,276 @@ 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" ) -// This file contains Torrent, until I decide where the private, lower-case -// "torrent" type belongs. That type is currently mostly in torrent.go. +// The Torrent's infohash. This is fixed and cannot change. It uniquely identifies a torrent. +func (t *Torrent) InfoHash() metainfo.Hash { + return t.infoHash +} -// The public handle to a live torrent within a Client. -type Torrent struct { - cl *Client - torrent *torrent +// 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 } -// The torrent's infohash. This is fixed and cannot change. It uniquely -// identifies a torrent. -func (t Torrent) InfoHash() InfoHash { - return t.torrent.InfoHash +// Returns the metainfo info dictionary, or nil if it's not yet available. +func (t *Torrent) Info() (info *metainfo.Info) { + t.nameMu.RLock() + info = t.info + t.nameMu.RUnlock() + return } -// 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.torrent.gotMetainfo +// 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 the metainfo info dictionary, or nil if it's not yet available. -func (t Torrent) Info() *metainfo.Info { - return t.torrent.Info +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 } -// 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, +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.torrent.stateMu.Lock() - defer t.torrent.stateMu.Unlock() - return t.torrent.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.torrent.stateMu.Lock() - defer t.torrent.stateMu.Unlock() - return t.torrent.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 { - return t.torrent.numPieces() +func (t *Torrent) NumPieces() pieceIndex { + return t.numPieces() +} + +// 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. -func (t Torrent) Drop() { - t.cl.mu.Lock() - t.cl.dropTorrent(t.torrent.InfoHash) - t.cl.mu.Unlock() +// 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() { + var wg sync.WaitGroup + defer wg.Wait() + t.cl.lock() + defer t.cl.unlock() + err := t.cl.dropTorrent(t.infoHash, &wg) + if err != nil { + panic(err) + } } -// Number of bytes of the entire torrent we have completed. -func (t Torrent) BytesCompleted() int64 { - t.cl.mu.RLock() - defer t.cl.mu.RUnlock() - return t.torrent.bytesCompleted() +// 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.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 { - return t.torrent.pieceStateChanges.Subscribe() +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.torrent) +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. -func (t Torrent) SetDisplayName(dn string) { - t.cl.mu.Lock() - defer t.cl.mu.Unlock() - t.torrent.setDisplayName(dn) +// 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.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.torrent.Name() -} - -func (t Torrent) Length() int64 { - select { - case <-t.GotInfo(): - return t.torrent.length - default: - return -1 - } +func (t *Torrent) Name() string { + 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 { + 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.torrent.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.lock() + defer t.cl.unlock() + if t.readers == nil { + t.readers = make(map[*reader]struct{}) + } + t.readers[r] = struct{}{} + r.posChanged() +} + +func (t *Torrent) deleteReader(r *reader) { + delete(t.readers, r) + t.readersChanged() +} + +// 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) 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) addReader(r *Reader) { - t.cl.mu.Lock() - defer t.cl.mu.Unlock() - if t.torrent.readers == nil { - t.torrent.readers = make(map[*Reader]struct{}) +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 } - t.torrent.readers[r] = struct{}{} - t.torrent.readersChanged() } -func (t Torrent) deleteReader(r *Reader) { - t.cl.mu.Lock() - defer t.cl.mu.Unlock() - delete(t.torrent.readers, r) - t.torrent.readersChanged() +// 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) DownloadPieces(begin, end int) { - t.cl.mu.Lock() - defer t.cl.mu.Unlock() - t.torrent.pendPieceRange(begin, end) +func (t *Torrent) AddPeers(pp []PeerInfo) (n int) { + t.cl.lock() + defer t.cl.unlock() + n = t.addPeers(pp) + 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) CancelPieces(begin, end int) { - t.cl.mu.Lock() - defer t.cl.mu.Unlock() - t.torrent.unpendPieceRange(begin, end) +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 }