Beispiel #1
0
 def __init__( self, seedNodes, reactor ) :
     QMainWindow.__init__( self )
     self.ui = Ui_MainWindow()
     self.ui.setupUi( self )
     self._setupIcons()
     self.seedNodes = seedNodes
     self.reactor = reactor
     self.session = UserSession( seedNodes, reactor )
     self.sm = self.session.sm
     self.ev = Eventer()
     self.profile = None
     self.baseTitle = str(self.windowTitle())
     self.exitAfterOffline = False
     self.reconnector = None
     self.keyInfoDialog = None
     self.addContactDialog = None
     self.sm.insertCallback( self._onStateChange )
     self.connect( self.ui.createKeyButton, SIGNAL('clicked()'), self.ui.actionCreateKey.trigger )
     self.connect( self.ui.createKeyButton1, SIGNAL('clicked()'), self.ui.actionCreateKey.trigger )
     self.connect( self.ui.goOnlineButton, SIGNAL('clicked()'), self.ui.actionGoOnline.trigger )
     self.ui.contacts.setWrapping( True )
     self.connect( self.ui.contacts, SIGNAL('customContextMenuRequested(const QPoint&)'),
             self._onContactsContextMenuRequested )
     self.connect( self.ui.contacts, SIGNAL('itemDoubleClicked(QListWidgetItem*)'),
             self._onContactDoubleClicked )
     self.contextMenu = QMenu()
     self.contextMenu.addAction( self.ui.actionCheckStatus )
     self.contextMenu.addAction( self.ui.actionRemoveContact )
     self.contextMenu.addAction( self.ui.actionContactInfo )
     self.connect( self.contextMenu, SIGNAL('triggered(QAction*)'), self._onContextMenuAction )
     self.actionManager = ActionManager( self.contextMenu )
     self.appletServer = AppletServer( self.session, self.actionManager, self.reactor )
     self.appletServer.registerServices()
     self.appletServer.registerActions()
     self.statusChecker = StatusChecker( self.ui.contacts, self.session, self.ev, self.reactor )
     self.contactInfoDialogs = {}
     self.trayMenu = QMenu( self )
     self.trayMenu.addAction( self.ui.actionExit )
     if HAS_TRAY :
         self.trayIcon = TrayIcon( QPixmap(':/images/cspace_offline.png'), 'CSpace', self.trayMenu, self )
         self.connect( self.trayIcon, SIGNAL('clicked(const QPoint&,int)'), self._onTrayClicked )
         self.trayIcon.show()
     else :
         self.trayIcon = None
     self.editPermissionsDialog = None
     self.autoUpdater = AutoUpdater( self.reactor )
     self.autoUpdater.setUpdateCallback( self._onUpdateDownloaded )
     self._updateUI()
     self._doCheckSettings()
Beispiel #2
0
    def __init__(self, seedNodes, settings=None, reactor=None):
        global logger
        logger.info("CSpaceService init")
        if reactor is None:
            reactor = SelectReactor()

        if settings is None:
            settings = localSettings()

        self.reactor = reactor
        self.settings = settings
        self.dispatcher = Eventer()
        self.seedNodes = seedNodes

        # Session Manager
        self.session = UserSession(self.seedNodes, reactor)

        # Our user profile
        self.profile = None

        # Session status State Machine
        self.sm = self.session.sm
        # Make sure we handle the edges/state transitions
        self.sm.insertCallback(self._onStateChange)
        # Manage actions
        self.actionManager = ActionManager()
        # Loads applets in subprocesses
        self.appletServer = AppletServer(self.session, self.actionManager, self.reactor)

        # Our contacts' statii
        self.statusProbe = ContactStatusChecker(self, reactor)

        # Our current status
        self.status = self.sm.current()
        self.statusmsg = self.STATII[self.status]
        self.exitAfterOffline = False

        self.reactor.callLater(0, self._onStarted)
