diff options
| -rw-r--r-- | pkg/requirements.pip | 1 | ||||
| -rw-r--r-- | src/leap/bitmask/gui/mail_status.py | 105 | ||||
| -rw-r--r-- | src/leap/bitmask/gui/mainwindow.py | 195 | ||||
| -rw-r--r-- | src/leap/bitmask/gui/statemachines.py | 414 | ||||
| -rw-r--r-- | src/leap/bitmask/services/connections.py | 10 | ||||
| -rw-r--r-- | src/leap/bitmask/services/eip/connection.py | 2 | ||||
| -rw-r--r-- | src/leap/bitmask/services/mail/conductor.py | 383 | ||||
| -rw-r--r-- | src/leap/bitmask/services/mail/connection.py | 103 | ||||
| -rw-r--r-- | src/leap/bitmask/services/soledad/soledadbootstrapper.py | 4 | 
9 files changed, 1007 insertions, 210 deletions
| diff --git a/pkg/requirements.pip b/pkg/requirements.pip index 458db39c..98610fbb 100644 --- a/pkg/requirements.pip +++ b/pkg/requirements.pip @@ -16,6 +16,7 @@ twisted  qt4reactor  python-daemon # this should not be needed for Windows.  keyring +zope.proxy  leap.common>=0.3.4  leap.soledad.client>=0.4.0 diff --git a/src/leap/bitmask/gui/mail_status.py b/src/leap/bitmask/gui/mail_status.py index 83533666..c2c06dbb 100644 --- a/src/leap/bitmask/gui/mail_status.py +++ b/src/leap/bitmask/gui/mail_status.py @@ -50,6 +50,8 @@ class MailStatusWidget(QtGui.QWidget):          QtGui.QWidget.__init__(self, parent)          self._systray = None +        self._disabled = True +        self._started = False          self.ui = Ui_MailStatusWidget()          self.ui.setupUi(self) @@ -98,29 +100,16 @@ class MailStatusWidget(QtGui.QWidget):                   callback=self._mail_handle_soledad_events,                   reqcbk=lambda req, resp: None) -        register(signal=proto.SMTP_SERVICE_STARTED, -                 callback=self._mail_handle_smtp_events, -                 reqcbk=lambda req, resp: None) - -        register(signal=proto.SMTP_SERVICE_FAILED_TO_START, -                 callback=self._mail_handle_smtp_events, -                 reqcbk=lambda req, resp: None) - -        register(signal=proto.IMAP_SERVICE_STARTED, +        register(signal=proto.IMAP_UNREAD_MAIL,                   callback=self._mail_handle_imap_events,                   reqcbk=lambda req, resp: None) - -        register(signal=proto.IMAP_SERVICE_FAILED_TO_START, +        register(signal=proto.IMAP_SERVICE_STARTED,                   callback=self._mail_handle_imap_events,                   reqcbk=lambda req, resp: None) - -        register(signal=proto.IMAP_UNREAD_MAIL, +        register(signal=proto.SMTP_SERVICE_STARTED,                   callback=self._mail_handle_imap_events,                   reqcbk=lambda req, resp: None) -        self._smtp_started = False -        self._imap_started = False -          self._soledad_event.connect(              self._mail_handle_soledad_events_slot)          self._imap_event.connect( @@ -176,6 +165,9 @@ class MailStatusWidget(QtGui.QWidget):          """          # TODO: Figure out how to handle this with the two status in different          # classes +        # XXX right now we could connect the state transition signals of the +        # two connection machines (EIP/Mail) to a class that keeps track of the +        # state -- kali          # status = self.tr("Encrypted Internet: {0}").format(self._eip_status)          # status += '\n'          # status += self.tr("Mail is {0}").format(self._mx_status) @@ -292,11 +284,9 @@ class MailStatusWidget(QtGui.QWidget):          """          # We want to ignore this kind of events once everything has          # started -        if self._smtp_started and self._imap_started: +        if self._started:              return -        self._set_mail_status(self.tr("Starting..."), ready=1) -          ext_status = ""          if req.event == proto.KEYMANAGER_LOOKING_FOR_KEY: @@ -340,14 +330,9 @@ class MailStatusWidget(QtGui.QWidget):          ext_status = ""          if req.event == proto.SMTP_SERVICE_STARTED: -            ext_status = self.tr("SMTP has started...")              self._smtp_started = True -            if self._smtp_started and self._imap_started: -                self._set_mail_status(self.tr("ON"), ready=2) -                ext_status = ""          elif req.event == proto.SMTP_SERVICE_FAILED_TO_START:              ext_status = self.tr("SMTP failed to start, check the logs.") -            self._set_mail_status(self.tr("Failed"))          else:              leap_assert(False,                          "Don't know how to handle this state: %s" @@ -355,6 +340,8 @@ class MailStatusWidget(QtGui.QWidget):          self._set_mail_status(ext_status, ready=2) +    # ----- XXX deprecate (move to mail conductor) +      def _mail_handle_imap_events(self, req):          """          Callback for the IMAP events @@ -376,27 +363,17 @@ class MailStatusWidget(QtGui.QWidget):          """          ext_status = None -        if req.event == proto.IMAP_SERVICE_STARTED: -            ext_status = self.tr("IMAP has started...") -            self._imap_started = True -            if self._smtp_started and self._imap_started: -                self._set_mail_status(self.tr("ON"), ready=2) -                ext_status = "" -        elif req.event == proto.IMAP_SERVICE_FAILED_TO_START: -            ext_status = self.tr("IMAP failed to start, check the logs.") -            self._set_mail_status(self.tr("Failed")) -        elif req.event == proto.IMAP_UNREAD_MAIL: -            if self._smtp_started and self._imap_started: +        if req.event == proto.IMAP_UNREAD_MAIL: + +            if self._started: +                print "printing foo"                  if req.content != "0":                      self._set_mail_status(self.tr("%s Unread Emails") %                                            (req.content,), ready=2)                  else:                      self._set_mail_status("", ready=2) -        else: -            leap_assert(False,  # XXX ??? -                        "Don't know how to handle this state: %s" -                        % (req.event)) - +        elif req.event == proto.IMAP_SERVICE_STARTED: +            self._imap_started = True          if ext_status is not None:              self._set_mail_status(ext_status, ready=1) @@ -414,8 +391,50 @@ class MailStatusWidget(QtGui.QWidget):          """          self._set_mail_status(self.tr("Disabled"), -1) -    def stopped_mail(self): +    # statuses + +    # XXX make the signal emit the label and state. + +    @QtCore.Slot() +    def mail_state_disconnected(self): +        """ +        Displays the correct UI for the disconnected state. +        """ +        # XXX this should handle the disabled state better. +        self._started = False +        if self._disabled: +            self.mail_state_disabled() +        else: +            self._set_mail_status(self.tr("OFF"), -1) + +    @QtCore.Slot() +    def mail_state_connecting(self): +        """ +        Displays the correct UI for the connecting state. +        """ +        self._disabled = False +        self._started = True +        self._set_mail_status(self.tr("Starting..."), 1) + +    @QtCore.Slot() +    def mail_state_disconnecting(self): +        """ +        Displays the correct UI for the connecting state. +        """ +        self._set_mail_status(self.tr("Disconnecting..."), 1) + +    @QtCore.Slot() +    def mail_state_connected(self): +        """ +        Displays the correct UI for the connected state. +        """ +        self._set_mail_status(self.tr("ON"), 2) + +    @QtCore.Slot() +    def mail_state_disabled(self):          """ -        Displayes the correct UI for the stopped state. +        Displays the correct UI for the disabled state.          """ -        self._set_mail_status(self.tr("OFF")) +        self._disabled = True +        self._set_mail_status( +            self.tr("You must be logged in to use encrypted email."), -1) diff --git a/src/leap/bitmask/gui/mainwindow.py b/src/leap/bitmask/gui/mainwindow.py index f5631c69..dddd53da 100644 --- a/src/leap/bitmask/gui/mainwindow.py +++ b/src/leap/bitmask/gui/mainwindow.py @@ -24,6 +24,7 @@ import keyring  from PySide import QtCore, QtGui  from twisted.internet import threads +from zope.proxy import ProxyBase, setProxiedObject, sameProxiedObjects  from leap.bitmask import __version__ as VERSION  from leap.bitmask.config.leapsettings import LeapSettings @@ -39,18 +40,15 @@ from leap.bitmask.gui.mail_status import MailStatusWidget  from leap.bitmask.gui.wizard import Wizard  from leap.bitmask import provider -from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper -from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper -from leap.bitmask.services.eip import eipconfig -# XXX: Soledad might not work out of the box in Windows, issue #2932 -from leap.bitmask.services.soledad.soledadbootstrapper import \ -    SoledadBootstrapper -from leap.bitmask.services.mail.smtpbootstrapper import SMTPBootstrapper -from leap.bitmask.services.mail import imap  from leap.bitmask.platform_init import IS_WIN, IS_MAC  from leap.bitmask.platform_init.initializers import init_platform +from leap.bitmask.provider.providerbootstrapper import ProviderBootstrapper +from leap.bitmask.services.mail import conductor as mail_conductor + +from leap.bitmask.services.eip import eipconfig  from leap.bitmask.services.eip import get_openvpn_management +from leap.bitmask.services.eip.eipbootstrapper import EIPBootstrapper  from leap.bitmask.services.eip.connection import EIPConnection  from leap.bitmask.services.eip.vpnprocess import VPN  from leap.bitmask.services.eip.vpnprocess import OpenVPNAlreadyRunning @@ -62,12 +60,12 @@ from leap.bitmask.services.eip.linuxvpnlauncher import EIPNoPkexecAvailable  from leap.bitmask.services.eip.linuxvpnlauncher import \      EIPNoPolkitAuthAgentAvailable  from leap.bitmask.services.eip.darwinvpnlauncher import EIPNoTunKextLoaded +from leap.bitmask.services.soledad.soledadbootstrapper import \ +    SoledadBootstrapper  from leap.bitmask.util.keyring_helpers import has_keyring  from leap.bitmask.util.leap_log_handler import LeapLogHandler -from leap.bitmask.services.mail.smtpconfig import SMTPConfig -  if IS_WIN:      from leap.bitmask.platform_init.locks import WindowsLock      from leap.bitmask.platform_init.locks import raise_window_ack @@ -93,10 +91,6 @@ class MainWindow(QtGui.QMainWindow):      # Keyring      KEYRING_KEY = "bitmask" -    # SMTP -    PORT_KEY = "port" -    IP_KEY = "ip_address" -      OPENVPN_SERVICE = "openvpn"      MX_SERVICE = "mx" @@ -257,10 +251,6 @@ class MainWindow(QtGui.QMainWindow):          self._soledad_bootstrapper.soledad_failed.connect(              self._mail_status.set_soledad_failed) -        self._smtp_bootstrapper = SMTPBootstrapper() -        self._smtp_bootstrapper.download_config.connect( -            self._smtp_bootstrapped_stage) -          self.ui.action_about_leap.triggered.connect(self._about)          self.ui.action_quit.triggered.connect(self.quit)          self.ui.action_wizard.triggered.connect(self._launch_wizard) @@ -307,12 +297,13 @@ class MainWindow(QtGui.QMainWindow):          # Services signals/slots connection          self.new_updates.connect(self._react_to_new_updates) + +        # XXX should connect to mail_conductor.start_mail_service instead +        self.soledad_ready.connect(self._start_smtp_bootstrapping)          self.soledad_ready.connect(self._start_imap_service) -        self.soledad_ready.connect(self._set_soledad_ready)          self.mail_client_logged_in.connect(self._fetch_incoming_mail)          self.logout.connect(self._stop_imap_service)          self.logout.connect(self._stop_smtp_service) -        self.logout.connect(self._mail_status.stopped_mail)          ################################# end Qt Signals connection ######## @@ -325,17 +316,18 @@ class MainWindow(QtGui.QMainWindow):          self._bypass_checks = bypass_checks -        self._soledad = None -        self._soledad_ready = False -        self._keymanager = None -        self._smtp_service = None -        self._smtp_port = None -        self._imap_service = None +        # We initialize Soledad and Keymanager instances as +        # transparent proxies, so we can pass the reference freely +        # around. +        self._soledad = ProxyBase(None) +        self._keymanager = ProxyBase(None)          self._login_defer = None          self._download_provider_defer = None -        self._smtp_config = SMTPConfig() +        self._mail_conductor = mail_conductor.MailConductor( +            self._soledad, self._keymanager) +        self._mail_conductor.connect_mail_signals(self._mail_status)          # Eip machine is a public attribute where the state machine for          # the eip connection will be available to the different components. @@ -347,6 +339,7 @@ class MainWindow(QtGui.QMainWindow):          self.eip_machine = None          # start event machines          self.start_eip_machine() +        self._mail_conductor.start_mail_machine(parent=self)          if self._first_run():              self._wizard_firstrun = True @@ -460,7 +453,7 @@ class MainWindow(QtGui.QMainWindow):          """          preferences_window = PreferencesWindow(self, self._srp_auth) -        if self._soledad_ready: +        if sameProxiedObjects(self._soledad, None):              preferences_window.set_soledad_ready(self._soledad)          else:              self.soledad_ready.connect( @@ -478,16 +471,6 @@ class MainWindow(QtGui.QMainWindow):          """          EIPPreferencesWindow(self).show() -    def _set_soledad_ready(self): -        """ -        SLOT -        TRIGGERS: -            self.soledad_ready - -        It sets the soledad object as ready to use. -        """ -        self._soledad_ready = True -      #      # updates      # @@ -803,6 +786,7 @@ class MainWindow(QtGui.QMainWindow):          provider configuration if it's not present, otherwise will          emit the corresponding signals inmediately          """ +        # XXX should rename this provider, name clash.          provider = self._login_widget.get_selected_provider()          pb = self._provider_bootstrapper @@ -823,6 +807,7 @@ class MainWindow(QtGui.QMainWindow):          :type data: dict          """          if data[self._provider_bootstrapper.PASSED_KEY]: +            # XXX should rename this provider, name clash.              provider = self._login_widget.get_selected_provider()              # If there's no loaded provider or @@ -1023,114 +1008,58 @@ class MainWindow(QtGui.QMainWindow):              logger.debug("ERROR on soledad bootstrapping:")              logger.error("%r" % data[self._soledad_bootstrapper.ERROR_KEY])              return -        else: -            logger.debug("Done bootstrapping Soledad") -            self._soledad = self._soledad_bootstrapper.soledad -            self._keymanager = self._soledad_bootstrapper.keymanager +        logger.debug("Done bootstrapping Soledad") + +        # Update the proxy objects to point to +        # the initialized instances. +        setProxiedObject(self._soledad, +                         self._soledad_bootstrapper.soledad) +        setProxiedObject(self._keymanager, +                         self._soledad_bootstrapper.keymanager)          # Ok, now soledad is ready, so we can allow other things that          # depend on soledad to start.          # this will trigger start_imap_service +        # and start_smtp_boostrapping          self.soledad_ready.emit() -        # TODO connect all these activations to the soledad_ready -        # signal so the logic is clearer to follow. - -        if self._provider_config.provides_mx() and \ -                self._enabled_services.count(self.MX_SERVICE) > 0: -            self._smtp_bootstrapper.run_smtp_setup_checks( -                self._provider_config, -                self._smtp_config, -                True) -      ###################################################################      # Service control methods: smtp -    def _smtp_bootstrapped_stage(self, data): +    @QtCore.Slot() +    def _start_smtp_bootstrapping(self):          """          SLOT          TRIGGERS: -          self._smtp_bootstrapper.download_config - -        If there was a problem, displays it, otherwise it does nothing. -        This is used for intermediate bootstrapping stages, in case -        they fail. - -        :param data: result from the bootstrapping stage for Soledad -        :type data: dict -        """ -        passed = data[self._smtp_bootstrapper.PASSED_KEY] -        if not passed: -            logger.error(data[self._smtp_bootstrapper.ERROR_KEY]) -            return -        logger.debug("Done bootstrapping SMTP") -        self._check_smtp_config() - -    def _check_smtp_config(self): -        """ -        Checks smtp config and tries to download smtp client cert if needed. +            self.soledad_ready          """ -        hosts = self._smtp_config.get_hosts() -        # TODO handle more than one host and define how to choose -        if len(hosts) > 0: -            hostname = hosts.keys()[0] -            logger.debug("Using hostname %s for SMTP" % (hostname,)) -            host = hosts[hostname][self.IP_KEY].encode("utf-8") -            port = hosts[hostname][self.PORT_KEY] - -            client_cert = self._smtp_config.get_client_cert_path( +        # TODO for simmetry, this should be called start_smtp_service +        # (and delegate all the checks to the conductor) +        if self._provider_config.provides_mx() and \ +                self._enabled_services.count(self.MX_SERVICE) > 0: +            self._mail_conductor.smtp_bootstrapper.run_smtp_setup_checks(                  self._provider_config, -                about_to_download=True) - -            if not os.path.isfile(client_cert): -                self._smtp_bootstrapper._download_client_certificates() -            if os.path.isfile(client_cert): -                self._start_smtp_service(host, port, client_cert) -            else: -                logger.warning("Tried to download email client " -                               "certificate, but could not find any") - -        else: -            logger.warning("No smtp hosts configured") - -    def _start_smtp_service(self, host, port, cert): -        """ -        Starts the smtp service. -        """ -        # TODO Make the encrypted_only configurable -        # TODO pick local smtp port in a better way -        # TODO remove hard-coded port and let leap.mail set -        # the specific default. - -        from leap.mail.smtp import setup_smtp_relay -        self._smtp_service, self._smtp_port = setup_smtp_relay( -            port=2013, -            keymanager=self._keymanager, -            smtp_host=host, -            smtp_port=port, -            smtp_cert=cert, -            smtp_key=cert, -            encrypted_only=False) +                self._mail_conductor.smtp_config, +                download_if_needed=True) +    # XXX --- should remove from here, and connecte directly to the state +    # machine. +    @QtCore.Slot()      def _stop_smtp_service(self):          """          SLOT          TRIGGERS:              self.logout          """ -        # There is a subtle difference here: -        # we are stopping the factory for the smtp service here, -        # but in the imap case we are just stopping the fetcher. -        if self._smtp_service is not None: -            logger.debug('Stopping smtp service.') -            self._smtp_port.stopListening() -            self._smtp_service.doStop() +        # TODO call stop_mail_service +        self._mail_conductor.stop_smtp_service()      ###################################################################      # Service control methods: imap +    @QtCore.Slot()      def _start_imap_service(self):          """          SLOT @@ -1139,11 +1068,7 @@ class MainWindow(QtGui.QMainWindow):          """          if self._provider_config.provides_mx() and \                  self._enabled_services.count(self.MX_SERVICE) > 0: -            logger.debug('Starting imap service') - -            self._imap_service = imap.start_imap_service( -                self._soledad, -                self._keymanager) +            self._mail_conductor.start_imap_service()      def _on_mail_client_logged_in(self, req):          """ @@ -1151,30 +1076,25 @@ class MainWindow(QtGui.QMainWindow):          """          self.mail_client_logged_in.emit() +    @QtCore.Slot()      def _fetch_incoming_mail(self):          """          SLOT          TRIGGERS:              self.mail_client_logged_in          """ -        # TODO have a mutex over fetch operation. -        if self._imap_service: -            logger.debug('Client connected, fetching mail...') -            self._imap_service.fetch() +        # TODO connect signal directly!!! +        self._mail_conductor.fetch_incoming_mail() +    @QtCore.Slot()      def _stop_imap_service(self):          """          SLOT          TRIGGERS:              self.logout          """ -        # There is a subtle difference here: -        # we are just stopping the fetcher here, -        # but in the smtp case we are stopping the factory. -        # We should homogenize both services. -        if self._imap_service is not None: -            logger.debug('Stopping imap service.') -            self._imap_service.stop() +        # TODO call stop_mail_service +        self._mail_conductor.stop_imap_service()      # end service control methods (imap) @@ -1623,8 +1543,8 @@ class MainWindow(QtGui.QMainWindow):          if ok:              self._logged_user = None -              self._login_widget.logged_out() +            self._mail_status.mail_state_disabled()          else:              self._login_widget.set_login_status( @@ -1700,8 +1620,7 @@ class MainWindow(QtGui.QMainWindow):          """          logger.debug('About to quit, doing cleanup...') -        if self._imap_service is not None: -            self._imap_service.stop() +        self._mail_conductor.stop_imap_service()          if self._srp_auth is not None:              if self._srp_auth.get_session_id() is not None or \ diff --git a/src/leap/bitmask/gui/statemachines.py b/src/leap/bitmask/gui/statemachines.py index 94726720..ee16a4c6 100644 --- a/src/leap/bitmask/gui/statemachines.py +++ b/src/leap/bitmask/gui/statemachines.py @@ -19,7 +19,8 @@ State machines for the Bitmask app.  """  import logging -from PySide.QtCore import QStateMachine, QState +from PySide import QtCore +from PySide.QtCore import QStateMachine, QState, Signal  from PySide.QtCore import QObject  from leap.bitmask.services import connections @@ -36,28 +37,255 @@ _CON = "connecting"  _DIS = "disconnecting" -class IntermediateState(QState): +class SignallingState(QState):      """ -    Intermediate state that emits a custom signal on entry +    A state that emits a custom signal on entry.      """ -    def __init__(self, signal): +    def __init__(self, signal, parent=None, name=None):          """          Initializer.          :param signal: the signal to be emitted on entry on this state.          :type signal: QtCore.QSignal          """ -        super(IntermediateState, self).__init__() +        super(SignallingState, self).__init__(parent)          self._signal = signal +        self._name = name      def onEntry(self, *args):          """          Emits the signal on entry.          """ -        logger.debug('IntermediateState entered. Emitting signal ...') +        logger.debug('State %s entered. Emitting signal ...' +                     % (self._name + self.__class__.__name__))          if self._signal is not None:              self._signal.emit() +class States(object): +    """ +    States for composite objects +    """ + +    class Off(SignallingState): +        pass + +    class Connecting(SignallingState): +        pass + +    class On(SignallingState): +        pass + +    class Disconnecting(SignallingState): +        pass + +    class StepsTrack(QObject): +        state_change = Signal() + +        def __init__(self, target): +            super(States.StepsTrack, self).__init__() +            self.received = set([]) +            self.target = set(target) + +        def is_all_done(self): +            return all([ev in self.target for ev in self.received]) + +        def is_any_done(self): +            return any([ev in self.target for ev in self.received]) + +        def seen(self, _type): +            if _type in self.target: +                self.received.add(_type) + +        def reset_seen(self): +            self.received = set([]) + +    class TransitionOR(QtCore.QSignalTransition): + +        def __init__(self, state): +            super(States.TransitionOR, self).__init__( +                state, QtCore.SIGNAL('state_change()')) +            self.state = state + +        def eventTest(self, e): +            self.state.seen(e.type()) +            done = self.state.is_any_done() +            if done: +                self.state.reset_seen() +            return done + +        def onTransition(self, e): +            pass + +    class TransitionAND(QtCore.QSignalTransition): + +        def __init__(self, state): +            super(States.TransitionAND, self).__init__( +                state, QtCore.SIGNAL('state_change()')) +            self.state = state + +        def eventTest(self, e): +            self.state.seen(e.type()) +            done = self.state.is_all_done() +            if done: +                self.state.reset_seen() +            return done + +        def onTransition(self, e): +            pass + + +class CompositeEvent(QtCore.QEvent): +    def __init__(self): +        super(CompositeEvent, self).__init__( +            QtCore.QEvent.Type(self.ID)) + + +class Composite(object): +    # TODO we should generate the connectingEvents dinamycally, +    # depending on how much composite states do we get. +    # This only supports up to 2 composite states. + +    class ConnectingEvent1(CompositeEvent): +        ID = QtCore.QEvent.User + 1 + +    class ConnectingEvent2(CompositeEvent): +        ID = QtCore.QEvent.User + 2 + +    class ConnectedEvent1(CompositeEvent): +        ID = QtCore.QEvent.User + 3 + +    class ConnectedEvent2(CompositeEvent): +        ID = QtCore.QEvent.User + 4 + +    class DisconnectingEvent1(CompositeEvent): +        ID = QtCore.QEvent.User + 5 + +    class DisconnectingEvent2(CompositeEvent): +        ID = QtCore.QEvent.User + 6 + +    class DisconnectedEvent1(CompositeEvent): +        ID = QtCore.QEvent.User + 7 + +    class DisconnectedEvent2(CompositeEvent): +        ID = QtCore.QEvent.User + 8 + + +class Events(QtCore.QObject): +    """ +    A Wrapper object for containing the events that will be +    posted to a composite state machine. +    """ +    def __init__(self, parent=None): +        """ +        Initializes the QObject with the given parent. +        """ +        QtCore.QObject.__init__(self, parent) + + +class CompositeMachine(QStateMachine): + +    def __init__(self, parent=None): +        QStateMachine.__init__(self, parent) + +        # events +        self.events = Events(parent) +        self.create_events() + +    def create_events(self): +        """ +        Creates a bunch of events to be posted to the state machine when +        the transitions say so. +        """ +        # XXX refactor into a dictionary? +        self.events.con_ev1 = Composite.ConnectingEvent1() +        self.events.con_ev2 = Composite.ConnectingEvent2() +        self.events.on_ev1 = Composite.ConnectedEvent1() +        self.events.on_ev2 = Composite.ConnectedEvent2() +        self.events.dis_ev1 = Composite.DisconnectingEvent1() +        self.events.dis_ev2 = Composite.DisconnectingEvent2() +        self.events.off_ev1 = Composite.DisconnectedEvent1() +        self.events.off_ev2 = Composite.DisconnectedEvent2() + +    def beginSelectTransitions(self, e): +        """ +        Weird. Having this method makes underlying backtraces +        to appear magically on the transitions. +        :param e: the received event +        :type e: QEvent +        """ +        pass + +    def _connect_children(self, child1, child2): +        """ +        Connects the state transition signals for children machines. + +        :param child1: the first child machine +        :type child1: QStateMachine +        :param child2: the second child machine +        :type child2: QStateMachine +        """ +        # TODO refactor and generalize for composites +        # of more than 2 connections. + +        c1 = child1.conn +        c1.qtsigs.connecting_signal.connect(self.con_ev1_slot) +        c1.qtsigs.connected_signal.connect(self.on_ev1_slot) +        c1.qtsigs.disconnecting_signal.connect(self.dis_ev1_slot) +        c1.qtsigs.disconnected_signal.connect(self.off_ev1_slot) + +        c2 = child2.conn +        c2.qtsigs.connecting_signal.connect(self.con_ev2_slot) +        c2.qtsigs.connected_signal.connect(self.on_ev2_slot) +        c2.qtsigs.disconnecting_signal.connect(self.dis_ev2_slot) +        c2.qtsigs.disconnected_signal.connect(self.off_ev2_slot) + +    # XXX why is this getting deletec in c++? +    #Traceback (most recent call last): +    #self.postEvent(self.events.on_ev2) +    #RuntimeError: Internal C++ object (ConnectedEvent2) already deleted. +    # XXX trying the following workaround, since +    # I cannot find why in the world this is getting deleted :( +    # XXX refactor! + +    # slots connection1 + +    def con_ev1_slot(self): +        # XXX if we just postEvent, we get the Internal C++ object deleted... +        # so the workaround is to re-create it each time. +        self.events.con_ev1 = Composite.ConnectingEvent1() +        self.postEvent(self.events.con_ev1) + +    def on_ev1_slot(self): +        self.events.on_ev1 = Composite.ConnectedEvent1() +        self.postEvent(self.events.on_ev1) + +    def dis_ev1_slot(self): +        self.events.dis_ev1 = Composite.DisconnectingEvent1() +        self.postEvent(self.events.dis_ev1) + +    def off_ev1_slot(self): +        self.events.off_ev1 = Composite.DisconnectedEvent1() +        self.postEvent(self.events.off_ev1) + +    # slots connection2 + +    def con_ev2_slot(self): +        self.events.con_ev2 = Composite.ConnectingEvent2() +        self.postEvent(self.events.con_ev2) + +    def on_ev2_slot(self): +        self.events.on_ev2 = Composite.ConnectedEvent2() +        self.postEvent(self.events.on_ev2) + +    def dis_ev2_slot(self): +        self.events.dis_ev2 = Composite.DisconnectingEvent2() +        self.postEvent(self.events.dis_ev2) + +    def off_ev2_slot(self): +        self.events.off_ev2 = Composite.DisconnectedEvent2() +        self.postEvent(self.events.off_ev2) + +  class ConnectionMachineBuilder(object):      """      Builder class for state machines made from LEAPConnections. @@ -65,16 +293,161 @@ class ConnectionMachineBuilder(object):      def __init__(self, connection):          """          :param connection: an instance of a concrete LEAPConnection -                      we will be building a state machine for. +                           we will be building a state machine for.          :type connection: AbstractLEAPConnection          """          self._conn = connection          leap_assert_type(self._conn, connections.AbstractLEAPConnection) -    def make_machine(self, button=None, action=None, label=None): +    def make_machine(self, **kwargs):          """          Creates a statemachine associated with the passed controls. +        It returns the state machine if the connection used for initializing +        the ConnectionMachineBuilder inherits exactly from +        LEAPAbstractConnection, and a tuple with the Composite Machine and its +        individual parts in case that it is a composite machine which +        connection definition inherits from more than one class that, on their +        time, inherit from LEAPAbstractConnection. + +        :params: see parameters for ``_make_simple_machine`` +        :returns: a QStateMachine, or a tuple with the form: +                  (CompositeStateMachine, (StateMachine1, StateMachine2)) +        :rtype: QStateMachine or tuple +        """ +        components = self._conn.components + +        if components is None: +        # simple case: connection definition inherits directly from +        # the abstract connection. + +            leap_assert_type(self._conn, connections.AbstractLEAPConnection) +            return self._make_simple_machine(self._conn, **kwargs) + +        if components: +            # composite case: connection definition inherits from several +            # classes, each one of which inherit from the abstract connection. +            child_machines = tuple( +                [ConnectionMachineBuilder(connection()).make_machine() +                    for connection in components]) +            composite_machine = self._make_composite_machine( +                self._conn, child_machines, **kwargs) + +            composite_machine._connect_children( +                *child_machines) + +            # XXX should also connect its own states with the signals +            # for the composite machine itself + +            return (composite_machine, child_machines) + +    def _make_composite_machine(self, conn, children, +                                **kwargs): +        """ +        Creates a composite machine. + +        :param conn: an instance of a connection definition. +        :type conn: LEAPAbstractConnection +        :param children: children machines +        :type children: tuple of state machines +        :returns: A composite state machine +        :rtype: QStateMachine +        """ +        # TODO split this method in smaller utility functions. +        parent = kwargs.get('parent', None) + +        # 1. create machine +        machine = CompositeMachine(parent=parent) + +        # 2. create states +        off = States.Off(conn.qtsigs.disconnected_signal, +                         parent=machine, +                         name=conn.name) +        off.setObjectName("off") + +        on = States.On(conn.qtsigs.connected_signal, +                       parent=machine, +                       name=conn.name) +        on.setObjectName("on") + +        connecting_state = States.Connecting( +            conn.qtsigs.connecting_signal, +            parent=machine, +            name=conn.name) +        connecting_state.setObjectName("connecting") + +        disconnecting_state = States.Disconnecting( +            conn.qtsigs.disconnecting_signal, +            parent=machine, +            name=conn.name) +        disconnecting_state.setObjectName("disconnecting") + +        # 3. TODO create as many connectingEvents as needed (dynamically create +        # classses for that) +        # (we have manually created classes for events under CompositeEvent for +        # now, to begin with the simple 2 states case for mail. + +        # 4. state tracking objects for each transition stage + +        connecting_track0 = States.StepsTrack( +            (Composite.ConnectingEvent1.ID, +             Composite.ConnectingEvent2.ID)) +        connecting_track0.setObjectName("connecting_step_0") + +        connecting_track1 = States.StepsTrack( +            (Composite.ConnectedEvent1.ID, +             Composite.ConnectedEvent2.ID)) +        connecting_track1.setObjectName("connecting_step_1") + +        disconnecting_track0 = States.StepsTrack( +            (Composite.DisconnectingEvent1.ID, +             Composite.DisconnectingEvent2.ID)) +        disconnecting_track0.setObjectName("disconnecting_step_0") + +        disconnecting_track1 = States.StepsTrack( +            (Composite.DisconnectedEvent1.ID, +             Composite.DisconnectedEvent2.ID)) +        disconnecting_track1.setObjectName("disconnecting_step_1") + +        # 5. definte the transitions with the matching state-tracking +        # objects. + +        # off -> connecting +        connecting_transition = States.TransitionOR( +            connecting_track0) +        connecting_transition.setTargetState(connecting_state) +        off.addTransition(connecting_transition) + +        # connecting -> on +        connected_transition = States.TransitionAND( +            connecting_track1) +        connected_transition.setTargetState(on) +        connecting_state.addTransition(connected_transition) + +        # on -> disconnecting +        disconnecting_transition = States.TransitionOR( +            disconnecting_track0) +        disconnecting_transition.setTargetState(disconnecting_state) +        on.addTransition(disconnecting_transition) + +        # disconnecting -> off +        disconnected_transition = States.TransitionAND( +            disconnecting_track1) +        disconnected_transition.setTargetState(off) +        disconnecting_state.addTransition(disconnected_transition) + +        machine.setInitialState(off) +        machine.conn = conn +        return machine + +    def _make_simple_machine(self, conn, +                             button=None, action=None, label=None): +        """ +        Creates a statemachine associated with the passed controls. + +        :param conn: the connection instance that defines this machine. +        :type conn: AbstractLEAPConnection +          :param button: the switch button.          :type button: QPushButton @@ -88,9 +461,7 @@ class ConnectionMachineBuilder(object):          :rtype: QStateMachine          """          machine = QStateMachine() -        conn = self._conn - -        states = self._make_states(button, action, label) +        states = self._make_states(conn, button, action, label)          # transitions: @@ -151,11 +522,17 @@ class ConnectionMachineBuilder(object):          for state in states.itervalues():              machine.addState(state)          machine.setInitialState(states[_OFF]) + +        machine.conn = conn          return machine -    def _make_states(self, button, action, label): +    def _make_states(self, conn, button, action, label):          """ -        Creates the four states for the state machine +        Creates the four states for the simple state machine. +        Adds the needed properties for the passed controls. + +        :param conn: the connection instance that defines this machine. +        :type conn: AbstractLEAPConnection          :param button: the switch button.          :type button: QPushButton @@ -169,7 +546,6 @@ class ConnectionMachineBuilder(object):          :returns: a dict of states          :rtype: dict          """ -        conn = self._conn          states = {}          # TODO add tooltip @@ -190,8 +566,9 @@ class ConnectionMachineBuilder(object):          states[_OFF] = off          # CONNECTING State ---------------- -        connecting = IntermediateState( -            conn.qtsigs.connecting_signal) +        connecting = SignallingState( +            conn.qtsigs.connecting_signal, +            name=conn.name)          on_label = _tr("Turn {0}").format(              conn.Disconnected.short_label)          if button: @@ -224,8 +601,9 @@ class ConnectionMachineBuilder(object):          states[_ON] = on          # DISCONNECTING State ------------- -        disconnecting = IntermediateState( -            conn.qtsigs.disconnecting_signal) +        disconnecting = SignallingState( +            conn.qtsigs.disconnecting_signal, +            name=conn.name)          if button:              disconnecting.assignProperty(                  button, 'enabled', False) diff --git a/src/leap/bitmask/services/connections.py b/src/leap/bitmask/services/connections.py index 8aeb4e0c..ecfd35ff 100644 --- a/src/leap/bitmask/services/connections.py +++ b/src/leap/bitmask/services/connections.py @@ -41,9 +41,6 @@ The different services should declare a ServiceConnection class that  inherits from AbstractLEAPConnection, so an instance of such class  can be used to inform the StateMachineBuilder of the particularities  of the state transitions for each particular connection. - -In the future, we will extend this class to allow composites in connections, -so we can apply conditional logic to the transitions.  """ @@ -79,12 +76,7 @@ class AbstractLEAPConnection(object):          """          return self._qtsigs -    # XXX for conditional transitions with composites, -    #     we might want to add -    #     a field with dependencies: what this connection -    #     needs for (ON) state. -    # XXX Look also at child states in the state machine. -    #depends = () +    components = None      # Signals that derived classes      # have to implement. diff --git a/src/leap/bitmask/services/eip/connection.py b/src/leap/bitmask/services/eip/connection.py index 962d9cf2..8a35d550 100644 --- a/src/leap/bitmask/services/eip/connection.py +++ b/src/leap/bitmask/services/eip/connection.py @@ -46,5 +46,5 @@ class EIPConnectionSignals(QtCore.QObject):  class EIPConnection(AbstractLEAPConnection):      def __init__(self): -        # XXX this should be public instead          self._qtsigs = EIPConnectionSignals() +        self._connection_name = "Encrypted Internet" diff --git a/src/leap/bitmask/services/mail/conductor.py b/src/leap/bitmask/services/mail/conductor.py new file mode 100644 index 00000000..3a3e16a3 --- /dev/null +++ b/src/leap/bitmask/services/mail/conductor.py @@ -0,0 +1,383 @@ +# -*- coding: utf-8 -*- +# conductor.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +""" +Mail Services Conductor +""" +import logging +import os + +from PySide import QtCore +from zope.proxy import sameProxiedObjects + +from leap.bitmask.gui import statemachines +from leap.bitmask.services.mail import imap +from leap.bitmask.services.mail import connection as mail_connection +from leap.bitmask.services.mail.smtpbootstrapper import SMTPBootstrapper +from leap.bitmask.services.mail.smtpconfig import SMTPConfig +from leap.bitmask.util import is_file + +from leap.common.check import leap_assert + +from leap.common.events import register as leap_register +from leap.common.events import events_pb2 as leap_events + +logger = logging.getLogger(__name__) + + +class IMAPControl(object): +    """ +    Methods related to IMAP control. +    """ +    def __init__(self): +        """ +        Initializes smtp variables. +        """ +        self.imap_machine = None +        self.imap_service = None +        self.imap_connection = None + +        leap_register(signal=leap_events.IMAP_SERVICE_STARTED, +                      callback=self._handle_imap_events, +                      reqcbk=lambda req, resp: None) +        leap_register(signal=leap_events.IMAP_SERVICE_FAILED_TO_START, +                      callback=self._handle_imap_events, +                      reqcbk=lambda req, resp: None) + +    def set_imap_connection(self, imap_connection): +        """ +        Sets the imap connection to an initialized connection. + +        :param imap_connection: an initialized imap connection +        :type imap_connection: IMAPConnection instance. +        """ +        self.imap_connection = imap_connection + +    def start_imap_service(self): +        """ +        Starts imap service. +        """ +        logger.debug('Starting imap service') +        leap_assert(sameProxiedObjects(self._soledad, None) +                    is not True, +                    "We need a non-null soledad for initializing imap service") +        leap_assert(sameProxiedObjects(self._keymanager, None) +                    is not True, +                    "We need a non-null keymanager for initializing imap " +                    "service") + +        if self.imap_service is None: +            # first time. +            self.imap_service = imap.start_imap_service( +                self._soledad, +                self._keymanager) +        else: +            # we have the fetcher. just start it. +            self.imap_service.start_loop() + +    def stop_imap_service(self): +        """ +        Stops imap service. + +        There is a subtle difference here: +        we are just stopping the fetcher here, +        but in the smtp case we are stopping the factory. +        """ +        self.imap_connection.qtsigs.disconnecting_signal.emit() +        # TODO We should homogenize both services. +        if self.imap_service is not None: +            logger.debug('Stopping imap service.') +            self.imap_service.stop() + +    def fetch_incoming_mail(self): +        """ +        Fetches incoming mail. +        """ +        # TODO have a mutex over fetch operation. +        if self.imap_service: +            logger.debug('Client connected, fetching mail...') +            self.imap_service.fetch() + +    # handle events + +    def _handle_imap_events(self, req): +        """ +        Callback handler for the IMAP events + +        :param req: Request type +        :type req: leap.common.events.events_pb2.SignalRequest +        """ +        if req.event == leap_events.IMAP_SERVICE_STARTED: +            self.on_imap_connected() +        elif req.event == leap_events.IMAP_SERVICE_FAILED_TO_START: +            self.on_imap_failed() + +    # emit connection signals + +    def on_imap_connecting(self): +        """ +        Callback for IMAP connecting state. +        """ +        self.imap_connection.qtsigs.connecting_signal.emit() + +    def on_imap_connected(self): +        """ +        Callback for IMAP connected state. +        """ +        self.imap_connection.qtsigs.connected_signal.emit() + +    def on_imap_failed(self): +        """ +        Callback for IMAP failed state. +        """ +        self.imap_connection.qtsigs.connetion_aborted_signal.emit() + + +class SMTPControl(object): + +    PORT_KEY = "port" +    IP_KEY = "ip_address" + +    def __init__(self): +        """ +        Initializes smtp variables. +        """ +        self.smtp_config = SMTPConfig() +        self.smtp_connection = None +        self.smtp_machine = None +        self._smtp_service = None +        self._smtp_port = None + +        self.smtp_bootstrapper = SMTPBootstrapper() +        self.smtp_bootstrapper.download_config.connect( +            self.smtp_bootstrapped_stage) + +        leap_register(signal=leap_events.SMTP_SERVICE_STARTED, +                      callback=self._handle_smtp_events, +                      reqcbk=lambda req, resp: None) +        leap_register(signal=leap_events.SMTP_SERVICE_FAILED_TO_START, +                      callback=self._handle_smtp_events, +                      reqcbk=lambda req, resp: None) + +    def set_smtp_connection(self, smtp_connection): +        """ +        Sets the smtp connection to an initialized connection. +        :param smtp_connection: an initialized smtp connection +        :type smtp_connection: SMTPConnection instance. +        """ +        self.smtp_connection = smtp_connection + +    def start_smtp_service(self, host, port, cert): +        """ +        Starts the smtp service. + +        :param host: the hostname of the remove SMTP server. +        :type host: str +        :param port: the port of the remote SMTP server +        :type port: str +        :param cert: the client certificate for authentication +        :type cert: str +        """ +        # TODO Make the encrypted_only configurable +        # TODO pick local smtp port in a better way +        # TODO remove hard-coded port and let leap.mail set +        # the specific default. +        self.smtp_connection.qtsigs.connecting_signal.emit() +        from leap.mail.smtp import setup_smtp_relay +        self._smtp_service, self._smtp_port = setup_smtp_relay( +            port=2013, +            keymanager=self._keymanager, +            smtp_host=host, +            smtp_port=port, +            smtp_cert=cert, +            smtp_key=cert, +            encrypted_only=False) + +    def stop_smtp_service(self): +        """ +        Stops the smtp service. + +        There is a subtle difference here: +        we are stopping the factory for the smtp service here, +        but in the imap case we are just stopping the fetcher. +        """ +        self.smtp_connection.qtsigs.disconnecting_signal.emit() +        # TODO We should homogenize both services. +        if self._smtp_service is not None: +            logger.debug('Stopping smtp service.') +            self._smtp_port.stopListening() +            self._smtp_service.doStop() + +    @QtCore.Slot() +    def smtp_bootstrapped_stage(self, data): +        """ +        SLOT +        TRIGGERS: +          self.smtp_bootstrapper.download_config + +        If there was a problem, displays it, otherwise it does nothing. +        This is used for intermediate bootstrapping stages, in case +        they fail. + +        :param data: result from the bootstrapping stage for Soledad +        :type data: dict +        """ +        passed = data[self.smtp_bootstrapper.PASSED_KEY] +        if not passed: +            logger.error(data[self.smtp_bootstrapper.ERROR_KEY]) +            return +        logger.debug("Done bootstrapping SMTP") +        self.check_smtp_config() + +    def check_smtp_config(self): +        """ +        Checks smtp config and tries to download smtp client cert if needed. +        Currently called when smtp_bootstrapped_stage has successfuly finished. +        """ +        logger.debug("Checking SMTP config...") +        leap_assert(self.smtp_bootstrapper._provider_config, +                    "smtp bootstrapper does not have a provider_config") + +        provider_config = self.smtp_bootstrapper._provider_config +        smtp_config = self.smtp_config +        hosts = smtp_config.get_hosts() +        # TODO handle more than one host and define how to choose +        if len(hosts) > 0: +            hostname = hosts.keys()[0] +            logger.debug("Using hostname %s for SMTP" % (hostname,)) +            host = hosts[hostname][self.IP_KEY].encode("utf-8") +            port = hosts[hostname][self.PORT_KEY] + +            client_cert = smtp_config.get_client_cert_path( +                provider_config, +                about_to_download=True) + +            # XXX change this logic! +            # check_config should be called from within start_service, +            # and not the other way around. +            if not is_file(client_cert): +                self.smtp_bootstrapper._download_client_certificates() +            if os.path.isfile(client_cert): +                self.start_smtp_service(host, port, client_cert) +            else: +                logger.warning("Tried to download email client " +                               "certificate, but could not find any") + +        else: +            logger.warning("No smtp hosts configured") + +    # handle smtp events + +    def _handle_smtp_events(self, req): +        """ +        Callback handler for the SMTP events. + +        :param req: Request type +        :type req: leap.common.events.events_pb2.SignalRequest +        """ +        if req.event == leap_events.SMTP_SERVICE_STARTED: +            self.on_smtp_connected() +        elif req.event == leap_events.SMTP_SERVICE_FAILED_TO_START: +            self.on_smtp_failed() + +    # emit connection signals + +    def on_smtp_connecting(self): +        """ +        Callback for SMTP connecting state. +        """ +        self.smtp_connection.qtsigs.connecting_signal.emit() + +    def on_smtp_connected(self): +        """ +        Callback for SMTP connected state. +        """ +        self.smtp_connection.qtsigs.connected_signal.emit() + +    def on_smtp_failed(self): +        """ +        Callback for SMTP failed state. +        """ +        self.smtp_connection.qtsigs.connection_aborted_signal.emit() + + +class MailConductor(IMAPControl, SMTPControl): +    """ +    This class encapsulates everything related to the initialization and +    process control for the mail services. +    Currently, it initializes IMAPConnection and SMPTConnection. +    """ +    # XXX We could consider to use composition instead of inheritance here. + +    def __init__(self, soledad, keymanager): +        """ +        Initializes the mail conductor. + +        :param soledad: a transparent proxy that eventually will point to a +                        Soledad Instance. +        :type soledad: zope.proxy.ProxyBase + +        :param keymanager: a transparent proxy that eventually will point to a +                           Keymanager Instance. +        :type soledad: zope.proxy.ProxyBase +        """ +        IMAPControl.__init__(self) +        SMTPControl.__init__(self) +        self._soledad = soledad +        self._keymanager = keymanager + +        self._mail_machine = None + +        self._mail_connection = mail_connection.MailConnection() + +    def start_mail_machine(self, **kwargs): +        """ +        Starts mail machine. +        """ +        logger.debug("Starting mail state machine...") +        builder = statemachines.ConnectionMachineBuilder(self._mail_connection) +        (mail, (imap, smtp)) = builder.make_machine(**kwargs) + +        # we have instantiated the connections while building the composite +        # machines, and we have to use the qtsigs instantiated there. +        # XXX we could probably use a proxy here too to make the thing +        # transparent. +        self.set_imap_connection(imap.conn) +        self.set_smtp_connection(smtp.conn) + +        self._mail_machine = mail +        # XXX ------------------- +        # need to keep a reference? +        #self._mail_events = mail.events +        self._mail_machine.start() + +        self._imap_machine = imap +        self._imap_machine.start() +        self._smtp_machine = smtp +        self._smtp_machine.start() + +    def connect_mail_signals(self, widget): +        """ +        Connects the mail signals to the mail_status widget slots. + +        :param widget: the widget containing the slots. +        :type widget: QtCore.QWidget +        """ +        qtsigs = self._mail_connection.qtsigs +        qtsigs.connected_signal.connect(widget.mail_state_connected) +        qtsigs.connecting_signal.connect(widget.mail_state_connecting) +        qtsigs.disconnecting_signal.connect(widget.mail_state_disconnecting) +        qtsigs.disconnected_signal.connect(widget.mail_state_disconnected) diff --git a/src/leap/bitmask/services/mail/connection.py b/src/leap/bitmask/services/mail/connection.py new file mode 100644 index 00000000..29378f62 --- /dev/null +++ b/src/leap/bitmask/services/mail/connection.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- +# connection.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +""" +Email Connections +""" +from PySide import QtCore + +from leap.bitmask.services.connections import AbstractLEAPConnection + + +class IMAPConnectionSignals(QtCore.QObject): +    """ +    Qt Signals used by IMAPConnection +    """ +    # commands +    do_connect_signal = QtCore.Signal() +    do_disconnect_signal = QtCore.Signal() + +    # intermediate stages +    connecting_signal = QtCore.Signal() +    disconnecting_signal = QtCore.Signal() + +    connected_signal = QtCore.Signal() +    disconnected_signal = QtCore.Signal() + +    connection_died_signal = QtCore.Signal() +    connection_aborted_signal = QtCore.Signal() + + +class IMAPConnection(AbstractLEAPConnection): + +    _connection_name = "IMAP" + +    def __init__(self): +        self._qtsigs = IMAPConnectionSignals() + + +class SMTPConnectionSignals(QtCore.QObject): +    """ +    Qt Signals used by SMTPConnection +    """ +    # commands +    do_connect_signal = QtCore.Signal() +    do_disconnect_signal = QtCore.Signal() + +    # intermediate stages +    connecting_signal = QtCore.Signal() +    disconnecting_signal = QtCore.Signal() + +    connected_signal = QtCore.Signal() +    disconnected_signal = QtCore.Signal() + +    connection_died_signal = QtCore.Signal() +    connection_aborted_signal = QtCore.Signal() + + +class SMTPConnection(AbstractLEAPConnection): + +    _connection_name = "IMAP" + +    def __init__(self): +        self._qtsigs = SMTPConnectionSignals() + + +class MailConnectionSignals(QtCore.QObject): +    """ +    Qt Signals used by MailConnection +    """ +    # commands +    do_connect_signal = QtCore.Signal() +    do_disconnect_signal = QtCore.Signal() + +    connecting_signal = QtCore.Signal() +    disconnecting_signal = QtCore.Signal() + +    connected_signal = QtCore.Signal() +    disconnected_signal = QtCore.Signal() + +    connection_died_signal = QtCore.Signal() +    connection_aborted_signal = QtCore.Signal() + + +class MailConnection(AbstractLEAPConnection): + +    components = IMAPConnection, SMTPConnection +    _connection_name = "Mail" + +    def __init__(self): +        self._qtsigs = MailConnectionSignals() diff --git a/src/leap/bitmask/services/soledad/soledadbootstrapper.py b/src/leap/bitmask/services/soledad/soledadbootstrapper.py index 4619ba80..1940fc68 100644 --- a/src/leap/bitmask/services/soledad/soledadbootstrapper.py +++ b/src/leap/bitmask/services/soledad/soledadbootstrapper.py @@ -25,6 +25,7 @@ from ssl import SSLError  from PySide import QtCore  from u1db import errors as u1db_errors +from zope.proxy import sameProxiedObjects  from leap.bitmask.config import flags  from leap.bitmask.config.providerconfig import ProviderConfig @@ -190,7 +191,8 @@ class SoledadBootstrapper(AbstractBootstrapper):              # soledad-launcher in the gui.              raise -        leap_check(self._soledad is not None, +        leap_assert(sameProxiedObjects(self._soledad, None) +                    is not True,                     "Null soledad, error while initializing")          # and now, let's sync | 
