Ejemplo n.º 1
0
    def __init__(self):
        if self:
            BlinkLogger().log_debug('Starting History Viewer')
            NSBundle.loadNibNamed_owner_("HistoryViewer", self)

            self.all_contacts = BlinkHistoryViewerContact('Any Address', name='All Contacts')
            self.bonjour_contact = BlinkHistoryViewerContact('bonjour.local', name='Bonjour Neighbours', icon=NSImage.imageNamed_("NSBonjour"))

            self.notification_center = NotificationCenter()
            self.notification_center.add_observer(self, name='ChatViewControllerDidDisplayMessage')
            self.notification_center.add_observer(self, name='AudioCallLoggedToHistory')
            self.notification_center.add_observer(self, name='BlinkContactsHaveChanged')
            self.notification_center.add_observer(self, name='BlinkTableViewSelectionChaged')
            self.notification_center.add_observer(self, name='BlinkConferenceContactPresenceHasChanged')
            self.notification_center.add_observer(self, name='BlinkShouldTerminate')

            self.searchText.cell().setSendsSearchStringImmediately_(True)
            self.searchText.cell().setPlaceholderString_(NSLocalizedString("Type text and press Enter", "Placeholder text"))

            self.chatViewController.setContentFile_(NSBundle.mainBundle().pathForResource_ofType_("ChatView", "html"))
            self.chatViewController.setHandleScrolling_(False)
            self.entriesView.setShouldCloseWithWindow_(False)

            for c in ('remote_uri', 'local_uri', 'date', 'type'):
                col = self.indexTable.tableColumnWithIdentifier_(c)
                descriptor = NSSortDescriptor.alloc().initWithKey_ascending_(c, True)
                col.setSortDescriptorPrototype_(descriptor)

            self.chat_history = ChatHistory()
            self.session_history = SessionHistory()
            self.setPeriod(1)

            self.selectedTableView = self.contactTable
Ejemplo n.º 2
0
    def initWithAccount_target_name_(self, account, target, display_name):
        self = super(SMSViewController, self).init()
        if self:
            self.notification_center = NotificationCenter()
            self.account = account
            self.target_uri = target
            self.display_name = display_name
            self.queue = []
            self.messages = {}

            self.history = ChatHistory()

            self.local_uri = '%s@%s' % (account.id.username, account.id.domain)
            self.remote_uri = '%s@%s' % (self.target_uri.user,
                                         self.target_uri.host)

            NSBundle.loadNibNamed_owner_("SMSView", self)

            self.chatViewController.setContentFile_(
                NSBundle.mainBundle().pathForResource_ofType_(
                    "ChatView", "html"))
            self.chatViewController.setAccount_(self.account)
            self.chatViewController.resetRenderedMessages()

            self.chatViewController.inputText.unregisterDraggedTypes()
            self.chatViewController.inputText.setMaxLength_(MAX_MESSAGE_LENGTH)
            self.splitView.setText_("%i chars left" % MAX_MESSAGE_LENGTH)

        return self
Ejemplo n.º 3
0
    def __init__(self):
        if self:
            NSBundle.loadNibNamed_owner_("HistoryViewer", self)

            self.all_contacts = BlinkContact('Any Address',
                                             name=u'All Contacts')
            self.bonjour_contact = BlinkContact(
                'bonjour',
                name=u'Bonjour Neighbours',
                icon=NSImage.imageNamed_("NSBonjour"))

            self.notification_center = NotificationCenter()
            self.notification_center.add_observer(
                self, name='ChatViewControllerDidDisplayMessage')
            self.notification_center.add_observer(
                self, name='AudioCallLoggedToHistory')
            self.notification_center.add_observer(
                self, name='BlinkContactsHaveChanged')
            self.notification_center.add_observer(
                self, name='BlinkTableViewSelectionChaged')

            self.searchText.cell().setSendsSearchStringImmediately_(True)
            self.searchText.cell().setPlaceholderString_(
                "Type text and press Enter")

            self.chatViewController.setContentFile_(
                NSBundle.mainBundle().pathForResource_ofType_(
                    "ChatView", "html"))

            for c in ('remote_uri', 'local_uri', 'date', 'type'):
                col = self.indexTable.tableColumnWithIdentifier_(c)
                descriptor = NSSortDescriptor.alloc().initWithKey_ascending_(
                    c, True)
                col.setSortDescriptorPrototype_(descriptor)

            self.history = ChatHistory()

            tag = self.afterDate.selectedItem().tag()
            if tag < 4:
                self.after_date = self.period_array[tag].strftime(
                    "%Y-%m-%d") if self.period_array[tag] else None
            else:
                self.before_date = self.period_array[tag].strftime(
                    "%Y-%m-%d") if self.period_array[tag] else None

            self.refreshViewer()

            self.selectedTableView = self.contactTable
Ejemplo n.º 4
0
    def add_to_history(self):
        FileTransferHistory().add_transfer(
            transfer_id=self.ft_info.transfer_id,
            direction=self.ft_info.direction,
            local_uri=self.ft_info.local_uri,
            remote_uri=self.ft_info.remote_uri,
            file_path=self.ft_info.file_path,
            bytes_transfered=self.ft_info.bytes_transfered,
            file_size=self.ft_info.file_size or 0,
            status=self.ft_info.status)

        message = "<h3>%s File Transfer</h3>" % self.ft_info.direction.capitalize(
        )
        message += "<p>%s (%s)" % (self.ft_info.file_path,
                                   format_size(self.ft_info.file_size or 0))
        media_type = 'file-transfer'
        local_uri = self.ft_info.local_uri
        remote_uri = self.ft_info.remote_uri
        direction = self.ft_info.direction
        status = 'delivered' if self.ft_info.status == 'completed' else 'failed'
        cpim_from = self.ft_info.remote_uri
        cpim_to = self.ft_info.remote_uri
        timestamp = str(ISOTimestamp.now())

        ChatHistory().add_message(self.ft_info.transfer_id, media_type,
                                  local_uri, remote_uri, direction, cpim_from,
                                  cpim_to, timestamp, message, "html", "0",
                                  status)
Ejemplo n.º 5
0
    def __init__(self):
        if self:
            NSBundle.loadNibNamed_owner_("HistoryViewer", self)

            self.all_contacts = BlinkContact('Any Address', name=u'All Contacts')
            self.bonjour_contact = BlinkContact('bonjour', name=u'Bonjour Neighbours', icon=NSImage.imageNamed_("NSBonjour"))

            self.notification_center = NotificationCenter()
            self.notification_center.add_observer(self, name='ChatViewControllerDidDisplayMessage')
            self.notification_center.add_observer(self, name='AudioCallLoggedToHistory')
            self.notification_center.add_observer(self, name='BlinkContactsHaveChanged')
            self.notification_center.add_observer(self, name='BlinkTableViewSelectionChaged')

            self.searchText.cell().setSendsSearchStringImmediately_(True)
            self.searchText.cell().setPlaceholderString_("Type text and press Enter")

            self.chatViewController.setContentFile_(NSBundle.mainBundle().pathForResource_ofType_("ChatView", "html"))

            for c in ('remote_uri', 'local_uri', 'date', 'type'):
                col = self.indexTable.tableColumnWithIdentifier_(c)
                descriptor = NSSortDescriptor.alloc().initWithKey_ascending_(c, True)
                col.setSortDescriptorPrototype_(descriptor)

            self.history = ChatHistory()

            tag = self.afterDate.selectedItem().tag()
            if tag < 4:
                self.after_date = self.period_array[tag].strftime("%Y-%m-%d") if self.period_array[tag] else None
            else:
                self.before_date = self.period_array[tag].strftime("%Y-%m-%d") if self.period_array[tag] else None

            self.refreshViewer()

            self.selectedTableView = self.contactTable
Ejemplo n.º 6
0
    def __init__(self):
        if self:
            BlinkLogger().log_debug('Starting History Viewer')
            NSBundle.loadNibNamed_owner_("HistoryViewer", self)

            self.all_contacts = BlinkHistoryViewerContact('Any Address', name=u'All Contacts')
            self.bonjour_contact = BlinkHistoryViewerContact('bonjour', name=u'Bonjour Neighbours', icon=NSImage.imageNamed_("NSBonjour"))

            self.notification_center = NotificationCenter()
            self.notification_center.add_observer(self, name='ChatViewControllerDidDisplayMessage')
            self.notification_center.add_observer(self, name='AudioCallLoggedToHistory')
            self.notification_center.add_observer(self, name='BlinkContactsHaveChanged')
            self.notification_center.add_observer(self, name='BlinkTableViewSelectionChaged')
            self.notification_center.add_observer(self, name='BlinkConferenceContactPresenceHasChanged')
            self.notification_center.add_observer(self, name='BlinkShouldTerminate')

            self.searchText.cell().setSendsSearchStringImmediately_(True)
            self.searchText.cell().setPlaceholderString_(NSLocalizedString("Type text and press Enter", "Placeholder text"))

            self.chatViewController.setContentFile_(NSBundle.mainBundle().pathForResource_ofType_("ChatView", "html"))
            self.chatViewController.setHandleScrolling_(False)
            self.entriesView.setShouldCloseWithWindow_(False)

            for c in ('remote_uri', 'local_uri', 'date', 'type'):
                col = self.indexTable.tableColumnWithIdentifier_(c)
                descriptor = NSSortDescriptor.alloc().initWithKey_ascending_(c, True)
                col.setSortDescriptorPrototype_(descriptor)

            self.chat_history = ChatHistory()
            self.session_history = SessionHistory()
            self.setPeriod(1)

            self.selectedTableView = self.contactTable
Ejemplo n.º 7
0
    def initWithAccount_target_name_(self, account, target, display_name):
        self = super(SMSViewController, self).init()
        if self:
            self.notification_center = NotificationCenter()
            self.account = account
            self.target_uri = target
            self.display_name = display_name
            self.queue = []
            self.messages = {}

            self.history=ChatHistory()

            self.local_uri = '%s@%s' % (account.id.username, account.id.domain)
            self.remote_uri = '%s@%s' % (self.target_uri.user, self.target_uri.host)

            NSBundle.loadNibNamed_owner_("SMSView", self)

            self.chatViewController.setContentFile_(NSBundle.mainBundle().pathForResource_ofType_("ChatView", "html"))
            self.chatViewController.setAccount_(self.account)
            self.chatViewController.resetRenderedMessages()

            self.chatViewController.inputText.unregisterDraggedTypes()
            self.chatViewController.inputText.setMaxLength_(MAX_MESSAGE_LENGTH)
            self.splitView.setText_("%i chars left" % MAX_MESSAGE_LENGTH)

        return self
Ejemplo n.º 8
0
    def initWithAccount_target_name_(self, account, target, display_name):
        self = objc.super(SMSViewController, self).init()
        if self:
            self.session_id = str(uuid.uuid1())

            self.notification_center = NotificationCenter()
            self.account = account
            self.target_uri = target
            self.display_name = display_name
            self.messages = {}

            self.encryption = OTREncryption(self)

            self.message_queue = EventQueue(self._send_message)

            self.history = ChatHistory()

            self.local_uri = '%s@%s' % (account.id.username, account.id.domain)
            self.remote_uri = '%s@%s' % (self.target_uri.user.decode(),
                                         self.target_uri.host.decode())
            self.contact = NSApp.delegate(
            ).contactsWindowController.getFirstContactFromAllContactsGroupMatchingURI(
                self.remote_uri)

            NSBundle.loadNibNamed_owner_("SMSView", self)

            self.chatViewController.setContentFile_(
                NSBundle.mainBundle().pathForResource_ofType_(
                    "ChatView", "html"))
            self.chatViewController.setAccount_(self.account)
            self.chatViewController.resetRenderedMessages()

            self.chatViewController.inputText.unregisterDraggedTypes()
            self.chatViewController.inputText.setMaxLength_(MAX_MESSAGE_LENGTH)
            self.splitView.setText_(
                NSLocalizedString("%i chars left", "Label") %
                MAX_MESSAGE_LENGTH)

            self.enableIsComposing = True

            self.log_info('Using local account %s' % self.local_uri)

            self.notification_center.add_observer(
                self, name='ChatStreamOTREncryptionStateChanged')
            self.started = False

        return self
Ejemplo n.º 9
0
    def add_to_history(self, media_type, local_uri, remote_uri, direction, cpim_from, cpim_to, timestamp, message, status):
        try:
            controller = next((controller for controller in NSApp.delegate().contactsWindowController.sessionControllersManager.sessionControllers if controller.session == self.session))
        except StopIteration:
            history_id = str(uuid.uuid1())
        else:
            history_id = controller.history_id

        ChatHistory().add_message(history_id, media_type, local_uri, remote_uri, direction, cpim_from, cpim_to, timestamp, message, "html", "0", status)
