Commit 8e38a1ae authored by Saul Ibarra's avatar Saul Ibarra

Added integration with Google Contacts

parent f8ba1d97
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
"""Definitions of datatypes for use in settings extensions.""" """Definitions of datatypes for use in settings extensions."""
__all__ = ['ApplicationDataPath', 'SoundFile', 'DefaultPath', 'CustomSoundFile', 'HTTPURL'] __all__ = ['ApplicationDataPath', 'SoundFile', 'DefaultPath', 'CustomSoundFile', 'HTTPURL', 'InvalidToken', 'AuthorizationToken']
import os import os
import re import re
...@@ -113,3 +113,33 @@ class HTTPURL(unicode): ...@@ -113,3 +113,33 @@ class HTTPURL(unicode):
return value return value
class InvalidToken(object):
def __repr__(self):
return self.__class__.__name__
class AuthorizationToken(object):
def __init__(self, token=None):
self.token = token
def __getstate__(self):
if self.token is InvalidToken:
return u'invalid'
else:
return u'value:%s' % (self.__dict__['token'])
def __setstate__(self, state):
match = re.match(r'^(?P<type>invalid|value:)(?P<token>.+?)?$', state)
if match is None:
raise ValueError('illegal value: %r' % state)
data = match.groupdict()
if data.pop('type') == 'invalid':
data['token'] = InvalidToken
self.__init__(data['token'])
def __nonzero__(self):
return self.token is not InvalidToken
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, self.token)
...@@ -12,7 +12,7 @@ from sipsimple.configuration import Setting, SettingsGroup, SettingsObjectExtens ...@@ -12,7 +12,7 @@ from sipsimple.configuration import Setting, SettingsGroup, SettingsObjectExtens
from sipsimple.configuration.settings import AudioSettings, LogsSettings, TLSSettings from sipsimple.configuration.settings import AudioSettings, LogsSettings, TLSSettings
from blink import __version__ from blink import __version__
from blink.configuration.datatypes import ApplicationDataPath, HTTPURL, SoundFile from blink.configuration.datatypes import ApplicationDataPath, AuthorizationToken, HTTPURL, SoundFile
from blink.resources import Resources from blink.resources import Resources
...@@ -20,6 +20,11 @@ class AudioSettingsExtension(AudioSettings): ...@@ -20,6 +20,11 @@ class AudioSettingsExtension(AudioSettings):
recordings_directory = Setting(type=ApplicationDataPath, default=ApplicationDataPath('recordings'), nillable=False) recordings_directory = Setting(type=ApplicationDataPath, default=ApplicationDataPath('recordings'), nillable=False)
class GoogleContactsSettings(SettingsGroup):
authorization_token = Setting(type=AuthorizationToken, default=None, nillable=True)
username = Setting(type=unicode, default=None, nillable=True)
class LogsSettingsExtension(LogsSettings): class LogsSettingsExtension(LogsSettings):
trace_sip = Setting(type=bool, default=False) trace_sip = Setting(type=bool, default=False)
trace_pjsip = Setting(type=bool, default=False) trace_pjsip = Setting(type=bool, default=False)
...@@ -42,6 +47,7 @@ class TLSSettingsExtension(TLSSettings): ...@@ -42,6 +47,7 @@ class TLSSettingsExtension(TLSSettings):
class SIPSimpleSettingsExtension(SettingsObjectExtension): class SIPSimpleSettingsExtension(SettingsObjectExtension):
audio = AudioSettingsExtension audio = AudioSettingsExtension
google_contacts = GoogleContactsSettings
logs = LogsSettingsExtension logs = LogsSettingsExtension
server = ServerSettings server = ServerSettings
sounds = SoundSettings sounds = SoundSettings
......
This diff is collapsed.
...@@ -410,6 +410,12 @@ class ContactEntry(PersonEntry): ...@@ -410,6 +410,12 @@ class ContactEntry(PersonEntry):
return a_link return a_link
return None return None
def get_entry_photo_data(self):
photo = self.GetPhotoLink()
if photo._other_attributes.get('{http://schemas.google.com/g/2005}etag'):
return (photo.href, photo._other_attributes.get('{http://schemas.google.com/g/2005}etag').strip('"'))
return (None, None)
class ContactsFeed(gdata_data.BatchFeed): class ContactsFeed(gdata_data.BatchFeed):
"""A collection of Contacts.""" """A collection of Contacts."""
......
...@@ -22,7 +22,7 @@ from sipsimple.configuration.settings import SIPSimpleSettings ...@@ -22,7 +22,7 @@ from sipsimple.configuration.settings import SIPSimpleSettings
from blink.aboutpanel import AboutPanel from blink.aboutpanel import AboutPanel
from blink.accounts import AccountModel, ActiveAccountModel, AddAccountDialog, ServerToolsAccountModel, ServerToolsWindow from blink.accounts import AccountModel, ActiveAccountModel, AddAccountDialog, ServerToolsAccountModel, ServerToolsWindow
from blink.contacts import BonjourNeighbour, Contact, ContactGroup, ContactEditorDialog, ContactModel, ContactSearchModel from blink.contacts import BonjourNeighbour, Contact, ContactGroup, ContactEditorDialog, ContactModel, ContactSearchModel, GoogleContactsDialog
from blink.sessions import SessionManager, SessionModel from blink.sessions import SessionManager, SessionModel
from blink.resources import Resources from blink.resources import Resources
from blink.util import call_in_auxiliary_thread, run_in_gui_thread from blink.util import call_in_auxiliary_thread, run_in_gui_thread
...@@ -81,6 +81,7 @@ class MainWindow(base_class, ui_class): ...@@ -81,6 +81,7 @@ class MainWindow(base_class, ui_class):
self.about_panel = AboutPanel(self) self.about_panel = AboutPanel(self)
self.add_account_dialog = AddAccountDialog(self) self.add_account_dialog = AddAccountDialog(self)
self.contact_editor_dialog = ContactEditorDialog(self.contact_model, self) self.contact_editor_dialog = ContactEditorDialog(self.contact_model, self)
self.google_contacts_dialog = GoogleContactsDialog(self)
self.server_tools_window = ServerToolsWindow(self.server_tools_account_model, None) self.server_tools_window = ServerToolsWindow(self.server_tools_account_model, None)
# Signals # Signals
...@@ -195,6 +196,7 @@ class MainWindow(base_class, ui_class): ...@@ -195,6 +196,7 @@ class MainWindow(base_class, ui_class):
self.about_panel.close() self.about_panel.close()
self.add_account_dialog.close() self.add_account_dialog.close()
self.contact_editor_dialog.close() self.contact_editor_dialog.close()
self.google_contacts_dialog.close()
self.server_tools_window.close() self.server_tools_window.close()
def set_user_icon(self, image_file_name): def set_user_icon(self, image_file_name):
...@@ -290,6 +292,15 @@ class MainWindow(base_class, ui_class): ...@@ -290,6 +292,15 @@ class MainWindow(base_class, ui_class):
settings.audio.output_device = action.data().toPyObject() settings.audio.output_device = action.data().toPyObject()
call_in_auxiliary_thread(settings.save) call_in_auxiliary_thread(settings.save)
def _AH_GoogleContactsActionTriggered(self):
settings = SIPSimpleSettings()
if settings.google_contacts.authorization_token:
settings = SIPSimpleSettings()
settings.google_contacts.authorization_token = None
settings.save()
else:
self.google_contacts_dialog.open()
def _AH_RedialActionTriggered(self): def _AH_RedialActionTriggered(self):
session_manager = SessionManager() session_manager = SessionManager()
if session_manager.last_dialed_uri is not None: if session_manager.last_dialed_uri is not None:
...@@ -493,6 +504,16 @@ class MainWindow(base_class, ui_class): ...@@ -493,6 +504,16 @@ class MainWindow(base_class, ui_class):
notification_center.add_observer(self, sender=account_manager) notification_center.add_observer(self, sender=account_manager)
self.silent_action.setChecked(settings.audio.silent) self.silent_action.setChecked(settings.audio.silent)
self.silent_button.setChecked(settings.audio.silent) self.silent_button.setChecked(settings.audio.silent)
if settings.google_contacts.authorization_token:
self.google_contacts_action.setText(u'Disable Google Contacts')
elif settings.google_contacts.authorization_token is not None:
# Token is invalid
self.google_contacts_action.setText(u'Disable Google Contacts')
# Maybe this should be moved to DidStart so that the dialog is shown *after* the MainWindow. -Saul
self.google_contacts_dialog.open_for_incorrect_password()
else:
self.google_contacts_action.setText(u'Enable Google Contacts')
self.google_contacts_action.triggered.connect(self._AH_GoogleContactsActionTriggered)
if all(not account.enabled for account in account_manager.iter_accounts()): if all(not account.enabled for account in account_manager.iter_accounts()):
self.display_name.setEnabled(False) self.display_name.setEnabled(False)
self.activity_note.setEnabled(False) self.activity_note.setEnabled(False)
...@@ -539,6 +560,15 @@ class MainWindow(base_class, ui_class): ...@@ -539,6 +560,15 @@ class MainWindow(base_class, ui_class):
if 'audio.alert_device' in notification.data.modified: if 'audio.alert_device' in notification.data.modified:
action = (action for action in self.alert_devices_group.actions() if action.data().toPyObject() == settings.audio.alert_device).next() action = (action for action in self.alert_devices_group.actions() if action.data().toPyObject() == settings.audio.alert_device).next()
action.setChecked(True) action.setChecked(True)
if 'google_contacts.authorization_token' in notification.data.modified:
authorization_token = notification.sender.google_contacts.authorization_token
if authorization_token:
self.google_contacts_action.setText(u'Disable Google Contacts')
elif authorization_token is not None:
# Token is invalid
self.google_contacts_dialog.open_for_incorrect_password()
else:
self.google_contacts_action.setText(u'Enable Google Contacts')
elif isinstance(notification.sender, (Account, BonjourAccount)): elif isinstance(notification.sender, (Account, BonjourAccount)):
if 'enabled' in notification.data.modified: if 'enabled' in notification.data.modified:
account = notification.sender account = notification.sender
......
...@@ -16,6 +16,7 @@ from application import log ...@@ -16,6 +16,7 @@ from application import log
from application.python.util import Singleton from application.python.util import Singleton
from application.system import unlink from application.system import unlink
from collections import deque from collections import deque
from hashlib import sha512
from sipsimple.util import classproperty, makedirs from sipsimple.util import classproperty, makedirs
...@@ -165,4 +166,39 @@ class IconCache(object): ...@@ -165,4 +166,39 @@ class IconCache(object):
self.available_names.appendleft(os.path.basename(destination_name)) self.available_names.appendleft(os.path.basename(destination_name))
return filename return filename
def store_image(self, data):
if data is None:
return None
data_hash = sha512(data).hexdigest()
try:
return self.filemap[data_hash].destination
except KeyError:
pass
try:
destination_name = os.path.join('images', self.available_names.popleft())
except IndexError:
# No more available file names.
return None
pixmap = QPixmap()
if pixmap.loadFromData(data):
pixmap = pixmap.scaled(32, 32, Qt.KeepAspectRatio, Qt.SmoothTransformation)
makedirs(ApplicationData.get('images'))
if pixmap.save(ApplicationData.get(destination_name)):
file_mapping = FileMapping(data_hash, destination_name)
self.filemap[data_hash] = file_mapping
map_filename = ApplicationData.get(os.path.join('images', '.cached_icons.map'))
map_tempname = map_filename + '.tmp'
try:
file = open(map_tempname, 'wb')
pickle.dump(self.filemap, file)
file.close()
if sys.platform == 'win32':
unlink(map_filename)
os.rename(map_tempname, map_filename)
except Exception, e:
log.error("could not save icon cache file mappings: %s" % e)
return destination_name
else:
self.available_names.appendleft(os.path.basename(destination_name))
return None
...@@ -1032,6 +1032,8 @@ padding: 2px;</string> ...@@ -1032,6 +1032,8 @@ padding: 2px;</string>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="file_transfers_action"/> <addaction name="file_transfers_action"/>
<addaction name="logs_action"/> <addaction name="logs_action"/>
<addaction name="separator"/>
<addaction name="google_contacts_action"/>
</widget> </widget>
<addaction name="blink_menu"/> <addaction name="blink_menu"/>
<addaction name="audio_menu"/> <addaction name="audio_menu"/>
...@@ -1249,6 +1251,11 @@ padding: 2px;</string> ...@@ -1249,6 +1251,11 @@ padding: 2px;</string>
<string>Donate if you like Blink</string> <string>Donate if you like Blink</string>
</property> </property>
</action> </action>
<action name="google_contacts_action">
<property name="text">
<string>Enable Google Contacts</string>
</property>
</action>
<action name="history_on_server_action"> <action name="history_on_server_action">
<property name="text"> <property name="text">
<string>Call history on server...</string> <string>Call history on server...</string>
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment