Ejemplo n.º 1
0
    def __init__(self, data_handler, overlay_bridge, session, buddycast_interval_function, log = '', dnsindb = None):
        """ Returns an instance of this class """
        #Keep reference to interval-function of BuddycastFactory
        self.interval = buddycast_interval_function
        self.data_handler = data_handler
        self.dnsindb = dnsindb
        self.log = log
        self.overlay_bridge = overlay_bridge
        self.channelcastdb = ChannelCastDBHandler.getInstance()
        self.votecastdb = VoteCastDBHandler.getInstance()
        self.rtorrent_handler = RemoteTorrentHandler.getInstance()
        self.my_permid = self.channelcastdb.my_permid
        self.session = session
        
        self.network_delay = 30
        #Reference to buddycast-core, set by the buddycast-core (as it is created by the
        #buddycast-factory after calling this constructor).
        self.buddycast_core = None
        
        #Extend logging with ChannelCast-messages and status
        if self.log:
            self.overlay_log = OverlayLogger.getInstance(self.log)
            self.dnsindb = self.data_handler.get_dns_from_peerdb
        
        self.hits = []
        
        self.notifier = Notifier.getInstance()

        self.metadataDbHandler = MetadataDBHandler.getInstance()
        
        #subtitlesHandler = SubtitlesHandler.getInstance()
        subtitleSupport = SubtitlesSupport.getInstance()
        # better if an instance of RMDInterceptor was provided from the
        # outside
        self.peersHaveManger = PeersHaveManager.getInstance()
        if not self.peersHaveManger.isRegistered():
                self.peersHaveManger.register(self.metadataDbHandler, self.overlay_bridge)
        self.richMetadataInterceptor = RichMetadataInterceptor(self.metadataDbHandler,self.votecastdb,
                                                               self.my_permid, subtitleSupport, self.peersHaveManger,
                                                               self.notifier)