Ejemplo n.º 10
0
class HistoryViewer(NSWindowController):
    implements(IObserver)

    chatViewController = objc.IBOutlet()
    indexTable = objc.IBOutlet()
    contactTable = objc.IBOutlet()
    toolbar = objc.IBOutlet()

    entriesView = objc.IBOutlet()

    period = objc.IBOutlet()
    searchText = objc.IBOutlet()
    searchMedia = objc.IBOutlet()
    searchContactBox = objc.IBOutlet()

    paginationButton = objc.IBOutlet()
    foundMessagesLabel = objc.IBOutlet()
    queryDatabaseLabel = objc.IBOutlet()
    busyIndicator = objc.IBOutlet()

    contactMenu = objc.IBOutlet()

    # viewer sections
    contacts = []
    dayly_entries = NSMutableArray.array()
    messages = []

    # database handler
    history = None

    # search filters
    start = 0
    search_text = None
    search_uris = None
    search_local = None
    search_media = None
    after_date = None
    before_date = None
    refresh_contacts_counter = 1

    daily_order_fields = {'date': 'DESC', 'local_uri': 'ASC', 'remote_uri': 'ASC'}
    media_type_array = {0: None, 1: 'audio', 2: 'chat', 3: 'sms', 4: 'file-transfer', 5: 'audio-recording', 6: 'availability', 7: 'voicemail', 8: 'missed-call'}
    period_array = {0: None,
                    1: datetime.datetime.now()-datetime.timedelta(days=1),
                    2: datetime.datetime.now()-datetime.timedelta(days=7),
                    3: datetime.datetime.now()-datetime.timedelta(days=31),
                    4: datetime.datetime.now()-datetime.timedelta(days=90),
                    5: datetime.datetime.now()-datetime.timedelta(days=180),
                    6: datetime.datetime.now()-datetime.timedelta(days=365),
                    -1: datetime.datetime.now()-datetime.timedelta(days=1),
                    -2: datetime.datetime.now()-datetime.timedelta(days=7),
                    -3: datetime.datetime.now()-datetime.timedelta(days=31),
                    -4: datetime.datetime.now()-datetime.timedelta(days=90),
                    -5: datetime.datetime.now()-datetime.timedelta(days=180),
                    -6: datetime.datetime.now()-datetime.timedelta(days=365)
                    }

    def format_media_type(self, media_type):
        if media_type == 'sms':
            return NSLocalizedString("Instant Messages", "Column title")
        elif media_type == 'chat':
            return NSLocalizedString("Chat Sessions", "Column title")
        elif media_type == 'audio':
            return NSLocalizedString("Audio Calls", "Column title")
        elif media_type == 'file-transfer':
            return NSLocalizedString("File Transfers", "Column title")
        else:
            return media_type.title()

    def __new__(cls, *args, **kwargs):
        return cls.alloc().init()

    def __init__(self):
        if self:
            BlinkLogger().log_debug('Starting History Viewer')
            NSBundle.loadNibNamed_owner_("HistoryViewer", self)

            self.all_contacts = BlinkHistoryViewerContact('Any Address', name=u'All Contacts')
            self.bonjour_contact = BlinkHistoryViewerContact('bonjour', name=u'Bonjour Neighbours', icon=NSImage.imageNamed_("NSBonjour"))

            self.notification_center = NotificationCenter()
            self.notification_center.add_observer(self, name='ChatViewControllerDidDisplayMessage')
            self.notification_center.add_observer(self, name='AudioCallLoggedToHistory')
            self.notification_center.add_observer(self, name='BlinkContactsHaveChanged')
            self.notification_center.add_observer(self, name='BlinkTableViewSelectionChaged')
            self.notification_center.add_observer(self, name='BlinkConferenceContactPresenceHasChanged')
            self.notification_center.add_observer(self, name='BlinkShouldTerminate')

            self.searchText.cell().setSendsSearchStringImmediately_(True)
            self.searchText.cell().setPlaceholderString_(NSLocalizedString("Type text and press Enter", "Placeholder text"))

            self.chatViewController.setContentFile_(NSBundle.mainBundle().pathForResource_ofType_("ChatView", "html"))
            self.chatViewController.setHandleScrolling_(False)
            self.entriesView.setShouldCloseWithWindow_(False)

            for c in ('remote_uri', 'local_uri', 'date', 'type'):
                col = self.indexTable.tableColumnWithIdentifier_(c)
                descriptor = NSSortDescriptor.alloc().initWithKey_ascending_(c, True)
                col.setSortDescriptorPrototype_(descriptor)

            self.chat_history = ChatHistory()
            self.session_history = SessionHistory()
            self.setPeriod(1)

            self.selectedTableView = self.contactTable

    def setPeriod(self, days):
        if days <= -365:
            tag = -6
        elif days <= -180:
            tag = -5
        elif days <= -90:
            tag = -4
        elif days <= -31:
            tag = -3
        elif days <= -7:
            tag = -2
        elif days <= -1:
            tag = -1
        elif days <= 1:
            tag = 1
        elif days <= 7:
            tag = 2
        elif days <= 31:
            tag = 3
        elif days <= 90:
            tag = 4
        elif days <= 180:
            tag = 5
        elif days <= 365:
            tag = 6
        else:
            tag = 0

        if tag == 0:
            self.before_date = None
            self.after_date = None
        elif tag < 0:
            try:
                date = self.period_array[tag]
            except KeyError:
                date = None

            self.before_date = date
            self.after_date = None
        else:
            try:
                date = self.period_array[tag]
            except KeyError:
                date = None

            self.after_date = date
            self.before_date = None

        self.period.selectItemWithTag_(tag)

    def awakeFromNib(self):
        NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(self, "contactSelectionChanged:", NSTableViewSelectionDidChangeNotification, self.contactTable)

        timer = NSTimer.timerWithTimeInterval_target_selector_userInfo_repeats_(0.3, self, "refreshContactsTimer:", None, True)
        NSRunLoop.currentRunLoop().addTimer_forMode_(timer, NSModalPanelRunLoopMode)
        NSRunLoop.currentRunLoop().addTimer_forMode_(timer, NSDefaultRunLoopMode)

        self.contactTable.setDoubleAction_("doubleClick:")

    def close_(self, sender):
        self.window().close()

    @allocate_autorelease_pool
    @run_in_gui_thread
    def refreshViewer(self):
        self.refreshContacts()
        self.refreshDailyEntries()
        self.refreshMessages()

    @run_in_green_thread
    def delete_messages(self, local_uri=None, remote_uri=None, date=None, after_date=None, before_date=None, media_type=None):
        self.chat_history.delete_messages(local_uri=local_uri, remote_uri=remote_uri, date=date, after_date=after_date, before_date=before_date, media_type=media_type)
        self.session_history.delete_entries(local_uri=local_uri, remote_uri=remote_uri, after_date=after_date, before_date=before_date)
        self.search_text = None
        self.search_uris = None
        self.search_local = None
        self.refreshViewer()

    @run_in_green_thread
    def refreshContacts(self):
        if self.chat_history:
            self.updateBusyIndicator(True)
            remote_uri = self.search_uris if self.search_uris else None
            media_type = self.search_media if self.search_media else None
            search_text = self.search_text if self.search_text else None
            after_date = self.after_date if self.after_date else None
            before_date = self.before_date if self.before_date else None
            results = self.chat_history.get_contacts(remote_uri=remote_uri, media_type=media_type, search_text=search_text, after_date=after_date, before_date=before_date)
            self.renderContacts(results)
            self.updateBusyIndicator(False)

    @allocate_autorelease_pool
    @run_in_gui_thread
    def renderContacts(self, results):
        index = 0
        found_uris = []

        for item in self.contacts:
            item.destroy()

        getFirstContactMatchingURI = NSApp.delegate().contactsWindowController.getFirstContactMatchingURI

        self.contacts = [self.all_contacts, self.bonjour_contact]

        if self.search_uris:
            for uri in self.search_uris:
                found_contact = getFirstContactMatchingURI(uri, exact_match=True)
                if found_contact:
                    contact_exist = False
                    for contact_uri in found_contact.uris:
                        if contact_uri.uri in found_uris:
                            contact_exist = True
                            break
                    if contact_exist:
                        continue
                    contact = BlinkHistoryViewerContact(found_contact.uri, name=found_contact.name, icon=found_contact.icon)
                    for contact_uri in found_contact.uris:
                        found_uris.append(contact_uri.uri)
                    self.contacts.append(contact)
                    if isinstance(found_contact, BlinkPresenceContact):
                        contact.setPresenceContact_(found_contact)
                else:
                    if uri in found_uris:
                        continue
                    found_uris.append(uri)
                    contact = BlinkHistoryViewerContact(unicode(uri), name=unicode(uri))

                try:
                    index = self.contacts.index(contact)
                except ValueError:
                    pass

        if results:
            for row in results:
                found_contact = getFirstContactMatchingURI(row[0], exact_match=True)
                if found_contact:
                    contact_exist = False
                    for contact_uri in found_contact.uris:
                        if contact_uri.uri in found_uris:
                            contact_exist = True
                            break
                    if contact_exist:
                        continue
                    contact = BlinkHistoryViewerContact(found_contact.uri, name=found_contact.name, icon=found_contact.icon, presence_contact=found_contact if isinstance(found_contact, BlinkPresenceContact) else None)
                    for contact_uri in found_contact.uris:
                        found_uris.append(contact_uri.uri)
                else:
                    if row[0] in found_uris:
                        continue
                    found_uris.append(row[0])
                    contact = BlinkHistoryViewerContact(unicode(row[0]), name=unicode(row[0]))

                self.contacts.append(contact)

        self.contactTable.reloadData()

        self.contactTable.selectRowIndexes_byExtendingSelection_(NSIndexSet.indexSetWithIndex_(index), False)
        self.contactTable.scrollRowToVisible_(index)

        self.updateContactsColumnHeader()

    @run_in_green_thread
    def refreshDailyEntries(self, order_text=None):
        if self.chat_history:
            self.resetDailyEntries()
            self.updateBusyIndicator(True)
            search_text = self.search_text if self.search_text else None
            remote_uri = self.search_uris if self.search_uris else None
            local_uri = self.search_local if self.search_local else None
            media_type = self.search_media if self.search_media else None
            after_date = self.after_date if self.after_date else None
            before_date = self.before_date if self.before_date else None
            results = self.chat_history.get_daily_entries(local_uri=local_uri, remote_uri=remote_uri, media_type=media_type, search_text=search_text, order_text=order_text, after_date=after_date, before_date=before_date)
            self.renderDailyEntries(results)
            self.updateBusyIndicator(False)

    @allocate_autorelease_pool
    @run_in_gui_thread
    def resetDailyEntries(self):
        self.dayly_entries = NSMutableArray.array()
        self.indexTable.reloadData()

    @allocate_autorelease_pool
    @run_in_gui_thread
    def renderDailyEntries(self, results):
        getFirstContactMatchingURI = NSApp.delegate().contactsWindowController.getFirstContactMatchingURI
        self.dayly_entries = NSMutableArray.array()
        for result in results:
            contact = getFirstContactMatchingURI(result[2], exact_match=True)
            if contact:
                remote_uri = '%s <%s>' % (contact.name, result[2])
            else:
                remote_uri = result[2]

            entry = NSDictionary.dictionaryWithObjectsAndKeys_(result[1], "local_uri", remote_uri, "remote_uri", result[2], "remote_uri_sql", result[0], 'date', result[3], 'type')
            self.dayly_entries.addObject_(entry)

        self.dayly_entries.sortUsingDescriptors_(self.indexTable.sortDescriptors())
        self.indexTable.reloadData()

        if self.search_uris and not self.dayly_entries:
            self.contactTable.deselectAll_(True)

    @run_in_green_thread
    @allocate_autorelease_pool
    def refreshMessages(self, count=SQL_LIMIT, remote_uri=None, local_uri=None, media_type=None, date=None, after_date=None, before_date=None):
        self.updateBusyIndicator(True)
        if self.chat_history:
            search_text = self.search_text if self.search_text else None
            if not remote_uri:
                remote_uri = self.search_uris if self.search_uris else None
            if not local_uri:
                local_uri = self.search_local if self.search_local else None
            if not media_type:
                media_type = self.search_media if self.search_media else None
            if not after_date:
                after_date = self.after_date if self.after_date else None
            if not before_date:
                before_date = self.before_date if self.before_date else None

            results = self.chat_history.get_messages(count=count, local_uri=local_uri, remote_uri=remote_uri, media_type=media_type, date=date, search_text=search_text, after_date=after_date, before_date=before_date)

            # cache message for pagination
            self.messages=[]
            for e in reversed(results):
                self.messages.append(e)

            # reset pagination to the last page
            start = len(self.messages) - len(self.messages)%MAX_MESSAGES_PER_PAGE if len(self.messages) > MAX_MESSAGES_PER_PAGE else 0
            self.renderMessages(start)
        self.updateBusyIndicator(False)

    @allocate_autorelease_pool
    @run_in_gui_thread
    def renderMessages(self, start=None):
        self.chatViewController.clear()
        self.chatViewController.resetRenderedMessages()

        start_from = start or self.start

        end = start_from + MAX_MESSAGES_PER_PAGE if start_from + MAX_MESSAGES_PER_PAGE < len(self.messages) else len(self.messages)
        for row in self.messages[start_from:end]:
            self.renderMessage(row)

        self.paginationButton.setEnabled_forSegment_(True if len(self.messages)>MAX_MESSAGES_PER_PAGE and start_from > MAX_MESSAGES_PER_PAGE else False, 0)
        self.paginationButton.setEnabled_forSegment_(True if start_from else False, 1)
        self.paginationButton.setEnabled_forSegment_(True if start_from+MAX_MESSAGES_PER_PAGE+1 < len(self.messages) else False, 2)
        self.paginationButton.setEnabled_forSegment_(True if len(self.messages)>MAX_MESSAGES_PER_PAGE and len(self.messages) - start_from > 2*MAX_MESSAGES_PER_PAGE else False, 3)

        text = u'No entry found'
        if len(self.messages):
            if len(self.messages) == 1:
                text = NSLocalizedString("Displaying 1 entry", "Text label")
            elif MAX_MESSAGES_PER_PAGE > len(self.messages):
                text = NSLocalizedString("Displaying %d entries" % end, "Text label")
            else:
                l = len(self.messages)
                text = NSLocalizedString("Displaying %d to "% start_from + 1, "Text label") + end + NSLocalizedString("out of %d entries" % l, "Text label")

        self.foundMessagesLabel.setStringValue_(text)

    @allocate_autorelease_pool
    @run_in_gui_thread
    def renderMessage(self, message):
        if message.direction == 'outgoing':
            icon = NSApp.delegate().contactsWindowController.iconPathForSelf()
        else:
            sender_uri = sipuri_components_from_string(message.cpim_from)[0]
            # TODO: How to render the icons from Address Book? Especially in sandbox mode we do not have access to other folders
            icon = NSApp.delegate().contactsWindowController.iconPathForURI(sender_uri)
        try:
            timestamp=ISOTimestamp(message.cpim_timestamp)
        except Exception:
            pass
        else:
            is_html = False if message.content_type == 'text' else True
            private = True if message.private == "1" else False
            self.chatViewController.showMessage(message.sip_callid, message.msgid, message.direction, message.cpim_from, icon, message.body, timestamp, is_private=private, recipient=message.cpim_to, state=message.status, is_html=is_html, history_entry=True, media_type=message.media_type, encryption=message.encryption if message.media_type == 'chat' else None)

    @objc.IBAction
    def paginateResults_(self, sender):
        if sender.selectedSegment() == 0:
           self.start = 0
        elif sender.selectedSegment() == 1:
           next_start = self.start - MAX_MESSAGES_PER_PAGE
           self.start = next_start if next_start >= 0 else self.start
        elif sender.selectedSegment() == 2:
           next_start = self.start + MAX_MESSAGES_PER_PAGE
           self.start = next_start if next_start < len(self.messages)-1 else self.start
        elif sender.selectedSegment() == 3:
           self.start = len(self.messages) - len(self.messages)%MAX_MESSAGES_PER_PAGE if len(self.messages) > MAX_MESSAGES_PER_PAGE else 0
        self.renderMessages()

    def tableView_deleteRow_(self, table, row):
        pass

    def tableView_sortDescriptorsDidChange_(self, table, odescr):
        self.dayly_entries.sortUsingDescriptors_(self.indexTable.sortDescriptors())
        self.indexTable.reloadData()

    @objc.IBAction
    def printDocument_(self, sender):
        printInfo = NSPrintInfo.sharedPrintInfo()
        printInfo.setTopMargin_(30)
        printInfo.setBottomMargin_(30)
        printInfo.setLeftMargin_(10)
        printInfo.setRightMargin_(10)
        printInfo.setOrientation_(NSPortraitOrientation)
        printInfo.setHorizontallyCentered_(True)
        printInfo.setVerticallyCentered_(False)
        printInfo.setHorizontalPagination_(NSFitPagination)
        printInfo.setVerticalPagination_(NSFitPagination)
        NSPrintInfo.setSharedPrintInfo_(printInfo)

        # print the content of the web view
        self.entriesView.mainFrame().frameView().documentView().print_(self)

    @objc.IBAction
    def search_(self, sender):
        if self.chat_history:
            self.search_text = unicode(sender.stringValue()).lower()
            self.refreshContacts()
            row = self.indexTable.selectedRow()
            if row > 0:
                self.refreshDailyEntries()
                self.refreshMessages(local_uri=self.dayly_entries[row].objectForKey_("local_uri"), date=self.dayly_entries[row].objectForKey_("date"), media_type=self.dayly_entries[row].objectForKey_("type"))
            else:
                row = self.contactTable.selectedRow()
                if row > 0:
                    self.refreshMessages()
                    self.refreshDailyEntries()
                else:
                    self.refreshDailyEntries()
                    self.refreshMessages()

    @objc.IBAction
    def searchContacts_(self, sender):
        text = unicode(self.searchContactBox.stringValue().strip())
        contacts = [contact for contact in self.contacts[2:] if text in contact] if text else self.contacts[2:]
        self.contacts = [self.all_contacts, self.bonjour_contact] + contacts
        self.contactTable.reloadData()
        self.contactTable.selectRowIndexes_byExtendingSelection_(NSIndexSet.indexSetWithIndex_(0), False)
        self.contactTable.scrollRowToVisible_(0)
        self.updateContactsColumnHeader()

        if not text:
            self.refreshContacts()

    def updateContactsColumnHeader(self):
        found_contacts = len(self.contacts)-2
        if found_contacts == 1:
            title = NSLocalizedString("1 Contact Found", "Column title")
        elif found_contacts > 1:
            title =  NSLocalizedString("%d Contacts Found" % found_contacts, "Column title")
        else:
            title = NSLocalizedString("Contacts", "Column title")

        self.contactTable.tableColumnWithIdentifier_('contacts').headerCell().setStringValue_(title)

    def tableViewSelectionDidChange_(self, notification):
        if self.chat_history:
            if notification.object() == self.contactTable:
                row = self.contactTable.selectedRow()
                if row < 0:
                    return
                elif row == 0:
                    self.search_local = None
                    self.search_uris = None
                    self.searchContactBox.setStringValue_('')
                    self.refreshContacts()
                elif row == 1:
                    self.search_local = 'bonjour'
                    self.search_uris = None
                elif row > 1:
                    self.search_local = None
                    if self.contacts[row].presence_contact is not None:
                        self.search_uris = list(unicode(contact_uri.uri) for contact_uri in self.contacts[row].presence_contact.uris)
                        self.chatViewController.expandSmileys = not self.contacts[row].presence_contact.contact.disable_smileys
                        self.chatViewController.toggleSmileys(self.chatViewController.expandSmileys)
                        try:
                            item = (item for item in self.toolbar.visibleItems() if item.itemIdentifier() == 'smileys').next()
                        except StopIteration:
                            pass
                        else:
                            item.setImage_(NSImage.imageNamed_("smiley_on" if self.chatViewController.expandSmileys else "smiley_off"))
                    else:
                        self.search_uris = (self.contacts[row].uri,)
                self.refreshDailyEntries()
                self.refreshMessages()
            else:
                row = self.indexTable.selectedRow()
                if row >= 0:
                    self.refreshMessages(remote_uri=(self.dayly_entries[row].objectForKey_("remote_uri_sql"),), local_uri=self.dayly_entries[row].objectForKey_("local_uri"), date=self.dayly_entries[row].objectForKey_("date"), media_type=self.dayly_entries[row].objectForKey_("type"))

    def numberOfRowsInTableView_(self, table):
        if table == self.indexTable:
            return self.dayly_entries.count()
        elif table == self.contactTable:
            return len(self.contacts)
        return 0

    def tableView_objectValueForTableColumn_row_(self, table, column, row):
        if table == self.indexTable:
            ident = column.identifier()
            if ident == 'type':
                return self.format_media_type(self.dayly_entries[row].objectForKey_(ident))

            try:
                return unicode(self.dayly_entries[row].objectForKey_(ident))
            except IndexError:
                return None
        elif table == self.contactTable:
            try:
                if type(self.contacts[row]) in (str, unicode):
                    return self.contacts[row]
                else:
                    return self.contacts[row].name
            except IndexError:
                return None
        return None

    def tableView_willDisplayCell_forTableColumn_row_(self, table, cell, tableColumn, row):
        if table == self.contactTable:
            try:
                if row < len(self.contacts):
                    if type(self.contacts[row]) in (str, unicode):
                        cell.setContact_(None)
                    else:
                        cell.setContact_(self.contacts[row])
            except:
                pass

    def showWindow_(self, sender):
        self.window().makeKeyAndOrderFront_(None)

    @run_in_gui_thread
    def filterByURIs(self, uris=(), media_type=None):
        self.search_text = None
        self.search_local = None
        if media_type != self.search_media:
            for tag in self.media_type_array.keys():
                if self.media_type_array[tag] == media_type:
                    self.searchMedia.selectItemAtIndex_(tag)

        self.search_media = media_type
        self.search_uris = uris
        self.refreshContacts()
        self.refreshDailyEntries()
        self.refreshMessages()

    @objc.IBAction
    def filterByMediaChanged_(self, sender):
        tag = sender.selectedItem().tag()
        self.search_media = self.media_type_array[tag]
        self.refreshContacts()
        self.refreshDailyEntries()
        self.refreshMessages()

    @objc.IBAction
    def filterByPeriodChanged_(self, sender):
        tag = sender.selectedItem().tag()

        if tag == 0:
            self.before_date = None
            self.after_date = None
        elif tag < 0:
            try:
                date = self.period_array[tag]
            except KeyError:
                date = None

            self.before_date = date
            self.after_date = None
        else:
            try:
                date = self.period_array[tag]
            except KeyError:
                date = None

            self.after_date = date
            self.before_date = None

        self.refreshContacts()
        self.refreshDailyEntries()
        self.refreshMessages()

    def validateToolbarItem_(self, item):
        if item.itemIdentifier() == NSToolbarPrintItemIdentifier and not self.messages:
            return False
        return True

    def toolbarWillAddItem_(self, notification):
        item = notification.userInfo()["item"]
        if item.itemIdentifier() == NSToolbarPrintItemIdentifier:
            item.setToolTip_("Print Current Entries")
            item.setTarget_(self)
            item.setAutovalidates_(True)

    @objc.IBAction
    def userClickedToolbarItem_(self, sender):
        if sender.itemIdentifier() == 'smileys':
            self.chatViewController.expandSmileys = not self.chatViewController.expandSmileys
            sender.setImage_(NSImage.imageNamed_("smiley_on" if self.chatViewController.expandSmileys else "smiley_off"))
            self.chatViewController.toggleSmileys(self.chatViewController.expandSmileys)

            row = self.contactTable.selectedRow()
            if row and row > 1 and self.contacts[row].presence_contact is not None:
                self.contacts[row].presence_contact.contact.disable_smileys = not self.contacts[row].presence_contact.contact.disable_smileys
                self.contacts[row].presence_contact.contact.save()

        elif sender.itemIdentifier() == 'delete':
            if self.selectedTableView == self.contactTable:
                try:
                    row = self.contactTable.selectedRow()
                    self.showDeleteConfirmationDialog(row)
                except IndexError:
                    pass

            elif self.selectedTableView == self.indexTable:
                try:
                    row = self.indexTable.selectedRow()
                    local_uri = self.dayly_entries[row].objectForKey_("local_uri")
                    remote_uri = self.dayly_entries[row].objectForKey_("remote_uri")
                    remote_uri_sql = self.dayly_entries[row].objectForKey_("remote_uri_sql")
                    date = self.dayly_entries[row].objectForKey_("date")
                    media_type = self.dayly_entries[row].objectForKey_("type")

                    label = NSLocalizedString("Please confirm the deletion of %s history entries" % media_type, "Text label") + NSLocalizedString(" from %s" % remote_uri, "SIP Address label") + NSLocalizedString(" on %s. " % date, "Date label") + NSLocalizedString("This operation cannot be undone. ", "Text label")
                    ret = NSRunAlertPanel(NSLocalizedString("Purge History Entries", "Window title"), label, NSLocalizedString("Confirm", "Button title"), NSLocalizedString("Cancel", "Button title"), None)
                    if ret == NSAlertDefaultReturn:
                        self.delete_messages(local_uri=local_uri, remote_uri=remote_uri_sql, media_type=media_type, date=date)
                except IndexError:
                    pass

    def showDeleteConfirmationDialog(self, row):
        media_print = self.search_media or NSLocalizedString("all", "Text label")
        tag = self.period.selectedItem().tag()

        period = '%s %s' % (NSLocalizedString(" newer than", "Date label") if tag < 4 else NSLocalizedString(" older than", "Date label"), self.period_array[tag].strftime("%Y-%m-%d")) if tag else ''

        if row == 0:
            label = NSLocalizedString("Please confirm the deletion of %s history entries" % media_print, "Text label") + period + ". "+ NSLocalizedString("This operation cannot be undone. ", "Text label")
            ret = NSRunAlertPanel(NSLocalizedString("Purge History Entries", "Window title"), label, NSLocalizedString("Confirm", "Button title"), NSLocalizedString("Cancel", "Button title"), None)
            if ret == NSAlertDefaultReturn:
                self.delete_messages(media_type=self.search_media, after_date=self.after_date, before_date=self.before_date)
        elif row == 1:
            remote_uri=self.contacts[row].uri
            label = NSLocalizedString("Please confirm the deletion of %s Bonjour history entries" % media_print, "Text label") + period + ". "+ NSLocalizedString("This operation cannot be undone. ", "Text label")
            ret = NSRunAlertPanel(NSLocalizedString("Purge History Entries", "Window title"), label, NSLocalizedString("Confirm", "Button title"), NSLocalizedString("Cancel", "Button title"), None)
            if ret == NSAlertDefaultReturn:
                self.delete_messages(local_uri='bonjour', media_type=self.search_media, after_date=self.after_date, before_date=self.before_date)
        else:
            contact = self.contacts[row]
            if contact.presence_contact is not None:
                remote_uri = list(unicode(contact.uri) for contact in contact.presence_contact.uris)
            else:
                remote_uri = contact.uri
            label = NSLocalizedString("Please confirm the deletion of %s history entries" % media_print, "Text label") + NSLocalizedString(" from", "Text label") + " " + contact.name + period + ". "+ NSLocalizedString("This operation cannot be undone. ", "Text label")
            ret = NSRunAlertPanel(NSLocalizedString("Purge History Entries", "Window title"), label, NSLocalizedString("Confirm", "Button title"), NSLocalizedString("Cancel", "Button title"), None)
            if ret == NSAlertDefaultReturn:
                self.delete_messages(remote_uri=remote_uri, media_type=self.search_media, after_date=self.after_date, before_date=self.before_date)

    @allocate_autorelease_pool
    @run_in_gui_thread
    def updateBusyIndicator(self, busy=False):
        if busy:
            self.queryDatabaseLabel.setHidden_(False)
            self.busyIndicator.setHidden_(False)
            self.busyIndicator.setIndeterminate_(True)
            self.busyIndicator.setStyle_(NSProgressIndicatorSpinningStyle)
            self.busyIndicator.startAnimation_(None)
        else:
            self.busyIndicator.stopAnimation_(None)
            self.busyIndicator.setHidden_(True)
            self.queryDatabaseLabel.setHidden_(True)

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

    def _NH_BlinkShouldTerminate(self, notification):
        if self.window():
            self.window().orderOut_(self)

    def _NH_ChatViewControllerDidDisplayMessage(self, notification):
        if notification.data.local_party != 'bonjour':
            exists = any(contact for contact in self.contacts if notification.data.remote_party == contact.uri)
            if not exists:
                self.refreshContacts()

    def _NH_AudioCallLoggedToHistory(self, notification):
        if notification.data.local_party != 'bonjour':
            exists = any(contact for contact in self.contacts if notification.data.remote_party == contact.uri)
            if not exists:
                self.refreshContacts()

    def _NH_BlinkContactsHaveChanged(self, notification):
        self.refresh_contacts_counter += 1

    def refreshContactsTimer_(self, timer):
        if self.refresh_contacts_counter:
            self.refresh_contacts_counter = 0
            self.refreshContacts()
            self.toolbar.validateVisibleItems()

    def _NH_BlinkTableViewSelectionChaged(self, notification):
        self.selectedTableView = notification.sender
        self.toolbar.validateVisibleItems()

    def _NH_BlinkConferenceContactPresenceHasChanged(self, notification):
        try:
            contact = (contact for contact in self.contacts[2:] if contact == notification.sender).next()
        except StopIteration:
            return
        else:
            try:
                idx = self.contacts.index(contact)
                self.contactTable.reloadDataForRowIndexes_columnIndexes_(NSIndexSet.indexSetWithIndex_(idx), NSIndexSet.indexSetWithIndex_(0))
            except ValueError:
                pass

    def contactSelectionChanged_(self, notification):
        pass

    def menuWillOpen_(self, menu):
        if menu == self.contactMenu:
            self.contactMenu.itemWithTag_(2).setEnabled_(False)
            self.contactMenu.itemWithTag_(3).setEnabled_(False)
            self.contactMenu.itemWithTag_(4).setEnabled_(False)
            try:
                row = self.contactTable.selectedRow()
            except:
                return

            if row < 2:
                return

            try:
                contact = self.contacts[row]
            except IndexError:
                return

            contact_exists = bool(contact.presence_contact is not None)
            self.contactMenu.itemWithTag_(2).setEnabled_(not is_anonymous(contact.uri))
            self.contactMenu.itemWithTag_(3).setEnabled_(not contact_exists and not is_anonymous(contact.uri))
            self.contactMenu.itemWithTag_(4).setEnabled_(contact_exists)


    @objc.IBAction
    def doubleClick_(self, sender):
        row = self.contactTable.selectedRow()

        if row < 2:
            return

        try:
            contact = self.contacts[row]
        except IndexError:
            return

        NSApp.delegate().contactsWindowController.startSessionWithTarget(contact.uri)

    @objc.IBAction
    def userClickedContactMenu_(self, sender):
        row = self.contactTable.selectedRow()

        try:
            contact = self.contacts[row]
        except IndexError:
            return

        tag = sender.tag()

        if tag == 1:
            self.showDeleteConfirmationDialog(row)
        elif tag == 2:
            NSApp.delegate().contactsWindowController.searchBox.setStringValue_(contact.uri)
            NSApp.delegate().contactsWindowController.searchContacts()
            NSApp.delegate().contactsWindowController.window().makeFirstResponder_(NSApp.delegate().contactsWindowController.searchBox)
            NSApp.delegate().contactsWindowController.window().deminiaturize_(sender)
            NSApp.delegate().contactsWindowController.window().makeKeyWindow()
        elif tag == 3:
            NSApp.delegate().contactsWindowController.addContact(contact.uri, contact.name)
        elif tag == 4 and contact.presence_contact is not None:
            NSApp.delegate().contactsWindowController.model.editContact(contact.presence_contact)

    @objc.IBAction
    def userClickedActionsButton_(self, sender):
        point = sender.window().convertScreenToBase_(NSEvent.mouseLocation())
        event = NSEvent.mouseEventWithType_location_modifierFlags_timestamp_windowNumber_context_eventNumber_clickCount_pressure_(
                    NSLeftMouseUp, point, 0, NSDate.timeIntervalSinceReferenceDate(), sender.window().windowNumber(),
                    sender.window().graphicsContext(), 0, 1, 0)
        NSMenu.popUpContextMenu_withEvent_forView_(self.contactMenu, event, sender)
Ejemplo n.º 11
0
 def add_to_history(self, media_type, local_uri, remote_uri, direction,
                    cpim_from, cpim_to, timestamp, message, status):
     ChatHistory().add_message(str(uuid.uuid1()), media_type, local_uri,
                               remote_uri, direction, cpim_from, cpim_to,
                               timestamp, message, "html", "0", status)
