gnome-applets r10912 - in trunk/invest-applet: . invest



Author: callum
Date: Sun Aug 17 00:12:28 2008
New Revision: 10912
URL: http://svn.gnome.org/viewvc/gnome-applets?rev=10912&view=rev

Log:
Invest applet: Replace gnome-vfs calls with urllib calls. Give status information through the tooltip. Remove a defunct URL in the about dialog. Add proxy support (non-authenticating)

Modified:
   trunk/invest-applet/ChangeLog
   trunk/invest-applet/invest/__init__.py
   trunk/invest-applet/invest/about.py
   trunk/invest-applet/invest/applet.py
   trunk/invest-applet/invest/chart.py
   trunk/invest-applet/invest/quotes.py
   trunk/invest-applet/invest/widgets.py

Modified: trunk/invest-applet/invest/__init__.py
==============================================================================
--- trunk/invest-applet/invest/__init__.py	(original)
+++ trunk/invest-applet/invest/__init__.py	Sun Aug 17 00:12:28 2008
@@ -1,7 +1,7 @@
 import os, sys
 from os.path import join, exists, isdir, isfile, dirname, abspath, expanduser
 
-import gtk, gtk.gdk, gconf
+import gtk, gtk.gdk, gconf, gobject
 import cPickle
 
 # Autotools set the actual data_dir in defs.py
@@ -58,15 +58,6 @@
 
 STOCKS_FILE = join(USER_INVEST_DIR, "stocks.pickle")
 
-GNOMEVFS_CHUNK_SIZE = 512*1024 # 512 KBytes
-AUTOREFRESH_TIMEOUT = 20*60*1000 # 15 minutes
-TICKER_TIMEOUT = 10000#3*60*1000#
-
-QUOTES_URL="http://finance.yahoo.com/d/quotes.csv?s=%(s)s&f=sl1d1t1c1ohgv&e=.csv"
-
-# Sample (25/4/2008): UCG.MI,"4,86",09:37:00,2008/04/25,"0,07","4,82","4,87","4,82",11192336
-QUOTES_CSV_FIELDS=["ticker", ("trade", float), "time", "date", ("variation", float), ("open", float)]
-
 try:
 	STOCKS = cPickle.load(file(STOCKS_FILE))
 except Exception, msg:
@@ -89,3 +80,32 @@
 #		"comission": 31,
 #	},
 #}
+
+client = gconf.client_get_default()
+
+# borrowed from Ross Burton
+# http://burtonini.com/blog/computers/postr
+def get_gnome_proxy(client):
+	if client.get_bool("/system/http_proxy/use_http_proxy"):
+		host = client.get_string("/system/http_proxy/host")
+		port = client.get_int("/system/http_proxy/port")
+		if host is None or host == "" or port == 0:
+			# gnome proxy is not valid, use enviroment if available
+			return None
+		
+		if client.get_bool("/system/http_proxy/use_authentication"):
+			user = client.get_string("/system/http_proxy/authentication_user")
+			password = client.get_string("/system/http_proxy/authentication_password")
+			if user and user != "":
+				url = "http://%s:%s %s:%d" % (user, password, host, port)
+			else:
+				url = "http://%s:%d"; % (host, port)
+		else:
+			url = "http://%s:%d"; % (host, port)
+
+		return {'http': url}
+	else:
+		# gnome proxy is not set, use enviroment if available
+		return None
+
+PROXY = get_gnome_proxy(client)

Modified: trunk/invest-applet/invest/about.py
==============================================================================
--- trunk/invest-applet/invest/about.py	(original)
+++ trunk/invest-applet/invest/about.py	Sun Aug 17 00:12:28 2008
@@ -3,17 +3,10 @@
 from gettext import gettext as _
 from invest.defs import VERSION
 import invest
-import gtk, gtk.gdk, gnomevfs, gobject
+import gtk, gtk.gdk
+from gnome import url_show
 
