예제 #1
0
class BlinkAppDelegate(NSObject):
    """Responsible for starting and stopping the application
       Register URL types handled by Blink
       Updating the dock icon with missed calls
       Migrating data from one version to another
       Start enrollment if run first time
       Calling Initial SIP URL if necessary
       Handle wake up from sleep
       Show about panel"""

    contactsWindowController = objc.IBOutlet()
    chatWindowController = objc.IBOutlet()
    debugWindow = objc.IBOutlet()
    aboutPanel = objc.IBOutlet()
    migrationPanel = objc.IBOutlet()
    migrationText = objc.IBOutlet()
    migrationProgressWheel = objc.IBOutlet()
    aboutVersion = objc.IBOutlet()
    aboutSlogan = objc.IBOutlet()
    aboutCopyright = objc.IBOutlet()
    aboutIcon = objc.IBOutlet()
    aboutzRTPIcon = objc.IBOutlet()
    ui_notification_center = None
    application_will_end = False
    wake_up_timestamp = None
    ip_change_timestamp = None
    transport_lost_timestamp = None

    debug = False

    blinkMenu = objc.IBOutlet()
    ready = False
    missedCalls = 0
    missedChats = 0
    urisToOpen = []
    wait_for_enrollment = False
    updater = None

    # branding
    about_version = "1.0"
    about_slogan = "A state of the art, easy to use SIP client"
    help_url = "http://help-pro.icanblink.com"
    last_history_entries = 10
    allowed_domains = []
    icloud_enabled = False
    answering_machine_enabled = True
    history_enabled = True
    recording_enabled = True
    file_logging_enabled = True
    advanced_options_enabled = True
    hidden_account_preferences_sections = ()
    chat_replication_password_hidden = True
    external_alert_enabled = True
    migrate_passwords_to_keychain = True
    service_provider_help_url  = None
    service_provider_name = None
    maximum_accounts = None
    account_extension = None
    sp_update_url = None
    main_window_title = None
    call_transfer_enabled = True
    phone_numbers_enabled = True
    ldap_directory_enabled = True
    chat_print_enabled = True
    pause_music_enabled = True
    about_image = 'about'
    account_extension = None
    general_extension = None

    supported_languages = {
                           "system_default": NSLocalizedString("System Default", "Menu item"),
                           "en": NSLocalizedString("English", "Menu item"),
                           "nl": NSLocalizedString("Nederlands", "Menu item"),
                           "es": NSLocalizedString("Spanish", "Menu item"),
                           "ro": NSLocalizedString("Romanian", "Menu item"),
                           "pt": NSLocalizedString("Portuguese", "Menu item")
                           }

    statusbar_menu_icon = 'invisible'
    about_copyright = "Copyright 2009-2021 AG Projects"
    active_transports = set()
    terminating = False

    @objc.python_method
    @property
    def video_devices(self):
        devices = set()
        for item in Engine().video_devices:
            if 'colorbar' in item.lower():
                continue
            if 'null' in item.lower():
                continue

            devices.add(item)
        return list(devices)
    
    def init(self):
        self = objc.super(BlinkAppDelegate, self).init()
        if self:
            self.applicationName = str(NSBundle.mainBundle().infoDictionary().objectForKey_("CFBundleExecutable"))
            self.applicationNamePrint = str(NSBundle.mainBundle().infoDictionary().objectForKey_("CFBundleName"))
            build = str(NSBundle.mainBundle().infoDictionary().objectForKey_("CFBundleVersion"))
            date = str(NSBundle.mainBundle().infoDictionary().objectForKey_("BlinkVersionDate"))

            branding_file = NSBundle.mainBundle().infoDictionary().objectForKey_("BrandingFile")

            try:
                branding = __import__(branding_file)
            except ImportError:
                try:
                    import branding
                except ImportError:
                    branding = Null

            branding.init(self)

            BlinkLogger().log_info("Starting %s %s" % (self.applicationNamePrint, build))

            self.registerURLHandler()
            NSWorkspace.sharedWorkspace().notificationCenter().addObserver_selector_name_object_(self, "computerDidWake:", NSWorkspaceDidWakeNotification, None)
            NSWorkspace.sharedWorkspace().notificationCenter().addObserver_selector_name_object_(self, "computerWillSleep:", NSWorkspaceWillSleepNotification, None)
            NSDistributedNotificationCenter.defaultCenter().addObserver_selector_name_object_suspensionBehavior_(self, "callFromAddressBook:", "CallTelephoneNumberWithBlinkFromAddressBookNotification", "AddressBook", NSNotificationSuspensionBehaviorDeliverImmediately)
            NSDistributedNotificationCenter.defaultCenter().addObserver_selector_name_object_suspensionBehavior_(self, "callFromAddressBook:", "CallSipAddressWithBlinkFromAddressBookNotification", "AddressBook", NSNotificationSuspensionBehaviorDeliverImmediately)

            NotificationCenter().add_observer(self, name="CFGSettingsObjectDidChange")
            NotificationCenter().add_observer(self, name="SIPApplicationDidStart")
            NotificationCenter().add_observer(self, name="SIPApplicationWillEnd")
            NotificationCenter().add_observer(self, name="SIPApplicationDidEnd")
            NotificationCenter().add_observer(self, name="NetworkConditionsDidChange")
            NotificationCenter().add_observer(self, name="SIPEngineTransportDidDisconnect")
            NotificationCenter().add_observer(self, name="SIPEngineTransportDidConnect")
            NotificationCenter().add_observer(self, name="DNSNameserversDidChange")
            NotificationCenter().add_observer(self, name="SystemDidWakeUpFromSleep")

            # remove obsolete settings
            userdef = NSUserDefaults.standardUserDefaults()
            userdef.removeObjectForKey_('SIPTrace')
            userdef.removeObjectForKey_('MSRPTrace')
            userdef.removeObjectForKey_('XCAPTrace')
            userdef.removeObjectForKey_('EnablePJSIPTrace')
            userdef.removeObjectForKey_('EnableNotificationsTrace')

            try:
                from Updater import Updater
            except ImportError:
                pass
            else:
                self.updater = Updater()

            self.purge_temporary_files()

        return self

    @objc.python_method
    @run_in_thread('file-io')
    def purge_temporary_files(self):
        for dir in ('.tmp_screenshots', '.tmp_snapshots', '.tmp_file_transfers'):
            folder = ApplicationData.get(dir)
            if os.path.exists(folder):
                try:
                    shutil.rmtree(folder)
                except EnvironmentError:
                    pass

    @objc.python_method
    def gui_notify(self, title, body, subtitle=None):
        if self.application_will_end:
            return
        major, minor = platform.mac_ver()[0].split('.')[0:2]
        if (int(major) == 10 and int(minor) >= 8) or int(major) > 10:
            if self.ui_notification_center is None:
                self.ui_notification_center = Foundation.NSUserNotificationCenter.defaultUserNotificationCenter()
                self.ui_notification_center.setDelegate_(self)

            notification = Foundation.NSUserNotification.alloc().init()
            notification.setTitle_(title)
            if subtitle is not None:
                notification.setSubtitle_(subtitle)
            notification.setInformativeText_(body)
            self.ui_notification_center.scheduleNotification_(notification)

    def userNotificationCenter_didDeliverNotification_(self, center, notification):
        pass

    def userNotificationCenter_didActivateNotification_(self, center, notification):
        pass

    def userNotificationCenter_shouldPresentNotification_(self, center, notification):
        return True

    # Needed by run_in_gui_thread and call_in_gui_thread
    def callObject_(self, callable):
        try:
            callable()
        except:
            log.err()

    # Needed by call_later
    def callTimerObject_(self, timer):
        callable = timer.userInfo()
        try:
            callable()
        except:
            log.err()

    @objc.python_method
    def enroll(self):
        enroll = EnrollmentController.alloc().init()
        enroll.setCreateAccount()
        enroll.runModal()

    @objc.python_method
    def updateDockTile(self):
        if self.missedCalls > 0 or self.missedChats > 0:
            icon = NSImage.imageNamed_("Blink")
            image = NSImageView.alloc().initWithFrame_(NSMakeRect(0, 0, 32, 32))
            image.setImage_(icon)
            if self.missedCalls > 0 and self.missedChats > 0:
                NSApp.dockTile().setBadgeLabel_("%i / %i" % (self.missedCalls, self.missedChats))
            else:
                NSApp.dockTile().setBadgeLabel_("%i" % (self.missedCalls + self.missedChats))
            NSApp.dockTile().setContentView_(image)
        else:
            NSApp.dockTile().setBadgeLabel_("")
            NSApp.dockTile().setContentView_(None)
            icon = None
        NSApp.setApplicationIconImage_(icon)
        NSApp.dockTile().display()

    @objc.python_method
    def noteNewMessage(self, window):
        if not NSApp.isActive():
            self.missedChats += 1
            self.updateDockTile()

    @objc.python_method
    def noteMissedCall(self):
        self.missedCalls += 1
        self.updateDockTile()
        NSApp.requestUserAttention_(NSInformationalRequest)

    def applicationShouldHandleReopen_hasVisibleWindows_(self, sender, flag):
        if not flag:
            self.contactsWindowController.showWindow_(None)
        self.missedCalls = 0
        self.missedChats = 0
        self.updateDockTile()
        return False

    def applicationDidBecomeActive_(self, notif):
        self.missedCalls = 0
        self.missedChats = 0
        self.updateDockTile()

    def applicationDidFinishLaunching_(self, sender):
        BlinkLogger().log_debug("Application launched")

        branding_file = NSBundle.mainBundle().infoDictionary().objectForKey_("BrandingFile")
        try:
            branding = __import__(branding_file)
        except ImportError:
            try:
                import branding
            except ImportError:
                branding = Null

        branding.setup(self)

        if self.updater and self.sp_update_url is not None:
            self.updater.sp.setFeedURL_(NSURL.URLWithString_(self.sp_update_url))

        self.blinkMenu.setTitle_(self.applicationNamePrint)

        config_file = ApplicationData.get('config')
        self.icloud_manager = iCloudManager()
        self.backend = SIPManager()

        self.contactsWindowController.setup(self.backend)

        while True:
            try:
                first_run = not os.path.exists(config_file)
                self.contactsWindowController.first_run = first_run

                self.backend.init()
                self.backend.fetch_account()
                accounts = AccountManager().get_accounts()
                if not accounts or (first_run and accounts == [BonjourAccount()]):
                    self.wait_for_enrollment = True
                    self.enroll()
                break

            except FileParserError as exc:
                BlinkLogger().log_warning("Error parsing configuration file: %s" % exc)
                if NSRunAlertPanel(NSLocalizedString("Error", "Window title"),
                    NSLocalizedString("The configuration file is corrupted. You will need to replace it and re-enter your account information. \n\nYour current configuration file will be backed up to %s.corrupted. ", "Label") % config_file,
                    NSLocalizedString("Replace", "Button title"), NSLocalizedString("Quit", "Button title"), None) != NSAlertDefaultReturn:
                    NSApp.terminate_(None)
                    return
                os.rename(config_file, config_file+".corrupted")
                BlinkLogger().log_info("Renamed configuration file to %s" % config_file+".corrupted")
            except BaseException as exc:
                import traceback
                print(traceback.print_exc())
                NSRunAlertPanel(NSLocalizedString("Error", "Window title"), NSLocalizedString("There was an error during startup of core functionality:\n%s", "Label") % exc,
                        NSLocalizedString("Quit", "Button title"), None, None)
                NSApp.terminate_(None)
                return

        # window should be shown only after enrollment check
        if self.wait_for_enrollment:
            BlinkLogger().log_info('Starting User Interface')
            self.contactsWindowController.model.moveBonjourGroupFirst()
            self.contactsWindowController.showWindow_(None)
            self.wait_for_enrollment = False

        self.contactsWindowController.setupFinished()
        SMSWindowManager.SMSWindowManager().setOwner_(self.contactsWindowController)
        self.debugWindow = DebugWindow.alloc().init()
        self.chatWindowController = ChatWindowController.ChatWindowController.alloc().init()

    def killSelfAfterTimeout_(self, arg):
        time.sleep(15)
        BlinkLogger().log_info("Application forcefully terminated because core engine did not be stop in a timely manner")
        os._exit(0)

    def applicationShouldTerminate_(self, sender):
        if self.terminating:
            return True

        self.terminating = True
        BlinkLogger().log_info('Application will be terminated')
        NSThread.detachNewThreadSelector_toTarget_withObject_("killSelfAfterTimeout:", self, None)
        NotificationCenter().post_notification("BlinkShouldTerminate", None)
        NotificationCenter().add_observer(self, name="SIPApplicationDidEnd")
        app = SIPApplication()
        app.stop()

        import Profiler
        Profiler.stop(os.path.join(ApplicationData.directory, 'logs', 'profiler.stats'))
        return False

    @objc.python_method
    @run_in_gui_thread
    def handle_notification(self, notification):
        handler = getattr(self, '_NH_%s' % notification.name, Null)
        handler(notification)

    @objc.python_method
    def _NH_SIPEngineTransportDidDisconnect(self, notification):
        self.transport_lost_timestamp = int(time.time())

        transport = '%s:%s' % (notification.data.transport, notification.data.remote_address)
        try:
            self.active_transports.remove(transport)
        except KeyError:
            return

        for account_info in self.contactsWindowController.accounts:
            account = account_info.account

            if account is BonjourAccount():
                continue

            if not account.enabled:
                continue

            if account_info.registrar != transport:
                continue

            account_info.register_state = 'failed'

            if host is None or host.default_ip is None:
                account_info.register_failure_reason = NSLocalizedString("No Internet connection", "Label")
            else:
                account_info.register_failure_reason = NSLocalizedString("Connection failed", "Label")

            self.contactsWindowController.refreshAccountList()
            BlinkLogger().log_info('Re-register account %s' % account.id)

            account.reregister()
            account.resubscribe()
            presence_state = account.presence_state
            account.presence_state = None
            account.presence_state = presence_state

        if notification.data.reason != 'Success':
            BlinkLogger().log_info("%s connection %s <-> %s lost" % (notification.data.transport, notification.data.local_address, notification.data.remote_address))
            #nc_title = NSLocalizedString("Connection failed", "Label")
            #nc_body = NSLocalizedString("Remote Address", "Label") + " %s:%s" % (notification.data.transport, notification.data.remote_address)
            #self.gui_notify(nc_title, nc_body)

        else:
            NotificationCenter().post_notification("BlinkTransportFailed", data=NotificationData(transport=transport))

    @objc.python_method
    def _NH_SIPEngineTransportDidConnect(self, notification):
        transport = "%s:%s" %(notification.data.transport, notification.data.remote_address)
        if transport not in self.active_transports:
            BlinkLogger().log_info("%s connection %s <-> %s established" % (notification.data.transport, notification.data.local_address, notification.data.remote_address))
            self.active_transports.add(transport)

    @objc.python_method
    def _NH_DNSNameserversDidChange(self, notification):
        BlinkLogger().log_info("DNS servers changed to %s" % ", ".join(notification.data.nameservers))

    @objc.python_method
    def _NH_NetworkConditionsDidChange(self, notification):
        self.ip_change_timestamp = int(time.time())
        BlinkLogger().log_info("Network conditions changed")
        if host.default_ip is None:
            BlinkLogger().log_info("No IP address")
        else:
            BlinkLogger().log_info("IP address changed to %s" % host.default_ip)

    @objc.python_method
    def _NH_SIPApplicationWillEnd(self, notification):
        BlinkLogger().log_info("Core engine will be stopped")
        self.purge_temporary_files()

    @objc.python_method
    def _NH_CFGSettingsObjectDidChange(self, notification):
        if 'gui.extended_debug' in notification.data.modified:
            settings = SIPSimpleSettings()
            self.debug = settings.gui.extended_debug

    @objc.python_method
    def _NH_SIPApplicationDidStart(self, notification):
        settings = SIPSimpleSettings()
        self.debug = settings.gui.extended_debug
        self.purge_temporary_files()

    @objc.python_method
    def _NH_SIPApplicationDidEnd(self, notification):
        BlinkLogger().log_info("Core engine stopped")
        NSApp.terminate_(self)

    def applicationWillTerminate_(self, notification):
        NotificationCenter().post_notification("BlinkWillTerminate", None)
        BlinkLogger().log_info("Application ended")

    def computerDidWake_(self, notification):
        self.wake_up_timestamp = int(time.time())
        NotificationCenter().post_notification("SystemDidWakeUpFromSleep", None)

    def computerWillSleep_(self, notification):
        NotificationCenter().post_notification("SystemWillSleep", None)

    def callFromAddressBook_(self, notification):
        url = notification.userInfo()["URI"]
        name = notification.userInfo()["DisplayName"]
        url = self.normalizeExternalURL(url)

        BlinkLogger().log_info("Will start outgoing session to %s %s from Address Book" % (name, url))
        if not self.ready:
            self.urisToOpen.append((str(url), ('audio'), list()))
        else:
            self.contactsWindowController.joinConference(str(url), ('audio'))

    @objc.IBAction
    def orderFrontAboutPanel_(self, sender):
        if not self.aboutPanel:
            NSBundle.loadNibNamed_owner_("About", self)
            self.aboutVersion.setStringValue_(self.about_version)
            self.aboutSlogan.setStringValue_(self.about_slogan)
            self.aboutIcon.setImage_(NSImage.imageNamed_(self.about_image))
            self.aboutCopyright.setStringValue_(self.about_copyright)

        self.aboutPanel.makeKeyAndOrderFront_(None)

    @objc.python_method
    def normalizeExternalURL(self, url):
        return external_url_pattern.sub("", url)

    def getURL_withReplyEvent_(self, event, replyEvent):
        participants = set()
        media_type = set()
        url = event.descriptorForKeyword_(fourcharToInt('----')).stringValue()
        url = self.normalizeExternalURL(url)

        BlinkLogger().log_info("Will start outgoing session from external link: %s" % url)

        url = urllib.parse.unquote(url).replace(" ", "")
        _split = url.split(';')
        _url = []
        for item in _split[:]:
            if item.startswith("participant="):
                puri = item.split("=")[1]
                participants.add(puri)
            elif item.startswith("media_type="):
                m = item.split("=")[1]
                media_type.add(m)
            else:
                _url.append(item)
                _split.remove(item)

        url = ";".join(_url)

        if not self.ready:
            self.urisToOpen.append((str(url), list(media_type), list(participants)))
        else:
            self.contactsWindowController.joinConference(str(url), list(media_type), list(participants))

    @objc.python_method
    def registerURLHandler(self):
        event_class = event_id = fourcharToInt("GURL")
        event_manager = NSAppleEventManager.sharedAppleEventManager()
        event_manager.setEventHandler_andSelector_forEventClass_andEventID_(self, "getURL:withReplyEvent:", event_class, event_id)

        bundleID = NSBundle.mainBundle().bundleIdentifier()
        LaunchServices.LSSetDefaultHandlerForURLScheme("sip", bundleID)
        LaunchServices.LSSetDefaultHandlerForURLScheme("tel", bundleID)