Ejemplo n.º 12
0
class SMSViewController(NSObject):
    implements(IObserver)

    chatViewController = objc.IBOutlet()
    splitView = objc.IBOutlet()
    smileyButton = objc.IBOutlet()
    outputContainer = objc.IBOutlet()
    addContactView = objc.IBOutlet()
    addContactLabel = objc.IBOutlet()

    showHistoryEntries = 50
    remoteTypingTimer = None
    enableIsComposing = False
    
    account = None
    target_uri = None
    routes = None
    queue = None
    queued_serial = 0

    def initWithAccount_target_name_(self, account, target, display_name):
        self = super(SMSViewController, self).init()
        if self:
            self.notification_center = NotificationCenter()
            self.account = account
            self.target_uri = target
            self.display_name = display_name
            self.queue = []
            self.messages = {}

            self.history=ChatHistory()

            self.local_uri = '%s@%s' % (account.id.username, account.id.domain)
            self.remote_uri = '%s@%s' % (self.target_uri.user, self.target_uri.host)

            NSBundle.loadNibNamed_owner_("SMSView", self)

            self.chatViewController.setContentFile_(NSBundle.mainBundle().pathForResource_ofType_("ChatView", "html"))
            self.chatViewController.setAccount_(self.account)
            self.chatViewController.resetRenderedMessages()

            self.chatViewController.inputText.unregisterDraggedTypes()
            self.chatViewController.inputText.setMaxLength_(MAX_MESSAGE_LENGTH)
            self.splitView.setText_("%i chars left" % MAX_MESSAGE_LENGTH)

        return self

    def dealloc(self):
        if self.remoteTypingTimer:
            self.remoteTypingTimer.invalidate()
        super(SMSViewController, self).dealloc()

    def awakeFromNib(self):
        # setup smiley popup 
        smileys = SmileyManager().get_smiley_list()

        menu = self.smileyButton.menu()
        while menu.numberOfItems() > 0:
            menu.removeItemAtIndex_(0)

        bigText = NSAttributedString.alloc().initWithString_attributes_(" ", NSDictionary.dictionaryWithObject_forKey_(NSFont.systemFontOfSize_(16), NSFontAttributeName))
        for text, file in smileys:
            image = NSImage.alloc().initWithContentsOfFile_(file)
            if not image:
                print "Can't load %s" % file
                continue
            image.setScalesWhenResized_(True)
            image.setSize_(NSMakeSize(16, 16))
            atext = bigText.mutableCopy()
            atext.appendAttributedString_(NSAttributedString.alloc().initWithString_(text))
            item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(text, "insertSmiley:", "")
            menu.addItem_(item)
            item.setTarget_(self)
            item.setAttributedTitle_(atext)
            item.setRepresentedObject_(NSAttributedString.alloc().initWithString_(text))
            item.setImage_(image)

    def isOutputFrameVisible(self):
        return True

    def log_info(self, text):
        BlinkLogger().log_info(u"[Message to %s] %s" % (self.remote_uri, text))

    @objc.IBAction
    def addContactPanelClicked_(self, sender):
        if sender.tag() == 1:
            NSApp.delegate().contactsWindowController.addContact(self.target_uri)
        
        self.addContactView.removeFromSuperview()
        frame = self.chatViewController.outputView.frame()
        frame.origin.y = 0
        frame.size = self.outputContainer.frame().size
        self.chatViewController.outputView.setFrame_(frame)
    
    def insertSmiley_(self, sender):
        smiley = sender.representedObject()
        self.chatViewController.appendAttributedString_(smiley)

    def matchesTargetAccount(self, target, account):
        that_contact = NSApp.delegate().contactsWindowController.getContactMatchingURI(target)
        this_contact = NSApp.delegate().contactsWindowController.getContactMatchingURI(self.target_uri)
        return (self.target_uri==target or (this_contact and that_contact and this_contact==that_contact)) and self.account==account

    def gotMessage(self, sender, message, is_html=False, state=None, timestamp=None):
        self.enableIsComposing = True
        icon = NSApp.delegate().contactsWindowController.iconPathForURI(format_identity_to_string(sender))
        timestamp = timestamp or Timestamp(datetime.datetime.now(tzlocal()))

        hash = hashlib.sha1()
        hash.update(message.encode('utf-8')+str(timestamp)+str(sender))
        msgid = hash.hexdigest()

        self.chatViewController.showMessage(msgid, 'incoming', format_identity_to_string(sender), icon, message, timestamp, is_html=is_html, state="delivered")

        self.notification_center.post_notification('ChatViewControllerDidDisplayMessage', sender=self, data=TimestampedNotificationData(direction='incoming', history_entry=False, remote_party=format_identity_to_string(sender), local_party=format_identity_to_string(self.account) if self.account is not BonjourAccount() else 'bonjour', check_contact=True))

        # save to history
        message = MessageInfo(msgid, direction='incoming', sender=sender, recipient=self.account, timestamp=timestamp, text=message, content_type="html" if is_html else "text", status="delivered")
        self.add_to_history(message)
 
    def remoteBecameIdle_(self, timer):
        window = timer.userInfo()
        if window:
            window.noteView_isComposing_(self, False)

        if self.remoteTypingTimer:
            self.remoteTypingTimer.invalidate()
        self.remoteTypingTimer = None

    def gotIsComposing(self, window, state, refresh, last_active):
        self.enableIsComposing = True

        flag = state == "active"
        if flag:
            if refresh is None:
                refresh = 120

            if last_active is not None and (last_active - datetime.datetime.now(tzlocal()) > datetime.timedelta(seconds=refresh)):
                # message is old, discard it
                return

            if self.remoteTypingTimer:
                # if we don't get any indications in the request refresh, then we assume remote to be idle
                self.remoteTypingTimer.setFireDate_(NSDate.dateWithTimeIntervalSinceNow_(refresh))
            else:
                self.remoteTypingTimer = NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(refresh, self, "remoteBecameIdle:", window, False)
        else:
            if self.remoteTypingTimer:
                self.remoteTypingTimer.invalidate()
                self.remoteTypingTimer = None

        window.noteView_isComposing_(self, flag)

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

    def _NH_DNSLookupDidFail(self, lookup, data):
        self.notification_center.remove_observer(self, sender=lookup)
        message = u"DNS lookup of SIP proxies for %s failed: %s" % (unicode(self.target_uri.host), data.error)
        self.setRoutesFailed(message)

    def _NH_DNSLookupDidSucceed(self, lookup, data):
        self.notification_center.remove_observer(self, sender=lookup)

        result_text = ', '.join(('%s:%s (%s)' % (result.address, result.port, result.transport.upper()) for result in data.result))
        self.log_info(u"DNS lookup for %s succeeded: %s" % (self.target_uri.host, result_text))
        routes = data.result
        if not routes:
            self.setRoutesFailed("No routes found to SIP Proxy")
        else:
            self.setRoutesResolved(routes)

    def _NH_SIPMessageDidSucceed(self, sender, data):
        BlinkLogger().log_info(u"SMS message delivery suceeded")

        self.composeReplicationMessage(sender, data.code)
        message = self.messages.pop(str(sender))

        if message.content_type != "application/im-iscomposing+xml":
            if data.code == 202:
                self.chatViewController.markMessage(message.msgid, MSG_STATE_DEFERRED)
                message.status='deferred'
            else:
                self.chatViewController.markMessage(message.msgid, MSG_STATE_DELIVERED)
                message.status='delivered'
            self.add_to_history(message)

        self.notification_center.remove_observer(self, sender=sender)

    def _NH_SIPMessageDidFail(self, sender, data):
        BlinkLogger().log_info(u"SMS message delivery failed: %s" % data.reason)

        self.composeReplicationMessage(sender, data.code)
        message = self.messages.pop(str(sender))
        
        if message.content_type != "application/im-iscomposing+xml":
            self.chatViewController.markMessage(message.msgid, MSG_STATE_FAILED)
            message.status='failed'
            self.add_to_history(message)

        self.notification_center.remove_observer(self, sender=sender)

    @run_in_green_thread
    def add_to_history(self, message):
        # writes the record to the sql database
        cpim_to = format_identity_to_string(message.recipient) if message.recipient else ''
        cpim_from = format_identity_to_string(message.sender) if message.sender else ''
        cpim_timestamp = str(message.timestamp)
        content_type="html" if "html" in message.content_type else "text"

        self.history.add_message(message.msgid, 'sms', self.local_uri, self.remote_uri, message.direction, cpim_from, cpim_to, cpim_timestamp, message.text, content_type, "0", message.status)

    def composeReplicationMessage(self, sent_message, response_code):
        if isinstance(self.account, Account):
            settings = SIPSimpleSettings()
            if settings.chat.sms_replication:
                contact = NSApp.delegate().contactsWindowController.getContactMatchingURI(self.target_uri)
                msg = CPIMMessage(sent_message.body.decode('utf-8'), sent_message.content_type, sender=CPIMIdentity(self.account.uri, self.account.display_name), recipients=[CPIMIdentity(self.target_uri, contact.display_name if contact else None)])
                self.sendReplicationMessage(response_code, str(msg), content_type='message/cpim')

    @run_in_green_thread
    def sendReplicationMessage(self, response_code, text, content_type="message/cpim", timestamp=None):
        timestamp = timestamp or datetime.datetime.now(tzlocal())
        # Lookup routes
        if self.account.sip.outbound_proxy is not None:
            uri = SIPURI(host=self.account.sip.outbound_proxy.host,
                         port=self.account.sip.outbound_proxy.port,
                         parameters={'transport': self.account.sip.outbound_proxy.transport})
        else:
            uri = SIPURI(host=self.account.id.domain)
        lookup = DNSLookup()
        settings = SIPSimpleSettings()
        try:
            routes = lookup.lookup_sip_proxy(uri, settings.sip.transport_list).wait()
        except DNSLookupError:
            pass
        else:
            utf8_encode = content_type not in ('application/im-iscomposing+xml', 'message/cpim')
            extra_headers = [Header("X-Offline-Storage", "no"), Header("X-Replication-Code", str(response_code)), Header("X-Replication-Timestamp", str(Timestamp(datetime.datetime.now())))]
            message_request = Message(FromHeader(self.account.uri, self.account.display_name), ToHeader(self.account.uri),
                                      RouteHeader(routes[0].get_uri()), content_type, text.encode('utf-8') if utf8_encode else text, credentials=self.account.credentials, extra_headers=extra_headers)
            message_request.send(15 if content_type != "application/im-iscomposing+xml" else 5)

    @allocate_autorelease_pool
    @run_in_gui_thread
    def setRoutesResolved(self, routes):
        self.routes = routes
        for msgid, text, content_type in self.queue:
            self._sendMessage(msgid, text, content_type)
        self.queue = []

    @allocate_autorelease_pool
    @run_in_gui_thread
    def setRoutesFailed(self, msg):
        BlinkLogger().log_error(u"DNS Lookup failed: %s" % msg)
        self.chatViewController.showSystemMessage("Cannot send SMS message to %s\n%s" % (self.target_uri, msg))
        for msgid, text, content_type in self.queue:
            message = self.messages.pop(msgid)
            if content_type not in ('application/im-iscomposing+xml', 'message/cpim'):
                message.status='failed'
                self.add_to_history(message)
        self.queue = []

    def _sendMessage(self, msgid, text, content_type="text/plain"):

        utf8_encode = content_type not in ('application/im-iscomposing+xml', 'message/cpim')
        message_request = Message(FromHeader(self.account.uri, self.account.display_name), ToHeader(self.target_uri),
                                  RouteHeader(self.routes[0].get_uri()), content_type, text.encode('utf-8') if utf8_encode else text, credentials=self.account.credentials)
        self.notification_center.add_observer(self, sender=message_request)
        message_request.send(15 if content_type!="application/im-iscomposing+xml" else 5)

        id=str(message_request)
        if content_type != "application/im-iscomposing+xml":
            BlinkLogger().log_info(u"Sent %s SMS message to %s" % (content_type, self.target_uri))
            self.enableIsComposing = True
            message = self.messages.pop(msgid)
            message.status='sent'
        else:
            message = MessageInfo(id, content_type=content_type)

        self.messages[id] = message
        return message

    def lookup_destination(self, target_uri):
        assert isinstance(target_uri, SIPURI)

        lookup = DNSLookup()
        self.notification_center.add_observer(self, sender=lookup)
        settings = SIPSimpleSettings()

        if isinstance(self.account, Account) and self.account.sip.outbound_proxy is not None:
            uri = SIPURI(host=self.account.sip.outbound_proxy.host, port=self.account.sip.outbound_proxy.port, 
                         parameters={'transport': self.account.sip.outbound_proxy.transport})
            self.log_info(u"Starting DNS lookup for %s through proxy %s" % (target_uri.host, uri))
        elif isinstance(self.account, Account) and self.account.sip.always_use_my_proxy:
            uri = SIPURI(host=self.account.id.domain)
            self.log_info(u"Starting DNS lookup for %s via proxy of account %s" % (target_uri.host, self.account.id))
        else:
            uri = target_uri
            self.log_info(u"Starting DNS lookup for %s" % target_uri.host)
        lookup.lookup_sip_proxy(uri, settings.sip.transport_list)

    def sendMessage(self, text, content_type="text/plain"):
        self.lookup_destination(self.target_uri)

        timestamp = Timestamp(datetime.datetime.now(tzlocal()))
        hash = hashlib.sha1()
        hash.update(text.encode("utf-8")+str(timestamp))
        msgid = hash.hexdigest()
 
        if content_type != "application/im-iscomposing+xml":
            icon = NSApp.delegate().contactsWindowController.iconPathForSelf()
            self.chatViewController.showMessage(msgid, 'outgoing', None, icon, text, timestamp, state="sent")
        
            recipient=CPIMIdentity(self.target_uri, self.display_name)
            self.messages[msgid] = MessageInfo(msgid, sender=self.account, recipient=recipient, timestamp=timestamp, content_type=content_type, text=text, status="queued")

        self.queue.append((msgid, text, content_type))

    def textView_doCommandBySelector_(self, textView, selector):
        if selector == "insertNewline:" and self.chatViewController.inputText == textView:
            text = unicode(textView.string())
            textView.setString_("")
            textView.didChangeText()

            if text:
                self.sendMessage(text)
            self.chatViewController.resetTyping()

            recipient=CPIMIdentity(self.target_uri, self.display_name)
            self.notification_center.post_notification('ChatViewControllerDidDisplayMessage', sender=self, data=TimestampedNotificationData(direction='outgoing', history_entry=False, remote_party=format_identity_to_string(recipient), local_party=format_identity_to_string(self.account) if self.account is not BonjourAccount() else 'bonjour', check_contact=True))

            return True
        return False

    def textDidChange_(self, notif):
        chars_left = MAX_MESSAGE_LENGTH - self.chatViewController.inputText.textStorage().length()
        self.splitView.setText_("%i chars left" % chars_left)

    def getContentView(self):
        return self.chatViewController.view

    def chatView_becameIdle_(self, chatView, last_active):
        if self.enableIsComposing:
            content = IsComposingMessage(state=State("idle"), refresh=Refresh(60), last_active=LastActive(last_active or datetime.now()), content_type=ContentType('text')).toxml()
            self.sendMessage(content, IsComposingDocument.content_type)

    def chatView_becameActive_(self, chatView, last_active):
        if self.enableIsComposing:
            content = IsComposingMessage(state=State("active"), refresh=Refresh(60), last_active=LastActive(last_active or datetime.now()), content_type=ContentType('text')).toxml()
            self.sendMessage(content, IsComposingDocument.content_type)

    def chatViewDidLoad_(self, chatView):
         self.replay_history()

    @run_in_green_thread
    def replay_history(self):
        results = self.history.get_messages(local_uri=self.local_uri, remote_uri=self.remote_uri, media_type='sms', count=self.showHistoryEntries)
        messages = [row for row in reversed(results)]
        self.render_history_messages(messages)

    @allocate_autorelease_pool
    @run_in_gui_thread
    def render_history_messages(self, messages):
        for message in messages:
            if message.direction == 'outgoing':
                icon = NSApp.delegate().contactsWindowController.iconPathForSelf()
            else:
                sender_uri = sipuri_components_from_string(message.cpim_from)[0]
                icon = NSApp.delegate().contactsWindowController.iconPathForURI(sender_uri)

            timestamp=Timestamp.parse(message.cpim_timestamp)
            is_html = False if message.content_type == 'text' else True

            self.chatViewController.showMessage(message.msgid, message.direction, message.cpim_from, icon, message.body, timestamp, recipient=message.cpim_to, state=message.status, is_html=is_html, history_entry=True)

    def webviewFinishedLoading_(self, notification):
        self.document = self.outputView.mainFrameDocument()
        self.finishedLoading = True
        for script in self.messageQueue:
            self.outputView.stringByEvaluatingJavaScriptFromString_(script)
        self.messageQueue = []

        if hasattr(self.delegate, "chatViewDidLoad_"):
            self.delegate.chatViewDidLoad_(self)

    def webView_decidePolicyForNavigationAction_request_frame_decisionListener_(self, webView, info, request, frame, listener):
        # intercept link clicks so that they are opened in Safari
        theURL = info[WebActionOriginalURLKey]
        if theURL.scheme() == "file":
            listener.use()
        else:
            listener.ignore()
            NSWorkspace.sharedWorkspace().openURL_(theURL)