Beispiel #3
0
class CSpaceService:
    OFFLINE = UserSession.OFFLINE
    CONNECTING = UserSession.CONNECTING
    ONLINE = UserSession.ONLINE
    DISCONNECTING = UserSession.DISCONNECTING
    UNKNOWN = -1

    STATII = {
        OFFLINE: "Offline",
        CONNECTING: "Connecting",
        ONLINE: "Online",
        DISCONNECTING: "Disconnecting",
        UNKNOWN: "Unknown",
    }

    ##########
    ## Service initialization
    ##
    def __init__(self, seedNodes, settings=None, reactor=None):
        global logger
        logger.info("CSpaceService init")
        if reactor is None:
            reactor = SelectReactor()

        if settings is None:
            settings = localSettings()

        self.reactor = reactor
        self.settings = settings
        self.dispatcher = Eventer()
        self.seedNodes = seedNodes

        # Session Manager
        self.session = UserSession(self.seedNodes, reactor)

        # Our user profile
        self.profile = None

        # Session status State Machine
        self.sm = self.session.sm
        # Make sure we handle the edges/state transitions
        self.sm.insertCallback(self._onStateChange)
        # Manage actions
        self.actionManager = ActionManager()
        # Loads applets in subprocesses
        self.appletServer = AppletServer(self.session, self.actionManager, self.reactor)

        # Our contacts' statii
        self.statusProbe = ContactStatusChecker(self, reactor)

        # Our current status
        self.status = self.sm.current()
        self.statusmsg = self.STATII[self.status]
        self.exitAfterOffline = False

        self.reactor.callLater(0, self._onStarted)

    def _onStarted(self):
        self.dispatcher.trigger("service.start")

    def _error(self, msg):
        logger.error(msg)
        self.dispatcher.trigger("error", msg)
        return (False, msg)

    def run(self):
        self.reactor.run()
        try:
            self.appletServer.bridgeThread.reactor.stop()
        except AttributeError:
            pass

    def svcstatus(self):
        return self.statusmsg

    def stop(self):
        # offline first
        if self.status != self.OFFLINE:
            self.exitAfterOffline = True
            return self.offline()

        for reactor in (self.appletServer.bridgeThread.reactor, self.reactor):
            reactor.stop()

        self.dispatcher.trigger("service.stop")
        return True

    ##########
    ## Go online and offline, change profiles
    ##
    def switch(self, profile, password):
        if self.status is not self.OFFLINE:
            return self._error("Not currently offline.")

        st = self.settings
        logger.debug("Changing profile...")
        profiles = [userName for userName, keyId, entry in listProfiles()]
        if not st.getInt("Settings/RememberKey", 0):
            return self._error("No key found.")

        if not profile in entries:
            return self._error("No profile %s found.")

        profile = loadProfile(profile, password)
        if not profile:
            return self._error("Profile couldn't be loaded.")

        st.setString("Settings/SavedProfile", profile.name)
        st.setString("Settings/SavedPassword", password)
        saveLocalSettings()

        self.dispatcher.trigger("profile.switch", profile)
        return True

    def online(self):
        global logger
        if self.status is not self.OFFLINE:
            return self._error("Not currently offline.")

        st = self.settings
        logger.debug("Going online...")
        entries = [entry for userName, keyId, entry in listProfiles()]
        if not st.getInt("Settings/RememberKey", 0):
            return self._error("No key found.")

        entry = st.getString("Settings/SavedProfile")
        password = st.getString("Settings/SavedPassword")
        if not (entry and password and (entry in entries)):
            return self._error("No profile or password.")

        profile = loadProfile(entry, password)
        if not profile:
            return self._error("Profile couldn't be loaded.")

        self.profile = profile
        logger.debug("Going online as %s keyID=%s ..." % (profile.name, profile.keyId))
        self.session.goOnline(self.profile)
        self.reconnector = Reconnector(self.profile, self._onReconnectTimer, self.reactor)

        return True

    def _onReconnectTimer(self):
        assert self.reconnector
        if self.reconnector.timeLeft > 0:
            return
        self.profile = self.reconnector.profile
        self.reconnector.shutdown()
        if self.session.sm.current() == 0:
            self.session.goOnline(self.profile)
            self.reconnector = Reconnector(self.profile, self._onReconnectTimer, self.reactor)
            self.dispatcher.trigger("profile.reconnecting", self.profile)

    def offline(self):
        if self.status is not self.ONLINE:
            return self._error("Not currently online.")
        self.session.goOffline()
        self.profile = None
        self.reconnector.shutdown()
        self.reconnector = None
        return True

    def _onStateChange(self):
        """ Respond to connecting/online/disconnecting/offline notices """

        self.status = self.sm.current()
        self.statusmsg = None

        # No longer online
        if self.sm.previous() == self.ONLINE:
            # Stop checking contact statii
            self.statusProbe.stopProbing()
            # Clean up any open applets
            self.appletServer.clearConnections()

        # Now offline
        if self.sm.current() == self.OFFLINE:
            profile = self.profile
            self.profile = None
            if self.reconnector:
                if self.sm.previous() == self.CONNECTING:
                    msg = "Connect failed."
                else:
                    msg = "Disconnected."
                self.reconnector.startReconnectTimer(msg)
                self.statusmsg = msg
            self.dispatcher.trigger("profile.offline", profile)
            if self.exitAfterOffline:
                self.stop()

        # Now online
        elif self.sm.current() == self.ONLINE:
            # Restart checking contact statii
            self.dispatcher.trigger("profile.online", self.profile)
            self.statusProbe.startProbing()

        # Now connecting
        elif self.sm.current() == self.CONNECTING:
            self.dispatcher.trigger("profile.connecting", self.profile)
        elif self.sm.current() == self.DISCONNECTING:
            self.dispatcher.trigger("profile.disconnecting", self.profile)

        if not self.statusmsg:
            self.statusmsg = self.STATII[self.status]

        logger.info("STATUS: " + self.statusmsg)

    ##########
    ## List, add and remove contacts
    ##
    def list(self):
        if self.status != self.ONLINE:
            return self._error("Not connected.")

        contactsByName = self.profile.contactNames
        statii = []
        STATII = CSpaceService.STATII
        for key in contactsByName:
            contact = contactsByName[key]
            if hasattr(contact, "status"):
                buddystatus = (key, contact.status, STATII[contact.status])
            else:
                buddystatus = (key, -1, STATII[-1])
            statii.append(buddystatus)

        statii.sort()
        return statii

    def add(self, cname, keyid):
        # Lookup the contact info and call _doAddContact
        if self.status != self.ONLINE:
            return self._error("Can't lookup buddy while not online.")
        httpOp = HttpRequest(self.reactor).get(
            # 'http://cspace.in/pubkey/%s' % keyid,
            "http://identity.datahaven.net/cspacekeys/%s" % keyid,
            self._onLookupResponse,
        )
        self._addOp = AsyncOp(self._onAddContact, httpOp.cancel)
        self._addOp.cname = cname
        return True

    def addKey(self, cname, key):
        if cname and not isValidUserName(cname):
            return self._error("Bad username.")

        k = RSAKey()
        try:
            k.fromPEM_PublicKey(pemPublicKey)
        except RSAError:
            return self._error("Bad PEM-encoded key.")

        contact = Contact(k, cname)
        self._onAddContact(contact)

    def _onLookupResponse(self, responseCode, data):
        if responseCode != 200:
            self._addOp.notify(None)
            return
        inp = StringIO.StringIO(data)
        name = inp.readline().strip()
        pemPublicKey = inp.read()
        if name and not isValidUserName(name):
            self._addOp.notify(None)
            return

        k = RSAKey()
        try:
            k.fromPEM_PublicKey(pemPublicKey)
        except RSAError:
            self._addOp.notify(None)

        contact = Contact(k, self._addOp.cname)
        self._addOp.notify(contact)

    def _onAddContact(self, contact):
        del self._addOp
        if contact == None:
            # Contact lookup failed.
            return

        self.profile.addContact(contact)
        saveProfileContacts(self.profile)
        self.dispatcher.trigger("contact.add", contact)

    def remove(self, cname=None):
        if self.status != self.ONLINE:
            return self._error("Can't modify contact list while not online.")
        # Lookup the contact in profile and remove it
        contact = self.profile.getContactByName(cname)
        if contact is None:
            return self._error("No such contact.")
        self.profile.removeContact(contact)
        saveProfileContacts(self.profile)
        self.dispatcher.trigger("contact.remove", contact)
        return True

    #########
    ## Perform contact action
    ##
    def action(self, buddy, action):
        if self.status != self.ONLINE:
            return False

        actionManager = self.actionManager
        if buddy not in self.profile.contactNames:
            return self._error("Bad buddy name: %s" % buddy)

        for actionItem in actionManager.actions:
            if actionItem[4] == action:
                logger.info("Running action %s on %s" % (action, buddy))
                retVal = actionManager.execAction(action, buddy)
                self.dispatcher.trigger("contact.action", self.profile.contactNames[buddy], action, retVal)
                return retVal
            else:
                logger.info("Not action %s" % actionItem[4])

        return self._error("Bad action name: %s" % action)

    def probe(self, buddy):
        if self.status != self.ONLINE:
            return self._error("Not online.")

        if buddy not in self.profile.contactNames:
            return self._error("Bad buddy name: %s" % buddy)

        self.statusProbe.probeContactStatus(buddy)
        return True

    def contactinfo(self, buddy):
        if self.status != self.ONLINE:
            return self._error("Not online.")

        if buddy not in self.profile.contactNames:
            return self._error("Bad buddy name: %s" % buddy)

        contact = self.profile.contactNames[buddy]
        contactKey = hexEncode(contact.publicKeyData)
        if hasattr(contact, "status"):
            contactStatus = contact.status
        else:
            contactStatus = -1
        return [contact.name, contactKey, self.STATII[contactStatus]]