-
-def on_email(about, mail):
-	gnomevfs.url_show("mailto:%s"; % mail)
-
-def on_url(about, link):
-	gnomevfs.url_show(link)
-
-gtk.about_dialog_set_email_hook(on_email)
-gtk.about_dialog_set_url_hook(on_url)
+gtk.about_dialog_set_email_hook(lambda dialog, email: url_show("mailto:%s"; % email))
 
 invest_logo = None
 try:

Modified: trunk/invest-applet/invest/applet.py
==============================================================================
--- trunk/invest-applet/invest/applet.py	(original)
+++ trunk/invest-applet/invest/applet.py	Sun Aug 17 00:12:28 2008
@@ -1,13 +1,16 @@
 import os, time
 from os.path import *
 import gnomeapplet, gtk, gtk.gdk, gconf, gobject
+gobject.threads_init()
 from gettext import gettext as _
 import gconf
 
-import invest, invest.about, invest.chart, invest.preferences
+import invest, invest.about, invest.chart, invest.preferences, invest.defs
 from invest.quotes import QuoteUpdater
 from invest.widgets import *
 
+gtk.window_set_default_icon_from_file(join(invest.ART_DATA_DIR, "invest_neutral.svg"))
+
 class InvestApplet:
 	def __init__(self, applet):
 		self.applet = applet
@@ -29,7 +32,8 @@
 		self.new_ilw()
 
 	def new_ilw(self):
-		self.quotes_updater = QuoteUpdater(self.set_applet_icon)
+		self.quotes_updater = QuoteUpdater(self.set_applet_icon,
+						   self.set_applet_tooltip)
 		self.investwidget = InvestWidget(self.quotes_updater)
 		self.ilw = InvestmentsListWindow(self.applet, self.investwidget)
 
@@ -73,6 +77,9 @@
 		else:
 			pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(join(invest.ART_DATA_DIR, "invest-22_down.png"), -1,-1)
 		self.applet_icon.set_from_pixbuf(pixbuf)
+	
+	def set_applet_tooltip(self, text):
+		self.applet_icon.set_tooltip_text(text)
 
 class InvestmentsListWindow(gtk.Window):
 	def __init__(self, applet, list):

Modified: trunk/invest-applet/invest/chart.py
==============================================================================
--- trunk/invest-applet/invest/chart.py	(original)
+++ trunk/invest-applet/invest/chart.py	Sun Aug 17 00:12:28 2008
@@ -2,13 +2,64 @@
 
 import gtk, gtk.gdk
 import gobject
-import gnomevfs
 import os
 import invest
 from gettext import gettext as _
 from invest import *
 import sys
 from os.path import join
