示例#1
0
文件: Bt.py 项目: kytvi2p/i2p.pybit
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
示例#2
0
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