Ejemplo n.º 13
0
class HistoryViewer(NSWindowController):

    chatViewController = objc.IBOutlet()
    indexTable = objc.IBOutlet()
    contactTable = objc.IBOutlet()
    toolbar = objc.IBOutlet()

    entriesView = objc.IBOutlet()

    period = objc.IBOutlet()
    searchText = objc.IBOutlet()
    searchMedia = objc.IBOutlet()
    searchContactBox = objc.IBOutlet()

    paginationButton = objc.IBOutlet()
    foundMessagesLabel = objc.IBOutlet()
    queryDatabaseLabel = objc.IBOutlet()
    busyIndicator = objc.IBOutlet()

    contactMenu = objc.IBOutlet()

    # viewer sections
    contacts = []
    dayly_entries = NSMutableArray.array()
    messages = []

    # database handler
    history = None

    # search filters
    start = 0
    search_text = None
    search_uris = None
    search_local = None
    search_media = None
    after_date = None
    before_date = None
    refresh_contacts_counter = 1
    contact_cache = {}
    display_name_cache = {}
    refresh_in_progress = False

    daily_order_fields = {'date': 'DESC', 'local_uri': 'ASC', 'remote_uri': 'ASC'}
    media_type_array = {0: None, 1: ('audio', 'video'), 2: ('chat', 'sms'), 3: 'file-transfer', 4: 'audio-recording', 5: 'availability', 6: 'voicemail', 7: 'video-recording'}
    period_array = {0: None,
                    1: datetime.datetime.now()-datetime.timedelta(days=1),
                    2: datetime.datetime.now()-datetime.timedelta(days=7),
                    3: datetime.datetime.now()-datetime.timedelta(days=31),
                    4: datetime.datetime.now()-datetime.timedelta(days=90),
                    5: datetime.datetime.now()-datetime.timedelta(days=180),
                    6: datetime.datetime.now()-datetime.timedelta(days=365),
                    -1: datetime.datetime.now()-datetime.timedelta(days=1),
                    -2: datetime.datetime.now()-datetime.timedelta(days=7),
                    -3: datetime.datetime.now()-datetime.timedelta(days=31),
                    -4: datetime.datetime.now()-datetime.timedelta(days=90),
                    -5: datetime.datetime.now()-datetime.timedelta(days=180),
                    -6: datetime.datetime.now()-datetime.timedelta(days=365)
                    }

    @objc.python_method
    def format_media_type(self, media_type):
        if media_type == 'sms':
            media_type_formated = NSLocalizedString("Short Messages", "Label")
        elif media_type == 'chat':
            media_type_formated = NSLocalizedString("Chat Sessions", "Label")
        elif media_type == 'audio':
            media_type_formated = NSLocalizedString("Audio Calls", "Label")
        elif media_type == 'file-transfer':
            media_type_formated = NSLocalizedString("File Transfers", "Label")
        elif media_type == 'availability':
            media_type_formated = NSLocalizedString("Availability", "Label")
        elif media_type == 'missed-call':
            media_type_formated = NSLocalizedString("Missed Call", "Label")
        elif media_type == 'voicemail':
            media_type_formated = NSLocalizedString("Voicemail", "Label")
        else:
            media_type_formated = media_type.title()

        return media_type_formated

    def __new__(cls, *args, **kwargs):
        return cls.alloc().init()

    def __init__(self):
        if self:
            BlinkLogger().log_debug('Starting History Viewer')
            NSBundle.loadNibNamed_owner_("HistoryViewer", self)

            self.all_contacts = BlinkHistoryViewerContact('Any Address', name='All Contacts')
            self.bonjour_contact = BlinkHistoryViewerContact('bonjour.local', name='Bonjour Neighbours', icon=NSImage.imageNamed_("NSBonjour"))

            self.notification_center = NotificationCenter()
            self.notification_center.add_observer(self, name='ChatViewControllerDidDisplayMessage')
            self.notification_center.add_observer(self, name='AudioCallLoggedToHistory')
            self.notification_center.add_observer(self, name='BlinkContactsHaveChanged')
            self.notification_center.add_observer(self, name='BlinkTableViewSelectionChaged')
            self.notification_center.add_observer(self, name='BlinkConferenceContactPresenceHasChanged')
            self.notification_center.add_observer(self, name='BlinkShouldTerminate')

            self.searchText.cell().setSendsSearchStringImmediately_(True)
            self.searchText.cell().setPlaceholderString_(NSLocalizedString("Type text and press Enter", "Placeholder text"))

            self.chatViewController.setContentFile_(NSBundle.mainBundle().pathForResource_ofType_("ChatView", "html"))
            self.chatViewController.setHandleScrolling_(False)
            self.entriesView.setShouldCloseWithWindow_(False)

            for c in ('remote_uri', 'local_uri', 'date', 'type'):
                col = self.indexTable.tableColumnWithIdentifier_(c)
                descriptor = NSSortDescriptor.alloc().initWithKey_ascending_(c, True)
                col.setSortDescriptorPrototype_(descriptor)

            self.chat_history = ChatHistory()
            self.session_history = SessionHistory()
            self.setPeriod(1)

            self.selectedTableView = self.contactTable

    @objc.python_method
    def setPeriod(self, days):
        if days <= -365:
            tag = -6
        elif days <= -180:
            tag = -5
        elif days <= -90:
            tag = -4
        elif days <= -31:
            tag = -3
        elif days <= -7:
            tag = -2
        elif days <= -1:
            tag = -1
        elif days <= 1:
            tag = 1
        elif days <= 7:
            tag = 2
        elif days <= 31:
            tag = 3
        elif days <= 90:
            tag = 4
        elif days <= 180:
            tag = 5
        elif days <= 365:
            tag = 6
        else:
            tag = 0

        if tag == 0:
            self.before_date = None
            self.after_date = None
        elif tag < 0:
            try:
                date = self.period_array[tag]
            except KeyError:
                date = None

            self.before_date = date
            self.after_date = None
        else:
            try:
                date = self.period_array[tag]
            except KeyError:
                date = None

            self.after_date = date
            self.before_date = None

        self.period.selectItemWithTag_(tag)

    def awakeFromNib(self):
        NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(self, "contactSelectionChanged:", NSTableViewSelectionDidChangeNotification, self.contactTable)

        timer = NSTimer.timerWithTimeInterval_target_selector_userInfo_repeats_(1, self, "refreshContactsTimer:", None, True)
        NSRunLoop.currentRunLoop().addTimer_forMode_(timer, NSModalPanelRunLoopMode)
        NSRunLoop.currentRunLoop().addTimer_forMode_(timer, NSDefaultRunLoopMode)

        self.contactTable.setDoubleAction_("doubleClick:")

    def close_(self, sender):
        self.window().close()

    @objc.python_method
    @run_in_green_thread
    def delete_messages(self, local_uri=None, remote_uri=None, date=None, after_date=None, before_date=None, media_type=None):
        block_on(self.chat_history.delete_messages(local_uri=local_uri, remote_uri=remote_uri, date=date, after_date=after_date, before_date=before_date, media_type=media_type))
        block_on(self.session_history.delete_entries(local_uri=local_uri, remote_uri=remote_uri, after_date=after_date, before_date=before_date))
        self.search_text = None
        self.search_uris = None
        self.search_local = None
        self.refreshViewer()

    @objc.python_method
    def refreshViewer(self):
        self.refreshContacts()
        self.refreshDailyEntries()
        self.refreshMessages()

    @objc.python_method
    @run_in_green_thread
    def refreshContacts(self):
        if self.refresh_in_progress:
            return

        self.refresh_in_progress = True
        self.refresh_contacts_counter = 0
        if self.chat_history:
            self.updateBusyIndicator(True)
            remote_uri = self.search_uris if self.search_uris else None
            media_type = self.search_media if self.search_media else None
            search_text = self.search_text if self.search_text else None
            after_date = self.after_date if self.after_date else None
            before_date = self.before_date if self.before_date else None
            results = self.chat_history.get_contacts(remote_uri=remote_uri, media_type=media_type, search_text=search_text, after_date=after_date, before_date=before_date)
            self.renderContacts(results)
            self.updateBusyIndicator(False)

    @objc.python_method
    @run_in_gui_thread
    def renderContacts(self, results):
        index = 0
        found_uris = []
        uris_without_display_name = []

        for item in self.contacts:
            item.destroy()

        getFirstContactMatchingURI = NSApp.delegate().contactsWindowController.getFirstContactMatchingURI

        self.contacts = [self.all_contacts, self.bonjour_contact]

        if self.search_uris:
            for uri in self.search_uris:
                try:
                    found_contact = self.contact_cache[uri]
                except KeyError:
                    found_contact = getFirstContactMatchingURI(uri, exact_match=True)
                    self.contact_cache[uri] = found_contact

                if found_contact:
                    contact_exist = False
                    for contact_uri in found_contact.uris:
                        if contact_uri.uri in found_uris:
                            contact_exist = True
                            break
                    if contact_exist:
                        continue
                    contact = BlinkHistoryViewerContact(found_contact.uri, name=found_contact.name, icon=found_contact.icon)
                    for contact_uri in found_contact.uris:
                        found_uris.append(contact_uri.uri)
                    self.contacts.append(contact)
                    if isinstance(found_contact, BlinkPresenceContact):
                        contact.setPresenceContact_(found_contact)
                else:
                    if uri in found_uris:
                        continue
                    found_uris.append(uri)
                    contact = BlinkHistoryViewerContact(str(uri), name=str(uri))

                try:
                    index = self.contacts.index(contact)
                except ValueError:
                    pass

        if results:
            for row in results:
                try:
                    found_contact = self.contact_cache[row[0]]
                except KeyError:
                    found_contact = getFirstContactMatchingURI(row[0], exact_match=True)
                    self.contact_cache[row[0]] = found_contact
                if found_contact:
                    contact_exist = False
                    for contact_uri in found_contact.uris:
                        if contact_uri.uri in found_uris:
                            contact_exist = True
                            break
                    if contact_exist:
                        continue
                    contact = BlinkHistoryViewerContact(found_contact.uri, name=found_contact.name, icon=found_contact.icon, presence_contact=found_contact if isinstance(found_contact, BlinkPresenceContact) else None)
                    for contact_uri in found_contact.uris:
                        found_uris.append(contact_uri.uri)
                else:
                    if row[0] in found_uris:
                        continue
                    found_uris.append(row[0])
                    try:
                        display_name = self.display_name_cache[row[0]]
                    except KeyError:
                        display_name = str(row[0])
                        uris_without_display_name.append(row[0])
                    contact = BlinkHistoryViewerContact(str(row[0]), name=display_name)

                self.contacts.append(contact)
    
        self.update_display_names(uris_without_display_name)
        self.contactTable.reloadData()

        self.contactTable.selectRowIndexes_byExtendingSelection_(NSIndexSet.indexSetWithIndex_(index), False)
        self.contactTable.scrollRowToVisible_(index)

        self.updateContactsColumnHeader()
        self.refresh_in_progress = False

    @objc.python_method
    @run_in_green_thread
    def update_display_names(self, uris_without_display_name):
        results = self.session_history.get_display_names(uris_without_display_name)
        self.updateDisplayNames(results)

    @objc.python_method
    @run_in_gui_thread
    def updateDisplayNames(self, results):
        must_reload = False
        for result in results:
            self.display_name_cache[result[0]]=result[1]
            for contact in self.contacts:
                if contact.uri == result[0] and contact.name != result[1]:
                    contact.name = result[1]
                    must_reload = True
        if must_reload:
            self.contactTable.reloadData()

        must_reload = False
        for entry in self.dayly_entries:
            if entry['remote_uri_sql'] == entry['remote_uri']:
                try:
                    display_name = self.display_name_cache[entry['remote_uri_sql']]
                except KeyError:
                    pass
                else:
                    entry['display_name'] = display_name
                    entry['remote_uri'] = '%s <%s>' % (display_name, entry['remote_uri_sql']) if '@' in entry['remote_uri_sql'] else display_name
                    must_reload = True

        self.dayly_entries.sortUsingDescriptors_(self.indexTable.sortDescriptors())
        self.indexTable.reloadData()

    @objc.python_method
    @run_in_green_thread
    def refreshDailyEntries(self, order_text=None):
        if self.chat_history:
            self.resetDailyEntries()
            self.updateBusyIndicator(True)
            search_text = self.search_text if self.search_text else None
            remote_uri = self.search_uris if self.search_uris else None
            local_uri = self.search_local if self.search_local else None
            media_type = self.search_media if self.search_media else None
            after_date = self.after_date if self.after_date else None
            before_date = self.before_date if self.before_date else None
            results = self.chat_history.get_daily_entries(local_uri=local_uri, remote_uri=remote_uri, media_type=media_type, search_text=search_text, order_text=order_text, after_date=after_date, before_date=before_date)
            self.renderDailyEntries(results)
            self.updateBusyIndicator(False)

    @objc.python_method
    @run_in_gui_thread
    def resetDailyEntries(self):
        self.dayly_entries = NSMutableArray.array()
        self.indexTable.reloadData()

    @objc.python_method
    @run_in_gui_thread
    def renderDailyEntries(self, results):
        getFirstContactMatchingURI = NSApp.delegate().contactsWindowController.getFirstContactMatchingURI
        self.dayly_entries = NSMutableArray.array()
        for result in results:
            display_name = None
            try:
                found_contact = self.contact_cache[result[2]]
            except KeyError:
                found_contact = getFirstContactMatchingURI(result[2], exact_match=True)
                self.contact_cache[result[2]] = found_contact

            if found_contact:
                display_name = found_contact.name
                remote_uri = '%s <%s>' % (display_name, result[2]) if '@' in result[2] else display_name
            else:
                try:
                    display_name = self.display_name_cache[result[2]]
                except KeyError:
                    remote_uri = result[2]
                else:
                    remote_uri = '%s <%s>' % (display_name, result[2]) if '@' in result[2] else display_name

            entry = NSMutableDictionary.dictionaryWithObjectsAndKeys_(result[1], "local_uri", remote_uri, "remote_uri", result[2], "remote_uri_sql", result[0], 'date', result[3], 'type', display_name, "display_name")
            self.dayly_entries.addObject_(entry)

        self.dayly_entries.sortUsingDescriptors_(self.indexTable.sortDescriptors())
        self.indexTable.reloadData()

        if self.search_uris and not self.dayly_entries:
            self.contactTable.deselectAll_(True)

    @objc.python_method
    @run_in_green_thread
    def refreshMessages(self, count=SQL_LIMIT, remote_uri=None, local_uri=None, media_type=None, date=None, after_date=None, before_date=None):
        if self.chat_history:
            self.updateBusyIndicator(True)
            search_text = self.search_text if self.search_text else None
            if not remote_uri:
                remote_uri = self.search_uris if self.search_uris else None
            if not local_uri:
                local_uri = self.search_local if self.search_local else None
            if not media_type:
                media_type = self.search_media if self.search_media else None
            if not after_date:
                after_date = self.after_date if self.after_date else None
            if not before_date:
                before_date = self.before_date if self.before_date else None
            results = self.chat_history.get_messages(count=count, local_uri=local_uri, remote_uri=remote_uri, media_type=media_type, date=date, search_text=search_text, after_date=after_date, before_date=before_date)
            self.renderMessages(results)
            self.updateBusyIndicator(False)

    @objc.python_method
    @run_in_gui_thread
    def renderMessages(self, messages=None):
        self.chatViewController.clear()
        self.chatViewController.resetRenderedMessages()
        self.chatViewController.last_sender = None

        if messages is not None:
            # new message list. cache for pagination and reset pagination to the last page
            self.messages = list(reversed(messages))
            message_count = len(messages)
            start = message_count // MAX_MESSAGES_PER_PAGE * MAX_MESSAGES_PER_PAGE
            end = message_count
        else:
            message_count = len(self.messages)
            start = self.start
            end = min(start + MAX_MESSAGES_PER_PAGE, message_count)

        for row in self.messages[start:end]:
            self.renderMessage(row)

        self.paginationButton.setEnabled_forSegment_(start > MAX_MESSAGES_PER_PAGE, 0)
        self.paginationButton.setEnabled_forSegment_(start > 0, 1)
        self.paginationButton.setEnabled_forSegment_(start + MAX_MESSAGES_PER_PAGE + 1 < message_count, 2)
        self.paginationButton.setEnabled_forSegment_(start + MAX_MESSAGES_PER_PAGE * 2 < message_count, 3)

        if message_count == 0:
            text = NSLocalizedString("No entry found", "Label")
        elif message_count == 1:
            text = NSLocalizedString("Displaying 1 entry", "Label")
        elif message_count < MAX_MESSAGES_PER_PAGE:
            text = NSLocalizedString("Displaying {} entries".format(end), "Label")
        else:
            text = NSLocalizedString("Displaying {} to {} out of {} entries", "Label").format(start+1, end, message_count)

        self.foundMessagesLabel.setStringValue_(text)

    @objc.python_method
    @run_in_gui_thread
    def renderMessage(self, message):
        if message.direction == 'outgoing':
            icon = NSApp.delegate().contactsWindowController.iconPathForSelf()
        else:
            sender_uri = sipuri_components_from_string(message.cpim_from)[0]
            # TODO: How to render the icons from Address Book? Especially in sandbox mode we do not have access to other folders
            icon = NSApp.delegate().contactsWindowController.iconPathForURI(sender_uri)
        try:
            timestamp=ISOTimestamp(message.cpim_timestamp)
        except Exception:
            pass
        else:
            is_html = False if message.content_type == 'text' else True
            private = True if message.private == "1" else False
            self.chatViewController.showMessage(message.sip_callid, message.msgid, message.direction, message.cpim_from, icon, message.body, timestamp, is_private=private, recipient=message.cpim_to, state=message.status, is_html=is_html, history_entry=True, media_type=message.media_type, encryption=message.encryption if message.media_type == 'chat' else None)

    @objc.IBAction
    def paginateResults_(self, sender):
        if sender.selectedSegment() == 0:
           self.start = 0
        elif sender.selectedSegment() == 1:
           next_start = self.start - MAX_MESSAGES_PER_PAGE
           self.start = next_start if next_start >= 0 else self.start
        elif sender.selectedSegment() == 2:
           next_start = self.start + MAX_MESSAGES_PER_PAGE
           self.start = next_start if next_start < len(self.messages)-1 else self.start
        elif sender.selectedSegment() == 3:
           self.start = len(self.messages) - len(self.messages)%MAX_MESSAGES_PER_PAGE if len(self.messages) > MAX_MESSAGES_PER_PAGE else 0
        self.renderMessages()

    def tableView_deleteRow_(self, table, row):
        pass

    def tableView_sortDescriptorsDidChange_(self, table, odescr):
        self.dayly_entries.sortUsingDescriptors_(self.indexTable.sortDescriptors())
        self.indexTable.reloadData()

    @objc.IBAction
    def printDocument_(self, sender):
        printInfo = NSPrintInfo.sharedPrintInfo()
        printInfo.setTopMargin_(30)
        printInfo.setBottomMargin_(30)
        printInfo.setLeftMargin_(10)
        printInfo.setRightMargin_(10)
        printInfo.setOrientation_(NSPortraitOrientation)
        printInfo.setHorizontallyCentered_(True)
        printInfo.setVerticallyCentered_(False)
        printInfo.setHorizontalPagination_(NSFitPagination)
        printInfo.setVerticalPagination_(NSFitPagination)
        NSPrintInfo.setSharedPrintInfo_(printInfo)

        # print the content of the web view
        self.entriesView.mainFrame().frameView().documentView().print_(self)

    @objc.IBAction
    def search_(self, sender):
        if self.chat_history:
            self.search_text = str(sender.stringValue()).lower()
            self.refreshContacts()
            row = self.indexTable.selectedRow()
            if row > 0:
                self.refreshDailyEntries()
                self.refreshMessages(local_uri=self.dayly_entries[row].objectForKey_("local_uri"), date=self.dayly_entries[row].objectForKey_("date"), media_type=self.dayly_entries[row].objectForKey_("type"))
            else:
                row = self.contactTable.selectedRow()
                if row > 0:
                    self.refreshMessages()
                    self.refreshDailyEntries()
                else:
                    self.refreshDailyEntries()
                    self.refreshMessages()

    @objc.IBAction
    def searchContacts_(self, sender):
        text = str(self.searchContactBox.stringValue().strip())
        contacts = [contact for contact in self.contacts[2:] if text in contact] if text else self.contacts[2:]
        self.contacts = [self.all_contacts, self.bonjour_contact] + contacts
        self.contactTable.reloadData()
        self.contactTable.selectRowIndexes_byExtendingSelection_(NSIndexSet.indexSetWithIndex_(0), False)
        self.contactTable.scrollRowToVisible_(0)
        self.updateContactsColumnHeader()

        if not text:
            self.refreshContacts()

    @objc.python_method
    def updateContactsColumnHeader(self):
        found_contacts = len(self.contacts)-2
        if found_contacts == 1:
            title = NSLocalizedString("1 contact found", "Label")
        elif found_contacts > 1:
            title = NSLocalizedString("%d contacts found", "Label") % found_contacts
        else:
            title = NSLocalizedString("Contacts", "Label")

        self.contactTable.tableColumnWithIdentifier_('contacts').headerCell().setStringValue_(title)

    def tableViewSelectionDidChange_(self, notification):
        if self.chat_history:
            if notification.object() == self.contactTable:
                row = self.contactTable.selectedRow()
                if row < 0:
                    return
                elif row == 0:
                    self.search_local = None
                    self.search_uris = None
                    self.searchContactBox.setStringValue_('')
                    self.refreshContacts()
                elif row == 1:
                    self.search_local = 'bonjour.local'
                    self.search_uris = None
                elif row > 1:
                    self.search_local = None
                    if self.contacts[row].presence_contact is not None:
                        self.search_uris = list(str(contact_uri.uri) for contact_uri in self.contacts[row].presence_contact.uris)
                        self.chatViewController.expandSmileys = not self.contacts[row].presence_contact.contact.disable_smileys
                        self.chatViewController.toggleSmileys(self.chatViewController.expandSmileys)
                        try:
                            item = next((item for item in self.toolbar.visibleItems() if item.itemIdentifier() == 'smileys'))
                        except StopIteration:
                            pass
                        else:
                            item.setImage_(NSImage.imageNamed_("smiley_on" if self.chatViewController.expandSmileys else "smiley_off"))
                    else:
                        self.search_uris = (self.contacts[row].uri,)
                self.refreshDailyEntries()
                self.refreshMessages()
            else:
                row = self.indexTable.selectedRow()
                if row >= 0:
                    self.refreshMessages(remote_uri=(self.dayly_entries[row].objectForKey_("remote_uri_sql"),), local_uri=self.dayly_entries[row].objectForKey_("local_uri"), date=self.dayly_entries[row].objectForKey_("date"), media_type=self.dayly_entries[row].objectForKey_("type"))

    def numberOfRowsInTableView_(self, table):
        if table == self.indexTable:
            return self.dayly_entries.count()
        elif table == self.contactTable:
            return len(self.contacts)
        return 0

    def tableView_objectValueForTableColumn_row_(self, table, column, row):
        if table == self.indexTable:
            ident = column.identifier()
            if ident == 'type':
                return self.format_media_type(self.dayly_entries[row].objectForKey_(ident))

            try:
                return str(self.dayly_entries[row].objectForKey_(ident))
            except IndexError:
                return None
        elif table == self.contactTable:
            try:
                if type(self.contacts[row]) in (str, str):
                    return self.contacts[row]
                else:
                    return self.contacts[row].name
            except IndexError:
                return None
        return None

    def tableView_willDisplayCell_forTableColumn_row_(self, table, cell, tableColumn, row):
        if table == self.contactTable:
            try:
                if row < len(self.contacts):
                    if type(self.contacts[row]) in (str, str):
                        cell.setContact_(None)
                    else:
                        cell.setContact_(self.contacts[row])
            except:
                pass

    def showWindow_(self, sender):
        self.window().makeKeyAndOrderFront_(None)

    @objc.python_method
    @run_in_gui_thread
    def filterByURIs(self, uris=(), media_type=None):

        self.search_text = None
        self.search_local = None
        if media_type != self.search_media:
            for tag in list(self.media_type_array.keys()):
                if self.media_type_array[tag] == media_type:
                    self.searchMedia.selectItemAtIndex_(tag)

        self.search_media = media_type
        self.search_uris = uris
        self.refreshContacts()
        self.refreshDailyEntries()
        self.refreshMessages()

    @objc.IBAction
    def filterByMediaChanged_(self, sender):
        tag = sender.selectedItem().tag()
        self.search_media = self.media_type_array[tag]
        self.refreshContacts()
        self.refreshDailyEntries()
        self.refreshMessages()

    @objc.IBAction
    def filterByPeriodChanged_(self, sender):
        tag = sender.selectedItem().tag()

        if tag == 0:
            self.before_date = None
            self.after_date = None
        elif tag < 0:
            try:
                date = self.period_array[tag]
            except KeyError:
                date = None

            self.before_date = date
            self.after_date = None
        else:
            try:
                date = self.period_array[tag]
            except KeyError:
                date = None

            self.after_date = date
            self.before_date = None

        self.refreshContacts()
        self.refreshDailyEntries()
        self.refreshMessages()

    def validateToolbarItem_(self, item):
        if item.itemIdentifier() == NSToolbarPrintItemIdentifier and not self.messages:
            return False
        return True

    def toolbarWillAddItem_(self, notification):
        item = notification.userInfo()["item"]
        if item.itemIdentifier() == NSToolbarPrintItemIdentifier:
            item.setToolTip_("Print Current Entries")
            item.setTarget_(self)
            item.setAutovalidates_(True)

    @objc.IBAction
    def userClickedToolbarItem_(self, sender):
        if sender.itemIdentifier() == 'smileys':
            self.chatViewController.expandSmileys = not self.chatViewController.expandSmileys
            sender.setImage_(NSImage.imageNamed_("smiley_on" if self.chatViewController.expandSmileys else "smiley_off"))
            self.chatViewController.toggleSmileys(self.chatViewController.expandSmileys)

            row = self.contactTable.selectedRow()
            if row and row > 1 and self.contacts[row].presence_contact is not None:
                self.contacts[row].presence_contact.contact.disable_smileys = not self.contacts[row].presence_contact.contact.disable_smileys
                self.contacts[row].presence_contact.contact.save()

        elif sender.itemIdentifier() == 'delete':
            if self.selectedTableView == self.contactTable:
                try:
                    row = self.contactTable.selectedRow()
                    self.showDeleteConfirmationDialog(row)
                except IndexError:
                    pass

            elif self.selectedTableView == self.indexTable:
                try:
                    row = self.indexTable.selectedRow()
                    local_uri = self.dayly_entries[row].objectForKey_("local_uri")
                    remote_uri = self.dayly_entries[row].objectForKey_("remote_uri")
                    remote_uri_sql = self.dayly_entries[row].objectForKey_("remote_uri_sql")
                    date = self.dayly_entries[row].objectForKey_("date")
                    media_type = self.dayly_entries[row].objectForKey_("type")

                    label = NSLocalizedString("Please confirm the deletion of %s history entries", "Label") % media_type + NSLocalizedString(" from %s", "SIP Address label") % remote_uri + NSLocalizedString(" on %s. ", "Date label") % date + NSLocalizedString("This operation cannot be undone. ", "Label")
                    ret = NSRunAlertPanel(NSLocalizedString("Purge History Entries", "Window title"), label, NSLocalizedString("Confirm", "Button title"), NSLocalizedString("Cancel", "Button title"), None)
                    if ret == NSAlertDefaultReturn:
                        self.delete_messages(local_uri=local_uri, remote_uri=remote_uri_sql, media_type=media_type, date=date)
                except IndexError:
                    pass

    @objc.python_method
    def showDeleteConfirmationDialog(self, row):
        media_print = self.search_media or NSLocalizedString("all", "Label")
        tag = self.period.selectedItem().tag()

        period = '%s %s' % (NSLocalizedString(" newer than", "Date label") if tag < 4 else NSLocalizedString(" older than", "Date label"), self.period_array[tag].strftime("%Y-%m-%d")) if tag else ''

        if row == 0:
            label = NSLocalizedString("Please confirm the deletion of %s history entries", "Label") % media_print + period + ". "+ NSLocalizedString("This operation cannot be undone. ", "Label")
            ret = NSRunAlertPanel(NSLocalizedString("Purge History Entries", "Window title"), label, NSLocalizedString("Confirm", "Button title"), NSLocalizedString("Cancel", "Button title"), None)
            if ret == NSAlertDefaultReturn:
                self.delete_messages(media_type=self.search_media, after_date=self.after_date, before_date=self.before_date)
        elif row == 1:
            remote_uri=self.contacts[row].uri
            label = NSLocalizedString("Please confirm the deletion of %s Bonjour history entries", "Label") % media_print + period + ". "+ NSLocalizedString("This operation cannot be undone. ", "Label")
            ret = NSRunAlertPanel(NSLocalizedString("Purge History Entries", "Window title"), label, NSLocalizedString("Confirm", "Button title"), NSLocalizedString("Cancel", "Button title"), None)
            if ret == NSAlertDefaultReturn:
                self.delete_messages(local_uri='bonjour.local', media_type=self.search_media, after_date=self.after_date, before_date=self.before_date)
        else:
            contact = self.contacts[row]
            if contact.presence_contact is not None:
                remote_uri = list(str(contact.uri) for contact in contact.presence_contact.uris)
            else:
                remote_uri = contact.uri
            label = NSLocalizedString("Please confirm the deletion of %s history entries", "Label") % media_print + NSLocalizedString(" from ", "Label") + contact.name + period + ". "+ NSLocalizedString("This operation cannot be undone. ", "Label")
            ret = NSRunAlertPanel(NSLocalizedString("Purge History Entries", "Window title"), label, NSLocalizedString("Confirm", "Button title"), NSLocalizedString("Cancel", "Button title"), None)
            if ret == NSAlertDefaultReturn:
                self.delete_messages(remote_uri=remote_uri, media_type=self.search_media, after_date=self.after_date, before_date=self.before_date)

    @objc.python_method
    @run_in_gui_thread
    def updateBusyIndicator(self, busy=False):
        if busy:
            self.queryDatabaseLabel.setHidden_(False)
            self.busyIndicator.setHidden_(False)
            self.busyIndicator.setIndeterminate_(True)
            self.busyIndicator.setStyle_(NSProgressIndicatorSpinningStyle)
            self.busyIndicator.startAnimation_(None)
        else:
            self.busyIndicator.stopAnimation_(None)
            self.busyIndicator.setHidden_(True)
            self.queryDatabaseLabel.setHidden_(True)

    @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_BlinkShouldTerminate(self, notification):
        if self.window():
            self.window().orderOut_(self)

    @objc.python_method
    def _NH_ChatViewControllerDidDisplayMessage(self, notification):
        if notification.data.local_party != 'bonjour.local':
            exists = any(contact for contact in self.contacts if notification.data.remote_party == contact.uri)
            if not exists:
                self.refreshContacts()

    @objc.python_method
    def _NH_AudioCallLoggedToHistory(self, notification):
        if notification.data.local_party != 'bonjour.local':
            exists = any(contact for contact in self.contacts if notification.data.remote_party == contact.uri)
            if not exists:
                self.refreshContacts()

    @objc.python_method
    def _NH_BlinkContactsHaveChanged(self, notification):
        self.refresh_contacts_counter += 1

    def refreshContactsTimer_(self, timer):
        if self.refresh_contacts_counter:
            self.refreshContacts()
            self.toolbar.validateVisibleItems()

    @objc.python_method
    def _NH_BlinkTableViewSelectionChaged(self, notification):
        self.selectedTableView = notification.sender
        self.toolbar.validateVisibleItems()

    @objc.python_method
    def _NH_BlinkConferenceContactPresenceHasChanged(self, notification):
        try:
            contact = next((contact for contact in self.contacts[2:] if contact == notification.sender))
        except StopIteration:
            return
        else:
            try:
                idx = self.contacts.index(contact)
                self.contactTable.reloadDataForRowIndexes_columnIndexes_(NSIndexSet.indexSetWithIndex_(idx), NSIndexSet.indexSetWithIndex_(0))
            except ValueError:
                pass

    def contactSelectionChanged_(self, notification):
        pass

    def menuWillOpen_(self, menu):
        if menu == self.contactMenu:
            self.contactMenu.itemWithTag_(2).setEnabled_(False)
            self.contactMenu.itemWithTag_(3).setEnabled_(False)
            self.contactMenu.itemWithTag_(4).setEnabled_(False)
            try:
                row = self.contactTable.selectedRow()
            except:
                return

            if row < 2:
                return

            try:
                contact = self.contacts[row]
            except IndexError:
                return

            contact_exists = bool(contact.presence_contact is not None)
            if '@' in contact.uri:
                self.contactMenu.itemWithTag_(2).setEnabled_(not is_anonymous(contact.uri))
                self.contactMenu.itemWithTag_(3).setEnabled_(not contact_exists and not is_anonymous(contact.uri))
                self.contactMenu.itemWithTag_(4).setEnabled_(contact_exists)
            else:
                bonjour_contact = NSApp.delegate().contactsWindowController.model.getBonjourContactMatchingDeviceId(contact.uri)
                self.contactMenu.itemWithTag_(2).setEnabled_(bool(bonjour_contact))
                self.contactMenu.itemWithTag_(3).setEnabled_(False)
                self.contactMenu.itemWithTag_(4).setEnabled_(False)


    @objc.IBAction
    def doubleClick_(self, sender):
        row = self.contactTable.selectedRow()

        if row < 2:
            return

        try:
            contact = self.contacts[row]
        except IndexError:
            return

        if '@' in contact.uri:
            NSApp.delegate().contactsWindowController.startSessionWithTarget(contact.uri)
        else:
            bonjour_contact = NSApp.delegate().contactsWindowController.model.getBonjourContactMatchingDeviceId(contact.uri)
            if not bonjour_contact:
                BlinkLogger().log_info("Bonjour neighbour %s was not found on this network" % contact.name)
                message = NSLocalizedString("Bonjour neighbour %s was not found on this network. ", "label") % contact.name
                NSRunAlertPanel(NSLocalizedString("Error", "Window title"), message, NSLocalizedString("OK", "Button title"), None, None)
                return
            NSApp.delegate().contactsWindowController.startSessionWithTarget(bonjour_contact.uri)

    @objc.IBAction
    def userClickedContactMenu_(self, sender):
        row = self.contactTable.selectedRow()

        try:
            contact = self.contacts[row]
        except IndexError:
            return

        tag = sender.tag()

        if tag == 1:
            self.showDeleteConfirmationDialog(row)
        elif tag == 2:
            NSApp.delegate().contactsWindowController.searchBox.setStringValue_(contact.uri)
            NSApp.delegate().contactsWindowController.searchContacts()
            NSApp.delegate().contactsWindowController.window().makeFirstResponder_(NSApp.delegate().contactsWindowController.searchBox)
            NSApp.delegate().contactsWindowController.window().deminiaturize_(sender)
            NSApp.delegate().contactsWindowController.window().makeKeyWindow()
        elif tag == 3:
            NSApp.delegate().contactsWindowController.addContact(uris=[(contact.uri, 'sip')], name=contact.name)
        elif tag == 4 and contact.presence_contact is not None:
            NSApp.delegate().contactsWindowController.model.editContact(contact.presence_contact)

    @objc.IBAction
    def userClickedActionsButton_(self, sender):
        point = sender.window().convertScreenToBase_(NSEvent.mouseLocation())
        event = NSEvent.mouseEventWithType_location_modifierFlags_timestamp_windowNumber_context_eventNumber_clickCount_pressure_(
                    NSLeftMouseUp, point, 0, NSDate.timeIntervalSinceReferenceDate(), sender.window().windowNumber(),
                    sender.window().graphicsContext(), 0, 1, 0)
        NSMenu.popUpContextMenu_withEvent_forView_(self.contactMenu, event, sender)