Ejemplo n.º 2
0
class ChannelCastCore:
    __single = None
    TESTASSERVER = False # for unit testing

    def __init__(self, data_handler, overlay_bridge, session, buddycast_interval_function, log = '', dnsindb = None):
        """ Returns an instance of this class """
        #Keep reference to interval-function of BuddycastFactory
        self.interval = buddycast_interval_function
        self.data_handler = data_handler
        self.dnsindb = dnsindb
        self.log = log
        self.overlay_bridge = overlay_bridge
        self.channelcastdb = ChannelCastDBHandler.getInstance()
        self.votecastdb = VoteCastDBHandler.getInstance()
        self.rtorrent_handler = RemoteTorrentHandler.getInstance()
        self.my_permid = self.channelcastdb.my_permid
        self.session = session
        
        self.network_delay = 30
        #Reference to buddycast-core, set by the buddycast-core (as it is created by the
        #buddycast-factory after calling this constructor).
        self.buddycast_core = None
        
        #Extend logging with ChannelCast-messages and status
        if self.log:
            self.overlay_log = OverlayLogger.getInstance(self.log)
            self.dnsindb = self.data_handler.get_dns_from_peerdb
        
        self.hits = []
        
        self.notifier = Notifier.getInstance()

        self.metadataDbHandler = MetadataDBHandler.getInstance()
        
        #subtitlesHandler = SubtitlesHandler.getInstance()
        subtitleSupport = SubtitlesSupport.getInstance()
        # better if an instance of RMDInterceptor was provided from the
        # outside
        self.peersHaveManger = PeersHaveManager.getInstance()
        if not self.peersHaveManger.isRegistered():
                self.peersHaveManger.register(self.metadataDbHandler, self.overlay_bridge)
        self.richMetadataInterceptor = RichMetadataInterceptor(self.metadataDbHandler,self.votecastdb,
                                                               self.my_permid, subtitleSupport, self.peersHaveManger,
                                                               self.notifier)
        
        

    
    def initialized(self):
        return self.buddycast_core is not None
 


    def getInstance(*args, **kw):
        if ChannelCastCore.__single is None:
            ChannelCastCore(*args, **kw)
        return ChannelCastCore.__single
    getInstance = staticmethod(getInstance)

   
    def createAndSendChannelCastMessage(self, target_permid, selversion):
        """ Create and send a ChannelCast Message """
        # ChannelCast feature starts from eleventh version; hence, do not send to lower version peers
        # Arno, 2010-02-05: v12 uses a different on-the-wire format, ignore those.
        
        # Andrea, 2010-04-08: sending the "old-style" channelcast message to older
        # peers, and enriched channelcast messages to new versions, for full backward
        # compatibility
        if selversion < OLPROTO_VER_THIRTEENTH:
            if DEBUG:
                print >> sys.stderr, "channelcast: Do not send to lower version peer:", selversion
            return
        
        # 3/5/2010 Andrea: adding the destination parameters to createChannelCastMessage for
        # logging reasons only. When logging will be disabled, that parameter will
        # become useless
        channelcast_data = self.createChannelCastMessage(selversion, target_permid)
        if channelcast_data is None or len(channelcast_data)==0:
            if DEBUG:
                print >>sys.stderr, "channelcast: No channels there.. hence we do not send"
            return
        channelcast_msg = bencode(channelcast_data)
        
        if self.log:
            dns = self.dnsindb(target_permid)
            if dns:
                ip,port = dns
                MSG_ID = "CHANNELCAST"
                msg = repr(channelcast_data)
                self.overlay_log('SEND_MSG', ip, port, show_permid(target_permid), selversion, MSG_ID, msg)
        
        data = CHANNELCAST + channelcast_msg
        self.overlay_bridge.send(target_permid, data, self.channelCastSendCallback)        
        #if DEBUG: print >> sys.stderr, "channelcast: Sent channelcastmsg",repr(channelcast_data)
    
    def createChannelCastMessage(self, selversion, dest_permid=None):
        """ 
        Create a ChannelCast Message 
        
        @param selversion: the protocol version of the destination
        @param dest_permid: the destination of the message. Actually this parameter is not really needed. If 
                            not none, it is used for logging purposes only
                            
        @return a channelcast message, possibly enrich with rich metadata content in the
                case selversion is sufficiently high
        """
        # 09-04-2010 Andrea: I addedd the selversion param, to intercept and modify
        # the ChannelCast message contents if the protocol version allows rich metadata
        # enrichment
        
        if DEBUG: 
            print >> sys.stderr, "channelcast: Creating channelcastmsg..."
        
        hits = self.channelcastdb.getRecentAndRandomTorrents(NUM_OWN_RECENT_TORRENTS,NUM_OWN_RANDOM_TORRENTS,NUM_OTHERS_RECENT_TORRENTS,NUM_OTHERS_RECENT_TORRENTS)
        # 3/5/2010 Andrea:  
        # hits is of the form: [(mod_id, mod_name, infohash, torrenthash, torrent_name, time_stamp, signature)]
        # adding the destination parameter to buildChannelcastMessageFrom Hits for
        # logging reasons only. When logging will be disabled, that parameter will
        # become useless
        d = self.buildChannelcastMessageFromHits(hits, selversion, dest_permid)
