#-----------------------------------------------------------------------------
# Name: peerlist.py
# Purpose:
#
# Author: Jeremy Arendt
#
# Created: 2004/30/01
# RCS-ID: $Id: peerlist.py,v 1.14 2005/11/30 00:57:59 inigo Exp $
# Copyright: (c) 2002
# Licence: See G3.LICENCE.TXT
#-----------------------------------------------------------------------------
import locale
import wx
from wx.lib.mixins.listctrl import ColumnSorterMixin,ListCtrlAutoWidthMixin
from traceback import print_exc
from images import Images
from g3listctrl import *
from btconfig import BTConfig
import friend
import types
import sys
import re
ipaddr_re = re.compile("\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}")
if sys.platform == "win32":
win32_flag = True
else:
win32_flag = False
def LongIP(ip):
address = [""] * 4
i = 0
for char in ip:
if char != '.':
address[i] += char
else:
i += 1
i = 24
longip = 0
for num in address:
longip += long(num) << i
i -= 8
return longip
class PeerList(G3ListCtrl, ListCtrlAutoWidthMixin, ColumnSorterMixin):
def __init__(self, parent, btconfig, pos=wx.DefaultPosition, size=wx.DefaultSize,
style = wx.LC_REPORT | wx.LC_VRULES,
bmps = None, friendfunc = None, colclickfunc = None, cachefunc = None):
G3ListCtrl.__init__(self, parent, btconfig, "PeerList", -1, pos, size, style, list_type=0)
ListCtrlAutoWidthMixin.__init__(self)
ColumnSorterMixin.__init__(self, 14)
self.itemDataMap = {}
self.list_rows = {}
self.initsort = False
self.FriendFunc = friendfunc
self.Colclickfunc = colclickfunc
self.peercache = cachefunc
self.infohash = None
self.btconfig = btconfig #cfg that is always in sync globably.
self.s_config = btconfig #cfg that can be in sync with btconfig, or with a independent session cfg
self.last_options = {'reverse_dns' : btconfig['reverse_dns'],
'country_flags' : btconfig['country_flags']}
if bmps == None:
bmps = Images()
self.pl_unchoked = wx.Color(255,255,240)
self.images = [1] * 4
self.i_list = wx.ImageList(16, 16)
mask_color = wx.Color(0,0,0)
b_mask_color = wx.Color(0,0,0)
self.images[0] = self.i_list.AddWithColourMask(bmps.GetImage('blank.bmp'), b_mask_color)
self.images[1] = self.i_list.AddWithColourMask(bmps.GetImage('usergreen.png'), mask_color)
self.images[2] = self.i_list.AddWithColourMask(bmps.GetImage('userblue.png'), mask_color)
self.images[3] = self.i_list.AddWithColourMask(bmps.GetImage('userred.png'), mask_color)
self.flags = {}
flag_bmps = bmps.GetImage('flags')
for key, flag in flag_bmps.items():
try:
self.flags[key] = self.i_list.AddIcon(flag)
except:
self.flags[key] = self.i_list.AddIcon(flag_bmps['checkered'])
self.flags['edu'] = self.i_list.AddIcon(flag_bmps['checkered'])
self.flags['org'] = self.i_list.AddIcon(flag_bmps['checkered'])
self.flags['us'] = self.i_list.AddIcon(flag_bmps['us'])
self.flags['gov'] = self.i_list.AddIcon(flag_bmps['us'])
self.flags['com'] = self.i_list.AddIcon(flag_bmps['us'])
self.flags['net'] = self.i_list.AddIcon(flag_bmps['us'])
self.SetImageList(self.i_list, wx.IMAGE_LIST_SMALL)
self.col2sort = 1
self.ascending = False
cols = [ [True, _("Peer IP Addresses"), wx.LIST_FORMAT_LEFT, wx.LIST_AUTOSIZE_USEHEADER],
[True, _("KB/s Dn"), wx.LIST_FORMAT_RIGHT, wx.LIST_AUTOSIZE_USEHEADER],
[True, _("KB/s Up"), wx.LIST_FORMAT_RIGHT, wx.LIST_AUTOSIZE_USEHEADER],
[True, "%", wx.LIST_FORMAT_RIGHT, 45],
[True, _("Progress"), wx.LIST_FORMAT_LEFT, 60],
[True, _("Downloaded"), wx.LIST_FORMAT_RIGHT, 75],
[True, _("Uploaded"), wx.LIST_FORMAT_RIGHT, 75],
[True, _("Initiation"), wx.LIST_FORMAT_LEFT, wx.LIST_AUTOSIZE_USEHEADER],
[True, _("Client Type"), wx.LIST_FORMAT_LEFT, 80],
[True, _("Name"), wx.LIST_FORMAT_LEFT, 73],
[True, _("Send"), wx.LIST_FORMAT_LEFT, 80],
[True, _("Recv"), wx.LIST_FORMAT_LEFT, 80],
[True, _("Raw Peerid"), wx.LIST_FORMAT_LEFT, wx.LIST_AUTOSIZE_USEHEADER],
[True, "", wx.LIST_FORMAT_LEFT, wx.LIST_AUTOSIZE_USEHEADER]
]
self.InsertColumns(cols)
self.SetEbedCol(4)
wx.EVT_LIST_COL_CLICK(self, -1, self.OnColClick)
wx.EVT_COMMAND_RIGHT_CLICK(self, -1, self.OnRightClick) # wxMSW
wx.EVT_RIGHT_UP(self, self.OnRightClick) # wxGTK
def SetColSort(self, data): #used to remember what column sort was set to between tab/torrent switches
col, ascending = data
if col != None and ascending != None: #if both set to None user switched between tabs only
self.col2sort = col
self.ascending = ascending
self.initsort = False
def GetColSort(self): #returns number of sorted col and whether it is ascending or not
return (self.col2sort, self.ascending)
def OnSortToggle(self, event):
self.s_config['sort_list'] = not self.s_config['sort_list']
def OnDnsToggle(self, event):
if self.s_config['reverse_dns']:
self.s_config['reverse_dns'] = False
self.s_config['country_flags'] = False
else:
self.s_config['reverse_dns'] = True
self.FillRows()
def OnFlagsToggle(self, event):
if self.s_config['country_flags']:
self.s_config['country_flags'] = False
else:
self.s_config['country_flags'] = True
self.s_config['reverse_dns'] = True
self.FillRows()
def OnRightClick(self, event):
self.selected_index = self.GetFirstSelected()
if not hasattr(self, "popupID1"):
self.popupID1 = wx.NewId()
self.popupID2 = wx.NewId()
self.popupID3 = wx.NewId()
self.popupID4 = wx.NewId()
self.popupID5 = wx.NewId()
self.popupID6 = wx.NewId()
wx.EVT_MENU(self, self.popupID1, self.OnSortToggle)
wx.EVT_MENU(self, self.popupID2, self.OnDnsToggle)
wx.EVT_MENU(self, self.popupID3, self.OnCBCopy)
wx.EVT_MENU(self, self.popupID4, self.OnMakeTempFriend)
wx.EVT_MENU(self, self.popupID5, self.OnMakeTempFoe)
wx.EVT_MENU(self, self.popupID6, self.OnFlagsToggle)
menu = wx.Menu()
menu.Append(self.popupID4, _("Give peer upload preference"), _("Give upload preference to this peer"), True)
menu.Append(self.popupID5, _("Never upload to this peer"), _("Never upload to this peer"), True)
menu.AppendSeparator()
menu.Append(self.popupID1, _("Keep Sorted"), _("Enable/Disable realtime sorting"), True)
menu.Append(self.popupID2, _("Reverse Dns"), _("Show full addresses where avaiable"), True)
menu.Append(self.popupID6, _("Show Country Flags"), _("Show country flags where avaiable"), True)
menu.Append(self.popupID3, _("Copy address"), _("Copy selected address to clipboard"))
if self.s_config['sort_list']:
menu.Check(self.popupID1, True)
if self.s_config['reverse_dns']:
menu.Check(self.popupID2, True)
if self.s_config['country_flags']:
menu.Check(self.popupID6, True)
self.PopupMenu(menu, self.ScreenToClient(wx.GetMousePosition()))
menu.Destroy()
def OnMakeTempFriend(self, event):
if self.FriendFunc == None:
return
if self.selected_index != -1:
data = self.itemDataMap.get( self.GetItemData(self.selected_index) )
if data != None:
#ip, peerid, ADDFRIENDTEMP, (port, infohash)
self.FriendFunc(data[13], data[15], friend.ADDFRIENDTEMP, (data[13], self.infohash))
if self.ReChoke:
self.ReChoke()
del self.selected_index
def OnMakeTempFoe(self, event):
if self.FriendFunc == None:
return
if self.selected_index != -1:
data = self.itemDataMap.get( self.GetItemData(self.selected_index) )
if data != None:
#ip, peerid, ADDFOETEMP, temp:True
self.FriendFunc(data[13], data[9], friend.ADDFOETEMP, True)
if self.ReChoke:
self.ReChoke()
del self.selected_index
def OnCBCopy(self, event):
msg = ""
index = self.GetFirstSelected()
while index != -1:
data = self.itemDataMap.get( self.GetItemData(index) )
if data != None:
msg += data[0] + '\n'
index = self.GetNextSelected(index)
if len(msg) > 0:
wx.TheClipboard.Open()
wx.TheClipboard.SetData(wx.TextDataObject(msg))
wx.TheClipboard.Close()
def OnColClick(self, event):
col = self.GetLogicalColumn( event.GetColumn() )
if self.col2sort != col:
self.ascending = False
else:
self.ascending = not self.ascending
self.col2sort = col
try:
self.SortListItems(self.col2sort, self.ascending)
except IndexError:
pass
if self.Colclickfunc != None:
self.Colclickfunc(col, self.ascending)
# Sort by first col in tie situations
def GetSecondarySortValues(self, col, key1, key2):
item1 = self.itemDataMap[key1][0]
item2 = self.itemDataMap[key2][0]
return (item1, item2)
def GetColumnSorter(self):
return self.CustColumnSorter
#Custom sorter needed to correctly sort IP addresses
def CustColumnSorter(self, key1, key2):
col = self._col
ascending = self._colSortFlag[col]
item1 = self.itemDataMap[key1][col]
item2 = self.itemDataMap[key2][col]
#if IP addresses convert to Long to enable correct sorting
if ipaddr_re.match(str(item1)) != None and ipaddr_re.match(str(item2)) != None:
item1 = LongIP(item1)
item2 = LongIP(item2)
cmpVal = cmp(item1, item2)
# If the items are equal then pick something else to make the sort value unique
if cmpVal == 0:
cmpVal = apply(cmp, self.GetSecondarySortValues(col, key1, key2))
if ascending:
return cmpVal
else:
return -cmpVal
def GetListCtrl(self):
return self
def Reset(self):
self.list_rows = {}
if self.GetItemCount() != 0:
self.DeleteAllItems()
self.Refresh(True)
self.Update()
def Populate(self, spew = [], infohash = None, rechokefunc = None, s_config=None):
if spew == None:
return
if len(spew) == 0:
self.Reset()
return
refresh_all = False
if s_config != None:
if s_config is not self.s_config:
self.s_config = s_config
refresh_all = True
else:
s_config = self.s_config
if not refresh_all and (s_config['country_flags'] != self.last_options['country_flags'] or \
s_config['reverse_dns'] != self.last_options['reverse_dns']):
self.last_options = {'reverse_dns': s_config['reverse_dns'],
'country_flags' : s_config['country_flags']}
refresh_all = True
# Optimization. Map current keys to existing indices
self.MapKey2Idx(self.itemDataMap)
completed_color = wx.Color(0,150,0)
old_list_rows = self.list_rows
self.list_rows = {}
self.itemDataMap = {}
self.infohash = infohash
self.ReChoke = rechokefunc
# Generate itemDataMap
i = 0
for c in spew:
ip = c['ip']
peer_id = c['peerid']
peer_info = self.peercache(ip, peer_id)
if self.peercache != None and s_config['reverse_dns'] and peer_info[0] != None:
address = peer_info[0]
else:
address = ip
if type(address) == types.StringType:
address = address.lower()
iurate, uinterested, uchoked = c['upload']
idrate, dinterested, dchoked, dsnubbed = c['download']
icompleted = c['completed']
# peer_info[1] = peer_info[1] + c['utotal']
# iutotal = peer_info[1]
iutotal = c['utotal']
# peer_info[2] = peer_info[2] + c['dtotal']
# idtotal = peer_info[2]
idtotal = c['dtotal']
initiation = c['initiation']
client_type = peer_info[3]
peer_name = peer_info[4]
raw_peerid = peer_info[5]
port = c['port']
have_bitfield = c['havelist']
last_send = c['last_send']
last_recv = c['last_recv']
if iutotal > 0:
utotal = str(iutotal / (1024)) + 'KB'
else:
utotal = ''
if idtotal > 0:
dtotal = str(idtotal / (1024)) + 'KB'
else:
dtotal = ''
if idrate > 0 and not dchoked and not dsnubbed:
drate = "%0.1f" % (float(idrate)/1024)
else:
idrate = 0
drate = ''
if iurate > 0 and not uchoked:
urate = "%0.1f" % (float(iurate)/1024)
else:
iurate = 0
urate = ''
completed = "%d%c" % (int(icompleted*100), '%')
key = hash(peer_id)
self.itemDataMap[key] = [address, idrate, iurate, icompleted, icompleted,
idtotal, iutotal, initiation, client_type, peer_name, last_send, last_recv, raw_peerid , ip, port, peer_id, have_bitfield, uchoked]
self.list_rows[key] = [key, address, drate, urate, completed, " ",
dtotal, utotal, initiation, client_type, peer_name, last_send, last_recv, raw_peerid, ""]
# Fill list with itemDataMap, cmp with old_list_rows
self.FillRows(old_list_rows, refresh_all)
#delete list items that are not current
deleted = False
if self.GetItemCount() != len(self.itemDataMap):
self.Freeze()
item = self.GetNextItem(-1)
i = 0
while i < self.GetItemCount() and item != -1:
nextitem = self.GetNextItem(item)
key = self.GetItemData(item)
if self.itemDataMap.has_key(key) == False:
self.DeleteItem(item)
deleted = True
else:
item = nextitem
i+=1
self.Thaw()
if not self.initsort: #inital sort on selection of torrent in master list
try:
self.initsort = True
self.SortListItems(self.col2sort, self.ascending)
except IndexError:
pass
if s_config['sort_list'] and len(self.itemDataMap) > 1 and not deleted:
try:
self.SortListItems(self.col2sort, self.ascending)
except IndexError:
pass
if not win32_flag:
self.OnPaint()
def FillRows(self, old_list_rows={}, refresh_all=True):
# Fill list with itemDataMap, cmp with old_list_rows
for key, rowdata in self.list_rows.items():
data = self.itemDataMap[key]
if refresh_all or (not old_list_rows.has_key(key) or (self.list_rows[key] != old_list_rows[key])):
#item_idx = self.InsertRow_Fast(key, rowdata[1:])
item_idx = self.InsertRow(key, rowdata[1:])
self.Insert_Ebed_Ctrl(key, data[4], data[16])
item = self.GetItem(item_idx)
item.m_mask = wx.LIST_MASK_IMAGE
idtotal = data[5]
iutotal = data[6]
uchoked = data[17]
if self.s_config['country_flags'] and self.s_config['reverse_dns']:
tld = data[0].split('.')
tld = tld[len(tld)-1]
if self.flags.has_key(tld):
if item.m_image != self.flags[tld]:
item.m_image = self.flags[tld]
else:
item.m_image = self.images[0]
else:
if idtotal > iutotal:
if item.m_image != self.images[1]:
item.m_image = self.images[1]
elif idtotal == iutotal:
if item.m_image != self.images[2]:
item.m_image = self.images[2]
elif idtotal < iutotal:
if item.m_image != self.images[3]:
item.m_image = self.images[3]
if not uchoked:
item.SetBackgroundColour(self.pl_unchoked )
else:
item.SetBackgroundColour(self.bg_color)
self.SetItem(item)
#---------------------------------------------------------------------------
class TestList(wx.Frame):
def __init__(self):
from threading import Event
from peerlistcache import PeerListCache
wx.Frame.__init__(self, None, -1, 'Test', size = wx.Size(600, 600), style = wx.DEFAULT_FRAME_STYLE)
panel = wx.Panel(self, -1)
self.doneflag = Event()
self.pl_dnsloop = PeerListCache(self.doneflag)
self.ppp = PeerList(panel, BTConfig(), cachefunc=self.pl_dnsloop.GetPeerInfo)
self.ppp.SetEbedCol(4)
testbutton1 = wx.Button(panel, 100, "test1")
testbutton2 = wx.Button(panel, 101, "test2")
wx.EVT_BUTTON(self, 100, self.OnClick1)
wx.EVT_BUTTON(self, 101, self.OnClick2)
wx.EVT_CLOSE(self, self.OnClose)
sizer = wx.FlexGridSizer(cols=1)
sizer.Add(self.ppp, 1, wx.EXPAND)
sizer.AddGrowableRow(0)
sizer.AddGrowableCol(0)
sizer.Add(testbutton1, 1)
sizer.Add(testbutton2, 1)
panel.SetSizer(sizer)
panel.Layout()
self.Show(True)
self.OnInit()
def OnInit(self):
self.pl_dnsloop.Start()
def OnClose(self, event):
self.doneflag.set()
self.pl_dnsloop.Stop()
def OnClick2(self, event):
spew = []
c = {}
for i in range(0, 2):
c = {'ip' : '%d' % i,
'upload': [1, 1, 1],
'download': [1 % 2, i % 3, i % 4, i % 5],
'completed': 1.0,
'utotal': 5000,
'dtotal': 1000,
'initiation': 'local',
'peerid': 'peerid1 %d' %i,
'port': 700,
'havelist': [1,1,1,1,1,1,1,1,1,1,1,1],
'last_send': "",
'last_recv': "",
}
spew.append(c)
self.ppp.Populate(spew)
def OnClick1(self, event):
import time
t = time.time()
spew = []
c = {}
for i in range(0, 500):
if i % 10: #send/recv data sim
data1 = "choke"
data2 = "unchoke"
else:
data1 = "unchoke"
data2 = "choke"
c = {'ip' : '%d' % i,
'upload': [0, 0, 0],
'download': [1 % 3, i % 4, i % 5, i % 6],
'completed': float(i)/(100),
'utotal': 0,
'dtotal': 0,
'initiation': 'local',
'peerid': 'peerid2 %d' %i,
'port': 7013,
'havelist': [0,1,0,1,0,1,0,1,0,1,0],
'last_send': data1,
'last_recv': data2,
}
spew.append(c)
self.ppp.Populate(spew)
## self.ppp.Reset()
## self.ppp.Populate(spew)
print time.time() - t
if __name__ == "__main__":
_ = lambda x: x # needed for gettext
app = wx.PySimpleApp()
ppp = TestList()
ppp.Show(True)
app.MainLoop()
|