Ejemplo n.º 14
0
class SMSViewController(NSObject):

    chatViewController = objc.IBOutlet()
    splitView = objc.IBOutlet()
    smileyButton = objc.IBOutlet()
    outputContainer = objc.IBOutlet()
    addContactView = objc.IBOutlet()
    addContactLabel = objc.IBOutlet()
    zoom_period_label = ''

    showHistoryEntries = 50
    remoteTypingTimer = None
    enableIsComposing = False
    handle_scrolling = True
    scrollingTimer = None
    scrolling_back = False
    message_count_from_history = 0

    contact = None

    account = None
    target_uri = None
    routes = None
    queue = None
    queued_serial = 0

    windowController = None
    last_route = None

    def initWithAccount_target_name_(self, account, target, display_name):
        self = objc.super(SMSViewController, self).init()
        if self:
            self.session_id = str(uuid.uuid1())

            self.notification_center = NotificationCenter()
            self.account = account
            self.target_uri = target
            self.display_name = display_name
            self.messages = {}

            self.encryption = OTREncryption(self)

            self.message_queue = EventQueue(self._send_message)

            self.history = ChatHistory()

            self.local_uri = '%s@%s' % (account.id.username, account.id.domain)
            self.remote_uri = '%s@%s' % (self.target_uri.user.decode(),
                                         self.target_uri.host.decode())
            self.contact = NSApp.delegate(
            ).contactsWindowController.getFirstContactFromAllContactsGroupMatchingURI(
                self.remote_uri)

            NSBundle.loadNibNamed_owner_("SMSView", self)

            self.chatViewController.setContentFile_(
                NSBundle.mainBundle().pathForResource_ofType_(
                    "ChatView", "html"))
            self.chatViewController.setAccount_(self.account)
            self.chatViewController.resetRenderedMessages()

            self.chatViewController.inputText.unregisterDraggedTypes()
            self.chatViewController.inputText.setMaxLength_(MAX_MESSAGE_LENGTH)
            self.splitView.setText_(
                NSLocalizedString("%i chars left", "Label") %
                MAX_MESSAGE_LENGTH)

            self.enableIsComposing = True

            self.log_info('Using local account %s' % self.local_uri)

            self.notification_center.add_observer(
                self, name='ChatStreamOTREncryptionStateChanged')
            self.started = False

        return self

    def dealloc(self):
        if self.remoteTypingTimer:
            self.remoteTypingTimer.invalidate()
        self.chatViewController.close()
        objc.super(SMSViewController, self).dealloc()

    def awakeFromNib(self):
        # setup smiley popup
        smileys = SmileyManager().get_smiley_list()

        menu = self.smileyButton.menu()
        while menu.numberOfItems() > 0:
            menu.removeItemAtIndex_(0)

        bigText = NSAttributedString.alloc().initWithString_attributes_(
            " ",
            NSDictionary.dictionaryWithObject_forKey_(
                NSFont.systemFontOfSize_(16), NSFontAttributeName))
        for text, file in smileys:
            image = NSImage.alloc().initWithContentsOfFile_(file)
            if not image:
                print("Can't load %s" % file)
                continue
            image.setScalesWhenResized_(True)
            image.setSize_(NSMakeSize(16, 16))
            atext = bigText.mutableCopy()
            atext.appendAttributedString_(
                NSAttributedString.alloc().initWithString_(text))
            item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
                text, "insertSmiley:", "")
            menu.addItem_(item)
            item.setTarget_(self)
            item.setAttributedTitle_(atext)
            item.setRepresentedObject_(
                NSAttributedString.alloc().initWithString_(text))
            item.setImage_(image)

    @objc.python_method
    def revalidateToolbar(self):
        pass

    @objc.python_method
    def isOutputFrameVisible(self):
        return True

    @objc.python_method
    def log_info(self, text):
        BlinkLogger().log_info("[SMS with %s] %s" % (self.remote_uri, text))

    @objc.IBAction
    def addContactPanelClicked_(self, sender):
        if sender.tag() == 1:
            NSApp.delegate().contactsWindowController.addContact(
                uris=[(self.target_uri, 'sip')])

        self.addContactView.removeFromSuperview()
        frame = self.chatViewController.outputView.frame()
        frame.origin.y = 0
        frame.size = self.outputContainer.frame().size
        self.chatViewController.outputView.setFrame_(frame)

    @objc.python_method
    def insertSmiley_(self, sender):
        smiley = sender.representedObject()
        self.chatViewController.appendAttributedString_(smiley)

    @objc.python_method
    def matchesTargetAccount(self, target, account):
        that_contact = NSApp.delegate(
        ).contactsWindowController.getFirstContactMatchingURI(target)
        this_contact = NSApp.delegate(
        ).contactsWindowController.getFirstContactMatchingURI(self.target_uri)
        return (self.target_uri == target or
                (this_contact and that_contact
                 and this_contact == that_contact)) and self.account == account

    @objc.python_method
    def gotMessage(self,
                   sender,
                   call_id,
                   content,
                   content_type,
                   is_replication_message=False,
                   timestamp=None,
                   window=None):
        is_html = content_type == 'text/html'
        encrypted = False
        try:
            content = self.encryption.otr_session.handle_input(
                content, content_type)
        except IgnoreMessage:
            return None
        except UnencryptedMessage:
            encrypted = False
            encryption_active = True
        except EncryptedMessageError as e:
            self.log_info('OTP encrypted message error: %s' % str(e))
            return None
        except OTRError as e:
            self.log_info('OTP error: %s' % str(e))
            return None
        else:
            encrypted = encryption_active = self.encryption.active

        content = content.decode() if isinstance(content, bytes) else content

        if content.startswith('?OTR:'):
            self.log_info('Dropped OTR message that could not be decoded')
            return None

        self.enableIsComposing = True

        icon = NSApp.delegate().contactsWindowController.iconPathForURI(
            format_identity_to_string(sender))
        timestamp = timestamp or ISOTimestamp.now()

        hash = hashlib.sha1()
        hash.update((content + str(timestamp) + str(sender)).encode('utf-8'))
        id = hash.hexdigest()

        encryption = ''
        if encrypted:
            encryption = 'verified' if self.encryption.verified else 'unverified'

        if not is_replication_message and not window.isKeyWindow():
            nc_body = html2txt(content) if is_html else content
            nc_title = NSLocalizedString("SMS Message Received", "Label")
            nc_subtitle = format_identity_to_string(sender, format='full')
            NSApp.delegate().gui_notify(nc_title, nc_body, nc_subtitle)

        self.log_info("Incoming message %s received" % call_id)

        self.chatViewController.showMessage(call_id,
                                            id,
                                            'incoming',
                                            format_identity_to_string(sender),
                                            icon,
                                            content,
                                            timestamp,
                                            is_html=is_html,
                                            state="delivered",
                                            media_type='sms',
                                            encryption=encryption)

        self.notification_center.post_notification(
            'ChatViewControllerDidDisplayMessage',
            sender=self,
            data=NotificationData(
                direction='incoming',
                history_entry=False,
                remote_party=format_identity_to_string(sender),
                local_party=format_identity_to_string(self.account)
                if self.account is not BonjourAccount() else 'bonjour.local',
                check_contact=True))

        # save to history
        if not is_replication_message or (is_replication_message and
                                          self.local_uri == self.account.id):
            message = MessageInfo(id,
                                  call_id=call_id,
                                  direction='incoming',
                                  sender=sender,
                                  recipient=self.account,
                                  timestamp=timestamp,
                                  content=content,
                                  content_type=content_type,
                                  status="delivered",
                                  encryption=encryption)
            self.add_to_history(message)

    def remoteBecameIdle_(self, timer):
        window = timer.userInfo()
        if window:
            window.noteView_isComposing_(self, False)

        if self.remoteTypingTimer:
            self.remoteTypingTimer.invalidate()
        self.remoteTypingTimer = None

    @objc.python_method
    def gotIsComposing(self, window, state, refresh, last_active):
        self.enableIsComposing = True

        flag = state == "active"
        if flag:
            if refresh is None:
                refresh = 120

            if last_active is not None and (
                    last_active - ISOTimestamp.now() >
                    datetime.timedelta(seconds=refresh)):
                # message is old, discard it
                return

            if self.remoteTypingTimer:
                # if we don't get any indications in the request refresh, then we assume remote to be idle
                self.remoteTypingTimer.setFireDate_(
                    NSDate.dateWithTimeIntervalSinceNow_(refresh))
            else:
                self.remoteTypingTimer = NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(
                    refresh, self, "remoteBecameIdle:", window, False)
        else:
            if self.remoteTypingTimer:
                self.remoteTypingTimer.invalidate()
                self.remoteTypingTimer = None

        window.noteView_isComposing_(self, flag)

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

    @objc.python_method
    def inject_otr_message(self, data):
        if not self.encryption.active:
            self.log_info('Negotiating OTR encryption...')
        messageObject = OTRInternalMessage(data)
        self.sendMessage(messageObject)

    @objc.python_method
    def _NH_DNSLookupDidFail(self, lookup, data):
        self.notification_center.remove_observer(self, sender=lookup)
        message = "DNS lookup of SIP proxies for %s failed: %s" % (str(
            self.target_uri.host), data.error)
        self.setRoutesFailed(message)

    @objc.python_method
    def _NH_DNSLookupDidSucceed(self, lookup, data):
        self.notification_center.remove_observer(self, sender=lookup)
        result_text = ', '.join(
            ('%s:%s (%s)' %
             (result.address, result.port, result.transport.upper())
             for result in data.result))
        self.log_info("DNS lookup for %s succeeded: %s" %
                      (self.target_uri.host.decode(), result_text))
        routes = data.result
        if not routes:
            self.setRoutesFailed("No routes found to SIP Proxy")
        else:
            self.setRoutesResolved(routes)

    @objc.python_method
    def _NH_ChatStreamOTREncryptionStateChanged(self, stream, data):
        if data.new_state is OTRState.Encrypted:
            local_fingerprint = stream.encryption.key_fingerprint
            remote_fingerprint = stream.encryption.peer_fingerprint
            self.log_info("Chat encryption activated using OTR protocol")
            self.log_info("OTR local fingerprint %s" % local_fingerprint)
            self.log_info("OTR remote fingerprint %s" % remote_fingerprint)
            self.chatViewController.showSystemMessage("0",
                                                      "Encryption enabled",
                                                      ISOTimestamp.now())
            self.showSystemMessage("Encryption enabled", ISOTimestamp.now())
        elif data.new_state is OTRState.Finished:
            self.log_info("Chat encryption deactivated")
            self.chatViewController.showSystemMessage("0",
                                                      "Encryption deactivated",
                                                      ISOTimestamp.now(),
                                                      is_error=True)
        elif data.new_state is OTRState.Plaintext:
            self.log_info("Chat encryption deactivated")
            self.chatViewController.showSystemMessage("0",
                                                      "Encryption deactivated",
                                                      ISOTimestamp.now(),
                                                      is_error=True)

    @objc.python_method
    def _NH_SIPMessageDidSucceed(self, sender, data):
        try:
            message = self.messages.pop(str(sender))
        except KeyError:
            pass
        else:
            call_id = data.headers['Call-ID'].body
            self.composeReplicationMessage(message, data.code)
            if message.content_type != "application/im-iscomposing+xml":
                self.log_info("Outgoing %s message %s delivered" %
                              (message.content_type, call_id))
                if data.code == 202:
                    self.chatViewController.markMessage(
                        message.id, MSG_STATE_DEFERRED)
                    message.status = 'deferred'
                else:
                    self.chatViewController.markMessage(
                        message.id, MSG_STATE_DELIVERED)
                    message.status = 'delivered'
                message.call_id = call_id
                self.add_to_history(message)

        self.notification_center.remove_observer(self, sender=sender)

    @objc.python_method
    def _NH_SIPMessageDidFail(self, sender, data):
        try:
            message = self.messages.pop(str(sender))
        except KeyError:
            pass
        else:
            if data.code == 408:
                self.last_route = None

            call_id = message.call_id
            self.composeReplicationMessage(message, data.code)
            if message.content_type != "application/im-iscomposing+xml":
                self.chatViewController.markMessage(message.id,
                                                    MSG_STATE_FAILED)
                message.status = 'failed'
                self.add_to_history(message)
                self.log_info("Outgoing message %s delivery failed: %s" %
                              (call_id.decode(), data.reason))
        self.notification_center.remove_observer(self, sender=sender)

    @objc.python_method
    def add_to_history(self, message):
        # writes the record to the sql database
        cpim_to = format_identity_to_string(
            message.recipient) if message.recipient else ''
        cpim_from = format_identity_to_string(
            message.sender) if message.sender else ''
        cpim_timestamp = str(message.timestamp)
        content_type = "html" if "html" in message.content_type else "text"

        self.history.add_message(message.id,
                                 'sms',
                                 self.local_uri,
                                 self.remote_uri,
                                 message.direction,
                                 cpim_from,
                                 cpim_to,
                                 cpim_timestamp,
                                 message.content,
                                 content_type,
                                 "0",
                                 message.status,
                                 call_id=message.call_id)

    @objc.python_method
    def composeReplicationMessage(self, sent_message, response_code):
        if sent_message.content_type == "application/im-iscomposing+xml":
            return

        if isinstance(self.account, Account):
            if not self.account.sms.disable_replication:
                contact = NSApp.delegate(
                ).contactsWindowController.getFirstContactMatchingURI(
                    self.target_uri)
                msg = CPIMPayload(
                    sent_message.content,
                    sent_message.content_type,
                    charset='utf-8',
                    sender=ChatIdentity(self.account.uri,
                                        self.account.display_name),
                    recipients=[
                        ChatIdentity(self.target_uri,
                                     contact.name if contact else None)
                    ])
                self.sendReplicationMessage(response_code,
                                            msg.encode()[0],
                                            content_type='message/cpim')

    @objc.python_method
    @run_in_green_thread
    def sendReplicationMessage(self,
                               response_code,
                               content,
                               content_type="message/cpim",
                               timestamp=None):
        timestamp = timestamp or ISOTimestamp.now()
        # Lookup routes
        if self.account.sip.outbound_proxy is not None:
            uri = SIPURI(host=self.account.sip.outbound_proxy.host,
                         port=self.account.sip.outbound_proxy.port,
                         parameters={
                             'transport':
                             self.account.sip.outbound_proxy.transport
                         })
        else:
            uri = SIPURI(host=self.account.id.domain)

        route = None
        if self.last_route is None:
            lookup = DNSLookup()
            settings = SIPSimpleSettings()
            try:
                routes = lookup.lookup_sip_proxy(
                    uri, settings.sip.transport_list).wait()
            except DNSLookupError:
                pass
            else:
                route = routes[0]
        else:
            route = self.last_route

        if route:
            extra_headers = [
                Header("X-Offline-Storage", "no"),
                Header("X-Replication-Code", str(response_code)),
                Header("X-Replication-Timestamp", str(ISOTimestamp.now()))
            ]
            message_request = Message(FromHeader(self.account.uri,
                                                 self.account.display_name),
                                      ToHeader(self.account.uri),
                                      RouteHeader(route.uri),
                                      content_type,
                                      content,
                                      credentials=self.account.credentials,
                                      extra_headers=extra_headers)
            message_request.send(
                15 if content_type != "application/im-iscomposing+xml" else 5)

    @objc.python_method
    @run_in_gui_thread
    def setRoutesResolved(self, routes):
        self.routes = routes
        self.last_route = self.routes[0]
        self.log_info('Proceed using route: %s' % self.last_route)

        if not self.started:
            self.message_queue.start()

        if not self.encryption.active and not self.started:
            self.encryption.start()

        self.started = True

    @objc.python_method
    @run_in_gui_thread
    def setRoutesFailed(self, reason):
        self.message_queue.stop()
        self.started = False

        for msgObject in self.message_queue:
            id = msgObject.id

            try:
                message = self.messages.pop(id)
            except KeyError:
                pass
            else:
                if content_type not in ('application/im-iscomposing+xml',
                                        'message/cpim'):
                    self.chatViewController.markMessage(
                        message.id, MSG_STATE_FAILED)
                    message.status = 'failed'
                    self.add_to_history(message)
                    log_text = NSLocalizedString("Routing failure: %s",
                                                 "Label") % msg
                    self.chatViewController.showSystemMessage(
                        '0', reason, ISOTimestamp.now(), True)
                    self.log_info(log_text)

    @objc.python_method
    def _send_message(self, message):
        if (not self.last_route):
            self.log_info('No route found')
            return

        message.timestamp = ISOTimestamp.now()

        if not isinstance(message, OTRInternalMessage):
            try:
                content = self.encryption.otr_session.handle_output(
                    message.content, message.content_type)
            except OTRError as e:
                if 'has ended the private conversation' in str(e):
                    self.log_info(
                        'Encryption has been disabled by remote party, please resend the message again'
                    )
                    self.encryption.stop()
                else:
                    self.log_info('Failed to encrypt outgoing message: %s' %
                                  str(e))
                return

        timeout = 5
        if message.content_type != "application/im-iscomposing+xml":
            self.enableIsComposing = True
            timeout = 15

        message_request = Message(FromHeader(self.account.uri,
                                             self.account.display_name),
                                  ToHeader(self.target_uri),
                                  RouteHeader(self.last_route.uri),
                                  message.content_type,
                                  message.content,
                                  credentials=self.account.credentials)

        self.notification_center.add_observer(self, sender=message_request)

        message_request.send(timeout)
        message.status = 'sent'
        message.call_id = message_request._request.call_id.decode()

        if not isinstance(message, OTRInternalMessage):
            if self.encryption.active:
                self.log_info(
                    'Sending encrypted %s message %s to %s' %
                    (message.content_type, message.id, self.last_route.uri))
            else:
                self.log_info(
                    'Sending %s message %s to %s' %
                    (message.content_type, message.id, self.last_route.uri))

        id = str(message_request)
        self.messages[id] = message

    @objc.python_method
    def lookup_destination(self, target_uri):
        assert isinstance(target_uri, SIPURI)

        lookup = DNSLookup()
        self.notification_center.add_observer(self, sender=lookup)
        settings = SIPSimpleSettings()

        if isinstance(self.account,
                      Account) and self.account.sip.outbound_proxy is not None:
            uri = SIPURI(host=self.account.sip.outbound_proxy.host,
                         port=self.account.sip.outbound_proxy.port,
                         parameters={
                             'transport':
                             self.account.sip.outbound_proxy.transport
                         })
            self.log_info("Starting DNS lookup for %s through proxy %s" %
                          (target_uri.host.decode(), uri))
        elif isinstance(self.account,
                        Account) and self.account.sip.always_use_my_proxy:
            uri = SIPURI(host=self.account.id.domain)
            self.log_info(
                "Starting DNS lookup for %s via proxy of account %s" %
                (target_uri.host.decode(), self.account.id))
        else:
            uri = target_uri
            self.log_info("Starting DNS lookup for %s" %
                          target_uri.host.decode())

        lookup.lookup_sip_proxy(uri, settings.sip.transport_list)

    @objc.python_method
    def sendMessage(self, content, content_type="text/plain"):
        # entry point for sending messages, they will be added to self.message_queue
        if content_type != "application/im-iscomposing+xml":
            icon = NSApp.delegate().contactsWindowController.iconPathForSelf()

            if not isinstance(content, OTRInternalMessage):
                timestamp = ISOTimestamp.now()
                hash = hashlib.sha1()
                content = content.decode() if isinstance(content,
                                                         bytes) else content
                hash.update((content + str(timestamp)).encode("utf-8"))
                id = hash.hexdigest()
                call_id = ''

                encryption = ''
                if self.encryption.active:
                    encryption = 'verified' if self.encryption.verified else 'unverified'

                self.chatViewController.showMessage(call_id,
                                                    id,
                                                    'outgoing',
                                                    None,
                                                    icon,
                                                    content,
                                                    timestamp,
                                                    state="sent",
                                                    media_type='sms',
                                                    encryption=encryption)

                recipient = ChatIdentity(self.target_uri, self.display_name)
                mInfo = MessageInfo(id,
                                    sender=self.account,
                                    recipient=recipient,
                                    timestamp=timestamp,
                                    content_type=content_type,
                                    content=content,
                                    status="queued",
                                    encryption=encryption)

                self.messages[id] = mInfo
                self.message_queue.put(mInfo)
            else:
                self.message_queue.put(content)

        # Async DNS lookup
        if host is None or host.default_ip is None:
            self.setRoutesFailed(
                NSLocalizedString("No Internet connection", "Label"))
            return

        if self.last_route is None:
            self.lookup_destination(self.target_uri)
        else:
            self.setRoutesResolved([self.last_route])

    def textView_doCommandBySelector_(self, textView, selector):
        if selector == "insertNewline:" and self.chatViewController.inputText == textView:
            content = str(textView.string())
            textView.setString_("")
            textView.didChangeText()

            if content:
                self.sendMessage(content)

            self.chatViewController.resetTyping()

            recipient = ChatIdentity(self.target_uri, self.display_name)
            self.notification_center.post_notification(
                'ChatViewControllerDidDisplayMessage',
                sender=self,
                data=NotificationData(
                    direction='outgoing',
                    history_entry=False,
                    remote_party=format_identity_to_string(recipient),
                    local_party=format_identity_to_string(self.account) if
                    self.account is not BonjourAccount() else 'bonjour.local',
                    check_contact=True))

            return True

        return False

    def textDidChange_(self, notif):
        chars_left = MAX_MESSAGE_LENGTH - self.chatViewController.inputText.textStorage(
        ).length()
        self.splitView.setText_(
            NSLocalizedString("%i chars left", "Label") % chars_left)

    @objc.python_method
    def getContentView(self):
        return self.chatViewController.view

    def chatView_becameIdle_(self, chatView, last_active):
        if self.enableIsComposing:
            content = IsComposingMessage(
                state=State("idle"),
                refresh=Refresh(60),
                last_active=LastActive(last_active or ISOTimestamp.now()),
                content_type=ContentType('text')).toxml()
            self.sendMessage(content, IsComposingDocument.content_type)

    def chatView_becameActive_(self, chatView, last_active):
        if self.enableIsComposing:
            content = IsComposingMessage(
                state=State("active"),
                refresh=Refresh(60),
                last_active=LastActive(last_active or ISOTimestamp.now()),
                content_type=ContentType('text')).toxml()
            self.sendMessage(content, IsComposingDocument.content_type)

    def chatViewDidLoad_(self, chatView):
        self.replay_history()

    @objc.python_method
    def scroll_back_in_time(self):
        self.chatViewController.clear()
        self.chatViewController.resetRenderedMessages()
        self.replay_history()

    @objc.python_method
    @run_in_green_thread
    def replay_history(self):
        blink_contact = NSApp.delegate(
        ).contactsWindowController.getFirstContactMatchingURI(self.target_uri)
        if not blink_contact:
            remote_uris = self.remote_uri
        else:
            remote_uris = list(
                str(uri.uri) for uri in blink_contact.uris if '@' in uri.uri)

        zoom_factor = self.chatViewController.scrolling_zoom_factor

        if zoom_factor:
            period_array = {
                1: datetime.datetime.now() - datetime.timedelta(days=2),
                2: datetime.datetime.now() - datetime.timedelta(days=7),
                3: datetime.datetime.now() - datetime.timedelta(days=31),
                4: datetime.datetime.now() - datetime.timedelta(days=90),
                5: datetime.datetime.now() - datetime.timedelta(days=180),
                6: datetime.datetime.now() - datetime.timedelta(days=365),
                7: datetime.datetime.now() - datetime.timedelta(days=3650)
            }

            after_date = period_array[zoom_factor].strftime("%Y-%m-%d")

            if zoom_factor == 1:
                self.zoom_period_label = NSLocalizedString(
                    "Displaying messages from last day", "Label")
            elif zoom_factor == 2:
                self.zoom_period_label = NSLocalizedString(
                    "Displaying messages from last week", "Label")
            elif zoom_factor == 3:
                self.zoom_period_label = NSLocalizedString(
                    "Displaying messages from last month", "Label")
            elif zoom_factor == 4:
                self.zoom_period_label = NSLocalizedString(
                    "Displaying messages from last three months", "Label")
            elif zoom_factor == 5:
                self.zoom_period_label = NSLocalizedString(
                    "Displaying messages from last six months", "Label")
            elif zoom_factor == 6:
                self.zoom_period_label = NSLocalizedString(
                    "Displaying messages from last year", "Label")
            elif zoom_factor == 7:
                self.zoom_period_label = NSLocalizedString(
                    "Displaying all messages", "Label")
                self.chatViewController.setHandleScrolling_(False)

            results = self.history.get_messages(
                remote_uri=remote_uris,
                media_type=('chat', 'sms'),
                after_date=after_date,
                count=10000,
                search_text=self.chatViewController.search_text)
        else:
            results = self.history.get_messages(
                remote_uri=remote_uris,
                media_type=('chat', 'sms'),
                count=self.showHistoryEntries,
                search_text=self.chatViewController.search_text)

        messages = [row for row in reversed(results)]
        self.render_history_messages(messages)

    @objc.python_method
    @run_in_gui_thread
    def render_history_messages(self, messages):
        if self.chatViewController.scrolling_zoom_factor:
            if not self.message_count_from_history:
                self.message_count_from_history = len(messages)
                self.chatViewController.lastMessagesLabel.setStringValue_(
                    self.zoom_period_label)
            else:
                if self.message_count_from_history == len(messages):
                    self.chatViewController.setHandleScrolling_(False)
                    self.chatViewController.lastMessagesLabel.setStringValue_(
                        NSLocalizedString(
                            "%s. There are no previous messages.", "Label") %
                        self.zoom_period_label)
                    self.chatViewController.setHandleScrolling_(False)
                else:
                    self.chatViewController.lastMessagesLabel.setStringValue_(
                        self.zoom_period_label)
        else:
            self.message_count_from_history = len(messages)
            if len(messages):
                self.chatViewController.lastMessagesLabel.setStringValue_(
                    NSLocalizedString("Scroll up for going back in time",
                                      "Label"))
            else:
                self.chatViewController.setHandleScrolling_(False)
                self.chatViewController.lastMessagesLabel.setStringValue_(
                    NSLocalizedString("There are no previous messages",
                                      "Label"))

        if len(messages):
            message = messages[0]
            delta = datetime.date.today() - message.date

            if not self.chatViewController.scrolling_zoom_factor:
                if delta.days <= 2:
                    self.chatViewController.scrolling_zoom_factor = 1
                elif delta.days <= 7:
                    self.chatViewController.scrolling_zoom_factor = 2
                elif delta.days <= 31:
                    self.chatViewController.scrolling_zoom_factor = 3
                elif delta.days <= 90:
                    self.chatViewController.scrolling_zoom_factor = 4
                elif delta.days <= 180:
                    self.chatViewController.scrolling_zoom_factor = 5
                elif delta.days <= 365:
                    self.chatViewController.scrolling_zoom_factor = 6
                elif delta.days <= 3650:
                    self.chatViewController.scrolling_zoom_factor = 7

        call_id = None
        seen_sms = {}
        last_media_type = 'sms'
        last_chat_timestamp = None
        for message in messages:
            if message.status == 'failed':
                continue

            if message.sip_callid != '' and message.media_type == 'sms':
                try:
                    seen = seen_sms[message.sip_callid]
                except KeyError:
                    seen_sms[message.sip_callid] = True
                else:
                    continue

            if message.direction == 'outgoing':
                icon = NSApp.delegate(
                ).contactsWindowController.iconPathForSelf()
            else:
                sender_uri = sipuri_components_from_string(
                    message.cpim_from)[0]
                icon = NSApp.delegate(
                ).contactsWindowController.iconPathForURI(sender_uri)

            timestamp = ISOTimestamp(message.cpim_timestamp)
            is_html = False if message.content_type == 'text' else True

            #if call_id is not None and call_id != message.sip_callid and message.media_type == 'chat':
            #   self.chatViewController.showSystemMessage(message.sip_callid, 'Chat session established', timestamp, False)

            #if message.media_type == 'sms' and last_media_type == 'chat':
            #   self.chatViewController.showSystemMessage(message.sip_callid, 'Short messages', timestamp, False)

            self.chatViewController.showMessage(message.sip_callid,
                                                message.id,
                                                message.direction,
                                                message.cpim_from,
                                                icon,
                                                message.body,
                                                timestamp,
                                                recipient=message.cpim_to,
                                                state=message.status,
                                                is_html=is_html,
                                                history_entry=True,
                                                media_type=message.media_type,
                                                encryption=message.encryption)

            call_id = message.sip_callid
            last_media_type = 'chat' if message.media_type == 'chat' else 'sms'
            if message.media_type == 'chat':
                last_chat_timestamp = timestamp

        self.chatViewController.loadingProgressIndicator.stopAnimation_(None)
        self.chatViewController.loadingTextIndicator.setStringValue_("")

    def webviewFinishedLoading_(self, notification):
        self.document = self.outputView.mainFrameDocument()
        self.finishedLoading = True
        for script in self.messageQueue:
            self.outputView.stringByEvaluatingJavaScriptFromString_(script)
        self.messageQueue = []

        if hasattr(self.delegate, "chatViewDidLoad_"):
            self.delegate.chatViewDidLoad_(self)

    def webView_decidePolicyForNavigationAction_request_frame_decisionListener_(
            self, webView, info, request, frame, listener):
        # intercept link clicks so that they are opened in Safari
        theURL = info[WebActionOriginalURLKey]
        if theURL.scheme() == "file":
            listener.use()
        else:
            listener.ignore()
            NSWorkspace.sharedWorkspace().openURL_(theURL)