+import urllib
+from threading import Thread
+import time
+
+AUTOREFRESH_TIMEOUT = 20*60*1000 # 15 minutes
+
+# based on http://www.johnstowers.co.nz/blog/index.php/2007/03/12/threading-and-pygtk/
+class _IdleObject(gobject.GObject):
+	"""
+	Override gobject.GObject to always emit signals in the main thread
+	by emmitting on an idle handler
+	"""
+	def __init__(self):
+		gobject.GObject.__init__(self)
+
+	def emit(self, *args):
+		gobject.idle_add(gobject.GObject.emit,self,*args)
+
+class ImageRetriever(Thread, _IdleObject):
+	"""
+	Thread which uses gobject signals to return information
+	to the GUI.
+	"""
+	__gsignals__ =  { 
+			"completed": (
+				gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
+			# FIXME: should we be making use of this?
+			#"progress": (
+			#	gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
+			#	gobject.TYPE_FLOAT])        #percent complete
+			}
+
+	def __init__(self, image_url):
+		Thread.__init__(self)
+		_IdleObject.__init__(self)	
+		self.image_url = image_url
+		self.retrieved = False
+		
+	def run(self):
+		self.image = gtk.Image()
+		try: sock = urllib.urlopen(self.image_url, proxies = invest.PROXY)
+		except:
+			if invest.DEBUGGING:
+				print "Error while opening %s" % self.image_url
+		else:
+			loader = gtk.gdk.PixbufLoader()
+			loader.connect("closed", lambda loader: self.image.set_from_pixbuf(loader.get_pixbuf()))
+			loader.write(sock.read())
+			sock.close()
+			loader.close()
+			self.retrieved = True
+		self.emit("completed")
 
 # p:
 #  eX = Exponential Moving Average
@@ -54,8 +105,6 @@
 		win.set_title(_("Financial Chart"))
 		
 		try:
-			pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(join(invest.ART_DATA_DIR, "invest-16_neutral.png"), -1,-1)
-			win.set_icon(pixbuf)
 			pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(join(invest.ART_DATA_DIR, "invest_neutral.svg"), 96,96)
 			self.ui.get_object("plot").set_from_pixbuf(pixbuf)
 		except Exception, msg:
@@ -92,8 +141,10 @@
 		if tickers.strip() == "":
 			return True
 		
-		if from_timer and not ustime.hour_between(9, 16):
-			return True
+		# FIXME: We don't just do US stocks, so we can't be this
+		# simplistic about it, but it is a good idea.
+		#if from_timer and not ustime.hour_between(9, 16):
+		#	return True
 			
 		tickers = [ticker.strip().upper() for ticker in tickers.split(' ') if ticker != ""]
 		
@@ -169,41 +220,22 @@
 		progress.set_text(_("Opening Chart"))
 		progress.show()
 		
-		gnomevfs.async.open(url, lambda h,e: self.on_chart_open(h,e,url))
+		image_retriever = ImageRetriever(url)
+		image_retriever.connect("completed", self.on_retriever_completed)
+		image_retriever.start()
 
 		# Update timer if needed
 		self.on_autorefresh_toggled(self.ui.get_object("autorefresh"))
-				
 		return True
 	
-	def on_chart_open(self, handle, exc_type, url):
+	def on_retriever_completed(self, retriever):
+		self.ui.get_object("plot").set_from_pixbuf(retriever.image.get_pixbuf())
 		progress = self.ui.get_object("progress")
-		progress.set_text(_("Downloading Chart"))
-		
-		if not exc_type:
-			loader = gtk.gdk.PixbufLoader()
-			handle.read(GNOMEVFS_CHUNK_SIZE, self.on_chart_read, (loader, url))
-		else:
-			handle.close(lambda *args: None)
-			progress.set_text("")
-
-	def on_chart_read(self, handle, data, exc_type, bytes_requested, udata):
-		loader, url = udata
-		progress = self.ui.get_object("progress")
-		progress.set_text(_("Reading Chart chunk"))
-		
-		if not exc_type:
-			loader.write(data)
-		
-		if exc_type:
-			loader.close()
-			handle.close(lambda *args: None)
-			self.ui.get_object("plot").set_from_pixbuf(loader.get_pixbuf())
-			progress.set_text(url)
+		if retriever.retrieved == True:
+			progress.set_text(_("Chart downloaded"))
 		else:
-			progress.set_text(_("Downloading Chart"))
-			handle.read(GNOMEVFS_CHUNK_SIZE, self.on_chart_read, udata)
-		
+			progress.set_text(_("Chart could not be downloaded"))
+	
 	def on_autorefresh_toggled(self, autorefresh):
 		if self.autorefresh_id != 0:
 			gobject.source_remove(self.autorefresh_id)
@@ -212,35 +244,6 @@
 		if autorefresh.get_active():
 			self.autorefresh_id = gobject.timeout_add(AUTOREFRESH_TIMEOUT, self.on_refresh_chart, True)
 
-def FinancialSparklineChartPixbuf(ticker, update_callback, userdata):
-	if len(ticker.split('.')) == 2:
-		url = 'http://ichart.europe.yahoo.com/h?s=%s' % ticker
-	else:
-		url = 'http://ichart.yahoo.com/h?s=%s' % ticker
-	
-	def read_cb(handle, buffer, result, size, loader):
-		if result:
-			loader.close()
-			handle.close(lambda *args: None)
-			update_callback(loader.get_pixbuf(), userdata)
-		else:
-			loader.write(buffer, size)
-			handle.read(GNOMEVFS_CHUNK_SIZE, read_cb, loader)
-
-	def open_cb(handle, result):
-		if result:
-			print "Open of sparkline chart for ticker %s failed:" % ticker, result
-			# FIXME: show 'n/a' if sparkline is not available
-			# this works but is very dangerous (infinite loop)
-			# FinancialSparklineChartPixbuf('WRONG', update_callback, userdata)
-			update_callback(None, userdata)
-		else:
-			loader = gtk.gdk.PixbufLoader()
-			handle.read(GNOMEVFS_CHUNK_SIZE, read_cb, loader)
-
-	gnomevfs.async.open(url, open_cb, gnomevfs.OPEN_READ,
-		gnomevfs.PRIORITY_DEFAULT)
-
 def show_chart(tickers):
 	ui = gtk.Builder();
 	ui.add_from_file(os.path.join(invest.BUILDER_DATA_DIR, "financialchart.ui"))

Modified: trunk/invest-applet/invest/quotes.py
==============================================================================
--- trunk/invest-applet/invest/quotes.py	(original)
+++ trunk/invest-applet/invest/quotes.py	Sun Aug 17 00:12:28 2008
@@ -1,54 +1,110 @@
-import os, time
-from os.path import *
+from os.path import join
 import gnomeapplet, gtk, gtk.gdk, gconf, gobject
 from gettext import gettext as _
-import gtk, gobject, gnomevfs
-import csv, os
+import csv
+from urllib import urlopen
+import datetime
+from threading import Thread
 
 import invest, invest.about, invest.chart
 
+CHUNK_SIZE = 512*1024 # 512 kB
+AUTOREFRESH_TIMEOUT = 10*60*1000 # 15 minutes
+
+QUOTES_URL="http://finance.yahoo.com/d/quotes.csv?s=%(s)s&f=sl1d1t1c1ohgv&e=.csv"
+
+# Sample (25/4/2008): UCG.MI,"4,86",09:37:00,2008/04/25,"0,07","4,82","4,87","4,82",11192336
+QUOTES_CSV_FIELDS=["ticker", ("trade", float), "time", "date", ("variation", float), ("open", float)]
+
+# based on http://www.johnstowers.co.nz/blog/index.php/2007/03/12/threading-and-pygtk/
+class _IdleObject(gobject.GObject):
+	"""
+	Override gobject.GObject to always emit signals in the main thread
+	by emmitting on an idle handler
+	"""
+	def __init__(self):
+		gobject.GObject.__init__(self)
+
+	def emit(self, *args):
+		gobject.idle_add(gobject.GObject.emit,self,*args)
+
+class QuotesRetriever(Thread, _IdleObject):
+	"""
+	Thread which uses gobject signals to return information
+	to the GUI.
+	"""
+	__gsignals__ =  { 
+			"completed": (
+				gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
+			# FIXME: We don't monitor progress, yet ...
+			#"progress": (
+			#	gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
+			#	gobject.TYPE_FLOAT])        #percent complete
+			}
+
+	def __init__(self, tickers):
+		Thread.__init__(self)
+		_IdleObject.__init__(self)	
+		self.tickers = tickers
+		self.retrieved = False
+		self.data = []
+
+	def run(self):
+		quotes_url = QUOTES_URL % {"s": self.tickers}
+		try:
+			quotes_file = urlopen(quotes_url, proxies = invest.PROXY)
+			self.data = quotes_file.readlines ()
+			quotes_file.close ()
+		except:
+			if invest.DEBUGGING:
+				print "Error while retrieving quotes data (url = %s)" % quotes_url
+		else:
+			self.retrieved = True
+		self.emit("completed")
+
+
 class QuoteUpdater(gtk.ListStore):
-	SYMBOL, TICKER_ONLY, BALANCE, BALANCE_PCT, VALUE, VARIATION_PCT = range(6)
-	def __init__ (self, change_icon_callback):
+	updated = False
+	last_updated = None
+	SYMBOL, TICKER_ONLY, BALANCE, BALANCE_PCT, VALUE, VARIATION_PCT, PB = range(7)
+	def __init__ (self, change_icon_callback, set_tooltip_callback):
 		gtk.ListStore.__init__ (self, gobject.TYPE_STRING, bool, float, float, float, float, gtk.gdk.Pixbuf)
-		gobject.timeout_add(invest.AUTOREFRESH_TIMEOUT, self.refresh)
+		gobject.timeout_add(AUTOREFRESH_TIMEOUT, self.refresh)
 		self.change_icon_callback = change_icon_callback
+		self.set_tooltip_callback = set_tooltip_callback
 		self.refresh()
 		
 	def refresh(self):
 		if len(invest.STOCKS) == 0:
 			return True
 			
-		s = ""
-		for ticker in invest.STOCKS.keys():
-			s += "%s+" % ticker
+		tickers = '+'.join(invest.STOCKS.keys())
+		quotes_retriever = QuotesRetriever(tickers)
+		quotes_retriever.connect("completed", self.on_retriever_completed)
+		quotes_retriever.start()
 		
 		self.quotes_valid = False
 
-		gnomevfs.async.open(invest.QUOTES_URL % {"s": s[:-1]}, self.on_quotes_open)
-		return True
-		
-	def on_quotes_open(self, handle, exc_type):
-		if not exc_type:
-			handle.read(invest.GNOMEVFS_CHUNK_SIZE, lambda h,d,e,b: self.on_quotes_read(h,d,e,b, ""))
-		else:
-			# In the event of an exception we try and
-			# close the handle. Chances are it was not
-			# opened, so we ignore the error.
-			try:
-				handle.close(lambda *args: None)
-			except:
-				pass
-
-	def on_quotes_read(self, handle, data, exc_type, bytes_requested, read):
-		if not exc_type:
-			read += data
-			
-		if exc_type:
-			handle.close(lambda *args: None)
-			self.populate(self.parse_yahoo_csv(csv.reader(read.split("\n"))))
+
+	def on_retriever_completed(self, retriever):
+		if retriever.retrieved == False:
+			tooltip = [_('Invest could not connect to Yahoo! Finance')]
+			if self.last_updated != None:
+				tooltip.append(_('Updated at %s') % self.last_updated.strftime("%H:%M"))
+			self.set_tooltip_callback('\n'.join(tooltip))
 		else:
-			handle.read(invest.GNOMEVFS_CHUNK_SIZE, lambda h,d,e,b: self.on_quotes_read(h,d,e,b, read))
+			self.populate(self.parse_yahoo_csv(csv.reader(retriever.data)))
+			self.updated = True
+			self.last_updated = datetime.datetime.now()
+			tooltip = []
+			if self.simple_quotes_count > 0:
+				tooltip.append(_('Quotes average change %%: %+.2f%%') % self.avg_simple_quotes_change)
+			if self.positions_count > 0:
+				tooltip.append(_('Positions balance: %+.2f') % self.positions_balance)
+			tooltip.append(_('Updated at %s') % self.last_updated.strftime("%H:%M"))
+			self.set_tooltip_callback('\n'.join(tooltip))
+	
+
 
 	def parse_yahoo_csv(self, csvreader):
 		result = {}
@@ -57,7 +113,7 @@
 				continue
 
 			result[fields[0]] = {}
-			for i, field in enumerate(invest.QUOTES_CSV_FIELDS):
+			for i, field in enumerate(QUOTES_CSV_FIELDS):
 				if type(field) == tuple:
 					try:
 						result[fields[0]][field[0]] = field[1](fields[i])
@@ -85,9 +141,9 @@
 		quote_items.sort ()
 
 		simple_quotes_change = 0
-		simple_quotes_count = 0
-		positions_balance = 0
-		positions_count = 0
+		self.simple_quotes_count = 0
+		self.positions_balance = 0
+		self.positions_count = 0
 
 		for ticker, val in quote_items:
 			pb = None
@@ -100,11 +156,11 @@
 					break
 			
 			if is_simple_quote:
-				simple_quotes_count += 1
+				self.simple_quotes_count += 1
 				row = self.insert(0, [ticker, True, 0, 0, val["trade"], val["variation_pct"], pb])
 				simple_quotes_change += val['variation_pct']
 			else:
-				positions_count += 1
+				self.positions_count += 1
 				current = sum([purchase["amount"]*val["trade"] for purchase in invest.STOCKS[ticker] if purchase["amount"] != 0])
 				paid = sum([purchase["amount"]*purchase["bought"] + purchase["comission"] for purchase in invest.STOCKS[ticker] if purchase["amount"] != 0])
 				balance = current - paid
@@ -113,22 +169,32 @@
 				else:
 					change = 100 # Not technically correct, but it will look more intuitive than the real result of infinity.
 				row = self.insert(0, [ticker, False, balance, change, val["trade"], val["variation_pct"], pb])
-				positions_balance += balance
-				
-			invest.chart.FinancialSparklineChartPixbuf(ticker, self.set_pb_callback, row)
-
-		if simple_quotes_count > 0:
-			change = simple_quotes_change/float(simple_quotes_count)
-			if change != 0:
-				self.change_icon_callback(change/abs(change))
+				self.positions_balance += balance
+
+			if len(ticker.split('.')) == 2:
+				url = 'http://ichart.europe.yahoo.com/h?s=%s' % ticker
 			else:
-				self.change_icon_callback(change)
+				url = 'http://ichart.yahoo.com/h?s=%s' % ticker
+			
+			image_retriever = invest.chart.ImageRetriever(url)
+			image_retriever.connect("completed", self.set_pb_callback, row)
+			image_retriever.start()
+
+		self.avg_simple_quotes_change = simple_quotes_change/float(self.simple_quotes_count)
+		if self.avg_simple_quotes_change != 0:
+			simple_quotes_change_sign = self.avg_simple_quotes_change / abs(self.avg_simple_quotes_change)
+		else:
+			simple_quotes_change_sign = 0
+
+		# change icon
+		if self.simple_quotes_count > 0:
+			self.change_icon_callback(simple_quotes_change_sign)
 		else:
-			self.change_icon_callback(positions_balance/abs(positions_balance))
+			positions_balance_sign = self.positions_balance/abs(self.positions_balance)
+			self.change_icon_callback(positions_balance_sign)
 
-	def set_pb_callback(self, pb, row):
-		if pb != None:
-			self.set_value(row, 6, pb)
+	def set_pb_callback(self, retriever, row):
+		self.set_value(row, 6, retriever.image.get_pixbuf())
 	
 	# check if we have only simple quotes
 	def simple_quotes_only(self):

Modified: trunk/invest-applet/invest/widgets.py
==============================================================================
--- trunk/invest-applet/invest/widgets.py	(original)
+++ trunk/invest-applet/invest/widgets.py	Sun Aug 17 00:12:28 2008
@@ -2,7 +2,7 @@
 from os.path import *
 import gnomeapplet, gtk, gtk.gdk, gconf, gobject, pango
 from gettext import gettext as _
-import gtk, gobject, gnomevfs
+import gtk, gobject
 import csv, os
 from gettext import gettext as _
 import invest, invest.about, invest.chart
@@ -35,6 +35,8 @@
 ]
 RED = COLORSCALE_NEGATIVE[-1]
 
+TICKER_TIMEOUT = 10000#3*60*1000#
+
 class InvestWidget(gtk.TreeView):
 	def __init__(self, quotes_updater):
 		gtk.TreeView.__init__(self)
@@ -43,7 +45,7 @@
 
 		simple_quotes_only = quotes_updater.simple_quotes_only()
 
-		# model: SYMBOL, TICKER_ONLY, BALANCE, BALANCE_PCT, VALUE, VARIATION_PCT
+		# model: SYMBOL, TICKER_ONLY, BALANCE, BALANCE_PCT, VALUE, VARIATION_PCT, PB
 		# Translators: these words all refer to a stock. Last is short
 		# for "last price". Gain is referring to the gain since the 
 		# stock was purchased.



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