예제 #2
0
class BlinkAppDelegate(NSObject):
    '''Responsable for starting and stoping the application
       Register URL types handled by Blink
       Updating the dock icon with missed calls
       Migrating data from one version to another
       Start enrollment if run first time
       Calling Initial SIP URL if necessary
       Handle wakeup from sleep
       Show about panel'''

    implements(IObserver)

    contactsWindowController = objc.IBOutlet()
    aboutPanel = objc.IBOutlet()
    migrationPanel = objc.IBOutlet()
    migrationText = objc.IBOutlet()
    migrationProgressWheel = objc.IBOutlet()
    aboutVersion = objc.IBOutlet()
    aboutBundle = objc.IBOutlet()
    aboutSlogan = objc.IBOutlet()
    aboutCopyright = objc.IBOutlet()
    aboutzRTPIcon = objc.IBOutlet()
    ui_notification_center = None
    application_will_end = False
    wake_up_timestamp = None

    debug = False

    blinkMenu = objc.IBOutlet()
    ready = False
    missedCalls = 0
    missedChats = 0
    urisToOpen = []
    wait_for_enrollment = False
    updater = None

    def init(self):
        self = super(BlinkAppDelegate, self).init()
        if self:
            self.applicationName = str(NSBundle.mainBundle().infoDictionary().objectForKey_("CFBundleExecutable"))
            self.applicationNamePrint = str(NSBundle.mainBundle().infoDictionary().objectForKey_("CFBundleName"))
            build = str(NSBundle.mainBundle().infoDictionary().objectForKey_("CFBundleVersion"))
            date = str(NSBundle.mainBundle().infoDictionary().objectForKey_("BlinkVersionDate"))

            BlinkLogger().log_info(u"Starting %s build %s from %s" % (self.applicationNamePrint, build, date))

            self.registerURLHandler()
            NSWorkspace.sharedWorkspace().notificationCenter().addObserver_selector_name_object_(self, "computerDidWake:", NSWorkspaceDidWakeNotification, None)
            NSWorkspace.sharedWorkspace().notificationCenter().addObserver_selector_name_object_(self, "computerWillSleep:", NSWorkspaceWillSleepNotification, None)
            NSDistributedNotificationCenter.defaultCenter().addObserver_selector_name_object_suspensionBehavior_(self, "callFromAddressBook:", "CallTelephoneNumberWithBlinkFromAddressBookNotification", "AddressBook", NSNotificationSuspensionBehaviorDeliverImmediately)
            NSDistributedNotificationCenter.defaultCenter().addObserver_selector_name_object_suspensionBehavior_(self, "callFromAddressBook:", "CallSipAddressWithBlinkFromAddressBookNotification", "AddressBook", NSNotificationSuspensionBehaviorDeliverImmediately)

            NotificationCenter().add_observer(self, name="CFGSettingsObjectDidChange")
            NotificationCenter().add_observer(self, name="SIPApplicationDidStart")
            NotificationCenter().add_observer(self, name="SIPApplicationWillEnd")
            NotificationCenter().add_observer(self, name="SIPApplicationDidEnd")
            NotificationCenter().add_observer(self, name="SystemIPAddressDidChange")
            NotificationCenter().add_observer(self, name="NetworkConditionsDidChange")
            NotificationCenter().add_observer(self, name="SIPEngineTransportDidDisconnect")
            NotificationCenter().add_observer(self, name="SIPEngineTransportDidConnect")
            NotificationCenter().add_observer(self, name="DNSNameserversDidChange")

            # remove obsolete settings
            userdef = NSUserDefaults.standardUserDefaults()
            userdef.removeObjectForKey_('SIPTrace')
            userdef.removeObjectForKey_('MSRPTrace')
            userdef.removeObjectForKey_('XCAPTrace')
            userdef.removeObjectForKey_('EnablePJSIPTrace')
            userdef.removeObjectForKey_('EnableNotificationsTrace')

            try:
                from Updater import Updater
            except ImportError:
                pass
            else:
                self.updater = Updater()

            call_in_thread('file-io', self.purge_temporary_files)

        return self

    def purge_temporary_files(self):
        for dir in ('.tmp_screenshots', '.tmp_snapshots', '.tmp_file_transfers'):
            folder = ApplicationData.get(dir)
            if os.path.exists(folder):
                try:
                    shutil.rmtree(folder)
                except EnvironmentError:
                    pass

    def gui_notify(self, title, body, subtitle=None):
        if self.application_will_end:
            return
        major, minor = platform.mac_ver()[0].split('.')[0:2]
        if (int(major) == 10 and int(minor) >= 8) or int(major) > 10:
            if self.ui_notification_center is None:
                self.ui_notification_center = Foundation.NSUserNotificationCenter.defaultUserNotificationCenter()
                self.ui_notification_center.setDelegate_(self)

            notification = Foundation.NSUserNotification.alloc().init()
            notification.setTitle_(title)
            if subtitle is not None:
                notification.setSubtitle_(subtitle)
            notification.setInformativeText_(body)
            self.ui_notification_center.scheduleNotification_(notification)

    def userNotificationCenter_didDeliverNotification_(self, center, notification):
        pass

    def userNotificationCenter_didActivateNotification_(self, center, notification):
        pass

    def userNotificationCenter_shouldPresentNotification_(self, center, notification):
        return True

    # Needed by run_in_gui_thread and call_in_gui_thread
    def callObject_(self, callable):
        try:
            callable()
        except:
            log.err()

    def enroll(self):
        enroll = EnrollmentController.alloc().init()
        enroll.setCreateAccount()
        enroll.runModal()

    def updateDockTile(self):
        if self.missedCalls > 0 or self.missedChats > 0:
            icon = NSImage.imageNamed_("Blink")
            image = NSImageView.alloc().initWithFrame_(NSMakeRect(0, 0, 32, 32))
            image.setImage_(icon)
            if self.missedCalls > 0 and self.missedChats > 0:
                NSApp.dockTile().setBadgeLabel_("%i / %i" % (self.missedCalls, self.missedChats))
            else:
                NSApp.dockTile().setBadgeLabel_("%i" % (self.missedCalls + self.missedChats))
            NSApp.dockTile().setContentView_(image)
        else:
            NSApp.dockTile().setBadgeLabel_("")
            NSApp.dockTile().setContentView_(None)
            icon = None
        NSApp.setApplicationIconImage_(icon)
        NSApp.dockTile().display()

    def noteNewMessage(self, window):
        if not NSApp.isActive():
            self.missedChats += 1
            self.updateDockTile()

    def noteMissedCall(self):
        self.missedCalls += 1
        self.updateDockTile()
        NSApp.requestUserAttention_(NSInformationalRequest)

    def applicationShouldHandleReopen_hasVisibleWindows_(self, sender, flag):
        if not flag:
            self.contactsWindowController.showWindow_(None)
        self.missedCalls = 0
        self.missedChats = 0
        self.updateDockTile()
        return False

    def applicationDidBecomeActive_(self, notif):
        self.missedCalls = 0
        self.missedChats = 0
        self.updateDockTile()

    def applicationDidFinishLaunching_(self, sender):
        self.blinkMenu.setTitle_(self.applicationNamePrint)

        config_file = ApplicationData.get('config')
        self.icloud_manager = iCloudManager()
        self.backend = SIPManager()

        self.contactsWindowController.setup(self.backend)

        while True:
            try:
                first_run = not os.path.exists(config_file)
                self.contactsWindowController.first_run = first_run

                self.backend.init()
                self.backend.fetch_account()
                accounts = AccountManager().get_accounts()
                if not accounts or (first_run and accounts == [BonjourAccount()]):
                    self.wait_for_enrollment = True
                    self.enroll()
                break

            except FileParserError, exc:
                BlinkLogger().log_warning(u"Error parsing configuration file: %s" % exc)
                if NSRunAlertPanel(NSLocalizedString("Error", "Window title"),
                    NSLocalizedString("The configuration file is corrupted. You will need to replace it and re-enter your account information. \n\nYour current configuration file will be backed up to %s.corrupted. ", "Label") % config_file,
                    NSLocalizedString("Replace", "Button title"), NSLocalizedString("Quit", "Button title"), None) != NSAlertDefaultReturn:
                    NSApp.terminate_(None)
                    return
                os.rename(config_file, config_file+".corrupted")
                BlinkLogger().log_info(u"Renamed configuration file to %s" % config_file+".corrupted")
            except BaseException, exc:
                import traceback
                print traceback.print_exc()
                NSRunAlertPanel(NSLocalizedString("Error", "Window title"), NSLocalizedString("There was an error during startup of core functionality:\n%s", "Label") % exc,
                        NSLocalizedString("Quit", "Button title"), None, None)
                NSApp.terminate_(None)
                return