from deskbar.core.GconfStore import GconfStore
from deskbar.core.Utils import strip_html, get_proxy
from deskbar.defs import VERSION
from deskbar.handlers.actions.CopyToClipboardAction import CopyToClipboardAction
from deskbar.handlers.actions.ShowUrlAction import ShowUrlAction
from gettext import gettext as _
from xml.sax.saxutils import unescape
import base64
import deskbar
import deskbar.interfaces.Action
import deskbar.interfaces.Match
import deskbar.interfaces.Module
import gtk
import gobject
import logging
import threading
import re
import xmlrpclib
import xml.sax
import xml.sax.handler
import gnomekeyring
JAIKU_UPDATE_URL = "http://jaiku.com/statuses/update.xml"
# Base64 encoded Jaiku logo
JAIKU_ICON = \
"""iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9gGEggwDnEEFBoAAAJ4SURBVDjLrZPNa5R3EMc/83ueTZ593xizG3WzJqa6CBIlQUFKQYlCSylE2pOn4sEe/BO8xENPPfgHlOIfIHjyIih4UxDpC6JIAyZrY5I1T5J93zy7v9/04KpUrJd2YOYwzHzny3xn4D+afJhouKXRsLG6dyOs0O5s41ASQYaxkSKFXKmZNuXVjwI8fXVrNpFNfdOuNRc/NdFLyGI5d+HaPwBWtu5dqrU2f/p15U6upasGE4EooDCIooLYIYbJu9nJr3dzmbGrk7lz12Wt8zAIt15uPli6mex5IYh906xv4fU9URXAkDYlZopfdoJY8iuzU69e/KNyN9kzIWcOXcE3SbAx1HkDN+AEVUVxqFoauszj5dvxnrXfGrX9H9tRiOJY2rmPc5bZ0nccPTBPzCSYHv2CbLw4ABFOlb4nZpJEro4SXfHb/dq4o4c6oZQ5SSZV4MVfz5jaf4xyYZ5ELEdkI5q7Idb1eL5xD7E+KhHOWWP07TbVoOrY2tngUPEonvGpRRWs6/FZ8Tgpb4IjxdOUC/NgQFEi7eLH/ZF1YWg8FQQMe2mqjee8rv+JikXF0mrXmTafc2TfaVpSoa9tBB/RPk77Xd/zhq4mhvf+0uwv8/vGLaxGgCIDBeq7KyxvGmrdNUQERInsLnFvnGQs/7Osdx8FL6tPOr+9uEPPew1icaLIOxUVRN6oMZDTs2nmphaYzs/tM+PBye50/sTZmYPnd1JMOmwc0ffFAyqIKKI+gR5wc1MLlMbKF/YEM+vvTvlR5cZsJj16ud4If/jUKWeyI4vl7MK1f32mplvKhY21QnW7QqtTQ50jHk+TH5kgn53YTpnDVf5P+xt+FxV3gR11mAAAAABJRU5ErkJggg=="""
# Singleton ref to the loaded lixbuf
_jaiku_pixbuf = None
LOGGER = logging.getLogger(__name__)
HANDLERS = ["JaikuHandler"]
VERSION = "0.1"
MIN_MESSAGE_LEN = 2
def load_base64_icon (string):
loader = gtk.gdk.PixbufLoader()
try:
loader.set_size(deskbar.ICON_HEIGHT, deskbar.ICON_HEIGHT)
loader.write(base64.b64decode(JAIKU_ICON))
except Exception, msg:
LOGGER.warning ("Failed to read base64 encoded image: " + msg)
finally:
loader.close()
return loader.get_pixbuf()
def load_jaiku_icon ():
global _jaiku_pixbuf
_jaiku_pixbuf = load_base64_icon (JAIKU_ICON)
if not _jaiku_pixbuf:
_jaiku_pixbuf = deskbar.core.Utils.ICON_THEME.load_icon("stock-unknown",
deskbar.ICON_HEIGHT,
gtk.ICON_LOOKUP_USE_BUILTIN)
return _jaiku_pixbuf
class Account :
"""
This is an abstraction used to make it easier to move
away from a GConf password storage solution (Seahorse anyone?)
WARNING: This API is synchronous. This does not matter much to deskbar since
web based modules will likely run in threads anyway.
This class was cpoied (almost) verbatim from Sebastian Rittau's blog
found on http://www.rittau.org/blog/20070726-00.
"""
def __init__(self, host, realm):
self._realm = realm
self._host = host
self._protocol = "http"
self._keyring = gnomekeyring.get_default_keyring_sync()
def has_credentials(self):
try:
attrs = {"server": self._host, "protocol": self._protocol}
items = gnomekeyring.find_items_sync(gnomekeyring.ITEM_NETWORK_PASSWORD, attrs)
if len(items) > 0 :
if items[0].attributes["user"] != "" and \
items[0].secret != "" :
return True
else :
return False
except gnomekeyring.DeniedError:
return False
except gnomekeyring.NoMatchError:
return False
def get_host (self):
return self._host
def get_realm (self):
return self._realm
def get_credentials(self):
attrs = {"server": self._host, "protocol": self._protocol}
items = gnomekeyring.find_items_sync(gnomekeyring.ITEM_NETWORK_PASSWORD, attrs)
return (items[0].attributes["user"], items[0].secret)
def set_credentials(self, user, pw):
attrs = {
"user": user,
"server": self._host,
"protocol": self._protocol,
}
gnomekeyring.item_create_sync(gnomekeyring.get_default_keyring_sync(),
gnomekeyring.ITEM_NETWORK_PASSWORD, self._realm, attrs, pw, True)
class AccountDialog (gtk.MessageDialog):
def __init__ (self, account):
gtk.MessageDialog.__init__(self, None,
type=gtk.MESSAGE_QUESTION,
buttons=gtk.BUTTONS_OK_CANCEL)
self._account = account
self.connect ("response", self._on_response)
self.set_markup (_("Login for %s") % account.get_host())
self.format_secondary_markup (_("Please provide your credentials for %s") % account.get_host())
self.set_title (_("Credentials for %s") % account.get_host())
self._user_entry = gtk.Entry()
self._password_entry = gtk.Entry()
self._password_entry.set_property("visibility", False) # Show '*' instead of text
user_label = gtk.Label (_("User name:"))
password_label = gtk.Label (_("Personal API Key:"))
table = gtk.Table (2, 2)
table.attach (user_label, 0, 1, 0, 1)
table.attach (self._user_entry, 1, 2, 0, 1)
table.attach (password_label, 0, 1, 1, 2)
table.attach (self._password_entry, 1, 2, 1, 2)
self.vbox.pack_end (table)
if self._account.has_credentials():
user, password = self._account.get_credentials()
self._user_entry.set_text(user)
self._password_entry.set_text(password)
self._set_ok_sensitivity ()
self._user_entry.connect ("changed", lambda entry : self._set_ok_sensitivity())
self._password_entry.connect ("changed", lambda entry : self._set_ok_sensitivity())
def _on_response (self, dialog, response_id):
if response_id == gtk.RESPONSE_OK:
LOGGER.debug ("Registering credentials for %s on %s" % (self._account.get_realm(), self._account.get_host()))
self._account.set_credentials(self.get_user(),
self.get_password())
else:
LOGGER.debug ("Credential registration for %s cancelled" % self._account.get_host())
def _set_ok_sensitivity (self):
if self._user_entry.get_text() != "" and self._password_entry.get_text() != "":
self.set_response_sensitive(gtk.RESPONSE_OK, True)
else:
self.set_response_sensitive(gtk.RESPONSE_OK, False)
def get_user (self):
return self._user_entry.get_text()
def get_password (self):
return self._password_entry.get_text()
class ConcurrentRequestsException (Exception):
"""
Raised by GnomeURLopener if there are multiple concurrent
requests to open_async()
"""
def __init__ (self):
Exception.__init__ (self)
class JaikuClient (gobject.GObject) :
__gsignals__ = {
"message-reply" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [gobject.TYPE_PYOBJECT]),
}
def __init__ (self):
gobject.GObject.__init__ (self)
self._account = Account("jaiku.com", "Jaiku API")
self._jaiku = xmlrpclib.ServerProxy ("http://api.jaiku.com/xmlrpc")
self._thread = None
self.connect ("message-reply", lambda client, reply : self._on_message_reply(reply))
def send_message (self, msg):
if not self._account.has_credentials ():
LOGGER.debug ("No twitter credentials in keyring. Asking for them...")
login_dialog = AccountDialog(self._account)
login_dialog.show_all()
login_dialog.run()
login_dialog.destroy()
if self._thread :
raise ConcurrentRequestsException()
self._thread = threading.Thread (target=self._do_send_message,
args=(msg,),
name="JaikuClient")
self._thread.start()
def _on_message_reply (self, reply):
LOGGER.debug ("Got reply from Jaiku '%s'", reply)
def _do_send_message (self, *args):
self._thread = None
gtk.gdk.threads_enter()
user, password = self._account.get_credentials()
msg = args[0]
gtk.gdk.threads_leave()
reply = self._jaiku.presence.send ({"user" : user,
"personal_key" : password,
"message" : msg})
gtk.gdk.threads_enter()
self.emit ("message-reply", reply)
gtk.gdk.threads_leave()
_FAIL_POST = _(
"""Failed to post update to jaiku.com. Please make sure that:
- Your innternet connection is working
- You can connect to http://jaiku.com in your web browser
"""
)
class JaikuUpdateAction(deskbar.interfaces.Action):
def __init__(self, msg, client):
deskbar.interfaces.Action.__init__ (self, msg)
self._msg = msg
self._client = client
def get_hash(self):
return "jaiku:"+self._msg
def get_icon(self):
# We use only pixbufs
return None
def get_pixbuf(self) :
global _jaiku_pixbuf
return _jaiku_pixbuf
def activate(self, text=None):
LOGGER.info ("Posting: '%s'" % self._msg)
try:
self._client.send_message (self._msg)
except IOError, e:
LOGGER.warning ("Failed to post to jaiku.com: %s" % e)
error = gtk.MessageDialog (None,
type=gtk.MESSAGE_WARNING,
buttons=gtk.BUTTONS_OK)
error.set_markup (_FAIL_POST)
error.set_title (_("Error posting to jaiku.com"))
error.show_all()
error.run()
error.destroy()
def get_verb(self):
return _('Post "%(msg)s"')
def get_tooltip(self, text=None):
return _("Update your Jaiku account with the message:\n\n\t%s") % self._msg
def get_name(self, text=None):
return {"name": self._msg, "msg" : self._msg}
def skip_history(self):
return True
class JaikuMatch(deskbar.interfaces.Match):
def __init__(self, msg, client, **args):
global _jaiku_pixbuf
deskbar.interfaces.Match.__init__ (self, category="web", pixbuf=_jaiku_pixbuf, name=msg, **args)
self.add_action( JaikuUpdateAction(self.get_name(), client) )
def get_hash(self):
return "jaiku:"+self.get_name()
class JaikuHandler(deskbar.interfaces.Module):
INFOS = {'icon': load_jaiku_icon (),
'name': _("Jaiku"),
'description': _("Post updates to your Jaiku account"),
'version': VERSION}
def __init__(self):
deskbar.interfaces.Module.__init__(self)
self._client = JaikuClient()
def query(self, qstring):
if len (qstring) <= MIN_MESSAGE_LEN and \
len (qstring) > 140: return None
self._emit_query_ready(qstring, [JaikuMatch(qstring, self._client)])
def has_config(self):
return True
def show_config(self, parent):
LOGGER.debug ("Showing config")
account = Account ("jaiku.com", "Jaiku API")
login_dialog = AccountDialog(account)
login_dialog.show_all()
login_dialog.run()
login_dialog.destroy()