gnome-applets r10912 - in trunk/invest-applet: . invest
- From: callum svn gnome org
- To: svn-commits-list gnome org
- Subject: gnome-applets r10912 - in trunk/invest-applet: . invest
- Date: Sun, 17 Aug 2008 00:12:28 +0000 (UTC)
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]