github.com/anacrolix/torrent@v1.61.0/t.go (about) 1 package torrent 2 3 import ( 4 "context" 5 "strconv" 6 "strings" 7 8 "github.com/anacrolix/chansync/events" 9 g "github.com/anacrolix/generics" 10 "github.com/anacrolix/missinggo/v2/pubsub" 11 "github.com/anacrolix/sync" 12 13 "github.com/anacrolix/torrent/metainfo" 14 ) 15 16 // The Torrent's infohash. This is fixed and cannot change. It uniquely 17 // identifies a torrent. TODO: If this doesn't change, should we stick to 18 // referring to a Torrent by the original infohash given to us? 19 func (t *Torrent) InfoHash() metainfo.Hash { 20 return *t.canonicalShortInfohash() 21 } 22 23 // Returns a channel that is closed when the info (.Info()) for the torrent has become available. 24 func (t *Torrent) GotInfo() events.Done { 25 return t.gotMetainfoC 26 } 27 28 // Returns the metainfo info dictionary, or nil if it's not yet available. 29 func (t *Torrent) Info() (info *metainfo.Info) { 30 t.nameMu.RLock() 31 info = t.info 32 t.nameMu.RUnlock() 33 return 34 } 35 36 // Returns a Reader bound to the torrent's data. All read calls block until the data requested is 37 // actually available. Note that you probably want to ensure the Torrent Info is available first. 38 func (t *Torrent) NewReader() Reader { 39 return t.newReader(0, t.length()) 40 } 41 42 func (t *Torrent) newReader(offset, length int64) Reader { 43 r := reader{ 44 mu: t.cl.locker(), 45 t: t, 46 offset: offset, 47 length: length, 48 ctx: context.Background(), 49 } 50 r.readaheadFunc = defaultReadaheadFunc 51 t.addReader(&r) 52 return &r 53 } 54 55 type PieceStateRuns []PieceStateRun 56 57 func (me PieceStateRuns) String() (s string) { 58 if len(me) > 0 { 59 var sb strings.Builder 60 sb.WriteString(me[0].String()) 61 for i := 1; i < len(me); i += 1 { 62 sb.WriteByte(' ') 63 sb.WriteString(me[i].String()) 64 } 65 return sb.String() 66 } 67 return 68 } 69 70 // Returns the state of pieces of the torrent. They are grouped into runs of same state. The sum of 71 // the state run-lengths is the number of pieces in the torrent. 72 func (t *Torrent) PieceStateRuns() (runs PieceStateRuns) { 73 t.cl.rLock() 74 runs = t.pieceStateRuns() 75 t.cl.rUnlock() 76 return 77 } 78 79 func (t *Torrent) PieceState(piece pieceIndex) (ps PieceState) { 80 t.cl.rLock() 81 ps = t.pieceState(piece) 82 t.cl.rUnlock() 83 return 84 } 85 86 // The number of pieces in the torrent. This requires that the info has been 87 // obtained first. 88 func (t *Torrent) NumPieces() pieceIndex { 89 return t.numPieces() 90 } 91 92 // Get missing bytes count for specific piece. 93 func (t *Torrent) PieceBytesMissing(piece int) int64 { 94 t.cl.rLock() 95 defer t.cl.rUnlock() 96 97 return int64(t.pieces[piece].bytesLeft()) 98 } 99 100 // Drop the torrent from the client, and close it. It's always safe to do this. No data corruption 101 // can, or should occur to either the torrent's data, or connected peers. 102 func (t *Torrent) Drop() { 103 if t.closed.IsSet() { 104 return 105 } 106 t.cl.lock() 107 defer t.cl.unlock() 108 if t.closed.IsSet() { 109 return 110 } 111 var wg sync.WaitGroup 112 t.close(&wg) 113 wg.Wait() 114 } 115 116 // Number of bytes of the entire torrent we have completed. This is the sum of 117 // completed pieces, and dirtied chunks of incomplete pieces. Do not use this 118 // for download rate, as it can go down when pieces are lost or fail checks. 119 // Sample Torrent.Stats.DataBytesRead for actual file data download rate. 120 func (t *Torrent) BytesCompleted() int64 { 121 t.cl.rLock() 122 defer t.cl.rUnlock() 123 return t.bytesCompleted() 124 } 125 126 // The subscription emits as (int) the index of pieces as their state changes. 127 // A state change is when the PieceState for a piece alters in value. 128 func (t *Torrent) SubscribePieceStateChanges() *pubsub.Subscription[PieceStateChange] { 129 return t.pieceStateChanges.Subscribe() 130 } 131 132 // Returns true if the torrent is currently being seeded. This occurs when the 133 // client is willing to upload without wanting anything in return. 134 func (t *Torrent) Seeding() (ret bool) { 135 t.cl.rLock() 136 ret = t.seeding() 137 t.cl.rUnlock() 138 return 139 } 140 141 // Clobbers the torrent display name if metainfo is unavailable. 142 // The display name is used as the torrent name while the metainfo is unavailable. 143 func (t *Torrent) SetDisplayName(dn string) { 144 t.nameMu.Lock() 145 if !t.haveInfo() { 146 t.displayName = dn 147 } 148 t.nameMu.Unlock() 149 } 150 151 // The current working name for the torrent. Either the name in the info dict, 152 // or a display name given such as by the dn value in a magnet link, or "". 153 func (t *Torrent) Name() string { 154 return t.name() 155 } 156 157 // The completed length of all the torrent data, in all its files. This is 158 // derived from the torrent info, when it is available. 159 func (t *Torrent) Length() int64 { 160 return t._length.Value 161 } 162 163 // Returns a run-time generated metainfo for the torrent that includes the 164 // info bytes and announce-list as currently known to the client. 165 func (t *Torrent) Metainfo() metainfo.MetaInfo { 166 t.cl.rLock() 167 defer t.cl.rUnlock() 168 return t.newMetaInfo() 169 } 170 171 func (t *Torrent) addReader(r *reader) { 172 t.cl.lock() 173 defer t.cl.unlock() 174 if t.readers == nil { 175 t.readers = make(map[*reader]struct{}) 176 } 177 t.readers[r] = struct{}{} 178 r.posChanged() 179 } 180 181 func (t *Torrent) deleteReader(r *reader) { 182 delete(t.readers, r) 183 t.readersChanged() 184 } 185 186 // Raise the priorities of pieces in the range [begin, end) to at least Normal 187 // priority. Piece indexes are not the same as bytes. Requires that the info 188 // has been obtained, see Torrent.Info and Torrent.GotInfo. 189 func (t *Torrent) DownloadPieces(begin, end pieceIndex) { 190 t.cl.lock() 191 t.downloadPiecesLocked(begin, end) 192 t.cl.unlock() 193 } 194 195 func (t *Torrent) downloadPiecesLocked(begin, end pieceIndex) { 196 for i := begin; i < end; i++ { 197 if t.pieces[i].priority.Raise(PiecePriorityNormal) { 198 t.updatePiecePriority(i, "Torrent.DownloadPieces") 199 } 200 } 201 } 202 203 func (t *Torrent) CancelPieces(begin, end pieceIndex) { 204 t.cl.lock() 205 t.cancelPiecesLocked(begin, end, "Torrent.CancelPieces") 206 t.cl.unlock() 207 } 208 209 func (t *Torrent) cancelPiecesLocked(begin, end pieceIndex, reason updateRequestReason) { 210 for i := begin; i < end; i++ { 211 p := t.piece(i) 212 // Intentionally cancelling only the piece-specific priority here. 213 if p.priority == PiecePriorityNone { 214 continue 215 } 216 p.priority = PiecePriorityNone 217 t.updatePiecePriority(i, reason) 218 } 219 } 220 221 func (t *Torrent) initFiles() { 222 info := t.info 223 var offset int64 224 t.files = new([]*File) 225 for _, fi := range t.info.UpvertedFiles() { 226 *t.files = append(*t.files, &File{ 227 t, 228 strings.Join(append([]string{info.BestName()}, fi.BestPath()...), "/"), 229 offset, 230 fi.Length, 231 fi, 232 fi.DisplayPath(info), 233 PiecePriorityNone, 234 fi.PiecesRoot, 235 }) 236 offset += fi.Length 237 if info.FilesArePieceAligned() { 238 offset = (offset + info.PieceLength - 1) / info.PieceLength * info.PieceLength 239 } 240 } 241 } 242 243 // Returns handles to the files in the torrent. This requires that the Info is 244 // available first. 245 func (t *Torrent) Files() []*File { 246 return *t.files 247 } 248 249 func (t *Torrent) AddPeers(pp []PeerInfo) (n int) { 250 t.cl.lock() 251 defer t.cl.unlock() 252 n = t.addPeers(pp) 253 return 254 } 255 256 // Marks the entire torrent for download. Requires the info first, see 257 // GotInfo. Sets piece priorities for historical reasons. 258 func (t *Torrent) DownloadAll() { 259 t.DownloadPieces(0, t.numPieces()) 260 } 261 262 func (t *Torrent) String() string { 263 s := t.name() 264 if s == "" { 265 return t.canonicalShortInfohash().HexString() 266 } else { 267 return strconv.Quote(s) 268 } 269 } 270 271 func (t *Torrent) AddTrackers(announceList [][]string) { 272 t.cl.lock() 273 defer t.cl.unlock() 274 t.addTrackers(announceList) 275 } 276 277 func (t *Torrent) ModifyTrackers(announceList [][]string) { 278 t.cl.lock() 279 defer t.cl.unlock() 280 t.modifyTrackers(announceList) 281 } 282 283 func (t *Torrent) Piece(i pieceIndex) *Piece { 284 return t.piece(i) 285 } 286 287 func (t *Torrent) PeerConns() []*PeerConn { 288 t.cl.rLock() 289 defer t.cl.rUnlock() 290 ret := make([]*PeerConn, 0, len(t.conns)) 291 for c := range t.conns { 292 ret = append(ret, c) 293 } 294 return ret 295 } 296 297 // TODO: Misleading method name. Webseed peers are not PeerConns. 298 func (t *Torrent) WebseedPeerConns() []*Peer { 299 t.cl.rLock() 300 defer t.cl.rUnlock() 301 ret := make([]*Peer, 0, len(t.conns)) 302 for _, c := range t.webSeeds { 303 ret = append(ret, &c.peer) 304 } 305 return ret 306 } 307 308 // Was dropped from the Client. 309 func (t *Torrent) isDropped() bool { 310 return !g.MapContains(t.cl.torrents, t) 311 }