1 #!/usr/bin/env python2.5
3 """Simple program to display local gigs
5 Intended for use on the N900, uses the devices gps to find local gigs.
8 __author__ = "Jon Staley"
9 __copyright__ = "Copyright 2010 Jon Staley"
13 from xml.dom.minidom import parseString
14 from datetime import datetime, date
22 from threading import Thread
25 gtk.gdk.threads_init()
29 def parse_xml(self, xml, lat, long):
30 """ Parse xml into a dict """
31 # TODO: filter to todays events only
34 dom = parseString(xml)
36 events = dom.getElementsByTagName('event')
38 title = event.getElementsByTagName('title')[0].childNodes[0].data
40 artists_element = event.getElementsByTagName('artists')[0]
42 for artist in artists_element.getElementsByTagName('artist'):
43 artist_list.append(artist.childNodes[0].data)
44 artists = ', '.join(artist_list)
46 venue_details = event.getElementsByTagName('venue')[0]
47 venue_name = venue_details.getElementsByTagName('name')[0].childNodes[0].data
48 address = self.get_address(venue_details.getElementsByTagName('location')[0])
49 geo_data = venue_details.getElementsByTagName('geo:point')[0]
50 venue_lat = geo_data.getElementsByTagName('geo:lat')[0].childNodes[0].data
51 venue_long = geo_data.getElementsByTagName('geo:long')[0].childNodes[0].data
52 distance = location.distance_between(float(lat),
57 start_date = self.parse_date(event.getElementsByTagName('startDate')[0].childNodes[0].data)
59 events_list.append({'title': title,
67 def get_address(self, location):
68 """ Return the venues address details from the xml element """
73 if location.getElementsByTagName('street')[0].childNodes:
74 street = location.getElementsByTagName('street')[0].childNodes[0].data
75 if location.getElementsByTagName('city')[0].childNodes:
76 city = location.getElementsByTagName('city')[0].childNodes[0].data
77 if location.getElementsByTagName('country')[0].childNodes:
78 country = location.getElementsByTagName('country')[0].childNodes[0].data
79 if location.getElementsByTagName('postalcode')[0].childNodes:
80 postalcode = location.getElementsByTagName('postalcode')[0].childNodes[0].data
81 return '\n'.join([street, city, country, postalcode])
83 def parse_date(self, date_string):
84 """ Parse date string into datetime object """
85 fmt = "%a, %d %b %Y %H:%M:%S"
86 result = time.strptime(date_string, fmt)
87 return datetime(result.tm_year,
94 class LocationUpdater:
99 self.loop = gobject.MainLoop()
101 self.control = location.GPSDControl.get_default()
102 self.control.set_properties(preferred_method=location.METHOD_USER_SELECTED,
103 preferred_interval=location.INTERVAL_DEFAULT)
104 self.control.connect("error-verbose", self.on_error, self.loop)
105 self.control.connect("gpsd-stopped", self.on_stop, self.loop)
107 self.device = location.GPSDevice()
108 self.device.connect("changed", self.on_changed, self.control)
110 def update_location(self):
111 """ Run the loop and update lat and long """
113 gobject.idle_add(self.start_location, self.control)
116 def on_error(self, control, error, data):
117 print "location error: %d... quitting" % error
120 def on_changed(self, device, data):
124 if device.fix[1] & location.GPS_DEVICE_LATLONG_SET:
125 self.lat, self.long = device.fix[4:6]
128 def on_stop(self, control, data):
132 def start_location(self, data):
137 """ Reset coordinates """
138 self.device.reset_last_known()
145 self.url_base = "http://ws.audioscrobbler.com/2.0/"
146 self.api_key = "1928a14bdf51369505530949d8b7e1ee"
149 self.parser = GigParser()
150 self.location = LocationUpdater()
152 program = hildon.Program.get_instance()
153 self.win = hildon.StackableWindow()
154 self.win.set_title('Gig Finder')
155 self.win.connect("destroy", gtk.main_quit, None)
157 pannable_area = hildon.PannableArea()
159 self.table = gtk.Table(columns=1)
160 self.table.set_row_spacings(10)
161 self.table.set_col_spacings(10)
163 pannable_area.add_with_viewport(self.table)
165 self.win.add(pannable_area)
167 Thread(target=self.update_gigs).start()
170 menu = self.create_menu()
171 self.win.set_app_menu(menu)
175 def update_gigs(self):
177 gobject.idle_add(self.show_message, "Getting events")
178 gobject.idle_add(self.location.update_location)
181 while not self.location.lat:
184 events = self.get_events(self.location.lat, self.location.long)
185 gobject.idle_add(self.hide_message)
186 gobject.idle_add(self.show_events, events)
188 def show_message(self, message):
189 """ set window progress indicator and show message """
190 hildon.hildon_gtk_window_set_progress_indicator(self.win, 1)
191 self.banner = hildon.hildon_banner_show_information(self.win,
195 def hide_message(self):
196 """ hide banner and sete progress indicator """
198 hildon.hildon_gtk_window_set_progress_indicator(self.win, 0)
200 def get_events(self, lat, long):
201 """ retrieve xml and parse into events list """
202 xml = self.get_xml(lat, long)
203 events = self.parser.parse_xml(xml,
208 def show_events(self, events):
209 """ sort events, set new window title and add events to table """
210 events = self.sort_gigs(events)
211 self.win.set_title('Gig Finder (%s)' % len(events))
212 self.add_events(events)
214 def distance_cmp(self, x, y):
215 """ compare distances for list sort """
223 def sort_gigs(self, events):
224 """ sort gig by distance """
225 events.sort(cmp=self.distance_cmp, key=lambda x: x['distance'])
228 def get_xml(self, lat, long):
229 """ Return xml from lastfm """
230 method = "geo.getevents"
231 params = urllib.urlencode({'method': method,
232 'api_key': self.api_key,
233 'distance': self.distance,
236 response = urllib.urlopen(self.url_base, params)
237 return response.read()
239 def create_menu(self):
240 """ build application menu """
241 menu = hildon.AppMenu()
242 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
243 button.set_label('Placeholder')
248 def show_details(self, widget, data):
249 """ Open new window showing gig details """
250 win = hildon.StackableWindow()
251 win.set_title(data['title'])
253 win.vbox = gtk.VBox()
256 scroll = hildon.PannableArea()
257 win.vbox.pack_start(scroll, True, True, 0)
259 view = hildon.TextView()
260 view.set_editable(False)
261 view.unset_flags(gtk.CAN_FOCUS)
262 view.set_wrap_mode(gtk.WRAP_WORD)
263 buffer = view.get_buffer()
264 end = buffer.get_end_iter()
265 buffer.insert(end, '%s\n' % data['title'])
266 buffer.insert(end, 'Artists: %s\n' % data['artists'])
267 buffer.insert(end, 'Venue: %s\n' % data['venue'])
268 buffer.insert(end, '%s\n' % data['address'])
269 buffer.insert(end, 'When: %s\n' % data['date'].strftime('%H:%M'))
270 buffer.insert(end, '\n')
271 scroll.add_with_viewport(view)
275 def add_events(self, events):
276 """ Add a table of buttons """
279 button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
280 hildon.BUTTON_ARRANGEMENT_VERTICAL)
281 button.set_text(event['title'], "distance: %0.02f km" % event['distance'])
282 button.connect("clicked", self.show_details, event)
283 self.table.attach(button, 0, 1, pos, pos+1)
285 self.table.show_all()
287 if __name__ == "__main__":