summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomás Touceda <chiiph@leap.se>2013-10-28 13:03:54 -0300
committerTomás Touceda <chiiph@leap.se>2013-10-28 13:03:54 -0300
commit856a3301b025f3d14c36a0ec0c90563358780670 (patch)
tree8193836786a205b19359e696cb357af3d2ac3c29
parente149f7e3e6a5c4621edd252fd7f75c580c5e49b3 (diff)
parent4c3329d67ec6730da0b26cc3b57a1ee689560cd0 (diff)
Merge remote-tracking branch 'kali/feature/refactor-mail-connections' into develop
-rw-r--r--changes/feature_4059-mail-state-refactor1
-rw-r--r--changes/feature_colored-logs1
-rw-r--r--pkg/requirements.pip1
-rw-r--r--src/leap/bitmask/app.py18
-rw-r--r--src/leap/bitmask/gui/mail_status.py105
-rw-r--r--src/leap/bitmask/gui/mainwindow.py195
-rw-r--r--src/leap/bitmask/gui/statemachines.py414
-rw-r--r--src/leap/bitmask/services/connections.py10
-rw-r--r--src/leap/bitmask/services/eip/connection.py2
-rw-r--r--src/leap/bitmask/services/mail/conductor.py383
-rw-r--r--src/leap/bitmask/services/mail/connection.py103
-rw-r--r--src/leap/bitmask/services/soledad/soledadbootstrapper.py4
12 files changed, 1022 insertions, 215 deletions
diff --git a/changes/feature_4059-mail-state-refactor b/changes/feature_4059-mail-state-refactor
new file mode 100644
index 00000000..2e3f5056
--- /dev/null
+++ b/changes/feature_4059-mail-state-refactor
@@ -0,0 +1 @@
+- Refactor mail connections to use state machine. Closes: #4059
diff --git a/changes/feature_colored-logs b/changes/feature_colored-logs
new file mode 100644
index 00000000..b28a38bd
--- /dev/null
+++ b/changes/feature_colored-logs
@@ -0,0 +1 @@
+- Use coloredlogs handler if present (for development, not a requirement).
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/app.py b/src/leap/bitmask/app.py
index 40a77075..3bb9c8c3 100644
--- a/src/leap/bitmask/app.py
+++ b/src/leap/bitmask/app.py
@@ -39,7 +39,6 @@
# M:::::::::::~NMMM7???7MMMM:::::::::::::::::::::::NMMMI??I7MMMM:::::::::::::M
# M::::::::::::::7MMMMMMM+:::::::::::::::::::::::::::?MMMMMMMZ:::::::::::::::M
# (thanks to: http://www.glassgiant.com/ascii/)
-
import logging
import signal
import sys
@@ -105,9 +104,16 @@ def add_logger_handlers(debug=False, logfile=None):
formatter = logging.Formatter(log_format)
# Console handler
- console = logging.StreamHandler()
- console.setLevel(level)
- console.setFormatter(formatter)
+ try:
+ import coloredlogs
+ console = coloredlogs.ColoredStreamHandler(level=level)
+ except ImportError:
+ console = logging.StreamHandler()
+ console.setLevel(level)
+ console.setFormatter(formatter)
+ using_coloredlog = False
+ else:
+ using_coloredlog = True
silencer = log_silencer.SelectiveSilencerFilter()
console.addFilter(silencer)
@@ -131,6 +137,9 @@ def add_logger_handlers(debug=False, logfile=None):
logger.addHandler(fileh)
logger.debug('File handler plugged!')
+ if not using_coloredlog:
+ replace_stdout_stderr_with_logging(logger)
+
return logger
@@ -185,7 +194,6 @@ def main():
BaseConfig.standalone = standalone
logger = add_logger_handlers(debug, logfile)
- replace_stdout_stderr_with_logging(logger)
# And then we import all the other stuff
from leap.bitmask.gui import locale_rc
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