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

Added integration with Google Contacts

parent f8ba1d97
......@@ -3,7 +3,7 @@
"""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 re
......@@ -113,3 +113,33 @@ class HTTPURL(unicode):
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
from sipsimple.configuration.settings import AudioSettings, LogsSettings, TLSSettings
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
......@@ -20,6 +20,11 @@ class AudioSettingsExtension(AudioSettings):
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):
trace_sip = Setting(type=bool, default=False)
trace_pjsip = Setting(type=bool, default=False)
......@@ -42,6 +47,7 @@ class TLSSettingsExtension(TLSSettings):
class SIPSimpleSettingsExtension(SettingsObjectExtension):
audio = AudioSettingsExtension
google_contacts = GoogleContactsSettings
logs = LogsSettingsExtension
server = ServerSettings
sounds = SoundSettings
......
This diff is collapsed.
......@@ -410,6 +410,12 @@ class ContactEntry(PersonEntry):
return a_link
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):
"""A collection of Contacts."""
......
......@@ -22,7 +22,7 @@ from sipsimple.configuration.settings import SIPSimpleSettings
from blink.aboutpanel import AboutPanel
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.resources import Resources
from blink.util import call_in_auxiliary_thread, run_in_gui_thread
......@@ -81,6 +81,7 @@ class MainWindow(base_class, ui_class):
self.about_panel = AboutPanel(self)
self.add_account_dialog = AddAccountDialog(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)
# Signals
......@@ -195,6 +196,7 @@ class MainWindow(base_class, ui_class):
self.about_panel.close()
self.add_account_dialog.close()
self.contact_editor_dialog.close()
self.google_contacts_dialog.close()
self.server_tools_window.close()
def set_user_icon(self, image_file_name):
......@@ -290,6 +292,15 @@ class MainWindow(base_class, ui_class):
settings.audio.output_device = action.data().toPyObject()
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):
session_manager = SessionManager()
if session_manager.last_dialed_uri is not None:
......@@ -493,6 +504,16 @@ class MainWindow(base_class, ui_class):
notification_center.add_observer(self, sender=account_manager)
self.silent_action.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()):
self.display_name.setEnabled(False)
self.activity_note.setEnabled(False)
......@@ -539,6 +560,15 @@ class MainWindow(base_class, ui_class):
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.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)):
if 'enabled' in notification.data.modified:
account = notification.sender
......
......@@ -16,6 +16,7 @@ from application import log
from application.python.util import Singleton
from application.system import unlink
from collections import deque
from hashlib import sha512
from sipsimple.util import classproperty, makedirs
......@@ -165,4 +166,39 @@ class IconCache(object):
self.available_names.appendleft(os.path.basename(destination_name))
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>
<addaction name="separator"/>
<addaction name="file_transfers_action"/>
<addaction name="logs_action"/>
<addaction name="separator"/>
<addaction name="google_contacts_action"/>
</widget>
<addaction name="blink_menu"/>
<addaction name="audio_menu"/>
......@@ -1249,6 +1251,11 @@ padding: 2px;</string>
<string>Donate if you like Blink</string>
</property>
</action>
<action name="google_contacts_action">
<property name="text">
<string>Enable Google Contacts</string>
</property>
</action>
<action name="history_on_server_action">
<property name="text">
<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