[Deskbar] Man I'm gonna regret this :)



Ok... *Only* because I promised to do this...

Here's a Beagle Live handler. It is heavily IN THE WORKS, but has some
basic functionality. It is only tested against Beagle 0.1.1 on Ubuntu
Breezy, and I have no idea how it fares against other Beagle
versions :-S

If you try it out, please post your experiences here. But be aware that
this handler probably never makes it into any release. It is a toy.
Caveat Emptor!

Cheers
Mikkel

PS: You almost certainly need a fresh cvs to use this handler.
import os, sys, signal
import popen2
from os.path import expanduser, exists, join
from gettext import gettext as _

import gnome, gnome.ui
import gtk

icon_theme = gtk.icon_theme_get_default()
thumb_factory = gnome.ui.ThumbnailFactory(16)

import deskbar
from deskbar.handler import AsyncHandler, Match
import deskbar.defs

QUERY_DELAY = 1
MAX_HITS=2

def _check_requirements():
	return (True, None)
	
HANDLERS = {
	"BeagleLiveHandler" : {
		"name": _("Live Beagle Queries"),
		"description": _("Query Beagle automatically as you type."),
		"requirements" : _check_requirements
	}
}


class UnknownBeagleTask (Exception):
	pass
	
class NoBeagleDaemon (Exception):
	pass

class BeagleProcess (popen2.Popen3):
	"""
	
	"""
	# Mental note to self: The beagle-query --max-hits switch sets 
	#max number of hits *per backend*.

	# Task definitions
	GET_DAEMON_VERSION = 0
	QUERY = 1
	
	def __init__ (self, task, query=None, max_hits=MAX_HITS):
		if task is self.GET_DAEMON_VERSION:
			popen2.Popen3.__init__(self, "beagle-info --daemon-version",)
			return
			
		elif task is self.QUERY and query is not None:
			print "Querying for", query
			popen2.Popen3.__init__(self, 
						"beagle-query %s --verbose --max-hits %s" % (query, MAX_HITS))
		else:
			raise UnknownBeagleTask()

#
# The following lists are separated (not using dicts of dicts)
# to allow easier iteration.
#

# This dict contains Beagle HitTypes as keys with
# sensible match name fields as values.
NAME_PATTERNS = {
	"Contact"	: "fixme:FileAs = ",
	"MailMessage" 	: "dc:title = ",
	"File" 		: "beagle:ExactFilename = ", 
	"FeedItem"	: "dc:title = ",
	"Note"		: "dc:title = ",
	"IMLog"		: "fixme:speakingto = "
}

ACTIONS = {
	"Contact"	: "evolution",
	"MailMessage" 	: "evolution",
	"File" 		: "gnome-open", 
	"FeedItem"	: "gnome-open",
	"Note"		: "tomboy --open-note", # FIXME: Doesn't work with spawnlp - we need to put the --open-note somewhere else
	"IMLog"		: "gnome-open" # FIXME: WHow do we call th eIM log viewer?
}

ICONS = {
	"Contact"	: "stock_contact",
	"MailMessage" 	: "stock_mail",
	"File" 		: "stock_new",
	"FeedItem"	: "stock_news",
	"Note"		: "stock_notes",
	"IMLog"		: "im"
}
#icon_theme.load_icon(drive.get_icon(), deskbar.ICON_SIZE, gtk.ICON_LOOKUP_USE_BUILTIN),

class BeagleMatch (Match):
	def __init__(self, handler, name, uri, hittype, mime=None):

		if mime:
			# We only get mimetypes for files. Fetch a matching icon.
			try:
				icon_name, num = gnome.ui.icon_lookup(icon_theme, thumb_factory,  file_uri=uri, custom_icon="")#uri, None, None, mime, None)#info)
				icon = icon_theme.load_icon(icon_name, deskbar.ICON_SIZE, gtk.ICON_LOOKUP_USE_BUILTIN)
				if icon:
					Match.__init__ (self, handler, name, icon)
				else:
					raise Exception()
			except Exception:
				Match.__init__ (self, handler, name, ICONS[hittype])
				print >> sys.stderr, "BeagleLive: Failed to load icon for file %s" % uri

		else:
			# We are not a file. Just use an icon from the ICON table
			Match.__init__ (self, handler, name, ICONS[hittype])
			
		self.__uri = uri
		self.__hittype = hittype
	
	def get_verb(self):
		# FIXME: We should prepend sensible strings for each hittype
		return "%(name)s"
		
	def action(self, text=None):
		os.spawnlp(os.P_NOWAIT, ACTIONS[self.__hittype], ACTIONS[self.__hittype], self.__uri)

	