Ejemplo n.º 15
0
class SMSViewController(NSObject):
    implements(IObserver)

    chatViewController = objc.IBOutlet()
    splitView = objc.IBOutlet()
    smileyButton = objc.IBOutlet()
    outputContainer = objc.IBOutlet()
    addContactView = objc.IBOutlet()
    addContactLabel = objc.IBOutlet()

    showHistoryEntries = 50
    remoteTypingTimer = None
    enableIsComposing = False

    account = None
    target_uri = None
    routes = None
    queue = None
    queued_serial = 0

    def initWithAccount_target_name_(self, account, target, display_name):
        self = super(SMSViewController, self).init()
        if self:
            self.notification_center = NotificationCenter()
            self.account = account
            self.target_uri = target
            self.display_name = display_name
            self.queue = []
            self.messages = {}

            self.history = ChatHistory()

            self.local_uri = '%s@%s' % (account.id.username, account.id.domain)
            self.remote_uri = '%s@%s' % (self.target_uri.user,
                                         self.target_uri.host)

            NSBundle.loadNibNamed_owner_("SMSView", self)

            self.chatViewController.setContentFile_(
                NSBundle.mainBundle().pathForResource_ofType_(
                    "ChatView", "html"))
            self.chatViewController.setAccount_(self.account)
            self.chatViewController.resetRenderedMessages()

            self.chatViewController.inputText.unregisterDraggedTypes()
            self.chatViewController.inputText.setMaxLength_(MAX_MESSAGE_LENGTH)
            self.splitView.setText_("%i chars left" % MAX_MESSAGE_LENGTH)

        return self

    def dealloc(self):
        if self.remoteTypingTimer:
            self.remoteTypingTimer.invalidate()
        super(SMSViewController, self).dealloc()

    def awakeFromNib(self):
        # setup smiley popup
        smileys = SmileyManager().get_smiley_list()

        menu = self.smileyButton.menu()
        while menu.numberOfItems() > 0:
            menu.removeItemAtIndex_(0)

        bigText = NSAttributedString.alloc().initWithString_attributes_(
            " ",
            NSDictionary.dictionaryWithObject_forKey_(
                NSFont.systemFontOfSize_(16), NSFontAttributeName))
        for text, file in smileys:
            image = NSImage.alloc().initWithContentsOfFile_(file)
            if not image:
                print "Can't load %s" % file
                continue
            image.setScalesWhenResized_(True)
            image.setSize_(NSMakeSize(16, 16))
            atext = bigText.mutableCopy()
            atext.appendAttributedString_(
                NSAttributedString.alloc().initWithString_(text))
            item = NSMenuItem.alloc().initWithTitle_action_keyEquivalent_(
                text, "insertSmiley:", "")
            menu.addItem_(item)
            item.setTarget_(self)
            item.setAttributedTitle_(atext)
            item.setRepresentedObject_(
                NSAttributedString.alloc().initWithString_(text))
            item.setImage_(image)

    def isOutputFrameVisible(self):
        return True

    def log_info(self, text):
        BlinkLogger().log_info(u"[Message to %s] %s" % (self.remote_uri, text))

    @objc.IBAction
    def addContactPanelClicked_(self, sender):
        if sender.tag() == 1:
            NSApp.delegate().contactsWindowController.addContact(
                self.target_uri)

        self.addContactView.removeFromSuperview()
        frame = self.chatViewController.outputView.frame()
        frame.origin.y = 0
        frame.size = self.outputContainer.frame().size
        self.chatViewController.outputView.setFrame_(frame)

    def insertSmiley_(self, sender):
        smiley = sender.representedObject()
        self.chatViewController.appendAttributedString_(smiley)

    def matchesTargetAccount(self, target, account):
        that_contact = NSApp.delegate(
        ).contactsWindowController.getContactMatchingURI(target)
        this_contact = NSApp.delegate(
        ).contactsWindowController.getContactMatchingURI(self.target_uri)
        return (self.target_uri == target or
                (this_contact and that_contact
                 and this_contact == that_contact)) and self.account == account

    def gotMessage(self,
                   sender,
                   message,
                   is_html=False,
                   state=None,
                   timestamp=None):
        self.enableIsComposing = True
        icon = NSApp.delegate().contactsWindowController.iconPathForURI(
            format_identity_to_string(sender))
        timestamp = timestamp or Timestamp(datetime.datetime.now(tzlocal()))

        hash = hashlib.sha1()
        hash.update(message.encode('utf-8') + str(timestamp) + str(sender))
        msgid = hash.hexdigest()

        self.chatViewController.showMessage(msgid,
                                            'incoming',
                                            format_identity_to_string(sender),
                                            icon,
                                            message,
                                            timestamp,
                                            is_html=is_html,
                                            state="delivered")

        self.notification_center.post_notification(
            'ChatViewControllerDidDisplayMessage',
            sender=self,
            data=TimestampedNotificationData(
                direction='incoming',
                history_entry=False,
                remote_party=format_identity_to_string(sender),
                local_party=format_identity_to_string(self.account)
                if self.account is not BonjourAccount() else 'bonjour',
                check_contact=True))

        # save to history
        message = MessageInfo(msgid,
                              direction='incoming',
                              sender=sender,
                              recipient=self.account,
                              timestamp=timestamp,
                              text=message,
                              content_type="html" if is_html else "text",
                              status="delivered")
        self.add_to_history(message)

    def remoteBecameIdle_(self, timer):
        window = timer.userInfo()
        if window:
            window.noteView_isComposing_(self, False)

        if self.remoteTypingTimer:
            self.remoteTypingTimer.invalidate()
        self.remoteTypingTimer = None

    def gotIsComposing(self, window, state, refresh, last_active):
        self.enableIsComposing = True

        flag = state == "active"
        if flag:
            if refresh is None:
                refresh = 120

            if last_active is not None and (
                    last_active - datetime.datetime.now(tzlocal()) >
                    datetime.timedelta(seconds=refresh)):
                # message is old, discard it
                return

            if self.remoteTypingTimer:
                # if we don't get any indications in the request refresh, then we assume remote to be idle
                self.remoteTypingTimer.setFireDate_(
                    NSDate.dateWithTimeIntervalSinceNow_(refresh))
            else:
                self.remoteTypingTimer = NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(
                    refresh, self, "remoteBecameIdle:", window, False)
        else:
            if self.remoteTypingTimer:
                self.remoteTypingTimer.invalidate()
                self.remoteTypingTimer = None

        window.noteView_isComposing_(self, flag)

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

    def _NH_DNSLookupDidFail(self, lookup, data):
        self.notification_center.remove_observer(self, sender=lookup)
        message = u"DNS lookup of SIP proxies for %s failed: %s" % (unicode(
            self.target_uri.host), data.error)
        self.setRoutesFailed(message)

    def _NH_DNSLookupDidSucceed(self, lookup, data):
        self.notification_center.remove_observer(self, sender=lookup)

        result_text = ', '.join(
            ('%s:%s (%s)' %
             (result.address, result.port, result.transport.upper())
             for result in data.result))
        self.log_info(u"DNS lookup for %s succeeded: %s" %
                      (self.target_uri.host, result_text))
        routes = data.result
        if not routes:
            self.setRoutesFailed("No routes found to SIP Proxy")
        else:
            self.setRoutesResolved(routes)

    def _NH_SIPMessageDidSucceed(self, sender, data):
        BlinkLogger().log_info(u"SMS message delivery suceeded")

        self.composeReplicationMessage(sender, data.code)
        message = self.messages.pop(str(sender))

        if message.content_type != "application/im-iscomposing+xml":
            if data.code == 202:
                self.chatViewController.markMessage(message.msgid,
                                                    MSG_STATE_DEFERRED)
                message.status = 'deferred'
            else:
                self.chatViewController.markMessage(message.msgid,
                                                    MSG_STATE_DELIVERED)
                message.status = 'delivered'
            self.add_to_history(message)

        self.notification_center.remove_observer(self, sender=sender)

    def _NH_SIPMessageDidFail(self, sender, data):
        BlinkLogger().log_info(u"SMS message delivery failed: %s" %
                               data.reason)

        self.composeReplicationMessage(sender, data.code)
        message = self.messages.pop(str(sender))

        if message.content_type != "application/im-iscomposing+xml":
            self.chatViewController.markMessage(message.msgid,
                                                MSG_STATE_FAILED)
            message.status = 'failed'
            self.add_to_history(message)

        self.notification_center.remove_observer(self, sender=sender)

    @run_in_green_thread
    def add_to_history(self, message):
        # writes the record to the sql database
        cpim_to = format_identity_to_string(
            message.recipient) if message.recipient else ''
        cpim_from = format_identity_to_string(
            message.sender) if message.sender else ''
        cpim_timestamp = str(message.timestamp)
        content_type = "html" if "html" in message.content_type else "text"

        self.history.add_message(message.msgid, 'sms', self.local_uri,
                                 self.remote_uri, message.direction, cpim_from,
                                 cpim_to, cpim_timestamp, message.text,
                                 content_type, "0", message.status)

    def composeReplicationMessage(self, sent_message, response_code):
        if isinstance(self.account, Account):
            settings = SIPSimpleSettings()
            if settings.chat.sms_replication:
                contact = NSApp.delegate(
                ).contactsWindowController.getContactMatchingURI(
                    self.target_uri)
                msg = CPIMMessage(
                    sent_message.body.decode('utf-8'),
                    sent_message.content_type,
                    sender=CPIMIdentity(self.account.uri,
                                        self.account.display_name),
                    recipients=[
                        CPIMIdentity(self.target_uri,
                                     contact.display_name if contact else None)
                    ])
                self.sendReplicationMessage(response_code,
                                            str(msg),
                                            content_type='message/cpim')

    @run_in_green_thread
    def sendReplicationMessage(self,
                               response_code,
                               text,
                               content_type="message/cpim",
                               timestamp=None):
        timestamp = timestamp or datetime.datetime.now(tzlocal())
        # Lookup routes
        if self.account.sip.outbound_proxy is not None:
            uri = SIPURI(host=self.account.sip.outbound_proxy.host,
                         port=self.account.sip.outbound_proxy.port,
                         parameters={
                             'transport':
                             self.account.sip.outbound_proxy.transport
                         })
        else:
            uri = SIPURI(host=self.account.id.domain)
        lookup = DNSLookup()
        settings = SIPSimpleSettings()
        try:
            routes = lookup.lookup_sip_proxy(
                uri, settings.sip.transport_list).wait()
        except DNSLookupError:
            pass
        else:
            utf8_encode = content_type not in (
                'application/im-iscomposing+xml', 'message/cpim')
            extra_headers = [
                Header("X-Offline-Storage", "no"),
                Header("X-Replication-Code", str(response_code)),
                Header("X-Replication-Timestamp",
                       str(Timestamp(datetime.datetime.now())))
            ]
            message_request = Message(
                FromHeader(self.account.uri, self.account.display_name),
                ToHeader(self.account.uri),
                RouteHeader(routes[0].get_uri()),
                content_type,
                text.encode('utf-8') if utf8_encode else text,
                credentials=self.account.credentials,
                extra_headers=extra_headers)
            message_request.send(
                15 if content_type != "application/im-iscomposing+xml" else 5)

    @allocate_autorelease_pool
    @run_in_gui_thread
    def setRoutesResolved(self, routes):
        self.routes = routes
        for msgid, text, content_type in self.queue:
            self._sendMessage(msgid, text, content_type)
        self.queue = []

    @allocate_autorelease_pool
    @run_in_gui_thread
    def setRoutesFailed(self, msg):
        BlinkLogger().log_error(u"DNS Lookup failed: %s" % msg)
        self.chatViewController.showSystemMessage(
            "Cannot send SMS message to %s\n%s" % (self.target_uri, msg))
        for msgid, text, content_type in self.queue:
            message = self.messages.pop(msgid)
            if content_type not in ('application/im-iscomposing+xml',
                                    'message/cpim'):
                message.status = 'failed'
                self.add_to_history(message)
        self.queue = []

    def _sendMessage(self, msgid, text, content_type="text/plain"):

        utf8_encode = content_type not in ('application/im-iscomposing+xml',
                                           'message/cpim')
        message_request = Message(
            FromHeader(self.account.uri, self.account.display_name),
            ToHeader(self.target_uri),
            RouteHeader(self.routes[0].get_uri()),
            content_type,
            text.encode('utf-8') if utf8_encode else text,
            credentials=self.account.credentials)
        self.notification_center.add_observer(self, sender=message_request)
        message_request.send(
            15 if content_type != "application/im-iscomposing+xml" else 5)

        id = str(message_request)
        if content_type != "application/im-iscomposing+xml":
            BlinkLogger().log_info(u"Sent %s SMS message to %s" %
                                   (content_type, self.target_uri))
            self.enableIsComposing = True
            message = self.messages.pop(msgid)
            message.status = 'sent'
        else:
            message = MessageInfo(id, content_type=content_type)

        self.messages[id] = message
        return message

    def lookup_destination(self, target_uri):
        assert isinstance(target_uri, SIPURI)

        lookup = DNSLookup()
        self.notification_center.add_observer(self, sender=lookup)
        settings = SIPSimpleSettings()

        if isinstance(self.account,
                      Account) and self.account.sip.outbound_proxy is not None:
            uri = SIPURI(host=self.account.sip.outbound_proxy.host,
                         port=self.account.sip.outbound_proxy.port,
                         parameters={
                             'transport':
                             self.account.sip.outbound_proxy.transport
                         })
            self.log_info(u"Starting DNS lookup for %s through proxy %s" %
                          (target_uri.host, uri))
        elif isinstance(self.account,
                        Account) and self.account.sip.always_use_my_proxy:
            uri = SIPURI(host=self.account.id.domain)
            self.log_info(
                u"Starting DNS lookup for %s via proxy of account %s" %
                (target_uri.host, self.account.id))
        else:
            uri = target_uri
            self.log_info(u"Starting DNS lookup for %s" % target_uri.host)
        lookup.lookup_sip_proxy(uri, settings.sip.transport_list)

    def sendMessage(self, text, content_type="text/plain"):
        self.lookup_destination(self.target_uri)

        timestamp = Timestamp(datetime.datetime.now(tzlocal()))
        hash = hashlib.sha1()
        hash.update(text.encode("utf-8") + str(timestamp))
        msgid = hash.hexdigest()

        if content_type != "application/im-iscomposing+xml":
            icon = NSApp.delegate().contactsWindowController.iconPathForSelf()
            self.chatViewController.showMessage(msgid,
                                                'outgoing',
                                                None,
                                                icon,
                                                text,
                                                timestamp,
                                                state="sent")

            recipient = CPIMIdentity(self.target_uri, self.display_name)
            self.messages[msgid] = MessageInfo(msgid,
                                               sender=self.account,
                                               recipient=recipient,
                                               timestamp=timestamp,
                                               content_type=content_type,
                                               text=text,
                                               status="queued")

        self.queue.append((msgid, text, content_type))

    def textView_doCommandBySelector_(self, textView, selector):
        if selector == "insertNewline:" and self.chatViewController.inputText == textView:
            text = unicode(textView.string())
            textView.setString_("")
            textView.didChangeText()

            if text:
                self.sendMessage(text)
            self.chatViewController.resetTyping()

            recipient = CPIMIdentity(self.target_uri, self.display_name)
            self.notification_center.post_notification(
                'ChatViewControllerDidDisplayMessage',
                sender=self,
                data=TimestampedNotificationData(
                    direction='outgoing',
                    history_entry=False,
                    remote_party=format_identity_to_string(recipient),
                    local_party=format_identity_to_string(self.account)
                    if self.account is not BonjourAccount() else 'bonjour',
                    check_contact=True))

            return True
        return False

    def textDidChange_(self, notif):
        chars_left = MAX_MESSAGE_LENGTH - self.chatViewController.inputText.textStorage(
        ).length()
        self.splitView.setText_("%i chars left" % chars_left)

    def getContentView(self):
        return self.chatViewController.view

    def chatView_becameIdle_(self, chatView, last_active):
        if self.enableIsComposing:
            content = IsComposingMessage(
                state=State("idle"),
                refresh=Refresh(60),
                last_active=LastActive(last_active or datetime.now()),
                content_type=ContentType('text')).toxml()
            self.sendMessage(content, IsComposingDocument.content_type)

    def chatView_becameActive_(self, chatView, last_active):
        if self.enableIsComposing:
            content = IsComposingMessage(
                state=State("active"),
                refresh=Refresh(60),
                last_active=LastActive(last_active or datetime.now()),
                content_type=ContentType('text')).toxml()
            self.sendMessage(content, IsComposingDocument.content_type)

    def chatViewDidLoad_(self, chatView):
        self.replay_history()

    @run_in_green_thread
    def replay_history(self):
        results = self.history.get_messages(local_uri=self.local_uri,
                                            remote_uri=self.remote_uri,
                                            media_type='sms',
                                            count=self.showHistoryEntries)
        messages = [row for row in reversed(results)]
        self.render_history_messages(messages)

    @allocate_autorelease_pool
    @run_in_gui_thread
    def render_history_messages(self, messages):
        for message in messages:
            if message.direction == 'outgoing':
                icon = NSApp.delegate(
                ).contactsWindowController.iconPathForSelf()
            else:
                sender_uri = sipuri_components_from_string(
                    message.cpim_from)[0]
                icon = NSApp.delegate(
                ).contactsWindowController.iconPathForURI(sender_uri)

            timestamp = Timestamp.parse(message.cpim_timestamp)
            is_html = False if message.content_type == 'text' else True

            self.chatViewController.showMessage(message.msgid,
                                                message.direction,
                                                message.cpim_from,
                                                icon,
                                                message.body,
                                                timestamp,
                                                recipient=message.cpim_to,
                                                state=message.status,
                                                is_html=is_html,
                                                history_entry=True)

    def webviewFinishedLoading_(self, notification):
        self.document = self.outputView.mainFrameDocument()
        self.finishedLoading = True
        for script in self.messageQueue:
            self.outputView.stringByEvaluatingJavaScriptFromString_(script)
        self.messageQueue = []

        if hasattr(self.delegate, "chatViewDidLoad_"):
            self.delegate.chatViewDidLoad_(self)

    def webView_decidePolicyForNavigationAction_request_frame_decisionListener_(
            self, webView, info, request, frame, listener):
        # intercept link clicks so that they are opened in Safari
        theURL = info[WebActionOriginalURLKey]
        if theURL.scheme() == "file":
            listener.use()
        else:
            listener.ignore()
            NSWorkspace.sharedWorkspace().openURL_(theURL)
