1 # Written by Ali Abbas, Arno Bakker
2 # see LICENSE.txt for license information
4 # TODO: either maintain connections to friends always or supplement the
5 # list of friends with a number of on-line taste buddies.
7 # TODO: at least add fifo order to msgs, otherwise clicking
8 # "make friend", "delete friend", "make friend" could arive in wrong order
18 from types import DictType
19 from traceback import print_exc
22 from BaseLib.Core.simpledefs import *
23 from BaseLib.Core.BitTornado.bencode import bencode, bdecode
25 from BaseLib.Core.BitTornado.BT1.MessageID import *
26 from BaseLib.Core.CacheDB.CacheDBHandler import PeerDBHandler, FriendDBHandler
27 from BaseLib.Core.CacheDB.SqliteFriendshipStatsCacheDB import FriendshipStatisticsDBHandler
28 from BaseLib.Core.CacheDB.sqlitecachedb import bin2str
29 from BaseLib.Core.Utilities.utilities import *
31 from BaseLib.Core.Overlay.SecureOverlay import OLPROTO_VER_SEVENTH
38 NOFRIEND -> I_INVITED or HE_INVITED
39 I_INVITED -> APPROVED or HE_DENIED
40 HE_INVITED -> APPROVED
41 HE_INVITED -> I_DENIED
43 In theory it could happen that he sends an response=1 RESP, in which case
44 he approved us. I consider that an HE_INIVITE
47 RESCHEDULE_INTERVAL = 60
48 RESEND_INTERVAL = 5*60
51 class FriendshipMsgHandler:
53 __lock = threading.Lock()
56 def getInstance(cls, *args, **kargs):
57 if not cls.__singleton:
60 if not cls.__singleton:
61 cls.__singleton = cls(*args, **kargs)
64 return cls.__singleton
67 if FriendshipMsgHandler.__singleton:
68 raise RuntimeError, "FriendshipMsgHandler is singleton"
69 self.overlay_bridge = None
71 self.online_fsext_peers = Set() # online peers that speak FRIENDSHIP ext
72 self.peerdb = PeerDBHandler.getInstance()
73 self.frienddb = FriendDBHandler.getInstance()
74 self.friendshipStatistics_db = FriendshipStatisticsDBHandler.getInstance()
75 self.list_no_of_conn_attempts_per_target= {}
76 self.usercallback = None
78 def register(self, overlay_bridge, session):
80 print >> sys.stderr, "friendship: register"
81 self.overlay_bridge = overlay_bridge
82 self.session = session
84 self.load_checkpoint()
87 self.overlay_bridge.add_task(self.reschedule_connects,RESCHEDULE_INTERVAL)
92 Delegate all outstanding messages to others
94 # Called by OverlayThread
95 self.delegate_friendship_making()
99 def register_usercallback(self,usercallback):
100 self.usercallback = usercallback
102 def anythread_send_friendship_msg(self,permid,type,params):
103 """ Called when user adds someone from the person found, or by
104 explicity adding someone with her credentials
105 It establishes overlay connection with the target peer """
106 # Called by any thread
108 olthread_func = lambda:self.send_friendship_msg(permid,type,params,submit=True)
109 self.overlay_bridge.add_task(olthread_func,0)
112 def send_friendship_msg(self,permid,type,params,submit=False):
113 # Called by overlay thread
117 print >>sys.stderr,"friendship: send_friendship_msg: Saving msg",show_permid_short(permid)
118 self.save_msg(permid,type,params)
120 if type == F_REQUEST_MSG:
121 # Make him my friend, pending his approval
122 self.frienddb.setFriendState(permid, commit=True,state=FS_I_INVITED)
123 elif type == F_RESPONSE_MSG:
124 # Mark response in DB
125 if params['response']:
129 self.frienddb.setFriendState(permid, commit=True,state=state)
131 func = lambda exc,dns,permid,selversion:self.fmsg_connect_callback(exc, dns, permid, selversion, type)
132 self.overlay_bridge.connect(permid,self.fmsg_connect_callback)
135 def fmsg_connect_callback(self,exc,dns,permid,selversion, type = None):
136 """ Callback function for the overlay connect function """
137 # Called by OverlayThread
140 if selversion < OLPROTO_VER_SEVENTH:
141 self.remove_msgs_for_ltv7_peer(permid)
145 sendlist = self.get_msgs_as_sendlist(targetpermid=permid)
147 print >> sys.stderr, 'friendship: fmsg_connect_callback: sendlist len',len(sendlist)
150 for i in range(0,len(sendlist)):
153 permid,msgid,msg = tuple
154 send_callback = lambda exc,permid:self.fmsg_send_callback(exc,permid,msgid)
157 print >>sys.stderr,"friendship: fmsg_connect_callback: Sending",`msg`,msgid
159 mypermid = self.session.get_permid()
161 commit = (i == len(sendlist)-1)
164 # if type == F_REQUEST_MSG:
166 # elif type == F_RESPONSE_MSG:
168 #Set forwarder to True and also no of helpers to 10
169 if type == F_FORWARD_MSG:
175 if permid in self.currmsgs:
176 msgid2rec = self.currmsgs[permid]
177 if msgid in msgid2rec:
178 msgrec = msgid2rec[msgid]
179 no_of_attempts = msgrec['attempt']
181 # insertFriendshipStatistics(self, my_permid, target_permid, current_time, isForwarder = 0, no_of_attempts = 0, no_of_helpers = 0, commit = True):
183 self.friendshipStatistics_db.insertOrUpdateFriendshipStatistics( bin2str(mypermid),
191 self.overlay_bridge.send(permid, FRIENDSHIP + bencode(msg), send_callback)
196 peer = self.peerdb.getPeer(permid)
198 print >>sys.stderr, 'friendship: Could not connect to peer', show_permid_short(permid),peer
200 print >>sys.stderr, 'friendship: Could not connect to peer', show_permid_short(permid),peer['name']
201 print >>sys.stderr,exc
203 mypermid = self.session.get_permid()
207 if type == F_FORWARD_MSG:
213 if permid in self.currmsgs:
214 msgid2rec = self.currmsgs[permid]
215 for msgid in msgid2rec:
216 msgrec = msgid2rec[msgid]
217 no_of_attempts = msgrec['attempt']
220 self.friendshipStatistics_db.insertOrUpdateFriendshipStatistics( bin2str(mypermid),
230 def fmsg_send_callback(self,exc,permid,msgid):
232 # If an exception arises
234 self.delete_msg(permid,msgid)
237 print >> sys.stderr, 'friendship: Could not send to ',show_permid_short(permid)
240 mypermid = self.session.get_permid()
245 if permid in self.currmsgs:
246 msgid2rec = self.currmsgs[permid]
247 for msgid in msgid2rec:
248 msgrec = msgid2rec[msgid]
249 no_of_attempts = msgrec['attempt']
250 if msgrec['forwarded'] == True:
254 self.friendshipStatistics_db.insertOrUpdateFriendshipStatistics( bin2str(mypermid),
262 def remove_msgs_for_ltv7_peer(self,permid):
263 """ Remove messages destined for a peer that does not speak >= v7 of
266 sendlist = self.get_msgs_as_sendlist(targetpermid=permid)
268 print >> sys.stderr, 'friendship: remove_msgs_for_ltv7_peer: sendlist len',len(sendlist)
270 for i in range(0,len(sendlist)):
273 permid,msgid,msg = tuple
274 self.delete_msg(permid,msgid)
278 # Incoming connections
280 def handleConnection(self, exc, permid, selversion, locally_initiated):
282 if selversion < OLPROTO_VER_SEVENTH:
286 self.online_fsext_peers.add(permid)
288 # if we meet peer otherwise, dequeue messages
290 print >> sys.stderr,"friendship: Met peer, attempting to deliver msgs",show_permid_short(permid)
292 # If we're initiating the connection from this handler, the
293 # fmsg_connect_callback will get called twice:
295 # 2. just a bit later when the callback for a successful connect()
297 # Solution: we delay this call, which should give 2. the time to
298 # run and remove msgs from the queue.
300 # Better: remove msgs from queue when sent and reinsert if send fails
302 friendship_delay_func = lambda:self.fmsg_connect_callback(None,None,permid,selversion)
303 self.overlay_bridge.add_task(friendship_delay_func,4)
306 self.online_fsext_peers.remove(permid)
316 def handleMessage(self, permid, selversion, message):
317 """ Handle incoming Friend Request, and their response"""
319 if selversion < OLPROTO_VER_SEVENTH:
321 print >> sys.stderr,"friendship: Got FRIENDSHIP msg from peer with old protocol",show_permid_short(permid)
325 d = bdecode(message[1:])
330 return self.process_message(permid,selversion,d)
333 def process_message(self,permid,selversion,d):
335 if self.isValidFriendMsg(d):
338 print >> sys.stderr,"friendship: Got FRIENDSHIP msg",d['msg type']
340 # If the message is to become a friend, i.e., a friendship request
341 if d['msg type'] == F_REQUEST_MSG:
342 self.process_request(permid,d)
344 # If the message is to have a response on friend request
345 elif d['msg type'] == F_RESPONSE_MSG:
346 self.process_response(permid,d)
348 # If the receiving message is to delegate the Friendship request to the target peer
349 elif d['msg type'] == F_FORWARD_MSG:
350 return self.process_forward(permid,selversion,d)
353 print >>sys.stderr,"friendship: Got unknown msg type",d['msg type']
359 print >>sys.stderr,"friendship: Got bad FRIENDSHIP message"
362 def process_request(self,permid,d):
363 # to see that the following peer is already a friend, or not
364 fs = self.frienddb.getFriendState(permid)
367 print >>sys.stderr,"friendship: process_request: Got request, fs",show_permid_short(permid),fs
370 if fs == FS_NOFRIEND or fs == FS_HE_DENIED:
371 # not on HE_INVITED, to filter out duplicates
373 # And if that peer is not already added as a friend, either approved, or unapproved
375 self.frienddb.setFriendState(permid, commit=True, state = FS_HE_INVITED)
377 # FUTURE: always do callback, such that we also know about failed
379 if self.usercallback is not None:
380 friendship_usercallback = lambda:self.usercallback(permid,[])
381 self.session.uch.perform_usercallback(friendship_usercallback)
382 elif fs == FS_I_INVITED:
383 # In case, requestee is already added as friend, just make this
384 # requestee as an approved friend
387 print >>sys.stderr,"friendship: process_request: Got request but I already invited him"
389 self.frienddb.setFriendState(permid, commit=True, state = FS_MUTUAL)
392 print >>sys.stderr,"friendship: process_request: Got request but I already invited him: sending reply"
394 self.send_friendship_msg(permid,F_RESPONSE_MSG,{'response':1},submit=True)
395 elif fs == FS_MUTUAL:
397 print >>sys.stderr,"friendship: process_request: Got request but already approved"
398 elif fs == FS_I_DENIED:
400 print >>sys.stderr,"friendship: process_request: Got request but I already denied"
402 print >>sys.stderr,"friendship: process_request: Got request, but fs is",fs
404 def process_response(self,permid,d):
406 mypermid = self.session.get_permid()
409 self.friendshipStatistics_db.updateFriendshipResponseTime( bin2str(mypermid),
414 fs = self.frienddb.getFriendState(permid)
416 # If the request to add has been approved
417 if d['response'] == 1:
418 if fs == FS_I_INVITED:
419 self.frienddb.setFriendState(permid, commit=True, state = FS_MUTUAL)
420 elif fs != FS_MUTUAL:
421 # Unsollicited response, consider this an invite, if not already friend
422 self.frienddb.setFriendState(permid, commit=True, state = FS_HE_INVITED)
424 # He denied our friendship
425 self.frienddb.setFriendState(permid, commit=True, state = FS_HE_DENIED)
428 def process_forward(self,permid,selversion,d):
430 mypermid = self.session.get_permid()
431 if d['dest']['permid'] == mypermid:
432 # This is a forward containing a message meant for me
434 # First add original sender to DB so we can connect back to it
435 self.addPeerToDB(d['source'])
437 self.process_message(d['source']['permid'],selversion,d['msg'])
445 print >>sys.stderr,"friendship: process_fwd: Forwarding immediately to",show_permid_short(d['dest']['permid'])
447 if permid != d['source']['permid']:
449 print >>sys.stderr,"friendship: process_fwd: Forwarding: Illegal, source is not sender, and dest is not me"
451 # First add dest to DB so we can connect to it
453 # FUTURE: don't let just any peer overwrite the IP+port of a peer
454 # if self.peer_db.hasPeer(d['dest']['permid']):
455 self.addPeerToDB(d['dest'])
457 self.send_friendship_msg(d['dest']['permid'],d['msg type'],d,submit=True)
460 def addPeerToDB(self,mpeer):
462 peer['permid'] = mpeer['permid']
463 peer['ip'] = mpeer['ip']
464 peer['port'] = mpeer['port']
465 peer['last_seen'] = 0
466 self.peerdb.addPeer(mpeer['permid'],peer,update_dns=True,commit=True)
469 def create_friendship_msg(self,type,params):
472 print >>sys.stderr,"friendship: create_fs_msg:",type,`params`
474 mypermid = self.session.get_permid()
475 myip = self.session.get_external_ip()
476 myport = self.session.get_listen_port()
479 if type == F_RESPONSE_MSG:
480 d['response'] = params['response']
481 elif type == F_FORWARD_MSG:
484 print >>sys.stderr,"friendship: create: fwd: params",`params`
485 peer = self.peerdb.getPeer(params['destpermid']) # ,keys=['ip', 'port'])
488 print >> sys.stderr, "friendship: create msg: Don't know IP + port of peer", show_permid_short(params['destpermid'])
491 # print >> sys.stderr, "friendship: create msg: Peer at",peer
493 # FUTURE: add signatures on ip+port
494 src = {'permid':mypermid,'ip':myip,'port':myport}
495 dst = {'permid':params['destpermid'],'ip':str(peer['ip']),'port':peer['port']}
496 d.update({'source':src,'dest':dst,'msg':params['msg']})
501 def isValidFriendMsg(self,d):
504 print >>sys.stderr,"friendship: msg: payload is",`d`
507 if type(d) != DictType:
509 print >>sys.stderr,"friendship: msg: payload is not bencoded dict"
511 if not 'msg type' in d:
513 print >>sys.stderr,"friendship: msg: dict misses key",'msg type'
516 if d['msg type'] == F_REQUEST_MSG:
520 print >>sys.stderr,"friendship: msg: REQ: contains superfluous keys",keys
524 if d['msg type'] == F_RESPONSE_MSG:
525 if (d.has_key('response') and (d['response'] == 1 or d['response'] == 0)):
529 print >>sys.stderr,"friendship: msg: RESP: something wrong",`d`
532 if d['msg type'] == F_FORWARD_MSG:
533 if not self.isValidPeer(d['source']):
535 print >>sys.stderr,"friendship: msg: FWD: source bad",`d`
537 if not self.isValidPeer(d['dest']):
539 print >>sys.stderr,"friendship: msg: FWD: dest bad",`d`
543 print >>sys.stderr,"friendship: msg: FWD: no msg",`d`
545 if not self.isValidFriendMsg(d['msg']):
547 print >>sys.stderr,"friendship: msg: FWD: bad msg",`d`
549 if d['msg']['msg type'] == F_FORWARD_MSG:
551 print >>sys.stderr,"friendship: msg: FWD: cannot contain fwd",`d`
558 def isValidPeer(self,d):
559 if (d.has_key('ip') and d.has_key('port') and d.has_key('permid')
560 and validPermid(d['permid'])
561 and validIP(d['ip'])and validPort(d['port'])):
567 def save_msg(self,permid,type,params):
569 if not permid in self.currmsgs:
570 self.currmsgs[permid] = {}
572 mypermid = self.session.get_permid()
576 base = mypermid+permid+str(now)+str(random.random())
577 msgid = sha(base).hexdigest()
578 msgrec = {'permid':permid,'type':type,'params':params,'attempt':attempt,'t':now,'forwarded':False}
580 msgid2rec = self.currmsgs[permid]
581 msgid2rec[msgid] = msgrec
583 def delete_msg(self,permid,msgid):
586 print >>sys.stderr,"friendship: Deleting msg",show_permid_short(permid),msgid
587 msgid2rec = self.currmsgs[permid]
593 def set_msg_forwarded(self,permid,msgid):
595 msgid2rec = self.currmsgs[permid]
596 msgid2rec[msgid]['forwarded'] = True
600 def reschedule_connects(self):
601 """ This function is run periodically and reconnects to peers when
602 messages meant for it are due to be retried
606 reconnectpermids = Set()
607 for permid in self.currmsgs:
608 msgid2rec = self.currmsgs[permid]
609 for msgid in msgid2rec:
610 msgrec = msgid2rec[msgid]
612 eta = self.calc_eta(msgrec)
620 peer = self.peerdb.getPeer(permid)
622 print >>sys.stderr,"friendship: reschedule: ETA: wtf, peer not in DB!",show_permid_short(permid)
624 print >>sys.stderr,"friendship: reschedule: ETA",show_permid_short(permid),peer['name'],diff
627 delmsgids.append((permid,msgid))
628 elif now > eta-1.0: # -1 for round off
630 reconnectpermids.add(permid)
631 msgrec['attempt'] = msgrec['attempt'] + 1
634 if msgrec['type'] == F_REQUEST_MSG and msgrec['attempt'] == 2:
635 self.delegate_friendship_making(targetpermid=permid,targetmsgid=msgid)
637 # Remove timed out messages
638 for permid,msgid in delmsgids:
640 print >>sys.stderr,"friendship: reschedule: Deleting",show_permid_short(permid),msgid
641 self.delete_msg(permid,msgid)
643 # Initiate connections to peers for which we have due messages
644 for permid in reconnectpermids:
646 print >>sys.stderr,"friendship: reschedule: Reconnect to",show_permid_short(permid)
648 self.overlay_bridge.connect(permid,self.fmsg_connect_callback)
650 # Reschedule this periodic task
651 self.overlay_bridge.add_task(self.reschedule_connects,RESCHEDULE_INTERVAL)
654 def calc_eta(self,msgrec):
655 if msgrec['type'] == F_FORWARD_MSG:
656 if msgrec['attempt'] >= 10:
657 # Stop trying to forward after a given period
659 # exponential backoff, on 10th attempt we would wait 24hrs
660 eta = msgrec['t'] + pow(3.116,msgrec['attempt'])
662 if msgrec['attempt'] >= int(7*24*3600/RESEND_INTERVAL):
663 # Stop trying to forward after a given period = 1 week
666 eta = msgrec['t'] + msgrec['attempt']*RESEND_INTERVAL
670 def get_msgs_as_sendlist(self,targetpermid=None):
673 if targetpermid is None:
674 permids = self.currmsgs.keys()
676 permids = [targetpermid]
678 for permid in permids:
679 msgid2rec = self.currmsgs.get(permid,{})
680 for msgid in msgid2rec:
681 msgrec = msgid2rec[msgid]
684 print >>sys.stderr,"friendship: get_msgs: Creating",msgrec['type'],`msgrec['params']`,msgid
685 if msgrec['type'] == F_FORWARD_MSG:
686 msg = msgrec['params']
688 msg = self.create_friendship_msg(msgrec['type'],msgrec['params'])
689 tuple = (permid,msgid,msg)
690 sendlist.append(tuple)
694 def get_msgs_as_fwd_sendlist(self,targetpermid=None,targetmsgid=None):
697 if targetpermid is None:
698 permids = self.currmsgs.keys()
700 permids = [targetpermid]
702 for permid in permids:
703 msgid2rec = self.currmsgs.get(permid,{})
704 for msgid in msgid2rec:
705 if targetmsgid is None or msgid == targetmsgid:
706 msgrec = msgid2rec[msgid]
707 if msgrec['type'] != F_FORWARD_MSG and msgrec['forwarded'] == False:
708 # Don't forward forwards, or messages already forwarded
710 # Create forward message for original
712 params['destpermid'] = permid
713 params['msg'] = self.create_friendship_msg(msgrec['type'],msgrec['params'])
715 msg = self.create_friendship_msg(F_FORWARD_MSG,params)
716 tuple = (permid,msgid,msg)
717 sendlist.append(tuple)
722 def delegate_friendship_making(self,targetpermid=None,targetmsgid=None):
724 print >>sys.stderr,"friendship: delegate:",show_permid_short(targetpermid),targetmsgid
726 # 1. See if there are undelivered msgs
727 sendlist = self.get_msgs_as_fwd_sendlist(targetpermid=targetpermid,targetmsgid=targetmsgid)
729 print >>sys.stderr,"friendship: delegate: Number of messages queued",len(sendlist)
731 if len(sendlist) == 0:
734 # 2. Get friends, not necess. online
735 friend_permids = self.frienddb.getFriends()
738 l = len(friend_permids)
739 print >>sys.stderr,"friendship: delegate: friend helpers",l
740 for permid in friend_permids:
741 print >>sys.stderr,"friendship: delegate: friend helper",show_permid_short(permid)
743 # 3. Sort online peers on similarity, highly similar should be tastebuddies
745 print >>sys.stderr,"friendship: delegate: Number of online v7 peers",len(self.online_fsext_peers)
746 tastebuddies = self.peerdb.getPeers(list(self.online_fsext_peers),['similarity','name'])
747 tastebuddies.sort(sim_desc_cmp)
750 print >>sys.stderr,"friendship: delegate: Sorted tastebuddies",`tastebuddies`
752 tastebuddies_permids = []
753 size = min(10,len(tastebuddies))
754 for i in xrange(0,size):
755 peer = tastebuddies[i]
757 print >>sys.stderr,"friendship: delegate: buddy helper",show_permid_short(peer['permid'])
758 tastebuddies_permids.append(peer['permid'])
760 # 4. Create list of helpers:
762 # Policy: Helpers are a mix of friends and online tastebuddies
763 # with 70% friends (if avail) and 30% tastebuddies
765 # I chose this policy because friends are not guaranteed to be online
766 # and waiting to see if we can connect to them before switching to
767 # the online taste buddies is complex code-wise and time-consuming.
768 # We don't have a lot of time when this thing is called by Session.shutdown()
771 nfriends = int(nwant * .7)
772 nbuddies = int(nwant * .3)
774 part1 = sampleorlist(friend_permids,nfriends)
775 fill = nfriends-len(part1) # if no friends, use tastebuddies
776 part2 = sampleorlist(tastebuddies_permids,nbuddies+fill)
777 helpers = part1 + part2
781 print >>sys.stderr,"friendship: delegate: end helpers",l
782 for permid in helpers:
783 print >>sys.stderr,"friendship: delegate: end helper",show_permid_short(permid),self.frienddb.getFriendState(permid),self.peerdb.getPeers([permid],['similarity','name'])
786 for tuple in sendlist:
787 destpermid,msgid,msg = tuple
788 for helperpermid in helpers:
789 if destpermid != helperpermid:
790 connect_callback = lambda exc,dns,permid,selversion:self.forward_connect_callback(exc,dns,permid,selversion,destpermid,msgid,msg)
793 print >>sys.stderr,"friendship: delegate: Connecting to",show_permid_short(helperpermid)
795 self.overlay_bridge.connect(helperpermid, connect_callback)
798 def forward_connect_callback(self,exc,dns,permid,selversion,destpermid,msgid,msg):
801 if selversion < OLPROTO_VER_SEVENTH:
804 send_callback = lambda exc,permid:self.forward_send_callback(exc,permid,destpermid,msgid)
806 print >>sys.stderr,"friendship: forward_connect_callback: Sending",`msg`
807 self.overlay_bridge.send(permid, FRIENDSHIP + bencode(msg), send_callback)
809 print >>sys.stderr,"friendship: forward: Could not connect to helper",show_permid_short(permid)
812 def forward_send_callback(self,exc,permid,destpermid,msgid):
816 print >>sys.stderr,"friendship: forward: Success forwarding to helper",show_permid_short(permid)
817 self.set_msg_forwarded(destpermid,msgid)
820 print >>sys.stderr,"friendship: forward: Failed to forward to helper",show_permid_short(permid)
822 def checkpoint(self):
823 statedir = self.session.get_state_dir()
824 newfilename = os.path.join(statedir,'new-friendship-msgs.pickle')
825 finalfilename = os.path.join(statedir,'friendship-msgs.pickle')
827 f = open(newfilename,"wb")
828 cPickle.dump(self.currmsgs,f)
831 os.remove(finalfilename)
833 # If first time, it doesn't exist
835 os.rename(newfilename,finalfilename)
839 def load_checkpoint(self):
840 statedir = self.session.get_state_dir()
841 finalfilename = os.path.join(statedir,'friendship-msgs.pickle')
843 f = open(finalfilename,"rb")
844 self.currmsgs = cPickle.load(f)
846 print >>sys.stderr, "friendship: could not read previous messages from", finalfilename
848 # Increase # attempts till current time
850 for permid in self.currmsgs:
851 msgid2rec = self.currmsgs[permid]
852 for msgid in msgid2rec:
853 msgrec = msgid2rec[msgid]
854 diff = now - msgrec['t']
855 a = int(diff/RESEND_INTERVAL)
858 print >>sys.stderr,"friendship: load_checkp: Changing #attempts from",msgrec['attempt'],a
859 msgrec['attempt'] = a
862 def sim_desc_cmp(peera,peerb):
863 if peera['similarity'] < peerb['similarity']:
865 elif peera['similarity'] > peerb['similarity']:
870 def sampleorlist(z,k):
874 return random.sample(k)