class BeagleLiveHandler (AsyncHandler):
	"""
	"""
	
	def __init__ (self):
		AsyncHandler.__init__ (self)
		self.__process = None
		
	def initialize (self):
		"""
		Loads needed icons into memory, also
		try to connect to the beagle daemon an get its' version number.
		
		This method raises a NoBeagleDaemon exception if the beagle daemon
		is not running.
		"""
		# Load the icons here. This ought to be thread safe.
		# Initially ICONS contains a dict of hittype:icon_name.
		# We replace icon_name with the corresponding pixbufs.
		# This is a *trick* not a hack :-)
		for hittype in ICONS.iterkeys():
			if ICONS[hittype]:
				try:
					ICONS[hittype] = icon_theme.load_icon(ICONS[hittype], deskbar.ICON_SIZE, gtk.ICON_LOOKUP_USE_BUILTIN)
				except Exception:
					ICONS[hittype] = None
					print >> sys.stderr, "BeagleLive: Unable to load icon for %s" % hittype
					
		# Check if beagled is running and get the version
		process = BeagleProcess (BeagleProcess.GET_DAEMON_VERSION)
		process.wait()
		self.__daemon_version =  process.fromchild.readline()
		idx = self.__daemon_version.find(":")
		if idx == -1:
			print >> sys.stderr, "BeagleLive: Unable to connect to Beagle daemon!"
			raise NoBeagleDaemon()
		self.__daemon_version = self.__daemon_version[idx+2:]
		print "BeagleLive: Using Beagle daemon version %s"% self.__daemon_version
		
	def query (self, qstring, qmax=5):
		# Delay query
		self.check_query_changed (timeout=QUERY_DELAY)
		
		print "BeagleLive: Querying for %s" % qstring
		
		self.__process = BeagleProcess (BeagleProcess.QUERY, qstring, qmax)
		self.parse (self.__process.fromchild)
		self.__process.wait()
		self.__process = None
		print "BeagleLive: query for '%s' completed" % qstring
		
	def __clean_up (self):
		# If we have a running beagle query, send it a SIGINT
		if self.__process:
			os.kill (self.__process.pid, signal.SIGINT)
			self.__process.wait()
			self.__process = None
			print "BeagleLive: Beagle query interupted."

	def parse (self, pipe):
		"""
		All the work is being done here. We basically walk line by 
		line through the beagle-query output and match each line against
		known keywords.
		"""
		result = {}
		line = "a"
		while True:
			line = pipe.readline()
			if line == "": 
				break

			line = line.strip()
			
			if line.startswith("Uri: ") or line == "":
				# A new hit is starting or we've hit EOF
				# Do we have a qualified match?
				if (	result.has_key("name") and 
					result.has_key("uri") and 
					result.has_key("hittype")):
						
					# If this is file with a detected mime type
					# we should retrieve an icon for it. This will
					# be done in BeagleMatch.__init__
					mime = None
					if result.has_key("mime") and result["hittype"] == "File":
						mime = result["mime"]
					
					# We have all we need. 
					# Emit "query-ready" to main loop
					# if the query is still valid
					self.check_query_changed(self.__clean_up)
					match = BeagleMatch(self, result["name"], result["uri"], result["hittype"], mime)
					self.emit_query_ready ([match])
				# Reset our result
				result = {}
			

			if not result.has_key("uri"):
				if line.startswith ("Uri: "):
					result ["uri"] = line[5:]
					
			elif not result.has_key("hittype"):
				if line.startswith ("Type: "):
					result["hittype"] = line[6:]

			elif not result.has_key("mime"):
				if line.startswith ("MimeT: "):
					result["mime"] = line [7:]
					print "Found MIME:", result["mime"]

			elif not result.has_key("name"):
				# Try and look up a name pattern by our hittype
				# Since there's no common field for a display_String
				# or the like we have to heuristically determine one.
				
				# First try to look up a name pattern from NAME_PATTERNS
				if result.has_key ("hittype")
					idx = line.find (NAME_PATTERNS[result["hittype"]])
					if idx != -1:
						result["name"] = line[idx+len(NAME_PATTERNS[result["hittype"]]):]

				else:
					# We have no detected hittype yet. 
					# Use brute force.
					for hittype in NAME_PATTERNS.itervalues():
						idx = line.find (hittype) 
						if idx != -1:
							result["name"] = line[idx+len(hittype):]
							break


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]