Ejemplo n.º 16
0
class HistoryViewer(NSWindowController):
    implements(IObserver)
    
    chatViewController = objc.IBOutlet()
    indexTable = objc.IBOutlet()
    contactTable = objc.IBOutlet()
    toolbar = objc.IBOutlet()

    entriesView = objc.IBOutlet()

    afterDate = objc.IBOutlet()
    searchText = objc.IBOutlet()
    searchMedia = objc.IBOutlet()
    searchContactBox = objc.IBOutlet()

    paginationButton = objc.IBOutlet()
    foundMessagesLabel = objc.IBOutlet()
    queryDatabaseLabel = objc.IBOutlet()
    busyIndicator = objc.IBOutlet()

    contactMenu = objc.IBOutlet()

    # viewer sections
    allContacts = []
    contacts = []
    dayly_entries = NSMutableArray.array()
    messages = []

    # database handler
    history = None

    # search filters
    start = 0
    search_text = None
    search_contact = None
    search_local = None
    search_media = None
    after_date = None
    before_date = None

    daily_order_fields = {'date': 'DESC', 'local_uri': 'ASC', 'remote_uri': 'ASC'}
    media_type_array = {0: None, 1: 'audio', 2: 'chat', 3: 'sms', 4: 'file-transfer', 5: 'audio-recording', 6: 'video-recording', 7: 'voicemail', 8: 'missed-call'}
    period_array = {0: None, 
                    1: datetime.datetime.now()-datetime.timedelta(days=1),
                    2: datetime.datetime.now()-datetime.timedelta(days=7),
                    3: datetime.datetime.now()-datetime.timedelta(days=31),
                    4: datetime.datetime.now()-datetime.timedelta(days=31),
                    5: datetime.datetime.now()-datetime.timedelta(days=90),
                    6: datetime.datetime.now()-datetime.timedelta(days=180),
                    7: datetime.datetime.now()-datetime.timedelta(days=365)
                    }

    def __new__(cls, *args, **kwargs):
        return cls.alloc().init()

    def __init__(self):
        if self:
            NSBundle.loadNibNamed_owner_("HistoryViewer", self)

            self.all_contacts = BlinkContact('Any Address', name=u'All Contacts')
            self.bonjour_contact = BlinkContact('bonjour', name=u'Bonjour Neighbours', icon=NSImage.imageNamed_("NSBonjour"))

            self.notification_center = NotificationCenter()
            self.notification_center.add_observer(self, name='ChatViewControllerDidDisplayMessage')
            self.notification_center.add_observer(self, name='AudioCallLoggedToHistory')
            self.notification_center.add_observer(self, name='BlinkContactsHaveChanged')
            self.notification_center.add_observer(self, name='BlinkTableViewSelectionChaged')

            self.searchText.cell().setSendsSearchStringImmediately_(True)
            self.searchText.cell().setPlaceholderString_("Type text and press Enter")

            self.chatViewController.setContentFile_(NSBundle.mainBundle().pathForResource_ofType_("ChatView", "html"))

            for c in ('remote_uri', 'local_uri', 'date', 'type'):
                col = self.indexTable.tableColumnWithIdentifier_(c)
                descriptor = NSSortDescriptor.alloc().initWithKey_ascending_(c, True)
                col.setSortDescriptorPrototype_(descriptor)

            self.history = ChatHistory()

            tag = self.afterDate.selectedItem().tag()
            if tag < 4:
                self.after_date = self.period_array[tag].strftime("%Y-%m-%d") if self.period_array[tag] else None
            else:
                self.before_date = self.period_array[tag].strftime("%Y-%m-%d") if self.period_array[tag] else None

            self.refreshViewer()

            self.selectedTableView = self.contactTable
 
    def awakeFromNib(self):
        NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(self, "contactSelectionChanged:", NSTableViewSelectionDidChangeNotification, self.contactTable)
        self.contactTable.setDoubleAction_("doubleClick:")

    def close_(self, sender):
        self.window().close()

    @allocate_autorelease_pool
    @run_in_gui_thread
    def refreshViewer(self):
        self.search_text = None
        self.search_contact = None
        self.search_local = None

        self.refreshContacts()
        self.refreshDailyEntries()
        self.refreshMessages()

    @run_in_green_thread
    def delete_messages(self, local_uri=None, remote_uri=None, date=None, after_date=None, before_date=None, media_type=None):
        self.history.delete_messages(local_uri=local_uri, remote_uri=remote_uri, date=date, after_date=after_date, before_date=before_date, media_type=media_type)
        self.refreshViewer()

    @run_in_green_thread
    def refreshContacts(self):
        if self.history:
            self.updateBusyIndicator(True)
            media_type = self.search_media if self.search_media else None
            search_text = self.search_text if self.search_text else None
            after_date = self.after_date if self.after_date else None
            before_date = self.before_date if self.before_date else None
            results = self.history.get_contacts(media_type=media_type, search_text=search_text, after_date=after_date, before_date=before_date)
            self.renderContacts(results)
            self.updateBusyIndicator(False)

    @allocate_autorelease_pool
    @run_in_gui_thread
    def renderContacts(self, results):
        getContactMatchingURI = NSApp.delegate().contactsWindowController.getContactMatchingURI

        self.contacts = [self.all_contacts, self.bonjour_contact]
        self.allContacts = []
        for row in results:
            contact = getContactMatchingURI(row[0])
            if contact:
                detail = contact.uri
                contact = BlinkContact(unicode(row[0]), name=contact.name, icon=contact.icon)
            else:
                detail = unicode(row[0])
                contact = BlinkContact(unicode(row[0]), name=unicode(row[0]))

            contact.setDetail(detail)
            self.contacts.append(contact)
            self.allContacts.append(contact)

        real_contacts = len(self.contacts)-2

        self.contactTable.reloadData()

        if self.search_contact:
            try:
                contact = (contact for contact in self.contacts if contact.uri == self.search_contact).next()
            except StopIteration:
                pass
            else:
                try:
                    row = self.contacts.index(contact)
                    self.contactTable.selectRowIndexes_byExtendingSelection_(NSIndexSet.indexSetWithIndex_(row), False)
                    self.contactTable.scrollRowToVisible_(row)
                except:
                    pass
        else:
            self.contactTable.selectRowIndexes_byExtendingSelection_(NSIndexSet.indexSetWithIndex_(0), False)
            self.contactTable.scrollRowToVisible_(0)

        self.contactTable.tableColumnWithIdentifier_('contacts').headerCell(). setStringValue_(u'%d Contacts'%real_contacts if real_contacts else u'Contacts')

    @run_in_green_thread
    def refreshDailyEntries(self, order_text=None):
        if self.history:
            self.resetDailyEntries()
            self.updateBusyIndicator(True)
            search_text = self.search_text if self.search_text else None
            remote_uri = self.search_contact if self.search_contact else None
            local_uri = self.search_local if self.search_local else None
            media_type = self.search_media if self.search_media else None
            after_date = self.after_date if self.after_date else None
            before_date = self.before_date if self.before_date else None
            results = self.history.get_daily_entries(local_uri=local_uri, remote_uri=remote_uri, media_type=media_type, search_text=search_text, order_text=order_text, after_date=after_date, before_date=before_date)
            self.renderDailyEntries(results)
            self.updateBusyIndicator(False)

    @allocate_autorelease_pool
    @run_in_gui_thread
    def resetDailyEntries(self):
        self.dayly_entries = NSMutableArray.array()
        self.indexTable.reloadData()

    @allocate_autorelease_pool
    @run_in_gui_thread
    def renderDailyEntries(self, results):
        getContactMatchingURI = NSApp.delegate().contactsWindowController.getContactMatchingURI
        self.dayly_entries = NSMutableArray.array()
        for result in results:
            contact = getContactMatchingURI(result[2])
            if contact:
                remote_uri = '%s <%s>' % (contact.name, contact.uri)
            else:
                remote_uri = result[2]

            entry = NSDictionary.dictionaryWithObjectsAndKeys_(result[1], "local_uri", remote_uri, "remote_uri", result[2], "remote_uri_sql", result[0], 'date', result[3], 'type')
            self.dayly_entries.addObject_(entry)

        self.dayly_entries.sortUsingDescriptors_(self.indexTable.sortDescriptors())
        self.indexTable.reloadData()

        if self.search_contact and not self.dayly_entries:
            self.contactTable.deselectAll_(True)

    @run_in_green_thread
    @allocate_autorelease_pool
    def refreshMessages(self, count=SQL_LIMIT, remote_uri=None, local_uri=None, media_type=None, date=None, after_date=None, before_date=None):
        self.updateBusyIndicator(True)
        if self.history:
            search_text = self.search_text if self.search_text else None
            if not remote_uri: 
                remote_uri = self.search_contact if self.search_contact else None
            if not local_uri: 
                local_uri = self.search_local if self.search_local else None
            if not media_type:
                media_type = self.search_media if self.search_media else None
            if not after_date:
                after_date = self.after_date if self.after_date else None
            if not before_date:
                before_date = self.before_date if self.before_date else None

            results = self.history.get_messages(count=count, local_uri=local_uri, remote_uri=remote_uri, media_type=media_type, date=date, search_text=search_text, after_date=after_date, before_date=before_date)

            # cache message for pagination
            self.messages=[]
            for e in reversed(results):
                self.messages.append(e)

            # reset pagination
            self.start=0
            self.renderMessages()
        self.updateBusyIndicator(False)
 
    @allocate_autorelease_pool
    @run_in_gui_thread
    def renderMessages(self):
        self.chatViewController.clear()
        self.chatViewController.resetRenderedMessages()

        end = self.start + MAX_MESSAGES_PER_PAGE if self.start + MAX_MESSAGES_PER_PAGE < len(self.messages) else len(self.messages)
        for row in self.messages[self.start:end]:
            self.renderMessage(row)

        self.paginationButton.setEnabled_forSegment_(True if len(self.messages)>MAX_MESSAGES_PER_PAGE and self.start > MAX_MESSAGES_PER_PAGE else False, 0)
        self.paginationButton.setEnabled_forSegment_(True if self.start else False, 1)
        self.paginationButton.setEnabled_forSegment_(True if self.start+MAX_MESSAGES_PER_PAGE+1 < len(self.messages) else False, 2)
        self.paginationButton.setEnabled_forSegment_(True if len(self.messages)>MAX_MESSAGES_PER_PAGE and len(self.messages) - self.start > 2*MAX_MESSAGES_PER_PAGE else False, 3)

        text = u'No entry found'
        if len(self.messages):
            if len(self.messages) == 1:
                text = u'Displaying 1 entry'
            elif MAX_MESSAGES_PER_PAGE > len(self.messages):
                text = u'Displaying %d entries' % end
            else:
                text = u'Displaying %d to %d out of %d entries' % (self.start+1, end, len(self.messages))

        self.foundMessagesLabel.setStringValue_(text)

    @allocate_autorelease_pool
    @run_in_gui_thread
    def renderMessage(self, message):
        if message.direction == 'outgoing':
            icon = NSApp.delegate().contactsWindowController.iconPathForSelf()
        else:
            sender_uri = sipuri_components_from_string(message.cpim_from)[0]
            # TODO: How to render the icons from Address Book? Especially in sandbox mode we do not have access to other folders
            icon = NSApp.delegate().contactsWindowController.iconPathForURI(sender_uri)
        try:
            timestamp=Timestamp.parse(message.cpim_timestamp)
        except ValueError:
            pass
        else:
            is_html = False if message.content_type == 'text' else True
            private = True if message.private == "1" else False
            self.chatViewController.showMessage(message.msgid, message.direction, message.cpim_from, icon, message.body, timestamp, is_private=private, recipient=message.cpim_to, state=message.status, is_html=is_html, history_entry=True)

    @objc.IBAction
    def paginateResults_(self, sender):
        if sender.selectedSegment() == 0:
           self.start = 0
        elif sender.selectedSegment() == 1:
           next_start = self.start - MAX_MESSAGES_PER_PAGE
           self.start = next_start if next_start >= 0 else self.start
        elif sender.selectedSegment() == 2:
           next_start = self.start + MAX_MESSAGES_PER_PAGE
           self.start = next_start if next_start < len(self.messages)-1 else self.start
        elif sender.selectedSegment() == 3:
           self.start = len(self.messages) - len(self.messages)%MAX_MESSAGES_PER_PAGE if len(self.messages) > MAX_MESSAGES_PER_PAGE else 0
        self.renderMessages()

    def tableView_deleteRow_(self, table, row):
        pass

    def tableView_sortDescriptorsDidChange_(self, table, odescr):
        self.dayly_entries.sortUsingDescriptors_(self.indexTable.sortDescriptors())
        self.indexTable.reloadData()

    @objc.IBAction
    def printDocument_(self, sender):
        printInfo = NSPrintInfo.sharedPrintInfo()
        printInfo.setTopMargin_(30)
        printInfo.setBottomMargin_(30)
        printInfo.setLeftMargin_(10)
        printInfo.setRightMargin_(10)
        printInfo.setOrientation_(NSPortraitOrientation)
        printInfo.setHorizontallyCentered_(True)
        printInfo.setVerticallyCentered_(False)
        printInfo.setHorizontalPagination_(NSFitPagination)
        printInfo.setVerticalPagination_(NSFitPagination)
        NSPrintInfo.setSharedPrintInfo_(printInfo)

        # print the content of the web view
        self.entriesView.mainFrame().frameView().documentView().print_(self)

    @objc.IBAction
    def search_(self, sender):
        if self.history:
            self.search_text = unicode(sender.stringValue()).lower()
            self.refreshContacts()
            row = self.indexTable.selectedRow()
            if row > 0:
                self.refreshDailyEntries()
                self.refreshMessages(local_uri=self.dayly_entries[row].objectForKey_("local_uri"), date=self.dayly_entries[row].objectForKey_("date"), media_type=self.dayly_entries[row].objectForKey_("type"))
            else: 
                row = self.contactTable.selectedRow()
                if row > 0:
                    self.refreshMessages()
                    self.refreshDailyEntries()
                else:    
                    self.refreshDailyEntries()
                    self.refreshMessages()

    @objc.IBAction
    def searchContacts_(self, sender):
        text = unicode(self.searchContactBox.stringValue().strip())
        contacts = [contact for contact in self.allContacts if text in contact] if text else self.allContacts
        self.contacts = [self.all_contacts, self.bonjour_contact] + contacts
        self.contactTable.reloadData()
        self.contactTable.selectRowIndexes_byExtendingSelection_(NSIndexSet.indexSetWithIndex_(0), False)
        self.contactTable.scrollRowToVisible_(0)

    def tableViewSelectionDidChange_(self, notification):
        if self.history:
            if not notification or notification.object() == self.contactTable:
                row = self.contactTable.selectedRow()
                if row == 0:
                    self.search_local = None 
                    self.search_contact = None
                elif row == 1:
                    self.search_local = 'bonjour' 
                    self.search_contact = None
                elif row > 1:
                    self.search_local = None
                    self.search_contact = self.contacts[row].uri

                self.refreshDailyEntries()
                self.refreshMessages()
            else:
                row = self.indexTable.selectedRow()
                if row >= 0:
                    self.refreshMessages(remote_uri=self.dayly_entries[row].objectForKey_("remote_uri_sql"), local_uri=self.dayly_entries[row].objectForKey_("local_uri"), date=self.dayly_entries[row].objectForKey_("date"), media_type=self.dayly_entries[row].objectForKey_("type"))

    def numberOfRowsInTableView_(self, table):
        if table == self.indexTable:
            return self.dayly_entries.count()
        elif table == self.contactTable:    
            return len(self.contacts)
        return 0

    def tableView_objectValueForTableColumn_row_(self, table, column, row):
        if table == self.indexTable:
            ident = column.identifier()
            try:
                return unicode(self.dayly_entries[row].objectForKey_(ident))
            except IndexError:
                return None    
        elif table == self.contactTable:
            try:
                if type(self.contacts[row]) in (str, unicode):
                    return self.contacts[row]
                else:
                    return self.contacts[row].name
            except IndexError:
                return None    
        return None 
    
    def tableView_willDisplayCell_forTableColumn_row_(self, table, cell, tableColumn, row):
        if table == self.contactTable:
            try:
                if row < len(self.contacts):
                    if type(self.contacts[row]) in (str, unicode):
                        cell.setContact_(None)
                    else:
                        cell.setContact_(self.contacts[row])
            except:
                pass
                
    def showWindow_(self, sender):
        self.window().makeKeyAndOrderFront_(None)

    @run_in_gui_thread
    def filterByContact(self, contact_uri, media_type=None):
        self.search_text = None
        self.search_local = None
        if media_type != self.search_media:
            for tag in self.media_type_array.keys():
                if self.media_type_array[tag] == media_type:
                    self.searchMedia.selectItemAtIndex_(tag)

        self.search_media = media_type
        self.search_contact = contact_uri
        self.refreshContacts()
        self.refreshDailyEntries()
        self.refreshMessages()

    @objc.IBAction
    def filterByMediaChanged_(self, sender):
        tag = sender.selectedItem().tag()
        self.search_media = self.media_type_array[tag]
        self.refreshContacts()
        self.refreshDailyEntries()
        self.refreshMessages()

    @objc.IBAction
    def filterByDateChanged_(self, sender):
        tag = sender.selectedItem().tag()
        if tag < 4:
            self.after_date = self.period_array[tag].strftime("%Y-%m-%d") if self.period_array[tag] else None
            self.before_date = None
        else:
            self.before_date = self.period_array[tag].strftime("%Y-%m-%d") if self.period_array[tag] else None
            self.after_date = None
            
        self.refreshContacts()
        self.refreshDailyEntries()
        self.refreshMessages()

    def validateToolbarItem_(self, item):
        if item.itemIdentifier() == NSToolbarPrintItemIdentifier and not self.messages:
            return False
        return True

    def toolbarWillAddItem_(self, notification):
        item = notification.userInfo()["item"]
        if item.itemIdentifier() == NSToolbarPrintItemIdentifier:
            item.setToolTip_("Print Current Entries")
            item.setTarget_(self)
            item.setAutovalidates_(True)

    @objc.IBAction
    def userClickedToolbarItem_(self, sender):
        if sender.itemIdentifier() == 'smileys':
            self.chatViewController.expandSmileys = not self.chatViewController.expandSmileys
            sender.setImage_(NSImage.imageNamed_("smiley_on" if self.chatViewController.expandSmileys else "smiley_off"))
            self.chatViewController.toggleSmileys(self.chatViewController.expandSmileys)
        elif sender.itemIdentifier() == 'delete':
            if self.selectedTableView == self.contactTable:
                try:
                    row = self.contactTable.selectedRow()
                    self.showDeleteConfirmationDialog(row)
                except IndexError:
                    pass

            elif self.selectedTableView == self.indexTable:
                try:
                    row = self.indexTable.selectedRow()
                    local_uri = self.dayly_entries[row].objectForKey_("local_uri")
                    remote_uri = self.dayly_entries[row].objectForKey_("remote_uri")
                    date = self.dayly_entries[row].objectForKey_("date")
                    media_type = self.dayly_entries[row].objectForKey_("type")

                    ret = NSRunAlertPanel(u"Purge History Entries", u"Please confirm the deletion of %s history entries from %s on %s. This operation cannot be undone."%(media_type, remote_uri, date), u"Confirm", u"Cancel", None)
                    if ret == NSAlertDefaultReturn:
                        self.delete_messages(local_uri=local_uri, remote_uri=remote_uri, media_type=media_type, date=date)
                except IndexError:
                    pass

    def showDeleteConfirmationDialog(self, row):
        media_print = self.search_media or 'All'
        tag = self.afterDate.selectedItem().tag()
        tag = self.afterDate.selectedItem().tag()

        period = '%s %s' % (' newer than ' if tag < 4 else ' older than ', self.period_array[tag].strftime("%Y-%m-%d")) if tag else ''

        if row == 0:
            ret = NSRunAlertPanel(u"Purge History Entries", u"Please confirm the deletion of %s history entries%s. This operation cannot be undone."%(media_print, period), u"Confirm", u"Cancel", None)
            if ret == NSAlertDefaultReturn:
                self.delete_messages(media_type=self.search_media, after_date=self.after_date, before_date=self.before_date)
        elif row == 1:
            remote_uri=self.contacts[row].uri
            ret = NSRunAlertPanel(u"Purge History Entries", u"Please confirm the deletion of %s Bonjour history entries%s. This operation cannot be undone."%(media_print, period), u"Confirm", u"Cancel", None)
            if ret == NSAlertDefaultReturn:
                self.delete_messages(local_uri='bonjour', media_type=self.search_media, after_date=self.after_date, before_date=self.before_date)
        else:
            remote_uri=self.contacts[row].uri
            ret = NSRunAlertPanel(u"Purge History Entries", u"Please confirm the deletion of %s history entries from %s%s. This operation cannot be undone."%(media_print, remote_uri, period), u"Confirm", u"Cancel", None)
            if ret == NSAlertDefaultReturn:
                self.delete_messages(remote_uri=remote_uri, media_type=self.search_media, after_date=self.after_date, before_date=self.before_date)

    @allocate_autorelease_pool
    @run_in_gui_thread
    def updateBusyIndicator(self, busy=False):
        if busy:
            self.queryDatabaseLabel.setHidden_(False)
            self.busyIndicator.setHidden_(False)
            self.busyIndicator.setIndeterminate_(True)
            self.busyIndicator.setStyle_(NSProgressIndicatorSpinningStyle)
            self.busyIndicator.startAnimation_(None)
        else:
            self.busyIndicator.stopAnimation_(None)
            self.busyIndicator.setHidden_(True)
            self.queryDatabaseLabel.setHidden_(True)

    @allocate_autorelease_pool
    def handle_notification(self, notification):
        if notification.name in ("ChatViewControllerDidDisplayMessage", "AudioCallLoggedToHistory"):
            if notification.data.local_party != 'bonjour':
                exists = any(contact for contact in self.contacts if notification.data.remote_party == contact.uri)
                if not exists:
                    self.refreshContacts()
        elif notification.name == 'BlinkContactsHaveChanged':
            self.refreshContacts()
            self.toolbar.validateVisibleItems()
        elif notification.name == 'BlinkTableViewSelectionChaged':
            self.selectedTableView = notification.sender
            self.toolbar.validateVisibleItems()

    def contactSelectionChanged_(self, notification):
        hasContactMatchingURI = NSApp.delegate().contactsWindowController.hasContactMatchingURI
        try:
            row = self.contactTable.selectedRow()
            remote_uri=self.contacts[row].uri
            self.contactMenu.itemWithTag_(1).setEnabled_(False if hasContactMatchingURI(remote_uri) or row < 2 else True)
            self.contactMenu.itemWithTag_(3).setEnabled_(False if row < 2 else True)
        except:
            self.contactMenu.itemWithTag_(1).setEnabled_(False)

    def menuWillOpen_(self, menu):
        if menu == self.contactMenu:
            pass

    @objc.IBAction
    def doubleClick_(self, sender):
        try:
            row = self.contactTable.selectedRow()
        except:
            return

        if row < 2:
            return

        try:
            contact = self.contacts[row]
        except IndexError:
            return

        NSApp.delegate().contactsWindowController.startSessionWithSIPURI(contact.uri)

    @objc.IBAction
    def userClickedContactMenu_(self, sender):
        try:
            row = self.contactTable.selectedRow()
        except:
            return

        try:
            contact = self.contacts[row]
        except IndexError:
            return

        tag = sender.tag()

        if tag == 1:
            NSApp.delegate().contactsWindowController.addContact(contact.uri, contact.display_name)
        elif tag == 2:
            self.showDeleteConfirmationDialog(row)
        elif tag == 3:
            NSApp.delegate().contactsWindowController.searchBox.setStringValue_(contact.uri)
            NSApp.delegate().contactsWindowController.searchContacts()
            NSApp.delegate().contactsWindowController.window().makeFirstResponder_(NSApp.delegate().contactsWindowController.searchBox)
            NSApp.delegate().contactsWindowController.window().deminiaturize_(sender)
            NSApp.delegate().contactsWindowController.window().makeKeyWindow()

    @objc.IBAction
    def userClickedActionsButton_(self, sender):
        point = sender.convertPointToBase_(NSZeroPoint)
        point.x += 20
        point.y -= 10
        event = NSEvent.mouseEventWithType_location_modifierFlags_timestamp_windowNumber_context_eventNumber_clickCount_pressure_(
                    NSLeftMouseUp, point, 0, NSDate.timeIntervalSinceReferenceDate(), sender.window().windowNumber(),
                    sender.window().graphicsContext(), 0, 1, 0)
        NSMenu.popUpContextMenu_withEvent_forView_(self.contactMenu, event, sender)
