#!/usr/bin/python
#########################################################################
#
# Author : Choopan RATTANAPOKA, Jie Yang, Arno Bakker
#
# Description : Main ABC [Yet Another Bittorrent Client] python script.
# you can run from source code by using
# >python abc.py
# need Python, WxPython in order to run from source code.
#########################################################################
import sys, locale
import os
import wx
#import hotshot
from threading import Thread,Timer,Event
from time import time,ctime
from traceback import print_exc,print_stack
from cStringIO import StringIO
import urllib
from interconn import ServerListener,ClientPassParam
from launchmanycore import ABCLaunchMany
from ABC.Toolbars.toolbars import ABCBottomBar2,ABCStatusBar,ABCMenuBar,ABCToolBar
from ABC.GUI.menu import ABCMenu
from ABC.Scheduler.scheduler import ABCScheduler
from webservice import WebListener
if (sys.platform == 'win32'):
from Dialogs.regdialog import RegCheckDialog
from ABC.GUI.list import ManagedList
from Utility.utility import Utility
from Utility.constants import *#IGNORE:W0611
from Swapper.__init__ import swapper_init,swapper_done
from BitTornado.__init__ import product_name
from safeguiupdate import DelayedInvocation
DEBUG = False
ALLOW_MULTIPLE = False
################################################################
#
# Class: FileDropTarget
#
# To enable drag and drop for ABC list in main menu
#
################################################################
class FileDropTarget(wx.FileDropTarget):
def __init__(self, utility):
# Initialize the wsFileDropTarget Object
wx.FileDropTarget.__init__(self)
# Store the Object Reference for dropped files
self.utility = utility
def OnDropFiles(self, x, y, filenames):
for filename in filenames:
self.utility.queue.addtorrents.AddTorrentFromFile(filename)
return True
##############################################################
#
# Class : ABCList
#
# ABC List class that contains the torrent list
#
##############################################################
class ABCList(ManagedList):
def __init__(self, parent):
style = wx.LC_REPORT|wx.LC_VRULES|wx.CLIP_CHILDREN
prefix = 'column'
minid = 4
maxid = 26
exclude = []
rightalign = [COL_PROGRESS,
COL_SIZE,
COL_DLSPEED,
COL_ULSPEED,
COL_RATIO,
COL_PEERPROGRESS,
COL_DLSIZE,
COL_ULSIZE,
COL_TOTALSPEED]
ManagedList.__init__(self, parent, style, prefix, minid, maxid, exclude, rightalign)
dragdroplist = FileDropTarget(self.utility)
self.SetDropTarget(dragdroplist)
self.lastcolumnsorted = -1
self.reversesort = 0
self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColLeftClick)
self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnItemSelected)
# Bring up advanced details on left double click
self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
# Bring up local settings on middle double click
self.Bind(wx.EVT_MIDDLE_DCLICK, self.utility.actions[ACTION_LOCALUPLOAD].action)
# Do thing when keys are pressed down
def OnKeyDown(self, event):
keycode = event.GetKeyCode()
if event.CmdDown():
if keycode == ord('a') or keycode == ord('A'):
# Select all files (CTRL-A)
self.selectAll()
elif keycode == ord('x') or keycode == ord('X'):
# Invert file selection (CTRL-X)
self.invertSelection()
elif keycode == wx.WXK_RETURN or keycode == wx.WXK_NUMPAD_ENTER:
# Open advanced details (Enter)
self.utility.actions[ACTION_DETAILS].action()
elif keycode == wx.WXK_SPACE:
# Open local settings (Space)
self.utility.actions[ACTION_LOCALUPLOAD].action()
elif keycode == 399:
# Open right-click menu (windows menu key)
self.OnItemSelected()
event.Skip()
def OnColLeftClick(self, event):
rank = event.GetColumn()
colid = self.columns.getIDfromRank(rank)
if colid == self.lastcolumnsorted:
self.reversesort = 1 - self.reversesort
else:
self.reversesort = 0
self.lastcolumnsorted = colid
self.utility.queue.sortList(colid, self.reversesort)
def selectAll(self):
self.updateSelected(select = range(0, self.GetItemCount()))
def updateSelected(self, unselect = None, select = None):
if unselect is not None:
for index in unselect:
self.SetItemState(index, 0, wx.LIST_STATE_SELECTED)
if select is not None:
for index in select:
self.Select(index)
self.SetFocus()
def getTorrentSelected(self, firstitemonly = False, reverse = False):
queue = self.utility.queue
torrentselected = []
for index in self.getSelected(firstitemonly, reverse):
ABCTorrentTemp = queue.getABCTorrent(index = index)
if ABCTorrentTemp is not None:
torrentselected.append(ABCTorrentTemp)
return torrentselected
def OnItemSelected(self, event = None):
selected = self.getTorrentSelected()
if not selected:
return
popupmenu = ABCMenu(self.utility, 'menu_listrightclick')
# Popup the menu. If an item is selected then its handler
# will be called before PopupMenu returns.
if event is None:
# use the position of the first selected item (key event)
ABCTorrentTemp = selected[0]
position = self.GetItemPosition(ABCTorrentTemp.listindex)
else:
# use the cursor position (mouse event)
position = event.GetPosition()
self.PopupMenu(popupmenu, position)
def OnLeftDClick(self, event):
event.Skip()
try:
self.utility.actions[ACTION_DETAILS].action()
except:
print_exc()
##############################################################
#
# Class : ABCPanel
#
# Main ABC Panel class
#
##############################################################
class ABCPanel(wx.Panel):
def __init__(self, parent):
style = wx.CLIP_CHILDREN
wx.Panel.__init__(self, parent, -1, style = style)
#Debug Output.
sys.stdout.write('Preparing GUI.\n');
self.utility = parent.utility
self.utility.window = self
self.queue = self.utility.queue
# List of deleting torrents events that occur when the RateManager is active
# Such events are processed after the RateManager finishes
# postponedevents is a list of tupples : each tupple contains the method of ABCPanel to be called to
# deal with the event and the event.
self.postponedevents = []
#Manual Bittorrent Adding UI
##############################
colSizer = wx.BoxSizer(wx.VERTICAL)
# List Control Display UI
###############################
self.list = ABCList(self)
self.utility.list = self.list
colSizer.Add(self.list, 1, wx.EXPAND|wx.ALL, 2)
#self.utility.bottomline2 = ABCBottomBar2(self)
#colSizer.Add(self.utility.bottomline2, 0, wx.ALL|wx.EXPAND, 3)
self.SetSizer(colSizer)
self.SetAutoLayout(True)
self.list.SetFocus()
def getSelectedList(self, event = None):
return self.list
######################################
# Update ABC on-the-fly
######################################
def updateColumns(self, force = False):
# Update display in column for inactive torrent
for ABCTorrentTemp in self.utility.torrents["all"]:
ABCTorrentTemp.updateColumns(force = force)
##############################################################
#
# Class : ABCTaskBarIcon
#
# Task Bar Icon
#
##############################################################
class ABCTaskBarIcon(wx.TaskBarIcon):
def __init__(self, parent):
wx.TaskBarIcon.__init__(self)
self.utility = parent.utility
self.TBMENU_RESTORE = wx.NewId()
# setup a taskbar icon, and catch some events from it
self.Bind(wx.EVT_TASKBAR_LEFT_DCLICK, parent.onTaskBarActivate)
self.Bind(wx.EVT_MENU, parent.onTaskBarActivate, id = self.TBMENU_RESTORE)
self.updateIcon(False)
def updateIcon(self,iconifying = False):
remove = True
mintray = self.utility.config.Read('mintray', "int")
if (mintray >= 2) or ((mintray >= 1) and iconifying):
remove = False
if remove and self.IsIconInstalled():
self.RemoveIcon()
elif not remove and not self.IsIconInstalled():
self.SetIcon(self.utility.icon, product_name)
def CreatePopupMenu(self):
menu = wx.Menu()
self.utility.actions[ACTION_STOPALL].addToMenu(menu, bindto = self)
self.utility.actions[ACTION_UNSTOPALL].addToMenu(menu, bindto = self)
menu.AppendSeparator()
menu.Append(self.TBMENU_RESTORE, self.utility.lang.get('showabcwindow'))
self.utility.actions[ACTION_EXIT].addToMenu(menu, bindto = self)
return menu
##############################################################
#
# Class : ABCFrame
#
# Main ABC Frame class that contains menu and menu bar management
# and contains ABCPanel
#
##############################################################
class ABCFrame(wx.Frame,DelayedInvocation):
def __init__(self, ID, params, utility):
self.utility = utility
self.utility.frame = self
title = self.utility.lang.get('title') + \
" " + \
self.utility.lang.get('version')
# Get window size and position from config file
size, position = self.getWindowSettings()
style = wx.DEFAULT_FRAME_STYLE | wx.CLIP_CHILDREN
wx.Frame.__init__(self, None, ID, title, position, size, style = style)
self.doneflag = Event()
DelayedInvocation.__init__(self)
# Put it here so an error is shown in the startup-error popup
self.serverlistener = ServerListener(self.utility)
self.tbicon = None
self.abc_sb = ABCStatusBar(self)
self.SetStatusBar(self.abc_sb)
try:
self.SetIcon(self.utility.icon)
except:
pass
# Don't update GUI as often when iconized
self.GUIupdate = True
# Start the scheduler before creating the ListCtrl
self.utility.queue = ABCScheduler(self.utility)
self.window = ABCPanel(self)
# Menu Options
############################
menuBar = ABCMenuBar(self)
if sys.platform == "darwin":
wx.App.SetMacExitMenuItemId(wx.ID_CLOSE)
self.SetMenuBar(menuBar)
self.tb = ABCToolBar(self)
self.SetToolBar(self.tb)
self.buddyFrame = None
self.fileFrame = None
self.buddyFrame_page = 0
self.buddyFrame_size = (800, 500)
self.buddyFrame_pos = None
self.fileFrame_size = (800, 500)
self.fileFrame_pos = None
# Menu Events
############################
self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
# self.Bind(wx.EVT_MENU, self.OnMenuExit, id = wx.ID_CLOSE)
# leaving here for the time being:
# wxMSW apparently sends the event to the App object rather than
# the top-level Frame, but there seemed to be some possibility of
# change
self.Bind(wx.EVT_QUERY_END_SESSION, self.OnCloseWindow)
self.Bind(wx.EVT_END_SESSION, self.OnCloseWindow)
try:
self.tbicon = ABCTaskBarIcon(self)
except:
pass
self.Bind(wx.EVT_ICONIZE, self.onIconify)
self.Bind(wx.EVT_SET_FOCUS, self.onFocus)
self.Bind(wx.EVT_SIZE, self.onSize)
# Check webservice for autostart webservice
#######################################################
WebListener(self.utility)
if self.utility.webconfig.Read("webautostart", "boolean"):
self.utility.webserver.start()
# Start up the controller
self.utility.controller = ABCLaunchMany(self.utility)
#self.utility.controller.start() # done by ABCLaunchMany parent
self.utility.queue.postInitTasks()
# Start single instance server listenner
############################################
self.serverthread = Thread(target = self.serverlistener.start)
self.serverthread.setDaemon(False)
self.serverthread.start()
#if server start with params run it
#####################################
if params[0] != "":
if sys.platform == "darwin":
self.utility.queue.addtorrents.AddTorrentFromFile(params[0])
else:
ClientPassParam(params[0])
sys.stdout.write('GUI Complete.\n')
self.Show(True)
# Check to see if ABC is associated with torrents
#######################################################
if (sys.platform == 'win32'):
if self.utility.config.Read('associate', "boolean"):
if not self.utility.regchecker.testRegistry():
dialog = RegCheckDialog(self)
dialog.ShowModal()
dialog.Destroy()
self.checkVersion()
def checkVersion(self):
t = Timer(2.0, self._checkVersion)
t.start()
def _checkVersion(self):
my_version = self.utility.getVersion()
try:
curr_status = urllib.urlopen('http://bit-torrent.sourceforge.net/version').readlines()
line1 = curr_status[0]
if len(curr_status) > 1:
self.update_url = curr_status[1].strip()
else:
self.update_url = 'http://bit-torrent.sourceforge.net'
_curr_status = line1.split()
self.curr_version = _curr_status[0]
if self.newversion(self.curr_version, my_version):
self.OnUpgrade()
except Exception,e:
print >> sys.stderr, "Version check failed", ctime(time()), str(e)
#print_exc()
def newversion(self, curr_version, my_version):
curr = curr_version.split('.')
my = my_version.split('.')
if len(my) >= len(curr):
nversion = len(my)
else:
nversion = len(curr)
for i in range(nversion):
if i < len(my):
my_v = int(my[i])
else:
my_v = 0
if i < len(curr):
curr_v = int(curr[i])
else:
curr_v = 0
if curr_v > my_v:
return True
elif curr_v < my_v:
return False
return False
def OnUpgrade(self, event=None):
str = self.utility.lang.get('upgradeabc')
title = self.utility.lang.get('upgradeabctitle')
mainpage = self.utility.lang.get('mainpage')
dlg = wx.MessageDialog(self, str,
title + self.curr_version,
wx.YES_NO|wx.ICON_EXCLAMATION
#wx.OK | wx.ICON_INFORMATION |
#wx.YES_NO | wx.NO_DEFAULT | wx.CANCEL | wx.ICON_INFORMATION
)
result = dlg.ShowModal()
dlg.Destroy()
if(result == wx.ID_YES):
import wx.lib.hyperlink as hl
self._hyper = hl.HyperLinkCtrl(self, wx.ID_ANY, mainpage,
URL="http://bit-torrent.sourceforge.net/")
self._hyper.GotoURL(self.update_url,True, True)
def onFocus(self, event = None):
if event is not None:
event.Skip()
self.window.getSelectedList(event).SetFocus()
def setGUIupdate(self, update):
oldval = self.GUIupdate
self.GUIupdate = update
if self.GUIupdate and not oldval:
# Force an update of all torrents
for torrent in self.utility.torrents["all"]:
torrent.updateColumns()
torrent.updateColor()
def taskbarCallback(self):
self.invokeLater(self.onTaskBarActivate,[])
#######################################
# minimize to tray bar control
#######################################
def onTaskBarActivate(self, event = None):
self.Iconize(False)
self.Show(True)
self.Raise()
if self.tbicon is not None:
self.tbicon.updateIcon(False)
self.window.list.SetFocus()
# Resume updating GUI
self.setGUIupdate(True)
def onIconify(self, event = None):
# This event handler is called both when being minimalized
# and when being restored.
if DEBUG:
if event is not None:
print "abc: onIconify(",event.Iconized()
else:
print "abc: onIconify event None"
if event.Iconized():
if (self.utility.config.Read('mintray', "int") > 0
and self.tbicon is not None):
self.tbicon.updateIcon(True)
self.Show(False)
# Don't update GUI while minimized
self.setGUIupdate(False)
else:
self.setGUIupdate(True)
if event is not None:
event.Skip()
def onSize(self, event = None):
# Arno: On Windows when I enable the tray icon and then change
# virtual desktop (see MS DeskmanPowerToySetup.exe)
# I get a onIconify(event.Iconized()==True) event, but when
# I switch back, I don't get an event. As a result the GUIupdate
# remains turned off. The wxWidgets wiki on the TaskBarIcon suggests
# catching the onSize event.
if DEBUG:
if event is not None:
print "abc: onSize:",event.GetSize()
else:
print "abc: onSize: None"
self.setGUIupdate(True)
if event is not None:
event.Skip()
def getWindowSettings(self):
width = self.utility.config.Read("window_width")
height = self.utility.config.Read("window_height")
try:
size = wx.Size(int(width), int(height))
except:
size = wx.Size(710, 400)
x = self.utility.config.Read("window_x")
y = self.utility.config.Read("window_y")
if (x == "" or y == ""):
position = wx.DefaultPosition
else:
position = wx.Point(int(x), int(y))
return size, position
def saveWindowSettings(self):
width, height = self.GetSizeTuple()
x, y = self.GetPositionTuple()
self.utility.config.Write("window_width", width)
self.utility.config.Write("window_height", height)
self.utility.config.Write("window_x", x)
self.utility.config.Write("window_y", y)
self.utility.config.Flush()
##################################
# Close Program
##################################
def OnCloseWindow(self, event = None):
# Don't do anything if the event gets called twice for some reason
if self.utility.abcquitting:
return
# Check to see if we can veto the shutdown
# (might not be able to in case of shutting down windows)
if event is not None:
try:
if event.CanVeto() and self.utility.config.Read('confirmonclose', "boolean"):
dialog = wx.MessageDialog(None, self.utility.lang.get('confirmmsg'), self.utility.lang.get('confirm'), wx.OK|wx.CANCEL)
result = dialog.ShowModal()
dialog.Destroy()
if result != wx.ID_OK:
event.Veto()
return
except:
data = StringIO()
print_exc(file = data)
sys.stderr.write(data.getvalue())
pass
self.utility.abcquitting = True
self.GUIupdate = False
# Close the Torrent Maker
self.utility.actions[ACTION_MAKETORRENT].closeWin()
try:
self.utility.webserver.stop()
except:
data = StringIO()
print_exc(file = data)
sys.stderr.write(data.getvalue())
pass
try:
# tell scheduler to close all active thread
self.utility.queue.clearScheduler()
except:
data = StringIO()
print_exc(file = data)
sys.stderr.write(data.getvalue())
pass
try:
# Restore the window before saving size and position
# (Otherwise we'll get the size of the taskbar button and a negative position)
self.onTaskBarActivate()
self.saveWindowSettings()
except:
data = StringIO()
print_exc(file = data)
sys.stderr.write(data.getvalue())
pass
try:
if self.buddyFrame is not None:
self.buddyFrame.Destroy()
if self.fileFrame is not None:
self.fileFrame.Destroy()
except:
pass
try:
if self.tbicon is not None:
self.tbicon.RemoveIcon()
self.tbicon.Destroy()
self.Destroy()
except:
data = StringIO()
print_exc(file = data)
sys.stderr.write(data.getvalue())
pass
# Arno: at the moment, Swapper gets a segmentation fault when the
# tray icon is always enabled. This SEGV occurs in the wx mainloop
# which is entered as soon as we leave this method. Hence I placed
# swapper_done() here, so the database are closed properly
# before the crash.
swapper_done(self.utility.getConfigPath())
def onWarning(self,exc):
# TODO: here we can use self.utility.lang
msg = "An non-fatal error occured during Swapper startup, you may need to change the network Preferences:\n\n"
msg += str(exc.__class__)+':'+str(exc)
dlg = wx.MessageDialog(None, msg, "Swapper Warning", wx.OK|wx.ICON_WARNING)
result = dlg.ShowModal()
dlg.Destroy()
##############################################################
#
# Class : ABCApp
#
# Main ABC application class that contains ABCFrame Object
#
##############################################################
class ABCApp(wx.App):
def __init__(self, x, params, single_instance_checker, abcpath):
self.params = params
self.single_instance_checker = single_instance_checker
self.error = None
try:
self.utility = Utility(abcpath)
swapper_init(self.utility.getConfigPath(),self.utility.getPath())
self.utility.setSwapperVariables()
# Set locale to determine localisation
locale.setlocale(locale.LC_ALL, '')
sys.stdout.write('Client Starting Up.\n')
sys.stdout.write('Build: ' + self.utility.lang.get('build') + '\n')
except Exception,e:
print_exc()
self.error = e
wx.App.__init__(self, x)
def MacOpenFile(self,filename):
self.utility.queue.addtorrents.AddTorrentFromFile(filename)
def OnInit(self):
if self.error is not None:
self.onError()
return False
try:
self.utility.postAppInit()
self.frame = ABCFrame(-1, self.params, self.utility)
self.Bind(wx.EVT_QUERY_END_SESSION, self.frame.OnCloseWindow)
self.Bind(wx.EVT_END_SESSION, self.frame.OnCloseWindow)
except Exception,e:
self.error = e
self.onError()
return False
return True
def onError(self):
# Don't use language independence stuff, self.utility may not be
# valid.
msg = "An error occured during Swapper startup:\n\n"
msg += str(self.error.__class__)+':'+str(self.error)
dlg = wx.MessageDialog(None, msg, "Swapper Fatal Error", wx.OK|wx.ICON_ERROR)
result = dlg.ShowModal()
dlg.Destroy()
def OnExit(self):
if not ALLOW_MULTIPLE:
del self.single_instance_checker
ClientPassParam("Close Connection")
return 0
##############################################################
#
# Main Program Start Here
#
##############################################################
def run(params = None):
if params is None:
params = [""]
if len(sys.argv) > 1:
params = sys.argv[1:]
# Create single instance semaphore
single_instance_checker = wx.SingleInstanceChecker("swapper-" + wx.GetUserId())
if not ALLOW_MULTIPLE and single_instance_checker.IsAnotherRunning():
#Send torrent info to abc single instance
ClientPassParam(params[0])
else:
abcpath = os.path.abspath(os.path.dirname(sys.argv[0]))
# Arno: don't chdir to allow testing as other user from other dir.
#os.chdir(abcpath)
# Launch first abc single instance
app = ABCApp(0, params, single_instance_checker, abcpath)
app.MainLoop()
if __name__ == '__main__':
run()
|