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 """ Handle errors """
118 print "location error: %d... quitting" % error
121 def on_changed(self, device, data):
122 """ Set long and lat """
126 # once fix is found and long, lat available set long lat
127 if device.fix[1] & location.GPS_DEVICE_LATLONG_SET:
128 self.lat, self.long = device.fix[4:6]
131 def on_stop(self, control, data):
132 """ Stop the location service """
136 def start_location(self, data):
137 """ Start the location service """
142 """ Reset coordinates """
143 self.device.reset_last_known()
150 self.url_base = "http://ws.audioscrobbler.com/2.0/"
151 self.api_key = "1928a14bdf51369505530949d8b7e1ee"
154 self.parser = GigParser()
155 self.location = LocationUpdater()
156 self.win = hildon.StackableWindow()
159 """ Build the gui and start the update thread """
160 program = hildon.Program.get_instance()
161 menu = self.create_menu()
163 self.table = gtk.Table(columns=1)
164 self.table.set_row_spacings(10)
165 self.table.set_col_spacings(10)
167 pannable_area = hildon.PannableArea()
168 pannable_area.add_with_viewport(self.table)
170 self.win.set_title('Gig Finder')
171 self.win.connect("destroy", gtk.main_quit, None)
172 self.win.set_app_menu(menu)
173 self.win.add(pannable_area)
175 Thread(target=self.update_gigs).start()
180 def update_gigs(self):
182 gobject.idle_add(self.show_message, "Getting events")
183 gobject.idle_add(self.location.update_location)
186 while not self.location.lat:
189 events = self.get_events(self.location.lat, self.location.long)
190 gobject.idle_add(self.hide_message)
191 gobject.idle_add(self.show_events, events)
193 def show_message(self, message):
194 """ Set window progress indicator and show message """
195 hildon.hildon_gtk_window_set_progress_indicator(self.win, 1)
196 self.banner = hildon.hildon_banner_show_information(self.win,
200 def hide_message(self):
201 """ Hide banner and sete progress indicator """
203 hildon.hildon_gtk_window_set_progress_indicator(self.win, 0)
205 def get_events(self, lat, long):
206 """ Retrieve xml and parse into events list """
207 xml = self.get_xml(lat, long)
208 events = self.parser.parse_xml(xml,
213 def show_events(self, events):
214 """ Sort events, set new window title and add events to table """
215 events = self.sort_gigs(events)
216 self.win.set_title('Gig Finder (%s)' % len(events))
217 self.add_events(events)
219 def distance_cmp(self, x, y):
220 """ Compare distances for list sort """
228 def sort_gigs(self, events):
229 """ Sort gig by distance """
230 events.sort(cmp=self.distance_cmp, key=lambda x: x['distance'])
233 def get_xml(self, lat, long):
234 """ Return xml from lastfm """
235 method = "geo.getevents"
236 params = urllib.urlencode({'method': method,
237 'api_key': self.api_key,
238 'distance': self.distance,
241 response = urllib.urlopen(self.url_base, params)
242 return response.read()
244 def create_menu(self):
245 """ Build application menu """
246 update_button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
247 update_button.set_label('Update')
249 about_button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
250 about_button.set_label('About')
252 menu = hildon.AppMenu()
253 menu.append(update_button)
254 menu.append(about_button)
258 def show_details(self, widget, data):
259 """ Open new window showing gig details """
260 win = hildon.StackableWindow()
261 win.set_title(data['title'])
263 win.vbox = gtk.VBox()
266 scroll = hildon.PannableArea()
267 win.vbox.pack_start(scroll, True, True, 0)
269 view = hildon.TextView()
270 view.set_editable(False)
271 view.unset_flags(gtk.CAN_FOCUS)
272 view.set_wrap_mode(gtk.WRAP_WORD)
273 buffer = view.get_buffer()
274 end = buffer.get_end_iter()
275 buffer.insert(end, '%s\n' % data['title'])
276 buffer.insert(end, 'Artists: %s\n' % data['artists'])
277 buffer.insert(end, 'Venue: %s\n' % data['venue'])
278 buffer.insert(end, '%s\n' % data['address'])
279 buffer.insert(end, 'When: %s\n' % data['date'].strftime('%H:%M'))
280 buffer.insert(end, '\n')
281 scroll.add_with_viewport(view)
285 def add_events(self, events):
286 """ Add a table of buttons """
289 button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
290 hildon.BUTTON_ARRANGEMENT_VERTICAL)
291 button.set_text(event['title'], "distance: %0.02f km" % event['distance'])
292 button.connect("clicked", self.show_details, event)
293 self.table.attach(button, 0, 1, pos, pos+1)
295 self.table.show_all()
297 if __name__ == "__main__":