Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWalter Bender <walter.bender@gmail.com>2013-04-03 03:50:36 (GMT)
committer Walter Bender <walter.bender@gmail.com>2013-04-03 03:50:36 (GMT)
commitd0110fcdeb9c0c5d83e3730215410ba720e21702 (patch)
treee9a42bbaddb25fa588a47bb9bf2fe193e661a12f
parent567769dc3139caf39ab1d4cac057f77255d1d2e3 (diff)
cleanup based on feedback from dnarvaez
-rw-r--r--extensions/web/facebook/Makefile.am2
-rw-r--r--extensions/web/facebook/facebookaccount.py281
-rw-r--r--extensions/web/twitter/Makefile.am2
-rw-r--r--extensions/web/twitter/twitteraccount.py283
-rw-r--r--src/jarabe/journal/journaltoolbox.py19
-rw-r--r--src/jarabe/journal/palettes.py4
-rw-r--r--src/jarabe/web/Makefile.am6
-rw-r--r--src/jarabe/web/__init__.py18
-rw-r--r--src/jarabe/web/account.py78
-rw-r--r--src/jarabe/web/accountsmanager.py85
10 files changed, 744 insertions, 34 deletions
diff --git a/extensions/web/facebook/Makefile.am b/extensions/web/facebook/Makefile.am
index e2e6d1f..f9bac63 100644
--- a/extensions/web/facebook/Makefile.am
+++ b/extensions/web/facebook/Makefile.am
@@ -3,4 +3,4 @@ SUBDIRS = facebook icons
sugardir = $(pkgdatadir)/extensions/web/facebook
sugar_PYTHON = \
__init__.py \
- facebook_online_account.py
+ facebookaccount.py
diff --git a/extensions/web/facebook/facebookaccount.py b/extensions/web/facebook/facebookaccount.py
new file mode 100644
index 0000000..f105fdb
--- /dev/null
+++ b/extensions/web/facebook/facebookaccount.py
@@ -0,0 +1,281 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2013 Walter Bender, Raul Gutierrez Segales
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+from gettext import gettext as _
+import logging
+import os
+import tempfile
+import time
+import json
+
+from gi.repository import Gtk
+from gi.repository import GdkPixbuf
+from gi.repository import GConf
+from gi.repository import GObject
+
+from sugar3.datastore import datastore
+from sugar3.graphics.alert import NotifyAlert
+from sugar3.graphics.icon import Icon
+
+from jarabe.journal import journalwindow
+from jarabe.web import account
+
+from facebook import facebook
+
+ACCOUNT_NEEDS_ATTENTION = 0
+ACCOUNT_ACTIVE = 1
+ACCOUNT_NAME = _('Facebook')
+COMMENTS = 'comments'
+COMMENT_IDS = 'fb_comment_ids'
+
+class FacebookAccount(account.Account):
+
+ ACCESS_TOKEN_KEY = "/desktop/sugar/collaboration/facebook_access_token"
+ ACCESS_TOKEN_KEY_EXPIRATION_DATE = \
+ "/desktop/sugar/collaboration/facebook_access_token_expiration_date"
+
+ def __init__(self):
+ self._client = GConf.Client.get_default()
+ facebook.FbAccount.set_access_token(self._access_token())
+ self._alert = None
+
+ def get_description(self):
+ return ACCOUNT_NAME
+
+ def is_configured(self):
+ return self._access_token() is not None
+
+ def is_active(self):
+ expiration_date = \
+ self._client.get_int(self.ACCESS_TOKEN_KEY_EXPIRATION_DATE)
+ return expiration_date != 0 and expiration_date > time.time()
+
+ def get_share_menu(self, journal_entry_metadata):
+ fb_share_menu = _FacebookShareMenu(journal_entry_metadata,
+ self.is_active())
+ self._connect_transfer_signals(fb_share_menu)
+ return fb_share_menu
+
+ def get_refresh_menu(self):
+ fb_refresh_menu = _FacebookRefreshMenu(self.is_active())
+ self._connect_transfer_signals(fb_refresh_menu)
+ return fb_refresh_menu
+
+ def _connect_transfer_signals(self, transfer_widget):
+ transfer_widget.connect('transfer-state-changed',
+ self._transfer_state_changed_cb)
+
+ def _transfer_state_changed_cb(self, widget, state_message):
+ logging.debug('_transfer_state_changed_cb')
+
+ # First, remove any existing alert
+ if self._alert is None:
+ logging.debug('creating new alert')
+ self._alert = NotifyAlert()
+ self._alert.props.title = ACCOUNT_NAME
+ self._alert.connect('response', self._alert_response_cb)
+ journalwindow.get_journal_window().add_alert(self._alert)
+ self._alert.show()
+
+ logging.debug(state_message)
+ self._alert.props.msg = state_message
+
+ def _alert_response_cb(self, alert, response_id):
+ journalwindow.get_journal_window().remove_alert(alert)
+ self._alert = None
+
+ def _access_token(self):
+ return self._client.get_string(self.ACCESS_TOKEN_KEY)
+
+
+class _FacebookShareMenu(account.MenuItem):
+ __gtype_name__ = 'JournalFacebookMenu'
+
+ def __init__(self, metadata, is_active):
+ account.MenuItem.__init__(self, ACCOUNT_NAME)
+
+ if is_active:
+ icon_name = 'facebook-share'
+ else:
+ icon_name = 'facebook-share-insensitive'
+ self.set_image(Icon(icon_name=icon_name,
+ icon_size=Gtk.IconSize.MENU))
+ self.show()
+ self._metadata = metadata
+ self._comment = '%s: %s' % (self._get_metadata_by_key('title'),
+ self._get_metadata_by_key('description'))
+
+ self.connect('activate', self._facebook_share_menu_cb)
+
+ def _get_metadata_by_key(self, key, default_value=''):
+ if key in self._metadata:
+ return self._metadata[key]
+ return default_value
+
+ def _facebook_share_menu_cb(self, menu_item):
+ logging.debug('_facebook_share_menu_cb')
+
+ self.emit('transfer-state-changed', _('Upload started'))
+ tmp_file = tempfile.mktemp()
+ self._image_file_from_metadata(tmp_file)
+
+ photo = facebook.FbPhoto()
+ photo.connect('photo-created', self._photo_created_cb, tmp_file)
+ photo.connect('photo-create-failed',
+ self._photo_create_failed_cb,
+ tmp_file)
+ photo.connect('transfer-state-changed',
+ self._transfer_state_changed_cb)
+
+ GObject.idle_add(photo.create, tmp_file)
+
+ def _photo_created_cb(self, fb_photo, fb_object_id, tmp_file):
+ logging.debug("_photo_created_cb")
+
+ if os.path.exists(tmp_file):
+ os.unlink(tmp_file)
+
+ fb_photo.connect('comment-added', self._comment_added_cb)
+ fb_photo.connect('comment-add-failed', self._comment_add_failed_cb)
+ fb_photo.add_comment(self._comment)
+
+ try:
+ ds_object = datastore.get(self._metadata['uid'])
+ ds_object.metadata['fb_object_id'] = fb_object_id
+ datastore.write(ds_object, update_mtime=False)
+ except Exception as ex:
+ logging.debug("_photo_created_cb failed to write to datastore: " % \
+ str(ex))
+
+ def _photo_create_failed_cb(self, fb_photo, failed_reason, tmp_file):
+ logging.debug("_photo_create_failed_cb")
+
+ if os.path.exists(tmp_file):
+ os.unlink(tmp_file)
+
+ def _comment_added_cb(self, fb_photo, fb_comment_id):
+ logging.debug("_comment_added_cb")
+
+ def _comment_add_failed_cb(self, fb_photo, failed_reason):
+ logging.debug("_comment_add_failed_cb")
+
+ def _image_file_from_metadata(self, image_path):
+ """ Load a pixbuf from a Journal object. """
+ pixbufloader = \
+ GdkPixbuf.PixbufLoader.new_with_mime_type('image/png')
+ pixbufloader.set_size(300, 225)
+ try:
+ pixbufloader.write(self._metadata['preview'])
+ pixbuf = pixbufloader.get_pixbuf()
+ except Exception as ex:
+ logging.debug("_image_file_from_metadata: %s" % (str(ex)))
+ pixbuf = None
+
+ pixbufloader.close()
+ if pixbuf:
+ pixbuf.savev(image_path, 'png', [], [])
+
+
+class _FacebookRefreshMenu(account.MenuItem):
+ def __init__(self, is_active):
+ account.MenuItem.__init__(self, ACCOUNT_NAME)
+
+ self._is_active = is_active
+ self._metadata = None
+
+ if is_active:
+ icon_name = 'facebook-refresh'
+ else:
+ icon_name = 'facebook-refresh-insensitive'
+ self.set_image(Icon(icon_name=icon_name,
+ icon_size=Gtk.IconSize.MENU))
+ self.show()
+
+ self.connect('activate', self._fb_refresh_menu_clicked_cb)
+
+ def set_metadata(self, metadata):
+ self._metadata = metadata
+ if self._is_active:
+ if self._metadata:
+ if 'fb_object_id' in self._metadata:
+ self.set_sensitive(True)
+ icon_name = 'facebook-refresh'
+ else:
+ self.set_sensitive(False)
+ icon_name = 'facebook-refresh-insensitive'
+ self.set_image(Icon(icon_name=icon_name,
+ icon_size=Gtk.IconSize.MENU))
+
+ def _fb_refresh_menu_clicked_cb(self, button):
+ logging.debug('_fb_refresh_menu_clicked_cb')
+
+ if self._metadata is None:
+ logging.debug('_fb_refresh_menu_clicked_cb called without metadata')
+ return
+
+ if 'fb_object_id' not in self._metadata:
+ logging.debug('_fb_refresh_menu_clicked_cb called without fb_object_id in metadata')
+ return
+
+ self.emit('transfer-state-changed', _('Download started'))
+ fb_photo = facebook.FbPhoto(self._metadata['fb_object_id'])
+ fb_photo.connect('comments-downloaded',
+ self._fb_comments_downloaded_cb)
+ fb_photo.connect('comments-download-failed',
+ self._fb_comments_download_failed_cb)
+ fb_photo.connect('transfer-state-changed',
+ self._transfer_state_changed_cb)
+ GObject.idle_add(fb_photo.refresh_comments)
+
+ def _fb_comments_downloaded_cb(self, fb_photo, comments):
+ logging.debug('_fb_comments_downloaded_cb')
+
+ ds_object = datastore.get(self._metadata['uid'])
+ if not COMMENTS in ds_object.metadata:
+ ds_comments = []
+ else:
+ ds_comments = json.loads(ds_object.metadata[COMMENTS])
+ if not COMMENT_IDS in ds_object.metadata:
+ ds_comment_ids = []
+ else:
+ ds_comment_ids = json.loads(ds_object.metadata[COMMENT_IDS])
+ new_comment = False
+ for comment in comments:
+ if comment['id'] not in ds_comment_ids:
+ # TODO: get avatar icon and add it to icon_theme
+ ds_comments.append({'from': comment['from'],
+ 'message': comment['message'],
+ 'icon': 'facebook-share'})
+ ds_comment_ids.append(comment['id'])
+ new_comment = True
+ if new_comment:
+ ds_object.metadata[COMMENTS] = json.dumps(ds_comments)
+ ds_object.metadata[COMMENT_IDS] = json.dumps(ds_comment_ids)
+ self.emit('comments-changed', ds_object.metadata[COMMENTS])
+
+ datastore.write(ds_object, update_mtime=False)
+
+ def _fb_comments_download_failed_cb(self, fb_photo, failed_reason):
+ logging.debug('_fb_comments_download_failed_cb: %s' % (failed_reason))
+
+def get_account():
+ return FacebookAccount()
diff --git a/extensions/web/twitter/Makefile.am b/extensions/web/twitter/Makefile.am
index 8346b41..438d3fa 100644
--- a/extensions/web/twitter/Makefile.am
+++ b/extensions/web/twitter/Makefile.am
@@ -3,4 +3,4 @@ SUBDIRS = twitter icons
sugardir = $(pkgdatadir)/extensions/web/twitter
sugar_PYTHON = \
__init__.py \
- twitter_online_account.py
+ twitteraccount.py
diff --git a/extensions/web/twitter/twitteraccount.py b/extensions/web/twitter/twitteraccount.py
new file mode 100644
index 0000000..a2e4bf0
--- /dev/null
+++ b/extensions/web/twitter/twitteraccount.py
@@ -0,0 +1,283 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2013 Walter Bender, Raul Gutierrez Segales, Martin Abente
+
+#Permission is hereby granted, free of charge, to any person obtaining a copy
+#of this software and associated documentation files (the "Software"), to deal
+#in the Software without restriction, including without limitation the rights
+#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+#copies of the Software, and to permit persons to whom the Software is
+#furnished to do so, subject to the following conditions:
+
+#The above copyright notice and this permission notice shall be included in
+#all copies or substantial portions of the Software.
+
+#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+#THE SOFTWARE.
+
+from gettext import gettext as _
+import logging
+import os
+import tempfile
+import time
+import json
+
+from gi.repository import Gtk
+from gi.repository import GdkPixbuf
+from gi.repository import GConf
+from gi.repository import GObject
+
+from sugar3.datastore import datastore
+from sugar3.graphics.alert import NotifyAlert
+from sugar3.graphics.icon import Icon
+
+from jarabe.journal import journalwindow
+from jarabe.web import account
+
+from twitter.twr_account import TwrAccount
+from twitter.twr_status import TwrStatus
+from twitter.twr_timeline import TwrTimeline
+
+ACCOUNT_NEEDS_ATTENTION = 0
+ACCOUNT_ACTIVE = 1
+ACCOUNT_NAME = _('Twitter')
+COMMENTS = 'comments'
+COMMENT_IDS = 'twr_comment_ids'
+COMMENT_LAST_ID = 'last_comment_id'
+
+
+class TwitterAccount(account.Account):
+
+ CONSUMER_TOKEN_KEY = "/desktop/sugar/collaboration/twitter_consumer_token"
+ CONSUMER_SECRET_KEY = "/desktop/sugar/collaboration/twitter_consumer_secret"
+ ACCESS_TOKEN_KEY = "/desktop/sugar/collaboration/twitter_access_token"
+ ACCESS_SECRET_KEY = "/desktop/sugar/collaboration/twitter_access_secret"
+
+ def __init__(self):
+ self._client = GConf.Client.get_default()
+ ctoken, csecret, atoken, asecret = self._access_tokens()
+ TwrAccount.set_secrets(ctoken, csecret, atoken, asecret)
+ self._alert = None
+
+ def get_description(self):
+ return ACCOUNT_NAME
+
+ def is_configured(self):
+ return None not in self._access_tokens()
+
+ def is_active(self):
+ # No expiration date
+ return None not in self._access_tokens()
+
+ def get_share_menu(self, journal_entry_metadata):
+ twr_share_menu = _TwitterShareMenu(journal_entry_metadata,
+ self.is_active())
+ self._connect_transfer_signals(twr_share_menu)
+ return twr_share_menu
+
+ def get_refresh_menu(self):
+ twr_refresh_menu = _TwitterRefreshMenu(self.is_active())
+ self._connect_transfer_signals(twr_refresh_menu)
+ return twr_refresh_menu
+
+ def _connect_transfer_signals(self, transfer_widget):
+ transfer_widget.connect('transfer-state-changed',
+ self._transfer_state_changed_cb)
+
+ def _transfer_state_changed_cb(self, widget, state_message):
+ logging.debug('_transfer_state_changed_cb')
+
+ # First, remove any existing alert
+ if self._alert is None:
+ self._alert = NotifyAlert()
+ self._alert.props.title = ACCOUNT_NAME
+ self._alert.connect('response', self._alert_response_cb)
+ journalwindow.get_journal_window().add_alert(self._alert)
+ self._alert.show()
+
+ logging.debug(state_message)
+ self._alert.props.msg = state_message
+
+ def _alert_response_cb(self, alert, response_id):
+ journalwindow.get_journal_window().remove_alert(alert)
+ self._alert = None
+
+ def _access_tokens(self):
+ return (self._client.get_string(self.CONSUMER_TOKEN_KEY),
+ self._client.get_string(self.CONSUMER_SECRET_KEY),
+ self._client.get_string(self.ACCESS_TOKEN_KEY),
+ self._client.get_string(self.ACCESS_SECRET_KEY))
+
+
+class _TwitterShareMenu(account.MenuItem):
+ __gtype_name__ = 'JournalTwitterMenu'
+
+ def __init__(self, metadata, is_active):
+ account.MenuItem.__init__(self, ACCOUNT_NAME)
+
+ if is_active:
+ icon_name = 'twitter-share'
+ else:
+ icon_name = 'twitter-share-insensitive'
+ self.set_image(Icon(icon_name=icon_name,
+ icon_size=Gtk.IconSize.MENU))
+ self.show()
+ self._metadata = metadata
+ self._comment = '%s: %s' % (self._get_metadata_by_key('title'),
+ self._get_metadata_by_key('description'))
+
+ self.connect('activate', self._twitter_share_menu_cb)
+
+ def _get_metadata_by_key(self, key, default_value=''):
+ if key in self._metadata:
+ return self._metadata[key]
+ return default_value
+
+ def _status_updated_cb(self, status, data, tmp_file):
+ if os.path.exists(tmp_file):
+ os.unlink(tmp_file)
+ try:
+ ds_object = datastore.get(self._metadata['uid'])
+ ds_object.metadata['twr_object_id'] = status._status_id
+ datastore.write(ds_object, update_mtime=False)
+ except Exception as e:
+ logging.debug('_status_updated_cb failed to write: %s', str(e))
+
+ def _status_updated_failed_cb(self, status, message, tmp_file):
+ if os.path.exists(tmp_file):
+ os.unlink(tmp_file)
+ logging.error('_status_updated_failed_cb %s', message)
+
+ def _twitter_share_menu_cb(self, menu_item):
+ logging.debug('_twitter_share_menu_cb')
+
+ self.emit('transfer-state-changed', _('Download started'))
+ tmp_file = tempfile.mktemp()
+ self._image_file_from_metadata(tmp_file)
+
+ status = TwrStatus()
+ status.connect('status-updated', self._status_updated_cb, tmp_file)
+ status.connect('status-updated-failed',
+ self._status_updated_failed_cb, tmp_file)
+ status.update_with_media(self._comment, tmp_file)
+
+ def _image_file_from_metadata(self, image_path):
+ """ Load a pixbuf from a Journal object. """
+ pixbufloader = \
+ GdkPixbuf.PixbufLoader.new_with_mime_type('image/png')
+ pixbufloader.set_size(300, 225)
+ try:
+ pixbufloader.write(self._metadata['preview'])
+ pixbuf = pixbufloader.get_pixbuf()
+ except Exception as ex:
+ logging.debug("_image_file_from_metadata: %s" % (str(ex)))
+ pixbuf = None
+
+ pixbufloader.close()
+ if pixbuf:
+ pixbuf.savev(image_path, 'png', [], [])
+
+
+class _TwitterRefreshMenu(account.MenuItem):
+ def __init__(self, is_active):
+ account.MenuItem.__init__(self, ACCOUNT_NAME)
+
+ self._is_active = is_active
+ self._metadata = None
+
+ if is_active:
+ icon_name = 'twitter-refresh'
+ else:
+ icon_name = 'twitter-refresh-insensitive'
+ self.set_image(Icon(icon_name=icon_name,
+ icon_size=Gtk.IconSize.MENU))
+ self.show()
+
+ self.connect('activate', self._twr_refresh_menu_clicked_cb)
+
+ def set_metadata(self, metadata):
+ self._metadata = metadata
+ if self._is_active:
+ if self._metadata:
+ if 'fb_object_id' in self._metadata:
+ self.set_sensitive(True)
+ icon_name = 'twitter-refresh'
+ else:
+ self.set_sensitive(False)
+ icon_name = 'twitter-refresh-insensitive'
+ self.set_image(Icon(icon_name=icon_name,
+ icon_size=Gtk.IconSize.MENU))
+
+ def _twr_refresh_menu_clicked_cb(self, button):
+ logging.debug('_twr_refresh_menu_clicked_cb')
+
+ if self._metadata is None:
+ logging.debug('_twr_refresh_menu_clicked_cb called without metadata')
+ return
+
+ if 'twr_object_id' not in self._metadata:
+ logging.debug('_twr_refresh_menu_clicked_cb called without twr_object_id in metadata')
+ return
+
+ self.emit('transfer-state-changed', _('Download started'))
+
+ status_id = self._metadata['twr_object_id']
+
+ # XXX is there other way to update metadata?
+ ds_object = datastore.get(self._metadata['uid'])
+ if COMMENT_LAST_ID in ds_object.metadata:
+ status_id = ds_object.metadata[COMMENT_LAST_ID]
+
+ timeline = TwrTimeline()
+ timeline.connect('mentions-downloaded', self._twr_mentions_downloaded_cb)
+ timeline.mentions_timeline(since_id=status_id)
+
+ def _twr_mentions_downloaded_cb(self, timeline, comments):
+ logging.debug('_twr_mentions_downloaded_cb')
+
+ ds_object = datastore.get(self._metadata['uid'])
+ if not COMMENTS in ds_object.metadata:
+ ds_comments = []
+ else:
+ ds_comments = json.loads(ds_object.metadata[COMMENTS])
+ if not COMMENT_IDS in ds_object.metadata:
+ ds_comment_ids = []
+ else:
+ ds_comment_ids = json.loads(ds_object.metadata[COMMENT_IDS])
+
+ comment_id = None
+ new_comment = False
+ for comment in comments:
+ # XXX hope for a better API
+ if comment['in_reply_to_status_id_str'] != self._metadata['twr_object_id']:
+ continue
+
+ if comment['id_str'] not in ds_comment_ids:
+ ds_comments.append({'from': comment['user']['name'],
+ 'message': comment['text'],
+ 'icon': 'twitter-share'})
+ ds_comment_ids.append(comment['id_str'])
+
+ # XXX keep the latest one
+ if comment['id_str'] > comment_id:
+ comment_id = comment['id_str']
+ new_comment = True
+
+ if new_comment:
+ ds_object.metadata[COMMENT_LAST_ID] = comment_id
+ ds_object.metadata[COMMENTS] = json.dumps(ds_comments)
+ ds_object.metadata[COMMENT_IDS] = json.dumps(ds_comment_ids)
+ self.emit('comments-changed', ds_object.metadata[COMMENTS])
+
+ datastore.write(ds_object, update_mtime=False)
+
+ def _twr_comments_download_failed_cb(self, tweet, failed_reason):
+ logging.debug('_twr_comments_download_failed_cb: %s' % (failed_reason))
+
+def get_account():
+ return TwitterAccount()
diff --git a/src/jarabe/journal/journaltoolbox.py b/src/jarabe/journal/journaltoolbox.py
index d5a3da4..bdd16c3 100644
--- a/src/jarabe/journal/journaltoolbox.py
+++ b/src/jarabe/journal/journaltoolbox.py
@@ -47,7 +47,7 @@ from jarabe.journal import model
from jarabe.journal.palettes import ClipboardMenu
from jarabe.journal.palettes import VolumeMenu
from jarabe.journal import journalwindow
-from jarabe.web import online_accounts_manager as oam
+from jarabe.web import accountsmanager
_AUTOSEARCH_TIMEOUT = 1000
@@ -383,11 +383,12 @@ class DetailToolbox(ToolbarBox):
self._duplicate.connect('clicked', self._duplicate_clicked_cb)
self.toolbar.insert(self._duplicate, -1)
- if len(oam.OnlineAccountsManager.configured_accounts()) > 0:
+ if len(accountsmanager.get_configured_accounts()) > 0:
self._refresh = ToolButton()
icon = Icon(icon_name='refresh', xo_color=color)
self._refresh.set_icon_widget(icon)
icon.show()
+
self._refresh.set_tooltip(_('Refresh'))
self._refresh.connect('clicked', self._refresh_clicked_cb)
self.toolbar.insert(self._refresh, -1)
@@ -416,6 +417,9 @@ class DetailToolbox(ToolbarBox):
def _copy_clicked_cb(self, button):
button.palette.popup(immediate=True, state=Palette.SECONDARY)
+ def _refresh_clicked_cb(self, button):
+ button.palette.popup(immediate=True, state=Palette.SECONDARY)
+
def _duplicate_clicked_cb(self, button):
file_path = model.get_file(self._metadata['uid'])
try:
@@ -426,9 +430,6 @@ class DetailToolbox(ToolbarBox):
_('Error while copying the entry. %s') % (e.strerror, ),
_('Error'))
- def _refresh_clicked_cb(self, button):
- button.palette.popup(immediate=True, state=Palette.SECONDARY)
-
def _erase_button_clicked_cb(self, button):
alert = Alert()
erase_string = _('Erase')
@@ -509,7 +510,7 @@ class DetailToolbox(ToolbarBox):
palette.menu.append(volume_menu)
volume_menu.show()
- for account in oam.OnlineAccountsManager.configured_accounts():
+ for account in accountsmanager.get_configured_accounts():
menu = account.get_share_menu(self._metadata)
palette.menu.append(menu)
@@ -525,13 +526,13 @@ class DetailToolbox(ToolbarBox):
self._duplicate.hide()
def _refresh_refresh_palette(self):
+ color = misc.get_icon_color(self._metadata)
+ self._refresh.get_icon_widget().props.xo_color = color
palette = self._refresh.get_palette()
-
for menu_item in palette.menu.get_children():
palette.menu.remove(menu_item)
- menu_item.destroy()
- for account in oam.OnlineAccountsManager.configured_accounts():
+ for account in accountsmanager.get_configured_accounts():
menu = account.get_refresh_menu()
palette.menu.append(menu)
menu.set_metadata(self._metadata)
diff --git a/src/jarabe/journal/palettes.py b/src/jarabe/journal/palettes.py
index ee484a7..5ba959e 100644
--- a/src/jarabe/journal/palettes.py
+++ b/src/jarabe/journal/palettes.py
@@ -39,7 +39,7 @@ from jarabe.model import mimeregistry
from jarabe.journal import misc
from jarabe.journal import model
from jarabe.journal import journalwindow
-from jarabe.web import online_accounts_manager as oam
+from jarabe.web import accountsmanager as oam
class ObjectPalette(Palette):
@@ -252,7 +252,7 @@ class CopyMenu(Gtk.Menu):
self.append(volume_menu)
volume_menu.show()
- for account in oam.OnlineAccountsManager.configured_accounts():
+ for account in oam.get_configured_accounts():
menu = account.get_share_menu(metadata)
self.append(menu)
diff --git a/src/jarabe/web/Makefile.am b/src/jarabe/web/Makefile.am
index b751410..a2caada 100644
--- a/src/jarabe/web/Makefile.am
+++ b/src/jarabe/web/Makefile.am
@@ -1,5 +1,5 @@
sugardir = $(pythondir)/jarabe/web
sugar_PYTHON = \
- __init__.py \
- online_account.py \
- online_accounts_manager.py
+ __init__.py \
+ account.py \
+ accountsmanager.py
diff --git a/src/jarabe/web/__init__.py b/src/jarabe/web/__init__.py
index 1e7e0f9..e69de29 100644
--- a/src/jarabe/web/__init__.py
+++ b/src/jarabe/web/__init__.py
@@ -1,18 +0,0 @@
-"""Graphics/controls for use in Sugar"""
-
-# Copyright (C) 2006-2007, Red Hat, Inc.
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the
-# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-# Boston, MA 02111-1307, USA.
diff --git a/src/jarabe/web/account.py b/src/jarabe/web/account.py
new file mode 100644
index 0000000..f9be23e
--- /dev/null
+++ b/src/jarabe/web/account.py
@@ -0,0 +1,78 @@
+# Copyright (c) 2013 Walter Bender, Raul Gutierrez Segales
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+import logging
+
+from gi.repository import GObject
+
+from sugar3.graphics.menuitem import MenuItem
+from sugar3.graphics.toolbutton import ToolButton
+
+
+class Account():
+ ''' Account is a prototype class for online accounts. It provides
+ stubs for five public methods that are used by online services:
+
+ get_description returns a brief description of the online service
+ used in palette menuitems and on the webservices control panel.
+
+ is_configured returns True if the service has been configured for
+ use, e.g., an access token has been acquired.
+
+ is_active returns True if the service is currently available,
+ e.g., the access token has not expired.
+
+ get_share_menu returns a menu item used on the Copy To palette in
+ the Journal and on the Journal detail view toolbar.
+
+ get_refresh_menu returns a menu item used on the Journal detail
+ view toolbar.
+ '''
+
+ def get_description(self):
+ raise NotImplementedError
+
+ def is_configured(self):
+ raise NotImplementedError
+
+ def is_active(self):
+ raise NotImplementedError
+
+ def get_share_menu(self):
+ raise NotImplementedError
+
+ def get_refresh_menu(self):
+ raise NotImplementedError
+
+
+class MenuItem(MenuItem):
+ __gsignals__ = {
+ 'transfer-started': (GObject.SignalFlags.RUN_FIRST, None,
+ ([int, int])),
+ 'transfer-progress': (GObject.SignalFlags.RUN_FIRST, None,
+ ([int, int, float])),
+ 'transfer-completed': (GObject.SignalFlags.RUN_FIRST, None,
+ ([int, int])),
+ 'transfer-failed': (GObject.SignalFlags.RUN_FIRST, None,
+ ([int, int, str])),
+ 'transfer-state-changed': (GObject.SignalFlags.RUN_FIRST, None,
+ ([str])),
+ 'comments-changed': (GObject.SignalFlags.RUN_FIRST, None, ([str])),
+ }
+
+ def set_metadata(self, metadata):
+ raise NotImplementedError
diff --git a/src/jarabe/web/accountsmanager.py b/src/jarabe/web/accountsmanager.py
new file mode 100644
index 0000000..be083c0
--- /dev/null
+++ b/src/jarabe/web/accountsmanager.py
@@ -0,0 +1,85 @@
+# Copyright (c) 2013 Walter Bender
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+import os
+import logging
+
+from gi.repository import Gtk
+
+from jarabe import config
+
+
+def get_all_accounts():
+ accounts = []
+
+ web_path = os.path.join(config.ext_path, 'web')
+ if os.path.exists(web_path):
+ for d in os.listdir(web_path):
+ dir_path = os.path.join(web_path, d)
+ module = _load_module(dir_path)
+ if module is not None:
+ accounts.append(module)
+ _load_icon_path(dir_path)
+
+ return accounts
+
+
+def _load_module(dir_path):
+ module = None
+ if os.path.isdir(dir_path):
+ for f in os.listdir(dir_path):
+ if f.endswith('.py') and not f.startswith('__'):
+ module_name = f[:-3]
+ logging.debug('OnlineAccountsManager loading %s' % (
+ module_name))
+
+ try:
+ mod = __import__(
+ 'web.' + os.path.basename(dir_path) + '.' + \
+ module_name,
+ globals(),
+ locals(),
+ [module_name])
+ if hasattr(mod, 'get_account'):
+ module = mod.get_account()
+
+ except Exception as e:
+ logging.exception('Exception while loading %s: %s' % (
+ module_name, str(e)))
+
+ return module
+
+
+def _load_icon_path(dir_path):
+ icon_theme = Gtk.IconTheme.get_default()
+ icon_search_path = icon_theme.get_search_path()
+
+ if os.path.isdir(dir_path):
+ for f in os.listdir(dir_path):
+ if f == 'icons':
+ icon_path = os.path.join(dir_path, f)
+ if os.path.isdir(icon_path) and \
+ icon_path not in icon_search_path:
+ icon_theme.append_search_path(icon_path)
+
+
+def get_configured_accounts():
+ return [a for a in get_all_accounts() if a.is_configured()]
+
+
+def get_active_accounts():
+ return [a for a in get_all_accounts() if a.is_active()]