class Bt: def __init__(self, config, eventSched, httpRequester, ownAddrFunc, peerId, persister, pInMeasure, pOutMeasure, peerPool, connBuilder, connListener, connHandler, choker, torrent, torrentIdent, torrentDataPath, version): ##global stuff self.config = config self.version = version self.peerPool = peerPool self.connBuilder = connBuilder self.connListener = connListener self.connHandler = connHandler self.choker = choker ##own stuff self.log = Logger('Bt', '%-6s - ', torrentIdent) self.torrent = torrent self.torrentIdent = torrentIdent self.log.debug("Creating object persister") self.btPersister = BtObjectPersister(persister, torrentIdent) self.log.debug("Creating measure classes") self.inRate = Measure(eventSched, 60, [pInMeasure]) self.outRate = Measure(eventSched, 60, [pOutMeasure]) self.inRate.stop() self.outRate.stop() self.log.debug("Creating storage class") self.storage = Storage(self.config, self.btPersister, torrentIdent, self.torrent, torrentDataPath) self.log.debug("Creating global status class") self.pieceStatus = PieceStatus(self.torrent.getTotalAmountOfPieces()) self.log.debug("Creating file priority class") self.filePrio = FilePriority(self.btPersister, self.version, self.pieceStatus, self.storage.getStatus(), self.torrent, torrentIdent) self.log.debug("Creating requester class") self.requester = Requester(self.config, self.torrentIdent, self.pieceStatus, self.storage, self.torrent) self.log.debug("Creating tracker requester class") self.trackerRequester = TrackerRequester(self.config, self.btPersister, eventSched, peerId, self.peerPool, ownAddrFunc, httpRequester, self.inRate, self.outRate, self.storage, self.torrent, self.torrentIdent, self.version) self.log.debug("Creating superseeding handler class") self.superSeedingHandler = SuperSeedingHandler(self.torrentIdent, self.btPersister, self.storage.getStatus(), self.pieceStatus) ##callbacks self.log.debug("Adding callbacks") self._addCallbacks() ##status self.state = 'stopped' self.started = False self.paused = True ##lock self.lock = threading.Lock() ##internal functions - callbacks def _addCallbacks(self): ownStatus = self.storage.getStatus() self.persistentStatusCallback = self.config.addCallback((('storage', 'persistPieceStatus'),), ownStatus.enablePersisting) def _removeCallbacks(self): self.config.removeCallback(self.persistentStatusCallback) ##internal functions - start/pause/stop - common def _halt(self, targetState): if self.paused and targetState in ('shutdown', 'remove'): #stopping and already paused, only need to stop the tracker requester and the callbacks self.log.debug("Removing callbacks") self._removeCallbacks() self.log.debug("Stopping tracker requester") self.trackerRequester.stop() else: #either stopping, removing or shutdown and still running or loading self.log.debug("Aborting storage loading just in case") self.storage.abortLoad() if self.started: #were already running self.started = False if targetState == 'stop': self.log.debug("Pausing tracker requester") self.trackerRequester.pause() else: self.log.debug("Removing callbacks") self._removeCallbacks() self.log.debug("Stopping tracker requester") self.trackerRequester.stop() self.log.debug("Removing us from choker") self.choker.removeTorrent(self.torrentIdent) self.log.debug("Removing us from connection builder") self.connBuilder.removeTorrent(self.torrentIdent) self.log.debug("Removing us from connection listener") self.connListener.removeTorrent(self.torrent.getTorrentHash()) self.log.debug("Removing us from connection handler") self.connHandler.removeTorrent(self.torrentIdent) self.log.debug("Stopping transfer measurement") self.inRate.stop() self.outRate.stop() #shutdown/removal specific tasks which need to be done regardless of current status if targetState in ('shutdown', 'remove'): self.log.debug("Removing all infos related to us from connection pool") self.peerPool.clear(self.torrentIdent) if targetState == 'remove': self.log.debug('Removing all persisted objects of this torrent') self.btPersister.removeAll() ##internal functions - start/pause/stop - specific def _start(self, loadSuccess): try: if loadSuccess: #loading was successful, add to handlers self.log.debug("Reseting requester") self.requester.reset() self.log.debug("Starting transfer measurement") self.inRate.start() self.outRate.start() self.log.debug("Adding us to connection handler") self.connHandler.addTorrent(self.torrentIdent, self.torrent, self.pieceStatus, self.inRate, self.outRate, self.storage, self.filePrio, self.requester, self.superSeedingHandler) self.log.debug("Adding us to connection listener") self.connListener.addTorrent(self.torrentIdent, self.torrent.getTorrentHash()) self.log.debug("Adding us to connection builder") self.connBuilder.addTorrent(self.torrentIdent, self.torrent.getTorrentHash()) self.log.debug("Adding us to choker") self.choker.addTorrent(self.torrentIdent, self.storage.getStatus(), self.superSeedingHandler) self.log.debug("Starting tracker requester") self.trackerRequester.start() self.started = True self.state = 'running' except: #something failed - hard self.log.error("Error in load function:\n%s", logTraceback()) ##external functions - state def start(self): #called when torrent is started self.lock.acquire() if self.paused: self.paused = False if self.storage.isLoaded(): self.log.debug("Storage already loaded, skipping hashing") self._start(True) else: self.storage.load(self._start) self.state = 'loading' self.lock.release() def stop(self): #called when torrent is stopped self.lock.acquire() if not self.paused: self._halt('stop') self.paused = True self.state = 'stopped' self.lock.release() def shutdown(self): #called on shutdown self.lock.acquire() self._halt('shutdown') self.paused = False self.state = 'stopped' self.lock.release() def remove(self): #called when torrent is removed self.lock.acquire() self._halt('remove') self.paused = False self.state = 'stopped' self.lock.release() ##external functions - stats def getStats(self, wantedStats): self.lock.acquire() stats = {} if wantedStats.get('state', False): stats['state'] = self.state #connections if wantedStats.get('connections', False): stats.update(self.connHandler.getStats(self.torrentIdent, connDetails=True)) #files if wantedStats.get('files', False): stats['files'] = self.filePrio.getStats() #peers if wantedStats.get('peers', False) or wantedStats.get('connectionAverages', False): #get peer stats connAverages = wantedStats.get('connectionAverages', False) stats.update(self.peerPool.getStats(self.torrentIdent)) stats.update(self.connHandler.getStats(self.torrentIdent, connSummary=True, connAverages=connAverages)) stats.update(self.trackerRequester.getStats(trackerSummary=True)) #normalise peer stats if stats['connectedLeeches'] > stats['knownLeeches']: stats['knownLeeches'] = stats['connectedLeeches'] if stats['connectedSeeds'] > stats['knownSeeds']: stats['knownSeeds'] = stats['connectedSeeds'] if stats['knownLeeches'] + stats['knownSeeds'] > stats['knownPeers']: stats['knownPeers'] = stats['knownLeeches'] + stats['knownSeeds'] elif stats['knownLeeches'] + stats['knownSeeds'] < stats['knownPeers']: stats['knownLeeches'] += stats['knownPeers'] - stats['knownSeeds'] #generate additional conn stats if necessary if connAverages: if stats['knownSeeds'] == 0: stats['knownLeechesPerSeed'] = 0 else: stats['knownLeechesPerSeed'] = (stats['knownLeeches'] * 1.0) / stats['knownSeeds'] #pieces if wantedStats.get('pieceAverages', False): stats.update(self.pieceStatus.getStats(pieceAverages=True)) #progress stats if wantedStats.get('progress', False): stats.update(self.storage.getStats()) #requests if wantedStats.get('requests', False) or wantedStats.get('pieceAverages', False): reqDetails = wantedStats.get('requests', False) pieceAverages = wantedStats.get('pieceAverages', False) stats.update(self.connHandler.getRequesterStats(self.torrentIdent, requestDetails=reqDetails, pieceAverages=pieceAverages)) #tracker if wantedStats.get('tracker', False): stats.update(self.trackerRequester.getStats(trackerDetails=True)) if wantedStats.get('trackerStatus', False): stats.update(self.trackerRequester.getStats(trackerStatus=True)) #transfer stats if wantedStats.get('transfer', False): stats['inRawBytes'] = self.inRate.getTotalTransferedBytes() stats['outRawBytes'] = self.outRate.getTotalTransferedBytes() stats['inPayloadBytes'] = self.inRate.getTotalTransferedPayloadBytes() stats['outPayloadBytes'] = self.outRate.getTotalTransferedPayloadBytes() stats['inRawSpeed'] = self.inRate.getCurrentRate() stats['outRawSpeed'] = self.outRate.getCurrentRate() stats['protocolOverhead'] = (100.0 * (stats['inRawBytes'] + stats['outRawBytes'] - stats['inPayloadBytes'] - stats['outPayloadBytes'])) / max(stats['inPayloadBytes'] + stats['outPayloadBytes'], 1.0) if wantedStats.get('transferAverages', False): stats['avgInRawSpeed'] = self.inRate.getAverageRate() * 1024 stats['avgOutRawSpeed'] = self.outRate.getAverageRate() * 1024 stats['avgInPayloadSpeed'] = self.inRate.getAveragePayloadRate() * 1024 stats['avgOutPayloadSpeed'] = self.outRate.getAveragePayloadRate() * 1024 #torrent stats if wantedStats.get('torrent', False): stats.update(self.torrent.getStats()) stats['superSeeding'] = self.superSeedingHandler.isEnabled() self.lock.release() return stats ##external funcs - actions def setFilePriority(self, fileIds, priority): self.lock.acquire() for fileId in fileIds: self.filePrio.setFilePriority(fileId, priority) self.lock.release() def setFileWantedFlag(self, fileIds, wanted): self.lock.acquire() if self.started: #already running, need to go through the connection handler because of syncing issues self.connHandler.setFileWantedFlag(self.torrentIdent, fileIds, wanted) else: #not running for fileId in fileIds: self.filePrio.setFileWantedFlag(fileId, wanted) self.lock.release() def setSuperSeeding(self, enabled): self.lock.acquire() if not enabled == self.superSeedingHandler.isEnabled(): if self.started: self.connHandler.setSuperSeeding(self.torrentIdent, enabled) else: self.superSeedingHandler.setEnabled(enabled) self.lock.release() ##external funcs - tracker actions def getTrackerInfo(self): self.lock.acquire() trackerInfo = self.trackerRequester.getTrackerInfo() self.lock.release() return trackerInfo def setTrackerInfo(self, newTrackerInfo): self.lock.acquire() self.trackerRequester.setTrackerInfo(newTrackerInfo) self.lock.release() ##external funcs - other def getInfohash(self): self.lock.acquire() infohash = self.torrent.getTorrentHash() self.lock.release() return infohash
class Connection: def __init__( self, connStatus, scheduler, conn, direction, remotePeerAddr, inMeasure, outMeasure, inMeasureParent, outMeasureParent, outLimiter, inLimiter, msgLenFunc, msgDecodeFunc, msgLengthFieldLen, maxMsgLength, keepaliveMsgFunc, log, ): self.sched = scheduler # connection self.conn = conn self.connIdent = self.conn.fileno() self.connectTime = time() self.direction = direction self.inMsgCount = 0 self.closed = False # peer self.remotePeerAddr = remotePeerAddr # conn status self.connStatus = connStatus self.connStatus.addConn(self.connIdent) # limiter self.outLimiter = outLimiter self.outLimiter.addUser(self.connIdent, callback=self.connStatus.allowedToSend, callbackArgs=[self.connIdent]) self.inLimiter = inLimiter self.inLimiter.addUser(self.connIdent, callback=self.connStatus.allowedToRecv, callbackArgs=[self.connIdent]) # rate if inMeasure is None: self.inRate = Measure(self.sched, 60, [inMeasureParent]) else: self.inRate = inMeasure self.inRate.start() if outMeasure is None: self.outRate = Measure(self.sched, 60, [outMeasureParent]) else: self.outRate = outMeasure self.outRate.start() # data buffer self.inBuffer = [] self.outBufferQueue = deque() self.outBufferMessages = {} self.outBufferMessageId = 0 # messages self.msgLenFunc = msgLenFunc self.msgDecodeFunc = msgDecodeFunc self.msgLengthFieldLen = msgLengthFieldLen self.maxMsgLength = maxMsgLength # log self.log = log # lock self.lock = threading.RLock() # events self.sendTimeoutEvent = self.sched.scheduleEvent(self.timeout, timedelta=300, funcArgs=["send timed out"]) self.recvTimeoutEvent = self.sched.scheduleEvent(self.timeout, timedelta=300, funcArgs=["read timed out"]) self.keepaliveEvent = self.sched.scheduleEvent( self.send, timedelta=100, funcArgs=[keepaliveMsgFunc()], repeatdelta=100 ) ##internal functions - socket def _recv(self): # really recv data - or at least try to msgs = [] self.sched.rescheduleEvent(self.recvTimeoutEvent, timedelta=300) wantedBytes = self.conn.getUsedInBufferSpace() allowedBytes = self.inLimiter.claimUnits(self.connIdent, wantedBytes) if not allowedBytes == 0: # may receive something, recv data data = self.conn.recv(allowedBytes) self.inRate.updateRate(len(data)) self.inBuffer.append(data) data = "".join(self.inBuffer) # process data msgLen = self.msgLenFunc(data) while msgLen is not None: msgLen += self.msgLengthFieldLen # because of the length field if msgLen > self.maxMsgLength: # way too large self._fail("message from peer exceeds size limit (%i bytes)" % (self.maxMsgLength,)) msgLen = None elif len(data) < msgLen: # incomplete message msgLen = None else: # finished a message msg = self.msgDecodeFunc(data[:msgLen]) self._gotMessage(msg) msgs.append((self.inMsgCount, msg)) self.inMsgCount += 1 data = data[msgLen:] msgLen = self.msgLenFunc(data) if data == "": # all processed self.inBuffer = [] else: # still data left self.inBuffer = [data] return msgs def _gotMessage(self, msg): pass def _send(self): # really send the buffered data - or at least try to self.sched.rescheduleEvent(self.sendTimeoutEvent, timedelta=300) self.sched.rescheduleEvent(self.keepaliveEvent, timedelta=100) while len(self.outBufferQueue) > 0: messageId = self.outBufferQueue[0] if not messageId in self.outBufferMessages: # message send got aborted self.outBufferQueue.popleft() if len(self.outBufferQueue) == 0: # nothing to send anymore, notify self.connStatus.wantsToSend(False, self.connIdent) else: # something to send message = self.outBufferMessages[messageId] messageLen = len(message[1]) wantedBytes = min(messageLen, self.conn.getFreeOutBufferSpace()) allowedBytes = self.outLimiter.claimUnits(self.connIdent, wantedBytes) if allowedBytes == 0: # may not even send a single byte ... break else: # at least something may be send sendBytes = self.conn.send(message[1][:allowedBytes]) self.outRate.updateRate(sendBytes) message[2] = True if sendBytes < messageLen: # but not all was send message[1] = message[1][sendBytes:] break else: # all was send self.outBufferQueue.popleft() del self.outBufferMessages[messageId] if len(self.outBufferQueue) == 0: # nothing to send anymore, notify self.connStatus.wantsToSend(False, self.connIdent) if message[0] is not None: # execute message[0][0](*message[0][1], **message[0][2]) def _queueSend(self, data, sendFinishedFunc=None, sendFinishedArgs=[], sendFinishedKws={}): if len(self.outBufferQueue) == 0: # first queued item, notify about send interest self.connStatus.wantsToSend(True, self.connIdent) messageId = self.outBufferMessageId if sendFinishedFunc is None: sendFinishedHandle = None else: sendFinishedHandle = (sendFinishedFunc, sendFinishedArgs, sendFinishedKws) self.outBufferMessageId += 1 self.outBufferQueue.append(messageId) self.outBufferMessages[messageId] = [sendFinishedHandle, data, False] return messageId def _abortSend(self, messageId): aborted = False message = self.outBufferMessages.get(messageId, None) if message is not None: # still queued if not message[2]: # send did not start up to now aborted = True del self.outBufferMessages[messageId] return aborted def _fail(self, reason=""): # cause the conn to fail self.log.info("Conn failed: %s", reason) self.conn.close(force=True) def _close(self): # set flag self.closed = True # close conn, update conn state self.conn.close(force=True) self.connStatus.removeConn(self.connIdent) # stop rate measurement self.inRate.stop() self.outRate.stop() # remove conn from limiter self.outLimiter.removeUser(self.connIdent) self.inLimiter.removeUser(self.connIdent) # clear buffers self.inBuffer = [] self.outBufferQueue.clear() self.outBufferMessages.clear() self.outBufferMessageId = 0 # remove events self.sched.removeEvent(self.sendTimeoutEvent) self.sched.removeEvent(self.recvTimeoutEvent) self.sched.removeEvent(self.keepaliveEvent) ##internal functions - other def _getPayloadRatio(self): inPayload = self.inRate.getTotalTransferedPayloadBytes() outPayload = self.outRate.getTotalTransferedPayloadBytes() if inPayload == 0 and outPayload == 0: ratio = 1.0 elif inPayload == 0 and outPayload != 0: ratio = 1.0 / outPayload elif inPayload != 0 and outPayload == 0: ratio = inPayload / 1.0 else: ratio = inPayload / (outPayload * 1.0) return ratio ##external functions - socket def recv(self): self.lock.acquire() msgs = [] if not self.closed: msgs = self._recv() self.lock.release() return msgs def send(self, data): self.lock.acquire() if not self.closed: self._queueSend(data) self.lock.release() def sendEvent(self): self.lock.acquire() if not self.closed: self._send() self.lock.release() def fileno(self): self.lock.acquire() value = self.connIdent self.lock.release() return value def timeout(self, reason): self.lock.acquire() if not self.closed: self._fail(reason) self.lock.release() def close(self): self.lock.acquire() if not self.closed: self._close() self.lock.release() ##external functions - get info def getInRate(self): self.lock.acquire() rate = self.inRate self.lock.release() return rate def getOutRate(self): self.lock.acquire() rate = self.outRate self.lock.release() return rate def getPayloadRatio(self): self.lock.acquire() ratio = self._getPayloadRatio() self.lock.release() return ratio def getRemotePeerAddr(self): self.lock.acquire() value = self.remotePeerAddr self.lock.release() return value def getShortRemotePeerAddr(self): self.lock.acquire() value = self.remotePeerAddr[:10] self.lock.release() return value