Ejemplo n.º 17
0
class HistoryViewer(NSWindowController):
    implements(IObserver)

    chatViewController = objc.IBOutlet()
    indexTable = objc.IBOutlet()
    contactTable = objc.IBOutlet()
    toolbar = objc.IBOutlet()

    entriesView = objc.IBOutlet()

    afterDate = objc.IBOutlet()
    searchText = objc.IBOutlet()
    searchMedia = objc.IBOutlet()
    searchContactBox = objc.IBOutlet()

    paginationButton = objc.IBOutlet()
    foundMessagesLabel = objc.IBOutlet()
    queryDatabaseLabel = objc.IBOutlet()
    busyIndicator = objc.IBOutlet()

    contactMenu = objc.IBOutlet()

    # viewer sections
    allContacts = []
    contacts = []
    dayly_entries = NSMutableArray.array()
    messages = []

    # database handler
    history = None

    # search filters
    start = 0
    search_text = None
    search_contact = None
    search_local = None
    search_media = None
    after_date = None
    before_date = None

    daily_order_fields = {
        'date': 'DESC',
        'local_uri': 'ASC',
        'remote_uri': 'ASC'
    }
    media_type_array = {
        0: None,
        1: 'audio',
        2: 'chat',
        3: 'sms',
        4: 'file-transfer',
        5: 'audio-recording',
        6: 'video-recording',
        7: 'voicemail',
        8: 'missed-call'
    }
    period_array = {
        0: None,
        1: datetime.datetime.now() - datetime.timedelta(days=1),
        2: datetime.datetime.now() - datetime.timedelta(days=7),
        3: datetime.datetime.now() - datetime.timedelta(days=31),
        4: datetime.datetime.now() - datetime.timedelta(days=31),
        5: datetime.datetime.now() - datetime.timedelta(days=90),
        6: datetime.datetime.now() - datetime.timedelta(days=180),
        7: datetime.datetime.now() - datetime.timedelta(days=365)
    }

    def __new__(cls, *args, **kwargs):
        return cls.alloc().init()

    def __init__(self):
        if self:
            NSBundle.loadNibNamed_owner_("HistoryViewer", self)

            self.all_contacts = BlinkContact('Any Address',
                                             name=u'All Contacts')
            self.bonjour_contact = BlinkContact(
                'bonjour',
                name=u'Bonjour Neighbours',
                icon=NSImage.imageNamed_("NSBonjour"))

            self.notification_center = NotificationCenter()
            self.notification_center.add_observer(
                self, name='ChatViewControllerDidDisplayMessage')
            self.notification_center.add_observer(
                self, name='AudioCallLoggedToHistory')
            self.notification_center.add_observer(
                self, name='BlinkContactsHaveChanged')
            self.notification_center.add_observer(
                self, name='BlinkTableViewSelectionChaged')

            self.searchText.cell().setSendsSearchStringImmediately_(True)
            self.searchText.cell().setPlaceholderString_(
                "Type text and press Enter")

            self.chatViewController.setContentFile_(
                NSBundle.mainBundle().pathForResource_ofType_(
                    "ChatView", "html"))

            for c in ('remote_uri', 'local_uri', 'date', 'type'):
                col = self.indexTable.tableColumnWithIdentifier_(c)
                descriptor = NSSortDescriptor.alloc().initWithKey_ascending_(
                    c, True)
                col.setSortDescriptorPrototype_(descriptor)

            self.history = ChatHistory()

            tag = self.afterDate.selectedItem().tag()
            if tag < 4:
                self.after_date = self.period_array[tag].strftime(
                    "%Y-%m-%d") if self.period_array[tag] else None
            else:
                self.before_date = self.period_array[tag].strftime(
                    "%Y-%m-%d") if self.period_array[tag] else None

            self.refreshViewer()

            self.selectedTableView = self.contactTable

    def awakeFromNib(self):
        NSNotificationCenter.defaultCenter().addObserver_selector_name_object_(
            self, "contactSelectionChanged:",
            NSTableViewSelectionDidChangeNotification, self.contactTable)
        self.contactTable.setDoubleAction_("doubleClick:")

    def close_(self, sender):
        self.window().close()

    @allocate_autorelease_pool
    @run_in_gui_thread
    def refreshViewer(self):
        self.search_text = None
        self.search_contact = None
        self.search_local = None

        self.refreshContacts()
        self.refreshDailyEntries()
        self.refreshMessages()

    @run_in_green_thread
    def delete_messages(self,
                        local_uri=None,
                        remote_uri=None,
                        date=None,
                        after_date=None,
                        before_date=None,
                        media_type=None):
        self.history.delete_messages(local_uri=local_uri,
                                     remote_uri=remote_uri,
                                     date=date,
                                     after_date=after_date,
                                     before_date=before_date,
                                     media_type=media_type)
        self.refreshViewer()

    @run_in_green_thread
    def refreshContacts(self):
        if self.history:
            self.updateBusyIndicator(True)
            media_type = self.search_media if self.search_media else None
            search_text = self.search_text if self.search_text else None
            after_date = self.after_date if self.after_date else None
            before_date = self.before_date if self.before_date else None
            results = self.history.get_contacts(media_type=media_type,
                                                search_text=search_text,
                                                after_date=after_date,
                                                before_date=before_date)
            self.renderContacts(results)
            self.updateBusyIndicator(False)

    @allocate_autorelease_pool
    @run_in_gui_thread
    def renderContacts(self, results):
        getContactMatchingURI = NSApp.delegate(
        ).contactsWindowController.getContactMatchingURI

        self.contacts = [self.all_contacts, self.bonjour_contact]
        self.allContacts = []
        for row in results:
            contact = getContactMatchingURI(row[0])
            if contact:
                detail = contact.uri
                contact = BlinkContact(unicode(row[0]),
                                       name=contact.name,
                                       icon=contact.icon)
            else:
                detail = unicode(row[0])
                contact = BlinkContact(unicode(row[0]), name=unicode(row[0]))

            contact.setDetail(detail)
            self.contacts.append(contact)
            self.allContacts.append(contact)

        real_contacts = len(self.contacts) - 2

        self.contactTable.reloadData()

        if self.search_contact:
            try:
                contact = (contact for contact in self.contacts
                           if contact.uri == self.search_contact).next()
            except StopIteration:
                pass
            else:
                try:
                    row = self.contacts.index(contact)
                    self.contactTable.selectRowIndexes_byExtendingSelection_(
                        NSIndexSet.indexSetWithIndex_(row), False)
                    self.contactTable.scrollRowToVisible_(row)
                except:
                    pass
        else:
            self.contactTable.selectRowIndexes_byExtendingSelection_(
                NSIndexSet.indexSetWithIndex_(0), False)
            self.contactTable.scrollRowToVisible_(0)

        self.contactTable.tableColumnWithIdentifier_('contacts').headerCell(
        ).setStringValue_(u'%d Contacts' %
                          real_contacts if real_contacts else u'Contacts')

    @run_in_green_thread
    def refreshDailyEntries(self, order_text=None):
        if self.history:
            self.resetDailyEntries()
            self.updateBusyIndicator(True)
            search_text = self.search_text if self.search_text else None
            remote_uri = self.search_contact if self.search_contact else None
            local_uri = self.search_local if self.search_local else None
            media_type = self.search_media if self.search_media else None
            after_date = self.after_date if self.after_date else None
            before_date = self.before_date if self.before_date else None
            results = self.history.get_daily_entries(local_uri=local_uri,
                                                     remote_uri=remote_uri,
                                                     media_type=media_type,
                                                     search_text=search_text,
                                                     order_text=order_text,
                                                     after_date=after_date,
                                                     before_date=before_date)
            self.renderDailyEntries(results)
            self.updateBusyIndicator(False)

    @allocate_autorelease_pool
    @run_in_gui_thread
    def resetDailyEntries(self):
        self.dayly_entries = NSMutableArray.array()
        self.indexTable.reloadData()

    @allocate_autorelease_pool
    @run_in_gui_thread
    def renderDailyEntries(self, results):
        getContactMatchingURI = NSApp.delegate(
        ).contactsWindowController.getContactMatchingURI
        self.dayly_entries = NSMutableArray.array()
        for result in results:
            contact = getContactMatchingURI(result[2])
            if contact:
                remote_uri = '%s <%s>' % (contact.name, contact.uri)
            else:
                remote_uri = result[2]

            entry = NSDictionary.dictionaryWithObjectsAndKeys_(
                result[1], "local_uri", remote_uri, "remote_uri", result[2],
                "remote_uri_sql", result[0], 'date', result[3], 'type')
            self.dayly_entries.addObject_(entry)

        self.dayly_entries.sortUsingDescriptors_(
            self.indexTable.sortDescriptors())
        self.indexTable.reloadData()

        if self.search_contact and not self.dayly_entries:
            self.contactTable.deselectAll_(True)

    @run_in_green_thread
    @allocate_autorelease_pool
    def refreshMessages(self,
                        count=SQL_LIMIT,
                        remote_uri=None,
                        local_uri=None,
                        media_type=None,
                        date=None,
                        after_date=None,
                        before_date=None):
        self.updateBusyIndicator(True)
        if self.history:
            search_text = self.search_text if self.search_text else None
            if not remote_uri:
                remote_uri = self.search_contact if self.search_contact else None
            if not local_uri:
                local_uri = self.search_local if self.search_local else None
            if not media_type:
                media_type = self.search_media if self.search_media else None
            if not after_date:
                after_date = self.after_date if self.after_date else None
            if not before_date:
                before_date = self.before_date if self.before_date else None

            results = self.history.get_messages(count=count,
                                                local_uri=local_uri,
                                                remote_uri=remote_uri,
                                                media_type=media_type,
                                                date=date,
                                                search_text=search_text,
                                                after_date=after_date,
                                                before_date=before_date)

            # cache message for pagination
            self.messages = []
            for e in reversed(results):
                self.messages.append(e)

            # reset pagination
            self.start = 0
            self.renderMessages()
        self.updateBusyIndicator(False)

    @allocate_autorelease_pool
    @run_in_gui_thread
    def renderMessages(self):
        self.chatViewController.clear()
        self.chatViewController.resetRenderedMessages()

        end = self.start + MAX_MESSAGES_PER_PAGE if self.start + MAX_MESSAGES_PER_PAGE < len(
            self.messages) else len(self.messages)
        for row in self.messages[self.start:end]:
            self.renderMessage(row)

        self.paginationButton.setEnabled_forSegment_(
            True if len(self.messages) > MAX_MESSAGES_PER_PAGE
            and self.start > MAX_MESSAGES_PER_PAGE else False, 0)
        self.paginationButton.setEnabled_forSegment_(
            True if self.start else False, 1)
        self.paginationButton.setEnabled_forSegment_(
            True if self.start + MAX_MESSAGES_PER_PAGE + 1 < len(self.messages)
            else False, 2)
        self.paginationButton.setEnabled_forSegment_(
            True if len(self.messages) > MAX_MESSAGES_PER_PAGE
            and len(self.messages) - self.start > 2 * MAX_MESSAGES_PER_PAGE
            else False, 3)

        text = u'No entry found'
        if len(self.messages):
            if len(self.messages) == 1:
                text = u'Displaying 1 entry'
            elif MAX_MESSAGES_PER_PAGE > len(self.messages):
                text = u'Displaying %d entries' % end
            else:
                text = u'Displaying %d to %d out of %d entries' % (
                    self.start + 1, end, len(self.messages))

        self.foundMessagesLabel.setStringValue_(text)

    @allocate_autorelease_pool
    @run_in_gui_thread
    def renderMessage(self, message):
        if message.direction == 'outgoing':
            icon = NSApp.delegate().contactsWindowController.iconPathForSelf()
        else:
            sender_uri = sipuri_components_from_string(message.cpim_from)[0]
            # TODO: How to render the icons from Address Book? Especially in sandbox mode we do not have access to other folders
            icon = NSApp.delegate().contactsWindowController.iconPathForURI(
                sender_uri)
        try:
            timestamp = Timestamp.parse(message.cpim_timestamp)
        except ValueError:
            pass
        else:
            is_html = False if message.content_type == 'text' else True
            private = True if message.private == "1" else False
            self.chatViewController.showMessage(message.msgid,
                                                message.direction,
                                                message.cpim_from,
                                                icon,
                                                message.body,
                                                timestamp,
                                                is_private=private,
                                                recipient=message.cpim_to,
                                                state=message.status,
                                                is_html=is_html,
                                                history_entry=True)

    @objc.IBAction
    def paginateResults_(self, sender):
        if sender.selectedSegment() == 0:
            self.start = 0
        elif sender.selectedSegment() == 1:
            next_start = self.start - MAX_MESSAGES_PER_PAGE
            self.start = next_start if next_start >= 0 else self.start
        elif sender.selectedSegment() == 2:
            next_start = self.start + MAX_MESSAGES_PER_PAGE
            self.start = next_start if next_start < len(
                self.messages) - 1 else self.start
        elif sender.selectedSegment() == 3:
            self.start = len(self.messages) - len(
                self.messages) % MAX_MESSAGES_PER_PAGE if len(
                    self.messages) > MAX_MESSAGES_PER_PAGE else 0
        self.renderMessages()

    def tableView_deleteRow_(self, table, row):
        pass

    def tableView_sortDescriptorsDidChange_(self, table, odescr):
        self.dayly_entries.sortUsingDescriptors_(
            self.indexTable.sortDescriptors())
        self.indexTable.reloadData()

    @objc.IBAction
    def printDocument_(self, sender):
        printInfo = NSPrintInfo.sharedPrintInfo()
        printInfo.setTopMargin_(30)
        printInfo.setBottomMargin_(30)
        printInfo.setLeftMargin_(10)
        printInfo.setRightMargin_(10)
        printInfo.setOrientation_(NSPortraitOrientation)
        printInfo.setHorizontallyCentered_(True)
        printInfo.setVerticallyCentered_(False)
        printInfo.setHorizontalPagination_(NSFitPagination)
        printInfo.setVerticalPagination_(NSFitPagination)
        NSPrintInfo.setSharedPrintInfo_(printInfo)

        # print the content of the web view
        self.entriesView.mainFrame().frameView().documentView().print_(self)

    @objc.IBAction
    def search_(self, sender):
        if self.history:
            self.search_text = unicode(sender.stringValue()).lower()
            self.refreshContacts()
            row = self.indexTable.selectedRow()
            if row > 0:
                self.refreshDailyEntries()
                self.refreshMessages(
                    local_uri=self.dayly_entries[row].objectForKey_(
                        "local_uri"),
                    date=self.dayly_entries[row].objectForKey_("date"),
                    media_type=self.dayly_entries[row].objectForKey_("type"))
            else:
                row = self.contactTable.selectedRow()
                if row > 0:
                    self.refreshMessages()
                    self.refreshDailyEntries()
                else:
                    self.refreshDailyEntries()
                    self.refreshMessages()

    @objc.IBAction
    def searchContacts_(self, sender):
        text = unicode(self.searchContactBox.stringValue().strip())
        contacts = [
            contact for contact in self.allContacts if text in contact
        ] if text else self.allContacts
        self.contacts = [self.all_contacts, self.bonjour_contact] + contacts
        self.contactTable.reloadData()
        self.contactTable.selectRowIndexes_byExtendingSelection_(
            NSIndexSet.indexSetWithIndex_(0), False)
        self.contactTable.scrollRowToVisible_(0)

    def tableViewSelectionDidChange_(self, notification):
        if self.history:
            if not notification or notification.object() == self.contactTable:
                row = self.contactTable.selectedRow()
                if row == 0:
                    self.search_local = None
                    self.search_contact = None
                elif row == 1:
                    self.search_local = 'bonjour'
                    self.search_contact = None
                elif row > 1:
                    self.search_local = None
                    self.search_contact = self.contacts[row].uri

                self.refreshDailyEntries()
                self.refreshMessages()
            else:
                row = self.indexTable.selectedRow()
                if row >= 0:
                    self.refreshMessages(
                        remote_uri=self.dayly_entries[row].objectForKey_(
                            "remote_uri_sql"),
                        local_uri=self.dayly_entries[row].objectForKey_(
                            "local_uri"),
                        date=self.dayly_entries[row].objectForKey_("date"),
                        media_type=self.dayly_entries[row].objectForKey_(
                            "type"))

    def numberOfRowsInTableView_(self, table):
        if table == self.indexTable:
            return self.dayly_entries.count()
        elif table == self.contactTable:
            return len(self.contacts)
        return 0

    def tableView_objectValueForTableColumn_row_(self, table, column, row):
        if table == self.indexTable:
            ident = column.identifier()
            try:
                return unicode(self.dayly_entries[row].objectForKey_(ident))
            except IndexError:
                return None
        elif table == self.contactTable:
            try:
                if type(self.contacts[row]) in (str, unicode):
                    return self.contacts[row]
                else:
                    return self.contacts[row].name
            except IndexError:
                return None
        return None

    def tableView_willDisplayCell_forTableColumn_row_(self, table, cell,
                                                      tableColumn, row):
        if table == self.contactTable:
            try:
                if row < len(self.contacts):
                    if type(self.contacts[row]) in (str, unicode):
                        cell.setContact_(None)
                    else:
                        cell.setContact_(self.contacts[row])
            except:
                pass

    def showWindow_(self, sender):
        self.window().makeKeyAndOrderFront_(None)

    @run_in_gui_thread
    def filterByContact(self, contact_uri, media_type=None):
        self.search_text = None
        self.search_local = None
        if media_type != self.search_media:
            for tag in self.media_type_array.keys():
                if self.media_type_array[tag] == media_type:
                    self.searchMedia.selectItemAtIndex_(tag)

        self.search_media = media_type
        self.search_contact = contact_uri
        self.refreshContacts()
        self.refreshDailyEntries()
        self.refreshMessages()

    @objc.IBAction
    def filterByMediaChanged_(self, sender):
        tag = sender.selectedItem().tag()
        self.search_media = self.media_type_array[tag]
        self.refreshContacts()
        self.refreshDailyEntries()
        self.refreshMessages()

    @objc.IBAction
    def filterByDateChanged_(self, sender):
        tag = sender.selectedItem().tag()
        if tag < 4:
            self.after_date = self.period_array[tag].strftime(
                "%Y-%m-%d") if self.period_array[tag] else None
            self.before_date = None
        else:
            self.before_date = self.period_array[tag].strftime(
                "%Y-%m-%d") if self.period_array[tag] else None
            self.after_date = None

        self.refreshContacts()
        self.refreshDailyEntries()
        self.refreshMessages()

    def validateToolbarItem_(self, item):
        if item.itemIdentifier(
        ) == NSToolbarPrintItemIdentifier and not self.messages:
            return False
        return True

    def toolbarWillAddItem_(self, notification):
        item = notification.userInfo()["item"]
        if item.itemIdentifier() == NSToolbarPrintItemIdentifier:
            item.setToolTip_("Print Current Entries")
            item.setTarget_(self)
            item.setAutovalidates_(True)

    @objc.IBAction
    def userClickedToolbarItem_(self, sender):
        if sender.itemIdentifier() == 'smileys':
            self.chatViewController.expandSmileys = not self.chatViewController.expandSmileys
            sender.setImage_(
                NSImage.imageNamed_("smiley_on" if self.chatViewController.
                                    expandSmileys else "smiley_off"))
            self.chatViewController.toggleSmileys(
                self.chatViewController.expandSmileys)
        elif sender.itemIdentifier() == 'delete':
            if self.selectedTableView == self.contactTable:
                try:
                    row = self.contactTable.selectedRow()
                    self.showDeleteConfirmationDialog(row)
                except IndexError:
                    pass

            elif self.selectedTableView == self.indexTable:
                try:
                    row = self.indexTable.selectedRow()
                    local_uri = self.dayly_entries[row].objectForKey_(
                        "local_uri")
                    remote_uri = self.dayly_entries[row].objectForKey_(
                        "remote_uri")
                    date = self.dayly_entries[row].objectForKey_("date")
                    media_type = self.dayly_entries[row].objectForKey_("type")

                    ret = NSRunAlertPanel(
                        u"Purge History Entries",
                        u"Please confirm the deletion of %s history entries from %s on %s. This operation cannot be undone."
                        % (media_type, remote_uri, date), u"Confirm",
                        u"Cancel", None)
                    if ret == NSAlertDefaultReturn:
                        self.delete_messages(local_uri=local_uri,
                                             remote_uri=remote_uri,
                                             media_type=media_type,
                                             date=date)
                except IndexError:
                    pass

    def showDeleteConfirmationDialog(self, row):
        media_print = self.search_media or 'All'
        tag = self.afterDate.selectedItem().tag()
        tag = self.afterDate.selectedItem().tag()

        period = '%s %s' % (
            ' newer than ' if tag < 4 else ' older than ',
            self.period_array[tag].strftime("%Y-%m-%d")) if tag else ''

        if row == 0:
            ret = NSRunAlertPanel(
                u"Purge History Entries",
                u"Please confirm the deletion of %s history entries%s. This operation cannot be undone."
                % (media_print, period), u"Confirm", u"Cancel", None)
            if ret == NSAlertDefaultReturn:
                self.delete_messages(media_type=self.search_media,
                                     after_date=self.after_date,
                                     before_date=self.before_date)
        elif row == 1:
            remote_uri = self.contacts[row].uri
            ret = NSRunAlertPanel(
                u"Purge History Entries",
                u"Please confirm the deletion of %s Bonjour history entries%s. This operation cannot be undone."
                % (media_print, period), u"Confirm", u"Cancel", None)
            if ret == NSAlertDefaultReturn:
                self.delete_messages(local_uri='bonjour',
                                     media_type=self.search_media,
                                     after_date=self.after_date,
                                     before_date=self.before_date)
        else:
            remote_uri = self.contacts[row].uri
            ret = NSRunAlertPanel(
                u"Purge History Entries",
                u"Please confirm the deletion of %s history entries from %s%s. This operation cannot be undone."
                % (media_print, remote_uri, period), u"Confirm", u"Cancel",
                None)
            if ret == NSAlertDefaultReturn:
                self.delete_messages(remote_uri=remote_uri,
                                     media_type=self.search_media,
                                     after_date=self.after_date,
                                     before_date=self.before_date)

    @allocate_autorelease_pool
    @run_in_gui_thread
    def updateBusyIndicator(self, busy=False):
        if busy:
            self.queryDatabaseLabel.setHidden_(False)
            self.busyIndicator.setHidden_(False)
            self.busyIndicator.setIndeterminate_(True)
            self.busyIndicator.setStyle_(NSProgressIndicatorSpinningStyle)
            self.busyIndicator.startAnimation_(None)
        else:
            self.busyIndicator.stopAnimation_(None)
            self.busyIndicator.setHidden_(True)
            self.queryDatabaseLabel.setHidden_(True)

    @allocate_autorelease_pool
    def handle_notification(self, notification):
        if notification.name in ("ChatViewControllerDidDisplayMessage",
                                 "AudioCallLoggedToHistory"):
            if notification.data.local_party != 'bonjour':
                exists = any(contact for contact in self.contacts
                             if notification.data.remote_party == contact.uri)
                if not exists:
                    self.refreshContacts()
        elif notification.name == 'BlinkContactsHaveChanged':
            self.refreshContacts()
            self.toolbar.validateVisibleItems()
        elif notification.name == 'BlinkTableViewSelectionChaged':
            self.selectedTableView = notification.sender
            self.toolbar.validateVisibleItems()

    def contactSelectionChanged_(self, notification):
        hasContactMatchingURI = NSApp.delegate(
        ).contactsWindowController.hasContactMatchingURI
        try:
            row = self.contactTable.selectedRow()
            remote_uri = self.contacts[row].uri
            self.contactMenu.itemWithTag_(1).setEnabled_(
                False if hasContactMatchingURI(remote_uri) or row < 2 else True
            )
            self.contactMenu.itemWithTag_(3).setEnabled_(
                False if row < 2 else True)
        except:
            self.contactMenu.itemWithTag_(1).setEnabled_(False)

    def menuWillOpen_(self, menu):
        if menu == self.contactMenu:
            pass

    @objc.IBAction
    def doubleClick_(self, sender):
        try:
            row = self.contactTable.selectedRow()
        except:
            return

        if row < 2:
            return

        try:
            contact = self.contacts[row]
        except IndexError:
            return

        NSApp.delegate().contactsWindowController.startSessionWithSIPURI(
            contact.uri)

    @objc.IBAction
    def userClickedContactMenu_(self, sender):
        try:
            row = self.contactTable.selectedRow()
        except:
            return

        try:
            contact = self.contacts[row]
        except IndexError:
            return

        tag = sender.tag()

        if tag == 1:
            NSApp.delegate().contactsWindowController.addContact(
                contact.uri, contact.display_name)
        elif tag == 2:
            self.showDeleteConfirmationDialog(row)
        elif tag == 3:
            NSApp.delegate(
            ).contactsWindowController.searchBox.setStringValue_(contact.uri)
            NSApp.delegate().contactsWindowController.searchContacts()
            NSApp.delegate().contactsWindowController.window(
            ).makeFirstResponder_(
                NSApp.delegate().contactsWindowController.searchBox)
            NSApp.delegate().contactsWindowController.window().deminiaturize_(
                sender)
            NSApp.delegate().contactsWindowController.window().makeKeyWindow()

    @objc.IBAction
    def userClickedActionsButton_(self, sender):
        point = sender.convertPointToBase_(NSZeroPoint)
        point.x += 20
        point.y -= 10
        event = NSEvent.mouseEventWithType_location_modifierFlags_timestamp_windowNumber_context_eventNumber_clickCount_pressure_(
            NSLeftMouseUp, point, 0, NSDate.timeIntervalSinceReferenceDate(),
            sender.window().windowNumber(),
            sender.window().graphicsContext(), 0, 1, 0)
        NSMenu.popUpContextMenu_withEvent_forView_(self.contactMenu, event,
                                                   sender)