Beispiel #4
0
class MainWindow( QMainWindow ) :
    OFFLINE = UserSession.OFFLINE
    CONNECTING = UserSession.CONNECTING
    ONLINE = UserSession.ONLINE
    DISCONNECTING = UserSession.DISCONNECTING

    def __init__( self, seedNodes, reactor ) :
        QMainWindow.__init__( self )
        self.ui = Ui_MainWindow()
        self.ui.setupUi( self )
        self._setupIcons()
        self.seedNodes = seedNodes
        self.reactor = reactor
        self.session = UserSession( seedNodes, reactor )
        self.sm = self.session.sm
        self.ev = Eventer()
        self.profile = None
        self.baseTitle = str(self.windowTitle())
        self.exitAfterOffline = False
        self.reconnector = None
        self.keyInfoDialog = None
        self.addContactDialog = None
        self.sm.insertCallback( self._onStateChange )
        self.connect( self.ui.createKeyButton, SIGNAL('clicked()'), self.ui.actionCreateKey.trigger )
        self.connect( self.ui.createKeyButton1, SIGNAL('clicked()'), self.ui.actionCreateKey.trigger )
        self.connect( self.ui.goOnlineButton, SIGNAL('clicked()'), self.ui.actionGoOnline.trigger )
        self.ui.contacts.setWrapping( True )
        self.connect( self.ui.contacts, SIGNAL('customContextMenuRequested(const QPoint&)'),
                self._onContactsContextMenuRequested )
        self.connect( self.ui.contacts, SIGNAL('itemDoubleClicked(QListWidgetItem*)'),
                self._onContactDoubleClicked )
        self.contextMenu = QMenu()
        self.contextMenu.addAction( self.ui.actionCheckStatus )
        self.contextMenu.addAction( self.ui.actionRemoveContact )
        self.contextMenu.addAction( self.ui.actionContactInfo )
        self.connect( self.contextMenu, SIGNAL('triggered(QAction*)'), self._onContextMenuAction )
        self.actionManager = ActionManager( self.contextMenu )
        self.appletServer = AppletServer( self.session, self.actionManager, self.reactor )
        self.appletServer.registerServices()
        self.appletServer.registerActions()
        self.statusChecker = StatusChecker( self.ui.contacts, self.session, self.ev, self.reactor )
        self.contactInfoDialogs = {}
        self.trayMenu = QMenu( self )
        self.trayMenu.addAction( self.ui.actionExit )
        if HAS_TRAY :
            self.trayIcon = TrayIcon( QPixmap(':/images/cspace_offline.png'), 'CSpace', self.trayMenu, self )
            self.connect( self.trayIcon, SIGNAL('clicked(const QPoint&,int)'), self._onTrayClicked )
            self.trayIcon.show()
        else :
            self.trayIcon = None
        self.editPermissionsDialog = None
        self.autoUpdater = AutoUpdater( self.reactor )
        self.autoUpdater.setUpdateCallback( self._onUpdateDownloaded )
        self._updateUI()
        self._doCheckSettings()

    def _setupIcons( self ) :
        def makeIcon( name ) :
            icon = QIcon()
            for res in '16 22 32'.split() :
                icon.addFile( ':/images/%s%s.png' % (name,res) )
            return icon
        actionIcons = (
                'CreateKey register',
                'GoOnline connect',
                'GoOffline disconnect',
                'Exit exit',
                'AddContact user_add',
                'RefreshStatus refresh',
                'CheckStatus refresh',
                'ContactInfo contact_info',
                'RemoveContact user_remove',
                'EditPermissions edit_permissions',
                'KeyInfo key_info' )
        for s in actionIcons :
            name,icon = s.split()
            action = getattr( self.ui, 'action%s'%name )
            action.setIcon( makeIcon(icon) )

    def _updateUI( self ) :
        state = self.sm.current()
        online = (state == self.ONLINE)
        offline = (state == self.OFFLINE)
        connecting = (state == self.CONNECTING)
        disconnecting = (state == self.DISCONNECTING)
        self.ui.actionCreateKey.setEnabled( offline )
        self.ui.actionGoOnline.setEnabled( offline )
        self.ui.actionGoOffline.setEnabled( online )
        self.ui.actionKeyInfo.setEnabled( online )
        self.ui.actionExit.setEnabled( True )
        self.ui.actionAddContact.setEnabled( online )
        self.ui.actionRefreshStatus.setEnabled( online )
        self.ui.actionCheckStatus.setEnabled( online )
        self.ui.actionContactInfo.setEnabled( online )
        self.ui.actionRemoveContact.setEnabled( online )
        self.ui.actionEditPermissions.setEnabled( online )
        self.ui.actionAboutCSpace.setEnabled( True )
        if online : msg = 'Online'
        if offline : msg = 'Offline'
        if connecting : msg = 'Going Online...'
        if disconnecting : msg = 'Going Offline...'
        if not offline :
            msg = '%s - %s' % (self.profile.name,msg)
        self.setWindowTitle( ' - '.join([msg,self.baseTitle]) )
        self.ui.createKeyButton.setEnabled( offline )
        self.ui.createKeyButton1.setEnabled( offline )
        self.ui.goOnlineButton.setEnabled( offline )
        self.ui.connectCancelButton.setEnabled( connecting or disconnecting )
        if online :
            self.ui.stack.setCurrentWidget( self.ui.contactsPage )
        elif offline :
            if self.reconnector :
                assert self.reconnector.reconnecting
                self.ui.connectCancelButton.setEnabled( True )
                self.ui.stack.setCurrentWidget( self.ui.connectingPage )
                self.ui.connectStatus.setText('<b>%s<br/>Reconnecting in %d second(s)...</b>'
                        % (self.reconnector.errorMsg, self.reconnector.timeLeft) )
            elif listProfiles() :
                self.ui.stack.setCurrentWidget( self.ui.offlinePage )
            else :
                self.ui.stack.setCurrentWidget( self.ui.offlineNoUsersPage )
        elif connecting :
            self.ui.stack.setCurrentWidget( self.ui.connectingPage )
            self.ui.connectStatus.setText( '<b>Connecting...</b>' )
        elif disconnecting :
            self.ui.stack.setCurrentWidget( self.ui.connectingPage )
            self.ui.connectStatus.setText( '<b>Disconnecting...</b>' )

    def _onStateChange( self ) :
        if self.sm.previous() == self.ONLINE :
            self.statusChecker.shutdown()
            self.appletServer.clearConnections()
            for dlg in self.contactInfoDialogs.values() :
                dlg.close()
            assert not self.contactInfoDialogs
            if self.editPermissionsDialog :
                self.editPermissionsDialog.close()
            assert not self.editPermissionsDialog
            self.ui.contacts.clear()
            if self.trayIcon is not None :
                self.trayIcon.setIcon( QPixmap(':/images/cspace_offline.png') )
                self.trayIcon.setToolTip( 'CSpace' )
        if self.sm.current() == self.OFFLINE :
            self.profile = None
            if self.reconnector :
                if self.sm.previous() == self.CONNECTING :
                    msg = 'Connect failed.'
                else :
                    msg = 'Disconnected.'
                self.reconnector.startReconnectTimer( msg )
            if self.exitAfterOffline :
                self.reactor.callLater( 0, self.ui.actionExit.trigger )
        elif self.sm.current() == self.ONLINE :
            self.ui.contacts.clear()
            for contactName in self.profile.getContactNames() :
                contact = self.profile.getContactByName( contactName )
                self._addContactItem( contact )
            self.statusChecker.initialize()
            if self.trayIcon is not None :
                self.trayIcon.setIcon( QPixmap(':/images/cspace_online.png') )
                self.trayIcon.setToolTip( 'CSpace - %s' % self.profile.name )
        self._updateUI()

    def _doCheckSettings( self ) :
        st = localSettings()
        windowWidth = st.getInt( 'Settings/WindowWidth', 0 )
        windowHeight = st.getInt( 'Settings/WindowHeight', 0 )
        if (windowWidth > 0) and (windowHeight > 0) :
            windowWidth = max( windowWidth, 100 )
            windowHeight = max( windowHeight, 100 )
            self.resize( QSize(windowWidth,windowHeight).expandedTo(self.minimumSizeHint()) )
        profiles = listProfiles()
        entries = [entry for userName,keyId,entry in profiles]
        if st.getInt('Settings/RememberKey',0) :
            entry = st.getString('Settings/SavedProfile')
            password = st.getString('Settings/SavedPassword')
            if entry and password and (entry in entries) :
                profile = loadProfile( entry, password )
                if profile :
                    self._doGoOnline( profile )

    def _sortContacts( self ) :
        self.ui.contacts.sortItems()

    def _addContactItem( self, contact ) :
        contactItem = QListWidgetItem( QIcon(':/images/user_offline.png'),
                contact.name, self.ui.contacts )
        self._sortContacts()
        self.statusChecker.check( contactItem )

    def _cancelReconnect( self ) :
        if self.reconnector :
            self.reconnector.shutdown()
            self.reconnector = None
            self._updateUI()

    @pyqtSignature( '' )
    def on_connectCancelButton_clicked( self ) :
        self._cancelReconnect()
        self.exitAfterOffline = False
        self.session.shutdown()

    @pyqtSignature( '' )
    def on_actionCreateKey_triggered( self ) :
        self._cancelReconnect()
        dlg = CreateKeyDialog( self, self.reactor )
        result = dlg.exec_()
        if result == QDialog.Rejected : return
        userName = dlg.userName
        password = dlg.password
        rsaKey = dlg.rsaKey
        keyId = dlg.keyId
        profile = createProfile( rsaKey, password, userName, keyId )
        dlg = CreateKeyDoneDialog( self, keyId )
        result = dlg.exec_()
        if result == QDialog.Rejected : return
        st = localSettings()
        if dlg.rememberKey :
            st.setInt( 'Settings/RememberKey', 1 )
            st.setString( 'Settings/SavedProfile', profile.storeEntry )
            st.setString( 'Settings/SavedPassword', password )
        else :
            st.setInt( 'Settings/RememberKey', 0 )
            st.remove( 'Settings/SavedProfile' )
            st.remove( 'Settings/SavedPassword' )
        self._updateUI()
        self._doGoOnline( profile )

    @pyqtSignature( '' )
    def on_actionGoOnline_triggered( self ) :
        self._cancelReconnect()
        dlg = GoOnlineDialog( self )
        if not dlg.profiles :
            QMessageBox.warning( self, 'No Private Keys',
                    'No Private Keys have been created. Please create your Private Key first.' )
            self.ui.actionCreateKey.trigger()
            return
        result = dlg.exec_()
        if result == QDialog.Rejected : return
        self._doGoOnline( dlg.profile )

    def _doGoOnline( self, profile ) :
        self.profile = profile
        self.session.goOnline( self.profile )
        self.reconnector = Reconnector( self.profile,
                self._onReconnectTimer, self.reactor )

    def _onReconnectTimer( self ) :
        assert self.reconnector
        if self.reconnector.timeLeft > 0 :
            self._updateUI()
            return
        self.profile = self.reconnector.profile
        self._cancelReconnect()
        self.session.goOnline( self.profile )
        self.reconnector = Reconnector( self.profile,
                self._onReconnectTimer, self.reactor )

    @pyqtSignature( '' )
    def on_actionGoOffline_triggered( self ) :
        self._cancelReconnect()
        self.session.goOffline()

    @pyqtSignature( '' )
    def on_actionKeyInfo_triggered( self ) :
        assert self.sm.current() == self.ONLINE
        if self.keyInfoDialog :
            self.keyInfoDialog.show()
            self.keyInfoDialog.activateWindow()
            return
        def onDialogClose() :
            self.keyInfoDialog = None
            self.sm.removeCallback( callbackId )
        def onStateChange() :
            self.keyInfoDialog.close()
            assert not self.keyInfoDialog
        dlg = KeyInfoDialog( self, self.profile, onDialogClose )
        dlg.show()
        self.keyInfoDialog = dlg
        callbackId = self.sm.insertCallback( onStateChange,
                src=self.ONLINE )

    @pyqtSignature( '' )
    def on_actionExit_triggered( self ) :
        self._cancelReconnect()
        if self.sm.current() == self.ONLINE :
            self.exitAfterOffline = True
            self.session.goOffline()
        else :
            self.exitAfterOffline = False
            self.appletServer.shutdown()
            self.session.shutdown()
            if self.trayIcon is not None :
                self.trayIcon.hide()
            st = localSettings()
            st.setInt( 'Settings/WindowWidth', self.width() )
            st.setInt( 'Settings/WindowHeight', self.height() )
            qApp.exit( 0 )

    @pyqtSignature( '' )
    def on_actionAddContact_triggered( self ) :
        assert self.sm.current() == self.ONLINE
        if self.addContactDialog :
            self.addContactDialog.show()
            self.addContactDialog.activateWindow()
            return
        def onDialogClose() :
            self.addContactDialog = None
            self.sm.removeCallback( callbackId )
        def onAddContact( contact ) :
            self.profile.addContact( contact )
            self._addContactItem( contact )
            saveProfileContacts( self.profile )
        def onStateChange() :
            self.addContactDialog.close()
            assert not self.addContactDialog
        dlg = AddContactDialog( self, self.reactor, self.profile,
                onAddContact, onDialogClose )
        dlg.show()
        self.addContactDialog = dlg
        callbackId = self.sm.insertCallback( onStateChange,
                src=self.ONLINE )

    @pyqtSignature( '' )
    def on_actionRefreshStatus_triggered( self ) :
        assert self.sm.current() == self.ONLINE
        for i in range(self.ui.contacts.count()) :
            item = self.ui.contacts.item( i )
            self.statusChecker.check( item )

    def _onContactsContextMenuRequested( self, pos ) :
        item = self.ui.contacts.itemAt( pos )
        if item is None : return
        curItem = self.ui.contacts.currentItem()
        if item is not curItem : return
        self.contextMenu.popup( self.ui.contacts.mapToGlobal(pos) )

    def _onContactDoubleClicked( self, item ) :
        assert self.sm.current() == self.ONLINE
        item = self.ui.contacts.currentItem()
        if item is None : return
        self.actionManager.execDefaultAction( str(item.text()) )

    def _onContextMenuAction( self, action ) :
        assert self.sm.current() == self.ONLINE
        item = self.ui.contacts.currentItem()
        if item is None : return
        self.actionManager.execAction( action, str(item.text()) )

    @pyqtSignature( '' )
    def on_actionCheckStatus_triggered( self ) :
        assert self.sm.current() == self.ONLINE
        item = self.ui.contacts.currentItem()
        if item is None : return
        self.statusChecker.check( item )

    @pyqtSignature( '' )
    def on_actionRemoveContact_triggered( self ) :
        assert self.sm.current() == self.ONLINE
        item = self.ui.contacts.currentItem()
        if item is None : return
        contact = self.profile.getContactByName( str(item.text()) )
        self.ev.trigger( 'contact.remove', item, contact )
        self.ui.contacts.takeItem( self.ui.contacts.row(item) )
        self.profile.removeContact( contact )
        saveProfileContacts( self.profile )
        permissions = self.session.getPermissions()
        permissions.removeContact( contact.name )
        if permissions.isModified() :
            permissions.savePermissions()

    @pyqtSignature( '' )
    def on_actionContactInfo_triggered( self ) :
        assert self.sm.current() == self.ONLINE
        item = self.ui.contacts.currentItem()
        if item is None : return
        dlg = self.contactInfoDialogs.get( item )
        if dlg is None :
            contact = self.profile.getContactByName( str(item.text()) )
            # FIXME: why is obj needed?
            obj = Dummy()
            def onUpdateName( newName ) :
                oldName = contact.name
                self.profile.changeContactName( oldName, newName )
                saveProfileContacts( self.profile )
                item.setText( newName )
                self._sortContacts()
                permissions = self.session.getPermissions()
                permissions.changeContactName( oldName, newName )
                if permissions.isModified() :
                    permissions.savePermissions()
            def onClose() :
                if obj.callbackId is not None :
                    self.ev.remove( obj.callbackId )
                del self.contactInfoDialogs[item]
            def onContactRemoved( removedItem, contact ) :
                if removedItem is item :
                    dlg.close()
            dlg = ContactInfoDialog( self, contact, self.profile,
                    onUpdateName, onClose )
            self.contactInfoDialogs[item] = dlg
            obj.callbackId = self.ev.register( 'contact.remove',
                    onContactRemoved )
        dlg.show()
        dlg.activateWindow()

    @pyqtSignature( '' )
    def on_actionEditPermissions_triggered( self ) :
        assert self.sm.current() == self.ONLINE
        if self.editPermissionsDialog :
            self.editPermissionsDialog.show()
            self.editPermissionsDialog.activateWindow()
            return
        def onClose() :
            self.editPermissionsDialog = None
        dlg = PermissionsDialog( self, self.session.getPermissions(),
                onClose )
        dlg.show()
        self.editPermissionsDialog = dlg

    @pyqtSignature( '' )
    def on_actionAboutCSpace_triggered( self ) :
        buildNumber = currentBuildNumber()
        listenPortMsg = ''
        if self.sm.current() == self.ONLINE :
            listenPortMsg = 'Applet Server Port: %d\n' % \
                    self.appletServer.getListenPort()
        QMessageBox.information( self, 'About CSpace',
                'CSpace Peer-to-Peer Communication Platform\n'+
                'Build %d\n' % buildNumber +
                listenPortMsg +
                '(c) Tachyon Technologies 2006',
                QMessageBox.Ok )

    def _onTrayClicked( self, pos, button ) :
        if (self.isMinimized()) or (not self.isVisible()) :
            self.showNormal()
        self.activateWindow()

    def _onUpdateDownloaded( self, updateFileName, updateFileData ) :
        def onInstall() :
            installerFile = self.autoUpdater.saveInstaller(
                    updateFileName, updateFileData )
            global execFileAfterExit
            execFileAfterExit = installerFile
            self.ui.actionExit.trigger()
            self.notifyWindow.close()
        self.notifyWindow = UpdateNotifyWindow( self.reactor,
                onInstall )

    def closeEvent( self, ev ) :
        self.showMinimized()
        if self.trayIcon is not None :
            self.hide()
        ev.ignore()