#        #assert validChannelCastMsg(d)
        return d
    
    def channelCastSendCallback(self, exc, target_permid, other=0):
        if DEBUG:
            if exc is None:
                print >> sys.stderr,"channelcast: *** msg was sent successfully to peer", show_permid_short(target_permid)
            else:
                print >> sys.stderr, "channelcast: *** warning - error in sending msg to", show_permid_short(target_permid), exc
 
    def gotChannelCastMessage(self, recv_msg, sender_permid, selversion):
        """ Receive and handle a ChannelCast message """
        # ChannelCast feature starts from eleventh version; hence, do not receive from lower version peers
        # Arno, 2010-02-05: v12 uses a different on-the-wire format, ignore those.
        
        # Andrea: 2010-04-08: v14 can still receive v13 channelcast messages
        if selversion < OLPROTO_VER_THIRTEENTH:
            if DEBUG:
                print >> sys.stderr, "channelcast: Do not receive from lower version peer:", selversion
            return True
                
        if DEBUG:
            print >> sys.stderr,'channelcast: Received a msg from ', show_permid_short(sender_permid)
            print >> sys.stderr,"channelcast: my_permid=", show_permid_short(self.my_permid)

        if not sender_permid or sender_permid == self.my_permid:
            if DEBUG:
                print >> sys.stderr, "channelcast: warning - got channelcastMsg from a None/Self peer", \
                        show_permid_short(sender_permid), recv_msg
            return False

        #if len(recv_msg) > self.max_length:
        #    if DEBUG:
        #        print >> sys.stderr, "channelcast: warning - got large channelCastHaveMsg", len(recv_msg)
        #    return False

        channelcast_data = {}

        try:
            channelcast_data = bdecode(recv_msg)
        except:
            print >> sys.stderr, "channelcast: warning, invalid bencoded data"
            return False

        # check message-structure
        if not validChannelCastMsg(channelcast_data):
            print >> sys.stderr, "channelcast: invalid channelcast_message"
            return False

        # 19/02/10 Boudewijn: validChannelCastMsg passes when
        # PUBLISHER_NAME and TORRENTNAME are either string or
        # unicode-string.  However, all further code requires that
        # these are unicode!
        for ch in channelcast_data.values():
            if isinstance(ch["publisher_name"], str):
                ch["publisher_name"] = str2unicode(ch["publisher_name"])
            if isinstance(ch["torrentname"], str):
                ch["torrentname"] = str2unicode(ch["torrentname"])

        self.handleChannelCastMsg(sender_permid, channelcast_data)
        
        #Log RECV_MSG of uncompressed message
        if self.log:
            dns = self.dnsindb(sender_permid)
            if dns:
                ip,port = dns
                MSG_ID = "CHANNELCAST"
                # 08/04/10 Andrea: representing the whole channelcast  + metadata message
                msg = repr(channelcast_data)
                self.overlay_log('RECV_MSG', ip, port, show_permid(sender_permid), selversion, MSG_ID, msg)
 
        if self.TESTASSERVER:
            self.createAndSendChannelCastMessage(sender_permid, selversion)
            
        return True       

    def handleChannelCastMsg(self, sender_permid, data):
        self._updateChannelInternal(sender_permid, None, data)

    def updateChannel(self,query_permid, query, hits):
        """
        This function is called when there is a reply from remote peer regarding updating of a channel
        @param query_permid: the peer who returned the results
        @param query: the query string (None if this is not the results of a query) 
        @param hits: details of all matching results related to the query
        """
        
        return self._updateChannelInternal(query_permid, query, hits)
    
        
        
    def _updateChannelInternal(self, query_permid, query, hits):
        listOfAdditions = list()
        
        # a single read from the db is more efficient
        all_spam_channels = self.votecastdb.getPublishersWithNegVote(bin2str(self.session.get_permid()))
        for k,v in hits.items():
            #check if the record belongs to a channel who we have "reported spam" (negative vote)
            if bin2str(v['publisher_id']) in all_spam_channels:
                # if so, ignore the incoming record
                continue
            
            # make everything into "string" format, if "binary"
            hit = (bin2str(v['publisher_id']),v['publisher_name'],bin2str(v['infohash']),bin2str(v['torrenthash']),v['torrentname'],v['time_stamp'],bin2str(k))

            listOfAdditions.append(hit)

        # Arno, 2010-06-11: We're on the OverlayThread
        self._updateChannelcastDB(query_permid, query, hits, listOfAdditions)
        
        ##return listOfAdditions
                
    
    def _updateChannelcastDB(self, query_permid, query, hits, listOfAdditions):
        
        publisher_ids = Set()
        
        #08/04/10: Andrea: processing rich metadata part.
        self.richMetadataInterceptor.handleRMetadata(query_permid, hits, fromQuery = query is not None)
        
        
        tmp_hits = {} #"binary" key

        def usercallback(infohash,metadata,filename):
            if tmp_hits.has_key(infohash):
                hit = tmp_hits[infohash]
                if self.channelcastdb.addTorrent(hit):
                    self.hits.append(hit)
            else:
                print >> sys.stderr, "channelcast: updatechannel: could not find infohash", bin2str(infohash)


        for hit in listOfAdditions:
            publisher_ids.add(hit[0])
            infohash = str2bin(hit[2])
            tmp_hits[infohash] = hit 
            # effectively v['infohash'] == str2bin(hit[2])


            if self.channelcastdb.existsTorrent(infohash):
                if self.channelcastdb.addTorrent(hit):
                    self.hits.append(hit)
            else:
                self.rtorrent_handler.download_torrent(query_permid,infohash,usercallback)
        
        # Arno, 2010-02-24: Generate event
        for publisher_id in publisher_ids:
            try:
                self.notifier.notify(NTFY_CHANNELCAST, NTFY_UPDATE, publisher_id)
            except:
                print_exc()

                
            


    def updateMySubscribedChannels(self):
        subscribed_channels = self.channelcastdb.getMySubscribedChannels()
        for permid, channel_name, num_subscriptions, notused in subscribed_channels:
            # query the remote peers, based on permid, to update the channel content
            q = "CHANNEL p "+permid
            self.session.query_connected_peers(q,usercallback=self.updateChannel)
        
        self.overlay_bridge.add_task(self.updateMySubscribedChannels, RELOAD_FREQUENCY)    


    def buildChannelcastMessageFromHits(self, hits, selversion, dest_permid=None, fromQuery=False):
        '''
        Creates a channelcast message from database hits.
        
        This method is used to create channel results both when a channelcast message
        is created in the "normal" buddycast epidemic protocol, and when a remote
        query for channels arrives and is processed. It substitutes a lot of duplicated
        code in the old versions.
        
        @param hits: a tuple (publisher_id, publisher_name, infohash, 
                     torrenthash, torrentname, time_stamp, signature) representing
                     a channelcast entry in the db
        @param selversion: the protocol version of the destination
        @param dest_permid: the permid of the destination of the message. Actually this parameter
                            is used for logging purposes only, when not None. If None, nothing
                            bad happens.
        '''
        # 09-04-2010 Andrea : I introduced this separate method because this code was 
        # duplicated in RemoteQueryMessageHandler
        enrichWithMetadata = False
        
        if selversion >= OLPROTO_VER_FOURTEENTH:
            enrichWithMetadata = True
            if DEBUG:
                print >> sys.stderr, "channelcast: creating enriched messages"\
                    "since peer has version: ", selversion
        d = {}
        for hit in hits:
            # ARNOUNICODE: temp fixes until data is sent not Base64-encoded
             
            # 08/04/10 Andrea: I substituted the keys with constnats, otherwise a change here
            # would break my code in the RichMetadataInterceptor
            r = {}
            r['publisher_id'] = str(hit[0]) # ARNOUNICODE: must be str
            r['publisher_name'] = hit[1].encode("UTF-8")  # ARNOUNICODE: must be explicitly UTF-8 encoded
            r['infohash'] = str(hit[2])     # ARNOUNICODE: must be str
            r['torrenthash'] = str(hit[3])  # ARNOUNICODE: must be str
            r['torrentname'] = hit[4].encode("UTF-8") # ARNOUNICODE: must be explicitly UTF-8 encoded
            r['time_stamp'] = int(hit[5])
            # hit[6]: signature, which is unique for any torrent published by a user
            signature = hit[6]
            d[signature] = r
            

        # 08/04/10 Andrea: intercepting a channelcast message and enriching it with
        # subtitles information
        # 3/5/2010 Andrea: adding the destination parameter to addRichMetadataContent for
        # logging reasons only. When logging will be disabled, that parameter will
        # become useless
        if enrichWithMetadata:
            d = self.richMetadataInterceptor.addRichMetadataContent(d, dest_permid, fromQuery)
    
        return d