def handle_account(account_id): try: account = AccountManager().get_account(account_id) except KeyError: return error_response(404, 'account not found') if request.method == 'GET': # Retrieve account state = get_state(account) if 'auth' in state: state['auth']['password'] = '******' return jsonify({'account': state}) elif request.method == 'PUT': # Update existing account state = get_json(request) if not state: return error_response(400, 'error processing PUT body') state.pop('id', None) try: set_state(account, state) except ValueError, e: # TODO: some settings may have been applied, what do we do? return error_response(400, str(e)) account.save() state = get_state(account) if 'auth' in state: state['auth']['password'] = '******' return jsonify({'account': state})
def removeSelectedAccount(self): account_info = self.selectedAccount() if account_info: account = account_info.account text = "Permanently remove account %s?" % account_info.name text = re.sub("%", "%%", text) # http://stackoverflow.com/questions/4498709/problem-in-displaying-in-nsrunalertpanel if NSRunAlertPanel("Remove Account", text, "Remove", "Cancel", None) != NSAlertDefaultReturn: return if account.tls.certificate and os.path.basename( account.tls.certificate.normalized) != 'default.crt': unlink(account.tls.certificate.normalized) account_manager = AccountManager() if account_manager.default_account is account: try: account_manager.default_account = ( acc for acc in account_manager.iter_accounts() if acc is not account and acc.enabled).next() except StopIteration: account_manager.default_account = None account.delete()
def _NH_CFGSettingsObjectDidChange(self, notification): if notification.sender is BlinkSettings(): account_manager = AccountManager() if 'presence.offline_note' in notification.data.modified: for account in (account for account in account_manager.get_accounts() if account.xcap.discovered): state = BlinkPresenceState(account).offline_state account.xcap_manager.set_offline_status(OfflineStatus(state) if state is not None else None) if 'presence.icon' in notification.data.modified: icon = IconManager().get('avatar') status_icon = Icon(icon.content, icon.content_type) if icon is not None else None for account in (account for account in account_manager.get_accounts() if account.xcap.discovered): account.xcap_manager.set_status_icon(status_icon) if 'presence.current_state' in notification.data.modified: for account in (account for account in account_manager.get_accounts() if account.enabled and account.presence.enabled): account.presence_state = BlinkPresenceState(account).online_state else: account = notification.sender if {'xcap.enabled', 'xcap.xcap_root'}.intersection(notification.data.modified): account.xcap.icon = None account.save() elif {'presence.enabled', 'display_name', 'xcap.icon'}.intersection(notification.data.modified) and account.presence.enabled: account.presence_state = BlinkPresenceState(account).online_state if account.xcap.discovered and (set(notification.data.modified) != {'xcap.icon'} or account.id in self._should_set_offline_status): state = BlinkPresenceState(account).offline_state account.xcap_manager.set_offline_status(OfflineStatus(state) if state is not None else None) if account.id in self._should_set_offline_status: # do not use set.discard() here to avoid race conditions. it should only be removed if present. self._should_set_offline_status.remove(account.id)
def migratePasswordsToKeychain(self): account_manager = AccountManager() configuration_manager = ConfigurationManager() bonjour_account = BonjourAccount() for account in (account for account in account_manager.iter_accounts() if account is not bonjour_account): try: stored_auth_password = configuration_manager.get( account.__key__ + ['auth', 'password']) except ObjectNotFoundError: stored_auth_password = None try: stored_ldap_password = configuration_manager.get( account.__key__ + ['ldap', 'password']) except ObjectNotFoundError: stored_ldap_password = None try: stored_web_password = configuration_manager.get( account.__key__ + ['server', 'web_password']) except ObjectNotFoundError: stored_web_password = None if (stored_auth_password, stored_ldap_password, stored_web_password) != ('keychain', 'keychain', 'keychain'): Account.auth.password.dirty[account.auth] = True Account.ldap.password.dirty[account.ldap] = True Account.server.web_password.dirty[account.server] = True account.save()
def retryTransfer_(self, sender): if self.oldTransferInfo: try: account = next( (account for account in AccountManager().iter_accounts() if account.id == self.oldTransferInfo.local_uri)) except StopIteration: account = AccountManager().default_account target_uri = normalize_sip_uri_for_outgoing_session( self.oldTransferInfo.remote_uri, AccountManager().default_account) filenames = [ unicodedata.normalize('NFC', self.oldTransferInfo.file_path) ] NSApp.delegate( ).contactsWindowController.sessionControllersManager.send_files_to_contact( account, target_uri, filenames) else: self.failed = False self.done = False self.updateProgressInfo() self.progressBar.setIndeterminate_(True) self.progressBar.startAnimation_(None) self.progressBar.setHidden_(True) self.updateChecksumProgressInfo(0) self.checksumProgressBar.setIndeterminate_(False) self.checksumProgressBar.startAnimation_(None) self.checksumProgressBar.setHidden_(False) self.sizeText.setTextColor_(NSColor.grayColor()) self.relayoutForRetry() self.transfer.retry()
def migratePasswordsToKeychain(self): if NSApp.delegate().applicationName == 'SIP2SIP': return account_manager = AccountManager() configuration_manager = ConfigurationManager() bonjour_account = BonjourAccount() for account in (account for account in account_manager.iter_accounts() if account is not bonjour_account): try: stored_auth_password = configuration_manager.get(account.__key__ + ['auth', 'password']) except ObjectNotFoundError: stored_auth_password = None try: stored_ldap_password = configuration_manager.get(account.__key__ + ['ldap', 'password']) except ObjectNotFoundError: stored_ldap_password = None try: stored_web_password = configuration_manager.get(account.__key__ + ['server', 'web_password']) except ObjectNotFoundError: stored_web_password = None if (stored_auth_password, stored_ldap_password, stored_web_password) != ('keychain', 'keychain', 'keychain'): Account.auth.password.dirty[account.auth] = True Account.ldap.password.dirty[account.ldap] = True Account.server.web_password.dirty[account.server] = True account.save()
def _NH_SIPApplicationWillStart(self, sender, data): settings = SIPSimpleSettings() settings.user_agent = "%s %s (MacOSX)" % ( NSApp.delegate().applicationName, self._version) BlinkLogger().log_info(u"Initializing SIP SIMPLE Client SDK %s" % sdk_version) build = str(NSBundle.mainBundle().infoDictionary().objectForKey_( "CFBundleVersion")) date = str(NSBundle.mainBundle().infoDictionary().objectForKey_( "BlinkVersionDate")) BlinkLogger().log_info(u"Build %s from %s" % (build, date)) self.migratePasswordsToKeychain() # Set audio settings compatible with AEC and Noise Supressor settings.audio.sample_rate = 16000 settings.audio.tail_length = 15 if settings.audio.enable_aec else 0 settings.save() BlinkLogger().log_info( u"Acoustic Echo Canceller is %s" % ('enabled' if settings.audio.enable_aec else 'disabled')) # Although this setting is set at enrollment time, people who have downloaded previous versions will not have it account_manager = AccountManager() for account in account_manager.iter_accounts(): must_save = False if account is not BonjourAccount( ) and account.sip.primary_proxy is None and account.sip.outbound_proxy and not account.sip.selected_proxy: account.sip.primary_proxy = account.sip.outbound_proxy must_save = True if account is not BonjourAccount( ) and settings.tls.verify_server != account.tls.verify_server: account.tls.verify_server = settings.tls.verify_server must_save = True if account.tls.certificate and os.path.basename( account.tls.certificate.normalized) != 'default.crt': account.tls.certificate = DefaultValue must_save = True if account.id.domain == "sip2sip.info": if account.server.settings_url is None: account.server.settings_url = "https://blink.sipthor.net/settings.phtml" must_save = True if not account.ldap.hostname: account.ldap.hostname = "ldap.sipthor.net" account.ldap.dn = "ou=addressbook, dc=sip2sip, dc=info" account.ldap.enabled = True must_save = True if must_save: account.save() logger = FileLogger() logger.start() self.ip_address_monitor.start()
def _AH_HistoryMenuTriggered(self, action): account_manager = AccountManager() session_manager = SessionManager() try: account = account_manager.get_account(action.entry.account_id) except KeyError: account = None contact, contact_uri = URIUtils.find_contact(action.entry.uri) session_manager.create_session(contact, contact_uri, [StreamDescription('audio')], account=account) # TODO: memorize media type and use it? -Saul (not sure about history in/out -Dan)
def _create_sip_account(self, username, password, email_address, display_name, timezone=None): red = '#cc0000' if timezone is None and sys.platform != 'win32': try: timezone = open('/etc/timezone').read().strip() except (OSError, IOError): try: timezone = '/'.join(os.readlink('/etc/localtime').split('/')[-2:]) except (OSError, IOError): pass enrollment_data = dict(username=username.lower().encode('utf-8'), password=password.encode('utf-8'), email=email_address.encode('utf-8'), display_name=display_name.encode('utf-8'), tzinfo=timezone) try: settings = SIPSimpleSettings() data = urllib.parse.urlencode(dict(enrollment_data)) response = urllib.request.urlopen(settings.server.enrollment_url, data.encode()) response_data = json.loads(response.read().decode('utf-8').replace(r'\/', '/')) response_data = defaultdict(lambda: None, response_data) if response_data['success']: try: passport = response_data['passport'] if passport is not None: certificate_path = self._save_certificates(response_data['sip_address'], passport['crt'], passport['key'], passport['ca']) else: certificate_path = None except (GNUTLSError, IOError, OSError): certificate_path = None account_manager = AccountManager() try: account = Account(response_data['sip_address']) except DuplicateIDError: account = account_manager.get_account(response_data['sip_address']) account.enabled = True account.display_name = display_name or None account.auth.password = password account.sip.outbound_proxy = response_data['outbound_proxy'] account.nat_traversal.msrp_relay = response_data['msrp_relay'] account.xcap.xcap_root = response_data['xcap_root'] account.tls.certificate = certificate_path account.server.conference_server = response_data['conference_server'] account.server.settings_url = response_data['settings_url'] account.save() account_manager.default_account = account call_in_gui_thread(self.accept) elif response_data['error'] == 'user_exists': call_in_gui_thread(self.username_editor.addException, username) else: call_in_gui_thread(setattr, self.create_status_label, 'value', Status(response_data['error_message'], color=red)) except (json.decoder.JSONDecodeError, KeyError): call_in_gui_thread(setattr, self.create_status_label, 'value', Status('Illegal server response', color=red)) except urllib.error.URLError as e: call_in_gui_thread(setattr, self.create_status_label, 'value', Status('Failed to contact server: %s' % e.reason, color=red)) finally: call_in_gui_thread(self.setEnabled, True)
def reregister_account(account_id): try: account = AccountManager().get_account(account_id) except KeyError: return error_response(404, 'account not found') if account is BonjourAccount(): return error_response(403, 'bonjour account does not register') account.reregister() return ''
def sync(self): if self.sync_active: return if not self.cloud_storage: return self.sync_active = True changes = 0 BlinkLogger().log_debug("Synchronizing accounts with iCloud") for account in AccountManager().get_accounts(): if isinstance(account, Account): if account.id not in self.storage_keys: if self.first_sync_completed: if isinstance(account, Account): # don't delete account because iCloud is unreliable pass # BlinkLogger().log_info(u"Removing %s because was removed from iCloud" % account.id) # account.delete() else: json_data = self._get_account_data(account) BlinkLogger().log_info( "Adding %s to iCloud (%s bytes)" % (account.id, len(json_data))) self.cloud_storage.setString_forKey_( json_data, account.id) changes += 1 else: local_json = self._get_account_data(account) remote_json = self.cloud_storage.stringForKey_(account.id) if self._has_difference(account, local_json, remote_json, True): BlinkLogger().log_info("Updating %s from iCloud" % account.id) self._update_account_from_cloud(account.id) changes += 1 for key in self.storage_keys: if '@' in key: try: AccountManager().get_account(key) except KeyError: BlinkLogger().log_info("Adding %s from iCloud" % key) self._update_account_from_cloud(key) changes += 1 if not self.first_sync_completed: self.first_sync_completed = True BlinkLogger().log_info( "First time synchronization with iCloud completed") elif not changes: BlinkLogger().log_info("iCloud is synchronized") else: BlinkLogger().log_info("Synchronization with iCloud completed") self.sync_active = False
def _NH_SIPApplicationWillStart(self, sender, data): settings = SIPSimpleSettings() _version = str(NSBundle.mainBundle().infoDictionary().objectForKey_("CFBundleShortVersionString")) settings.user_agent = "%s %s (MacOSX)" % (NSApp.delegate().applicationName, _version) BlinkLogger().log_debug("SIP User Agent: %s" % settings.user_agent) settings.save() self.migratePasswordsToKeychain() self.cleanupIcons() # Set audio settings compatible with AEC and Noise Suppression settings.audio.sample_rate = 32000 if settings.audio.echo_canceller.enabled else 48000 if NSApp.delegate().service_provider_help_url and settings.service_provider.help_url != NSApp.delegate().service_provider_help_url: settings.service_provider.help_url = NSApp.delegate().service_provider_help_url settings.save() if NSApp.delegate().service_provider_name and settings.service_provider.name != NSApp.delegate().service_provider_name: settings.service_provider.name = NSApp.delegate().service_provider_name settings.save() BlinkLogger().log_debug("Audio engine sampling rate %dKHz covering 0-%dKHz spectrum" % (settings.audio.sample_rate/1000, settings.audio.sample_rate/1000/2)) BlinkLogger().log_debug("Acoustic Echo Canceller is %s" % ('enabled' if settings.audio.echo_canceller.enabled else 'disabled')) account_manager = AccountManager() for account in account_manager.iter_accounts(): must_save = False if account is not BonjourAccount() and account.sip.primary_proxy is None and account.sip.outbound_proxy and not account.sip.selected_proxy: account.sip.primary_proxy = account.sip.outbound_proxy if account is not BonjourAccount() and settings.tls.verify_server != account.tls.verify_server: account.tls.verify_server = settings.tls.verify_server if account.tls.certificate and os.path.basename(account.tls.certificate.normalized) != 'default.crt': account.tls.certificate = DefaultValue if account.rtp.encryption_type == '': account.rtp.encryption.enabled = False elif account.rtp.encryption_type == 'opportunistic': account.rtp.encryption.enabled = True account.rtp.encryption.key_negotiation = 'opportunistic' elif account.rtp.encryption_type == 'sdes_optional': account.rtp.encryption.enabled = True account.rtp.encryption.key_negotiation = 'sdes_optional' elif account.rtp.encryption_type == 'sdes_mandatory': account.rtp.encryption.enabled = True account.rtp.encryption.key_negotiation = 'sdes_mandatory' elif account.rtp.encryption_type == 'zrtp': account.rtp.encryption.enabled = True account.rtp.encryption.key_negotiation = 'zrtp' account.save() logger = FileLogger() logger.start() self.ip_address_monitor.start()
def _create_sip_account(self, username, password, email_address, display_name, timezone=None): red = '#cc0000' if timezone is None and sys.platform != 'win32': try: timezone = open('/etc/timezone').read().strip() except (OSError, IOError): try: timezone = '/'.join(os.readlink('/etc/localtime').split('/')[-2:]) except (OSError, IOError): pass enrollment_data = dict(username=username.lower().encode('utf-8'), password=password.encode('utf-8'), email=email_address.encode('utf-8'), display_name=display_name.encode('utf-8'), tzinfo=timezone) try: settings = SIPSimpleSettings() response = urllib2.urlopen(settings.server.enrollment_url, urllib.urlencode(dict(enrollment_data))) response_data = cjson.decode(response.read().replace(r'\/', '/')) response_data = defaultdict(lambda: None, response_data) if response_data['success']: from blink import Blink try: certificate_path = None passport = response_data['passport'] if passport is not None: certificate_path = Blink().save_certificates(response_data['sip_address'], passport['crt'], passport['key'], passport['ca']) except (GNUTLSError, IOError, OSError): pass account_manager = AccountManager() try: account = Account(response_data['sip_address']) except DuplicateIDError: account = account_manager.get_account(response_data['sip_address']) account.enabled = True account.display_name = display_name or None account.auth.password = password account.sip.outbound_proxy = response_data['outbound_proxy'] account.nat_traversal.msrp_relay = response_data['msrp_relay'] account.xcap.xcap_root = response_data['xcap_root'] account.tls.certificate = certificate_path account.server.conference_server = response_data['conference_server'] account.server.settings_url = response_data['settings_url'] account.save() account_manager.default_account = account call_in_gui_thread(self.accept) elif response_data['error'] == 'user_exists': call_in_gui_thread(self.username_editor.addException, username) else: call_in_gui_thread(setattr, self.create_status_label, 'value', Status(response_data['error_message'], color=red)) except (cjson.DecodeError, KeyError): call_in_gui_thread(setattr, self.create_status_label, 'value', Status('Illegal server response', color=red)) except urllib2.URLError, e: call_in_gui_thread(setattr, self.create_status_label, 'value', Status('Failed to contact server: %s' % e.reason, color=red))
def init_configurations(self): account_manager = AccountManager() settings = SIPSimpleSettings() self.notification_center.add_observer(self, sender=settings) # fixup default account self._selected_account = account_manager.default_account if self._selected_account is None: self._selected_account = account_manager.get_accounts()[0] default_ca = open(Resources.get('ca.crt'), "r").read().strip() self.set_default_certificate_authority(default_ca)
def _SH_AcceptButtonClicked(self): if self.panel_view.currentWidget() is self.add_account_panel: account = Account(self.sip_address) account.enabled = True account.display_name = self.display_name or None account.auth.password = self.password account.save() account_manager = AccountManager() account_manager.default_account = account self.accept() else: self.setEnabled(False) self.create_status_label.value = Status('Creating account on server...') self._create_sip_account(self.username, self.password, self.email_address, self.display_name)
def _SH_AcceptButtonClicked(self): if self.panel_view.currentWidget() is self.add_account_panel: account = Account(self.sip_address) account.enabled = True account.display_name = self.display_name account.auth.password = self.password call_in_auxiliary_thread(account.save) account_manager = AccountManager() account_manager.default_account = account self.accept() else: self.setEnabled(False) self.create_status_label.value = Status("Creating account on server...") self._create_sip_account(self.username, self.password, self.email_address, self.display_name)
def _NH_SIPApplicationWillStart(self, sender, data): settings = SIPSimpleSettings() settings.user_agent = "%s %s (MacOSX)" % (NSApp.delegate().applicationName, self._version) BlinkLogger().log_info(u"Initializing SIP SIMPLE Client SDK %s" % sdk_version) build = str(NSBundle.mainBundle().infoDictionary().objectForKey_("CFBundleVersion")) date = str(NSBundle.mainBundle().infoDictionary().objectForKey_("BlinkVersionDate")) BlinkLogger().log_info(u"Build %s from %s" % (build, date)) self.migratePasswordsToKeychain() # Set audio settings compatible with AEC and Noise Supressor settings.audio.sample_rate = 16000 settings.audio.tail_length = 15 if settings.audio.enable_aec else 0 settings.save() BlinkLogger().log_info(u"Acoustic Echo Canceller is %s" % ('enabled' if settings.audio.enable_aec else 'disabled')) # Although this setting is set at enrollment time, people who have downloaded previous versions will not have it account_manager = AccountManager() for account in account_manager.iter_accounts(): must_save = False if account is not BonjourAccount() and account.sip.primary_proxy is None and account.sip.outbound_proxy and not account.sip.selected_proxy: account.sip.primary_proxy = account.sip.outbound_proxy must_save = True if account is not BonjourAccount() and settings.tls.verify_server != account.tls.verify_server: account.tls.verify_server = settings.tls.verify_server must_save = True if account.tls.certificate and os.path.basename(account.tls.certificate.normalized) != 'default.crt': account.tls.certificate = DefaultValue must_save = True if account.id.domain == "sip2sip.info": if account.server.settings_url is None: account.server.settings_url = "https://blink.sipthor.net/settings.phtml" must_save = True if not account.ldap.hostname: account.ldap.hostname = "ldap.sipthor.net" account.ldap.dn = "ou=addressbook, dc=sip2sip, dc=info" account.ldap.enabled = True must_save = True if must_save: account.save() logger = FileLogger() logger.start() self.ip_address_monitor.start()
def _NH_SIPAccountManagerDidStart(self, notification): account = AccountManager().default_account if account is not None: model = self.model() source_model = model.sourceModel() account_index = source_model.accounts.index(account) self.setCurrentIndex(model.mapFromSource(source_model.index(account_index)).row())
def _NH_SIPApplicationWillStart(self, notification): account_manager = AccountManager() settings = SIPSimpleSettings() self.silent_action.setChecked(settings.audio.silent) self.silent_button.setChecked(settings.audio.silent) self.answering_machine_action.setChecked(settings.answering_machine.enabled) self.auto_accept_chat_action.setChecked(settings.chat.auto_accept) self.received_messages_sound_action.setChecked(settings.sounds.play_message_alerts) if settings.google_contacts.enabled: self.google_contacts_action.setText('Disable &Google Contacts') else: self.google_contacts_action.setText('Enable &Google Contacts...') if not any(account.enabled for account in account_manager.iter_accounts()): self.display_name.setEnabled(False) self.activity_note.setEnabled(False) self.account_state.setEnabled(False)
def init(self): self = super(PreferencesController, self).init() if self: notification_center = NotificationCenter() notification_center.add_observer(self, name="SIPAccountDidDeactivate") notification_center.add_observer( self, name="SIPAccountRegistrationDidSucceed") notification_center.add_observer( self, name="SIPAccountRegistrationDidFail") notification_center.add_observer( self, name="SIPAccountRegistrationDidEnd") notification_center.add_observer( self, name="SIPAccountRegistrationGotAnswer") notification_center.add_observer(self, name="SIPAccountWillRegister") notification_center.add_observer(self, name="BonjourAccountWillRegister") notification_center.add_observer( self, name="BonjourAccountRegistrationDidSucceed") notification_center.add_observer( self, name="BonjourAccountRegistrationDidFail") notification_center.add_observer( self, name="BonjourAccountRegistrationDidEnd") notification_center.add_observer(self, name="SIPAccountChangedByICloud") notification_center.add_observer(self, sender=AccountManager()) return self
def _shutdown_subsystems(self): # cleanup internals if self._timer is not None and self._timer.active(): self._timer.cancel() self._timer = None # shutdown middleware components dns_manager = DNSManager() account_manager = AccountManager() addressbook_manager = AddressbookManager() session_manager = SessionManager() procs = [ proc.spawn(dns_manager.stop), proc.spawn(account_manager.stop), proc.spawn(addressbook_manager.stop), proc.spawn(session_manager.stop) ] proc.waitall(procs) # stop video device self.video_device.producer.close() # shutdown engine self.engine.stop() self.engine.join(timeout=5) # stop threads thread_manager = ThreadManager() thread_manager.stop() # stop the reactor reactor.stop()
def addExistingAccount(self): try: display_name = str(self.displayNameText.stringValue().strip()) address = str(self.addressText.stringValue().strip()) password = str(self.passwordText.stringValue().strip()) sync_with_icloud = True if self.syncWithiCloudCheckbox.state() == NSOnState else False account = Account(str(address)) account.display_name = display_name account.auth.password = password account.enabled = True account.gui.sync_with_icloud = sync_with_icloud account.xcap.enabled = True if self.syncContactsCheckBox.state() == NSOnState else False account.presence.enabled = True if self.syncContactsCheckBox.state() == NSOnState else False if account.id.domain == 'sip2sip.info': account.server.settings_url = "https://blink.sipthor.net/settings.phtml" account.ldap.hostname = "ldap.sipthor.net" account.ldap.dn = "ou=addressbook, dc=sip2sip, dc=info" account.ldap.enabled = True account.nat_traversal.use_ice = True account.rtp.srtp_encryption = 'optional' account.save() except ValueError as e: NSRunAlertPanel(NSLocalizedString("Sign In to SIP Account", "Window title"), NSLocalizedString("Cannot add SIP Account: %s", "Label") % e, NSLocalizedString("OK", "Button title"), None, None) return False AccountManager().default_account = account return True
def __init__(self, contact, group): NSBundle.loadNibNamed_owner_("AddContact", self) self.storagePlacePopUp.removeAllItems() self.storagePlacePopUp.addItemWithTitle_("None") item = self.storagePlacePopUp.lastItem() item.setRepresentedObject_(None) accounts = [acct for acct in AccountManager().get_accounts() if not isinstance(acct, BonjourAccount)] for account in sorted(accounts, key=attrgetter('order')): self.storagePlacePopUp.addItemWithTitle_(u'%s'%account.id) item = self.storagePlacePopUp.lastItem() item.setRepresentedObject_(account) # display the contact data self.addressText.setStringValue_(contact.uri or "") self.nameText.setStringValue_(contact.name or "") self.groupCombo.setStringValue_(group or "") self.photoImage.setImage_(contact.icon or self.defaultPhotoImage) self.aliasText.setStringValue_("; ".join(contact.aliases)) index = self.storagePlacePopUp.indexOfItemWithRepresentedObject_(contact.stored_in_account) if contact.stored_in_account else 0 self.storagePlacePopUp.selectItemAtIndex_(index if index >0 else 0) self.preferredMedia.selectCellWithTag_(2 if contact.preferred_media == "chat" else 1) self.contact = contact
def _NH_SIPApplicationDidStart(self, notification): self.ip_address_monitor.start() self.main_window.show() accounts = AccountManager().get_accounts() if not accounts or (self.first_run and accounts == [BonjourAccount()]): self.main_window.preferences_window.show_create_account_dialog() self.update_manager.initialize()
def _NH_SIPApplicationDidStart(self, notification): account_manager = AccountManager() self.xcap_manager = XCAPManager(account_manager.default_account) notification_center = NotificationCenter() notification_center.add_observer(self, sender=self.xcap_manager) self.xcap_manager.load(os.path.realpath('xcap-cache')) self.xcap_manager.start()
def validateAddAccountAction(self): if NSApp.delegate().applicationName == 'Blink Lite': return len([ account for account in AccountManager().iter_accounts() if not isinstance(account, BonjourAccount) ]) <= 2 return True
def updateBonjourServersPopupButton(self): settings = SIPSimpleSettings() account = AccountManager().default_account if isinstance(account, BonjourAccount): self.bonjour_server_combolist.removeAllItems() if SIPManager().bonjour_conference_services.servers: servers = set() servers_dict = {} for server in (server for server in SIPManager().bonjour_conference_services.servers if server.uri.transport in settings.sip.transport_list): servers_dict.setdefault("%s@%s" % (server.uri.user, server.uri.host), []).append(server) for transport in (transport for transport in ('tls', 'tcp', 'udp') if transport in settings.sip.transport_list): for k, v in servers_dict.items(): try: server = next((server for server in v if server.uri.transport == transport)) except StopIteration: pass else: servers.add(server) break for server in servers: self.bonjour_server_combolist.addItemWithTitle_('%s (%s)' % (server.host, server.uri.host)) item = self.bonjour_server_combolist.lastItem() item.setRepresentedObject_(server) self.ok_button.setEnabled_(True) else: self.bonjour_server_combolist.addItemWithTitle_(NSLocalizedString("No conference server in this neighbourhood", "Menu item")) self.bonjour_server_combolist.lastItem().setEnabled_(False) self.ok_button.setEnabled_(False) else: self.ok_button.setEnabled_(False)
def init_configurations(self): account_manager = AccountManager() settings = SIPSimpleSettings() # fixup default account self._selected_account = account_manager.default_account if self._selected_account is None: self._selected_account = account_manager.get_accounts()[0] # save default ca if needed ca = open(Resources.get('ca.crt'), "r").read().strip() try: X509Certificate(ca) except GNUTLSError, e: BlinkLogger().log_error(u"Invalid Certificate Authority: %s" % e) return
def _shutdown_subsystems(self): dns_manager = DNSManager() account_manager = AccountManager() session_manager = SessionManager() # terminate all sessions p = proc.spawn(session_manager.stop) p.wait() # shutdown SylkServer components procs = [proc.spawn(self.web_server.stop), proc.spawn(self.request_handler.stop), proc.spawn(self.thor_interface.stop)] proc.waitall(procs) # shutdown other middleware components procs = [proc.spawn(dns_manager.stop), proc.spawn(account_manager.stop)] proc.waitall(procs) # shutdown engine self.engine.stop() self.engine.join(timeout=5) # stop threads thread_manager = ThreadManager() thread_manager.stop() # stop the reactor reactor.stop()
def addContactToModel(self, watcher): # adds a new contact into the contact list model try: account = AccountManager().get_account(watcher.account) except KeyError: return try: contact = account.contact_manager.get_contact(watcher.address) except KeyError: return if contact.group is None: try: group = (g for g in ContactGroupManager().iter_groups() if g.name == self.groupCombo.stringValue()).next() except StopIteration: # insert after last editable group index = 0 for g in NSApp.delegate( ).contactsWindowController.model.contactGroupsList: if not g.editable: break index += 1 group = ContactGroup(self.groupCombo.stringValue()) group.position = index group.save() contact.group = group contact.save() NSUserDefaults.standardUserDefaults().setValue_forKey_( self.groupCombo.stringValue(), "LastGroupForWatcher")
def userButtonClicked_(self, sender): if sender.tag() == 1: # account popup changed try: account = AccountManager().get_account( self.accountPop.titleOfSelectedItem()) self.account = account and account.id self.refreshPolicyTable() NSUserDefaults.standardUserDefaults().setValue_forKey_( self.account, "SelectedPolicyPresenceAccount") except KeyError: pass elif sender.tag() == 2: # add a new policy entry # the outcome of the operation is a change to an existing contact or the addition of a new one contact # the notifications emitted later by the contact will refresh the UI if self.account: created_new_contact = False i = 0 while not created_new_contact: try: account = AccountManager().get_account(self.account) address = "new_entry@" + account.id.domain if not i else "new_entry" + str( i) + '@' + account.id.domain contact = Contact(address, account=account) self.setContactPolicyForEvent(contact, self.event, self.defaultPolicy) contact.save() self.last_edited_address = contact.uri created_new_contact = True except DuplicateIDError: i += 1 elif sender.tag() == 3: # delete a policy entry # the outcome of the operation is a change to an existing contact or the deletion of a contact # the notifications emitted later by the contact will refresh the UI if self.account: view = self.tabViewForEvent[self.event] if view.selectedRow() >= 0: filter = unicode( self.searchSubscriberBox.stringValue().strip()) i_row = self.filtered_contacts_map[ view.selectedRow()] if filter else view.selectedRow() self.deletePolicyAction( self.account, self.event, self.policy_data[self.account][self.event][i_row]) elif sender.tag() == 4: # close window self.window().close()
def _NH_SIPAccountManagerDidChangeDefaultAccount(self, notification): self.room.setStringValue_('') self.nickname_textfield.setStringValue_('') account = AccountManager().default_account if account is not None: self.nickname_textfield.setStringValue_(account.conference.nickname or account.display_name) self.updatePopupButtons()
def _NH_SIPApplicationWillStart(self, notification): account_manager = AccountManager() settings = SIPSimpleSettings() self.silent_action.setChecked(settings.audio.silent) self.silent_button.setChecked(settings.audio.silent) self.answering_machine_action.setChecked(settings.answering_machine.enabled) self.auto_accept_chat_action.setChecked(settings.chat.auto_accept) self.received_messages_sound_action.setChecked(settings.sounds.play_message_alerts) if settings.google_contacts.authorization_token is None: self.google_contacts_action.setText(u'Enable &Google Contacts...') else: self.google_contacts_action.setText(u'Disable &Google Contacts') self.google_contacts_action.triggered.connect(self._AH_GoogleContactsActionTriggered) if not any(account.enabled for account in account_manager.iter_accounts()): self.display_name.setEnabled(False) self.activity_note.setEnabled(False) self.account_state.setEnabled(False)
def _NH_CFGSettingsObjectDidChange(self, notification): settings = SIPSimpleSettings() blink_settings = BlinkSettings() icon_manager = IconManager() if notification.sender is settings: if 'audio.muted' in notification.data.modified: self.mute_action.setChecked(settings.audio.muted) self.mute_button.setChecked(settings.audio.muted) if 'audio.silent' in notification.data.modified: self.silent_action.setChecked(settings.audio.silent) self.silent_button.setChecked(settings.audio.silent) if 'audio.output_device' in notification.data.modified: action = (action for action in self.output_devices_group.actions() if action.data() == settings.audio.output_device).next() action.setChecked(True) if 'audio.input_device' in notification.data.modified: action = (action for action in self.input_devices_group.actions() if action.data() == settings.audio.input_device).next() action.setChecked(True) if 'audio.alert_device' in notification.data.modified: action = (action for action in self.alert_devices_group.actions() if action.data() == settings.audio.alert_device).next() action.setChecked(True) if 'video.device' in notification.data.modified: action = (action for action in self.video_devices_group.actions() if action.data() == settings.video.device).next() action.setChecked(True) if 'answering_machine.enabled' in notification.data.modified: self.answering_machine_action.setChecked(settings.answering_machine.enabled) if 'chat.auto_accept' in notification.data.modified: self.auto_accept_chat_action.setChecked(settings.chat.auto_accept) if 'sounds.play_message_alerts' in notification.data.modified: self.received_messages_sound_action.setChecked(settings.sounds.play_message_alerts) if 'google_contacts.authorization_token' in notification.data.modified: authorization_token = notification.sender.google_contacts.authorization_token if authorization_token is None: self.google_contacts_action.setText(u'Enable &Google Contacts...') else: self.google_contacts_action.setText(u'Disable &Google Contacts') if authorization_token is InvalidToken: self.google_contacts_dialog.open_for_incorrect_password() elif notification.sender is blink_settings: if 'presence.current_state' in notification.data.modified: state = getattr(AccountState, blink_settings.presence.current_state.state, AccountState.Available) self.account_state.setState(state, blink_settings.presence.current_state.note) if 'presence.icon' in notification.data.modified: self.set_user_icon(icon_manager.get('avatar')) if 'presence.offline_note' in notification.data.modified: # TODO: set offline note -Saul pass elif isinstance(notification.sender, (Account, BonjourAccount)): account_manager = AccountManager() account = notification.sender if 'enabled' in notification.data.modified: action = (action for action in self.accounts_menu.actions() if action.data() is account).next() action.setChecked(account.enabled) if 'display_name' in notification.data.modified and account is account_manager.default_account: self.display_name.setText(account.display_name or u'') if set(['enabled', 'message_summary.enabled', 'message_summary.voicemail_uri']).intersection(notification.data.modified): action = (action for action in self.voicemail_menu.actions() if action.data() is account).next() action.setVisible(False if account is BonjourAccount() else account.enabled and account.message_summary.enabled) action.setEnabled(False if account is BonjourAccount() else account.voicemail_uri is not None)
def _SH_AcceptButtonClicked(self): if self.panel_view.currentWidget() is self.add_account_panel: account = Account(self.sip_address) account.enabled = True account.display_name = self.display_name or None account.auth.password = self.password if account.id.domain == 'sip2sip.info': account.server.settings_url = "https://blink.sipthor.net/settings.phtml" account.save() account_manager = AccountManager() account_manager.default_account = account self.accept() else: self.setEnabled(False) self.create_status_label.value = Status( 'Creating account on server...') self._create_sip_account(self.username, self.password, self.email_address, self.display_name)
def cloudStorgeDidChange_(self, notification): BlinkLogger().log_info(u"iCloud storage has changed") reason = notification.userInfo()["NSUbiquitousKeyValueStoreChangeReasonKey"] if reason == 2: BlinkLogger().log_info(u"iCloud quota exeeded") for key in notification.userInfo()["NSUbiquitousKeyValueStoreChangedKeysKey"]: if '@' in key: am = AccountManager() try: account = am.get_account(key) local_json = self.getJsonAccountData(account) remote_json = self.cloud_storage.stringForKey_(key) if self.hasDifference(account, local_json, remote_json, True): BlinkLogger().log_info(u"Updating %s from iCloud" % key) self.updateAccountFromCloud(key) except KeyError: BlinkLogger().log_info(u"Adding %s from iCloud" % key) self.updateAccountFromCloud(key)
def _NH_SIPApplicationWillStart(self, notification): account_manager = AccountManager() settings = SIPSimpleSettings() self.silent_action.setChecked(settings.audio.silent) self.silent_button.setChecked(settings.audio.silent) self.answering_machine_action.setChecked(settings.answering_machine.enabled) self.auto_accept_chat_action.setChecked(settings.chat.auto_accept) self.auto_accept_files_action.setChecked(settings.file_transfer.auto_accept) if settings.google_contacts.authorization_token is None: self.google_contacts_action.setText(u'Enable Google Contacts') else: self.google_contacts_action.setText(u'Disable Google Contacts') self.google_contacts_action.triggered.connect(self._AH_GoogleContactsActionTriggered) if not any(account.enabled for account in account_manager.iter_accounts()): self.display_name.setEnabled(False) self.activity_note.setEnabled(False) self.status.setEnabled(False) self.status.setCurrentIndex(self.status.findText(u'Offline'))
def __init__(self, target=None, participants=[], media_type=["chat"], default_domain=None, autostart=False): NSBundle.loadNibNamed_owner_("JoinConferenceWindow", self) self.autostart = autostart self.notification_center = NotificationCenter() self.notification_center.add_observer( self, name='BonjourConferenceServicesDidRemoveServer') self.notification_center.add_observer( self, name='BonjourConferenceServicesDidUpdateServer') self.notification_center.add_observer( self, name='BonjourConferenceServicesDidAddServer') self.notification_center.add_observer( self, name='SIPAccountManagerDidChangeDefaultAccount') self.startWhenParticipantsAvailable.setEnabled_(False) self.storage_path = ApplicationData.get('conference/rooms.pickle') self.selected_configuration = None self.default_domain = default_domain self.nickname = None if target is not None and "@" not in target and self.default_domain: target = '%s@%s' % (target, self.default_domain) if target is not None and validateParticipant(target): self.room.setStringValue_(target) account = AccountManager().default_account if account is not None: self.nickname_textfield.setStringValue_(account.conference.nickname or account.display_name) if participants: self._participants = participants else: self._participants = [] self.participantsTable.reloadData() self.removeAllParticipants.setHidden_( False if len(self._participants) > 1 else True) if media_type: self.audio.setState_(NSOnState if "audio" in media_type else NSOffState) self.chat.setState_(NSOnState if "chat" in media_type else NSOffState) self.updatePopupButtons()
def _NH_SIPApplicationDidStart(self, notification): self.ip_address_monitor.start() self.fetch_account() self.main_window.show() settings = SIPSimpleSettings() accounts = AccountManager().get_accounts() if not accounts or (self.first_run and accounts==[BonjourAccount()]): self.main_window.preferences_window.show_create_account_dialog() if settings.google_contacts.authorization_token is InvalidToken: self.main_window.google_contacts_dialog.open_for_incorrect_password() self.update_manager.initialize()
def updatePopupButtons(self): account = AccountManager().default_account if isinstance(account, BonjourAccount): self.configurationsButton.setHidden_(True) self.bonjour_server_combolist.setHidden_(False) self.updateBonjourServersPopupButton() else: self.configurationsButton.setHidden_(False) self.bonjour_server_combolist.setHidden_(True) self.loadConfigurations() self.updateConfigurationsPopupButton() self.ok_button.setEnabled_(True)
def removeSelectedAccount(self): account_info = self.selectedAccount() if account_info: account = account_info.account text = "Permanently remove account %s?" % account_info.name text = re.sub("%", "%%", text) # http://stackoverflow.com/questions/4498709/problem-in-displaying-in-nsrunalertpanel if NSRunAlertPanel("Remove Account", text, "Remove", "Cancel", None) != NSAlertDefaultReturn: return if account.tls.certificate and os.path.basename(account.tls.certificate.normalized) != 'default.crt': unlink(account.tls.certificate.normalized) account_manager = AccountManager() if account_manager.default_account is account: try: account_manager.default_account = (acc for acc in account_manager.iter_accounts() if acc is not account and acc.enabled).next() except StopIteration: account_manager.default_account = None account.delete()
def _NH_SIPApplicationWillStart(self, sender, data): settings = SIPSimpleSettings() _version = str(NSBundle.mainBundle().infoDictionary().objectForKey_("CFBundleShortVersionString")) settings.user_agent = "%s %s (MacOSX)" % (NSApp.delegate().applicationName, _version) BlinkLogger().log_info(u"SIP User Agent: %s" % settings.user_agent) self.migratePasswordsToKeychain() self.cleanupIcons() # Set audio settings compatible with AEC and Noise Supressor settings.audio.sample_rate = 32000 if settings.audio.echo_canceller.enabled else 48000 if NSApp.delegate().applicationName == 'SIP2SIP': settings.service_provider.help_url = 'http://wiki.sip2sip.info' settings.service_provider.name = 'SIP2SIP' settings.save() BlinkLogger().log_info(u"Audio engine sampling rate %dKHz covering 0-%dKHz spectrum" % (settings.audio.sample_rate/1000, settings.audio.sample_rate/1000/2)) BlinkLogger().log_info(u"Acoustic Echo Canceller is %s" % ('enabled' if settings.audio.echo_canceller.enabled else 'disabled')) # Although this setting is set at enrollment time, people who have downloaded previous versions will not have it account_manager = AccountManager() for account in account_manager.iter_accounts(): must_save = False if account is not BonjourAccount() and account.sip.primary_proxy is None and account.sip.outbound_proxy and not account.sip.selected_proxy: account.sip.primary_proxy = account.sip.outbound_proxy must_save = True if account is not BonjourAccount() and settings.tls.verify_server != account.tls.verify_server: account.tls.verify_server = settings.tls.verify_server must_save = True if account.tls.certificate and os.path.basename(account.tls.certificate.normalized) != 'default.crt': account.tls.certificate = DefaultValue must_save = True if must_save: account.save() logger = FileLogger() logger.start() self.ip_address_monitor.start()
def _NH_CFGSettingsObjectDidChange(self, notification): if notification.sender is BlinkSettings(): account_manager = AccountManager() if 'presence.offline_note' in notification.data.modified: for account in (account for account in account_manager.get_accounts() if hasattr(account, 'xcap') and account.enabled and account.xcap.enabled and account.xcap.discovered): state = BlinkPresenceState(account).offline_state account.xcap_manager.set_offline_status(OfflineStatus(state) if state is not None else None) if 'presence.icon' in notification.data.modified: icon_data = IconManager().get_image('avatar') icon = Icon(icon_data, 'image/png') if icon_data is not None else None for account in (account for account in account_manager.get_accounts() if hasattr(account, 'xcap') and account.enabled and account.xcap.enabled and account.xcap.discovered): account.xcap_manager.set_status_icon(icon) if 'presence.current_state' in notification.data.modified: for account in (account for account in account_manager.get_accounts() if account.enabled and account.presence.enabled): account.presence_state = BlinkPresenceState(account).online_state else: account = notification.sender if set(['xcap.enabled', 'xcap.xcap_root']).intersection(notification.data.modified): account.xcap.icon = None account.save() elif set(['presence.enabled', 'display_name', 'xcap.icon']).intersection(notification.data.modified) and account.presence.enabled: account.presence_state = BlinkPresenceState(account).online_state
def _NH_CFGSettingsObjectDidChange(self, notification): if isinstance(notification.sender, Account): account = notification.sender if set(['xcap.enabled', 'xcap.xcap_root']).intersection(notification.data.modified): account.xcap.icon = None account.save() elif set(['display_name', 'presence.enabled', 'presence.disable_location', 'presence.disable_timezone', 'presence.homepage', 'xcap.icon']).intersection(notification.data.modified): account.presence_state = self.build_pidf(account) if 'presence.disable_location' in notification.data.modified and not account.presence.disable_location: self.get_location([account]) elif notification.sender is SIPSimpleSettings(): account_manager = AccountManager() if set(['chat.disabled', 'video.device', 'screen_sharing_server.disabled', 'file_transfer.disabled', 'presence_state.icon', 'presence_state.status', 'presence_state.note']).intersection(notification.data.modified): self.publish() if 'presence_state.offline_note' in notification.data.modified: for account in (account for account in account_manager.get_accounts() if hasattr(account, 'xcap') and account.enabled and account.xcap.enabled and account.xcap.discovered): offline_pidf = self.build_offline_pidf(account) offline_status = OfflineStatus(offline_pidf) if offline_pidf is not None else None account.xcap_manager.set_offline_status(offline_status) if 'presence_state.icon' in notification.data.modified: status_icon = self.build_status_icon() icon = Icon(status_icon, 'image/png') if status_icon is not None else None for account in (account for account in account_manager.get_accounts() if hasattr(account, 'xcap') and account.enabled and account.xcap.enabled and account.xcap.discovered): account.xcap_manager.set_status_icon(icon)
def _SH_IdentityChanged(self, index): account_manager = AccountManager() account_manager.default_account = self.identity.itemData(index).account
data = cjson.decode(data.replace('\\/', '/')) except (OSError, IOError), e: BlinkLogger().log_error(u"Failed to read json data from ~/.blink_account: %s" % e) return except cjson.DecodeError, e: BlinkLogger().log_error(u"Failed to decode json data from ~/.blink_account: %s" % e) return finally: unlink(filename) data = defaultdict(lambda: None, data) account_id = data['sip_address'] if account_id is None: return account_manager = AccountManager() try: account = account_manager.get_account(account_id) except KeyError: account = Account(account_id) account.display_name = data['display_name'] default_account = account else: default_account = account_manager.default_account account.auth.username = data['auth_username'] account.auth.password = data['password'] or '' account.sip.outbound_proxy = data['outbound_proxy'] account.xcap.xcap_root = data['xcap_root'] account.nat_traversal.msrp_relay = data['msrp_relay']
def updateAccountFromCloud(self, key): def set_state(obj, state): for name, value in state.iteritems(): attribute = getattr(obj.__class__, name, None) if isinstance(attribute, SettingsGroupMeta): group = getattr(obj, name) set_state(group, value) elif isinstance(attribute, Setting): if issubclass(attribute.type, bool) and isinstance(value, bool): value = str(value) try: attribute.__setstate__(obj, value) except ValueError: pass else: attribute.dirty[obj] = True json_data = self.cloud_storage.stringForKey_(key) am = AccountManager() if json_data: try: data = cjson.decode(json_data) except TypeError: # account has been deleted in the mean time try: account = am.get_account(key) if isinstance(account, Account): # don't delete account because iCloud is unreliable # account.delete() pass except KeyError: pass try: account = am.get_account(key) except KeyError: account = Account(key) set_state(account, data) account.save() # update keychain passwords if isinstance(account, Account): passwords = {'auth': {'label': '%s (%s)' % (NSApp.delegate().applicationName, account.id), 'value': account.auth.password}, 'web': {'label': '%s WEB (%s)' % (NSApp.delegate().applicationName, account.id), 'value': account.server.web_password}, 'ldap': {'label': '%s LDAP (%s)' % (NSApp.delegate().applicationName, account.id), 'value': account.ldap.password}, 'chat': {'label': '%s ChatReplication (%s)' % (NSApp.delegate().applicationName, account.id), 'value': account.chat.replication_password} } for p in passwords.keys(): label = passwords[p]['label'] value = passwords[p]['value'] k = EMGenericKeychainItem.genericKeychainItemForService_withUsername_(label, account.id) if k is None and value: EMGenericKeychainItem.addGenericKeychainItemForService_withUsername_password_(label, account.id, value) elif k is not None: if value: k.setPassword_(value) else: k.removeFromKeychain() else: try: account = am.get_account(key) if isinstance(account, Account): pass # don't delete account because iCloud is unreliable # account.delete() except KeyError: pass self.notification_center.post_notification("SIPAccountChangedByICloud", sender=self, data=NotificationData(account=key))
def _create_sip_account(self, username, password, email_address, display_name, timezone=None): red = "#cc0000" if timezone is None and sys.platform != "win32": try: timezone = open("/etc/timezone").read().strip() except (OSError, IOError): try: timezone = "/".join(os.readlink("/etc/localtime").split("/")[-2:]) except (OSError, IOError): pass enrollment_data = dict( username=username.lower().encode("utf-8"), password=password.encode("utf-8"), email=email_address.encode("utf-8"), display_name=display_name.encode("utf-8"), tzinfo=timezone, ) try: settings = SIPSimpleSettings() response = urllib2.urlopen(settings.server.enrollment_url, urllib.urlencode(dict(enrollment_data))) response_data = cjson.decode(response.read().replace(r"\/", "/")) response_data = defaultdict(lambda: None, response_data) if response_data["success"]: from blink import Blink try: certificate_path = None passport = response_data["passport"] if passport is not None: certificate_path = Blink().save_certificates( response_data["sip_address"], passport["crt"], passport["key"], passport["ca"] ) except (GNUTLSError, IOError, OSError): pass account_manager = AccountManager() try: account = Account(response_data["sip_address"]) except AccountExists: account = account_manager.get_account(response_data["sip_address"]) account.enabled = True account.display_name = display_name account.auth.password = password account.sip.outbound_proxy = response_data["outbound_proxy"] account.nat_traversal.msrp_relay = response_data["msrp_relay"] account.xcap.xcap_root = response_data["xcap_root"] account.tls.certificate = certificate_path account.server.settings_url = response_data["settings_url"] account.save() account_manager.default_account = account call_in_gui_thread(self.accept) elif response_data["error"] == "user_exists": call_in_gui_thread(self.username_editor.addException, username) else: call_in_gui_thread( setattr, self.create_status_label, "value", Status(response_data["error_message"], color=red) ) except (cjson.DecodeError, KeyError): call_in_gui_thread(setattr, self.create_status_label, "value", Status("Illegal server response", color=red)) except urllib2.URLError, e: call_in_gui_thread( setattr, self.create_status_label, "value", Status("Failed to contact server: %s" % e.reason, color=red) )