6e2be69ddf0b9256f9ff5198acf3262440b0a681
[gigfinder] / gig_finder.py
1 #!/usr/bin/env python2.5
2
3 """Simple program to display local gigs
4
5 Intended for use on the N900, uses the devices gps to find local gigs.
6 """
7
8 __author__ = "Jon Staley"
9 __copyright__ = "Copyright 2010 Jon Staley"
10 __license__ = "MIT"
11 __version__ = "0.0.1"
12
13 from xml.dom.minidom import parseString
14 from datetime import datetime, date
15 import urllib
16 import time
17 import gtk
18 import hildon
19 import location
20 import time
21 import gobject
22
23 gtk.gdk.threads_init()
24
25 class GigParser:
26
27     def parse_xml(self, xml, lat, long):
28         """ Parse xml into a dict """
29         # TODO: filter to todays events only
30         events_list = []
31         today = date.today()
32         dom = parseString(xml)
33
34         events = dom.getElementsByTagName('event')
35         for event in events:
36             title = event.getElementsByTagName('title')[0].childNodes[0].data
37             
38             artists_element = event.getElementsByTagName('artists')[0]
39             artist_list = []
40             for artist in artists_element.getElementsByTagName('artist'):
41                 artist_list.append(artist.childNodes[0].data)
42             artists = ', '.join(artist_list)
43
44             venue_details = event.getElementsByTagName('venue')[0]
45             venue_name = venue_details.getElementsByTagName('name')[0].childNodes[0].data
46             address = self.get_address(venue_details.getElementsByTagName('location')[0])
47             geo_data = venue_details.getElementsByTagName('geo:point')[0]
48             venue_lat = geo_data.getElementsByTagName('geo:lat')[0].childNodes[0].data
49             venue_long = geo_data.getElementsByTagName('geo:long')[0].childNodes[0].data
50             distance = location.distance_between(float(lat), 
51                                                  float(long), 
52                                                  float(venue_lat), 
53                                                  float(venue_long))
54             
55             start_date = self.parse_date(event.getElementsByTagName('startDate')[0].childNodes[0].data)
56             
57             events_list.append({'title': title,
58                                 'venue': venue_name,
59                                 'address': address,
60                                 'distance': distance,
61                                 'artists': artists,
62                                 'date': start_date})
63         return events_list
64     
65     def get_address(self, location):
66         """ Return the venues address details from the xml element """
67         street = ''
68         city = ''
69         country = ''
70         postalcode = ''
71         if location.getElementsByTagName('street')[0].childNodes:
72             street = location.getElementsByTagName('street')[0].childNodes[0].data
73         if location.getElementsByTagName('city')[0].childNodes:
74             city = location.getElementsByTagName('city')[0].childNodes[0].data
75         if location.getElementsByTagName('country')[0].childNodes:
76             country = location.getElementsByTagName('country')[0].childNodes[0].data
77         if location.getElementsByTagName('postalcode')[0].childNodes:
78             postalcode = location.getElementsByTagName('postalcode')[0].childNodes[0].data
79         return '\n'.join([street, city, country, postalcode])
80
81     def parse_date(self, date_string):
82         """ Parse date string into datetime object """
83         fmt =  "%a, %d %b %Y %H:%M:%S"
84         result = time.strptime(date_string, fmt)
85         return datetime(result.tm_year, 
86                         result.tm_mon, 
87                         result.tm_mday, 
88                         result.tm_hour, 
89                         result.tm_min, 
90                         result.tm_sec)
91
92 class LocationUpdater:
93
94     def __init__(self):
95         self.lat = None
96         self.long = None
97         self.loop = gobject.MainLoop()
98
99         self.control = location.GPSDControl.get_default()
100         self.control.set_properties(preferred_method=location.METHOD_USER_SELECTED,
101                                preferred_interval=location.INTERVAL_DEFAULT)
102         self.control.connect("error-verbose", self.on_error, self.loop)
103         self.control.connect("gpsd-stopped", self.on_stop, self.loop)
104
105         self.device = location.GPSDevice()
106         self.device.connect("changed", self.on_changed, self.control)
107
108     def update_location(self):
109         """ Run the loop and update lat and long """
110         self.device.reset_last_known()
111         gobject.idle_add(self.start_location, self.control)
112         self.loop.run()
113
114     def on_error(self, control, error, data):
115         print "location error: %d... quitting" % error
116         data.quit()
117
118     def on_changed(self, device, data):
119         if not device:
120             return
121         if device.fix:
122             if device.fix[1] & location.GPS_DEVICE_LATLONG_SET:
123                 self.lat, self.long = device.fix[4:6]
124                 data.stop()
125
126     def on_stop(self, control, data):
127         print "quitting"
128         data.quit()
129
130     def start_location(self, data):
131         data.start()
132         return False
133
134 class GigFinder:
135
136     def __init__(self):
137         self.lat = None
138         self.long = None
139         self.url_base = "http://ws.audioscrobbler.com/2.0/"
140         self.api_key = "1928a14bdf51369505530949d8b7e1ee"
141         self.distance = '10'
142
143         program = hildon.Program.get_instance()
144         self.win = hildon.StackableWindow()
145         self.win.set_title('Gig Finder')
146         self.win.connect("destroy", gtk.main_quit, None)
147
148         pannable_area = hildon.PannableArea()
149
150         self.table = gtk.Table(columns=1)
151         self.table.set_row_spacings(10)
152         self.table.set_col_spacings(10)
153
154         pannable_area.add_with_viewport(self.table)
155
156         self.win.add(pannable_area)
157
158         self.location_updater = LocationUpdater()
159         self.location_updater.update_location()
160         gobject.idle_add(self.update_gigs)
161
162     def main(self):
163         self.win.show_all()
164         gtk.main()
165
166     def update_gigs(self):
167         """ Get gig info """
168         hildon.hildon_gtk_window_set_progress_indicator(self.win, 1)
169
170         # if no gps fix wait
171         while not location.GPS_DEVICE_LATLONG_SET:
172             time.sleep(1)
173
174         xml = self.get_xml()
175         parser = GigParser()
176         events = parser.parse_xml(xml, self.location_updater.lat,
177                 self.location_updater.long)
178         events = self.sort_gigs(events)
179         self.win.set_title('Gig Finder (%s)' % len(events))
180         self.add_events(events)
181         hildon.hildon_gtk_window_set_progress_indicator(self.win, 0)
182
183     def distance_cmp(self, x, y):
184         """ compare distances for list sort """
185         if x > y:
186             return 1
187         elif x == y:
188             return 0
189         else:
190             return -1
191
192     def sort_gigs(self, events):
193         """ sort gig by distance """
194         events.sort(cmp=self.distance_cmp, key=lambda x: x['distance'])
195         return events
196         
197     def get_xml(self):
198         """ Return xml from lastfm """
199         method = "geo.getevents"
200         params = urllib.urlencode({'method': method,
201                                    'api_key': self.api_key,
202                                    'distance': self.distance,
203                                    'long': self.location_updater.long,
204                                    'lat': self.location_updater.lat})
205         response = urllib.urlopen(self.url_base, params)
206         return response.read()
207
208
209     def show_details(self, widget, data):
210         """ Open new window showing gig details """
211         win = hildon.StackableWindow()
212         win.set_title(data['title'])
213
214         win.vbox = gtk.VBox()
215         win.add(win.vbox)
216
217         scroll = hildon.PannableArea()
218         win.vbox.pack_start(scroll, True, True, 0)
219
220         view = hildon.TextView()
221         view.set_editable(False)
222         view.unset_flags(gtk.CAN_FOCUS)
223         view.set_wrap_mode(gtk.WRAP_WORD)
224         buffer = view.get_buffer()
225         end = buffer.get_end_iter()
226         buffer.insert(end, '%s\n' % data['title'])
227         buffer.insert(end, 'Artists: %s\n' % data['artists'])
228         buffer.insert(end, 'Venue: %s\n' % data['venue'])
229         buffer.insert(end, '%s\n' % data['address'])
230         buffer.insert(end, 'When: %s\n' % data['date'].strftime('%H:%M'))
231         buffer.insert(end, '\n')
232         scroll.add_with_viewport(view)
233
234         win.show_all()
235         
236     def add_events(self, events):
237         """ Return table of buttons """
238         pos = 0
239         for event in events:
240             button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, 
241                                    hildon.BUTTON_ARRANGEMENT_VERTICAL)
242             button.set_text(event['title'], "distance: %0.02f km" % event['distance'])
243             button.connect("clicked", self.show_details, event)
244             self.table.attach(button, 0, 1, pos, pos+1)
245             pos += 1
246         self.table.show_all()
247
248     #banner = hildon.hildon_banner_show_information(win,
249     #                                               "Updating", 
250     #                                               "Retrieving gig info")
251    
252 if __name__ == "__main__":
253     finder = GigFinder()
254     finder.main()