Merge branch 'master' of https://vcs.maemo.org/git/gigfinder master
authorJon <jmstaley>
Tue, 3 Aug 2010 21:32:24 +0000 (22:32 +0100)
committerJon <jmstaley>
Tue, 3 Aug 2010 21:32:24 +0000 (22:32 +0100)
README.rst [new file with mode: 0644]
build/build_gigfinder.py [new file with mode: 0644]
src/opt/gigfinder/LICENSE [new file with mode: 0644]
src/opt/gigfinder/events.py [new file with mode: 0644]
src/opt/gigfinder/gigfinder.py [new file with mode: 0755]
src/opt/gigfinder/locator.py [new file with mode: 0644]
src/opt/gigfinder/resultsparser.py [new file with mode: 0644]
src/usr/share/applications/hildon/gigfinder.desktop [new file with mode: 0644]
src/usr/share/icons/hicolor/48x48/hildon/gigfinder.png [new file with mode: 0644]

diff --git a/README.rst b/README.rst
new file mode 100644 (file)
index 0000000..a2d6109
--- /dev/null
@@ -0,0 +1,6 @@
+Gig Finder
+==========
+
+Simple program for the Nokia N900, uses the devices gps to find gigs nearby
+
+Released under the MIT license.
diff --git a/build/build_gigfinder.py b/build/build_gigfinder.py
new file mode 100644 (file)
index 0000000..9432c77
--- /dev/null
@@ -0,0 +1,55 @@
+#!/usr/bin/python2.5
+# -*- coding: utf-8 -*-
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published
+# by the Free Software Foundation; version 2 only.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+import py2deb
+import os
+if __name__ == "__main__":
+    try:
+        os.chdir(os.path.dirname(sys.argv[0]))
+    except:
+        pass
+    print
+    p=py2deb.Py2deb("gigfinder")   
+    p.description="Simple app that uses last.fm event feeds to find gigs nearby."
+    p.author="Jon Staley"
+    p.mail="jmstaley@gmail.com"
+    p.depends = "python2.5, python2.5-gtk2, python-location"
+    p.section="user/navigation"
+    #p.icon = "/home/user/MyDocs/mclock/mClock.png"
+    p.arch="all"                #should be all for python, any for all arch
+    p.urgency="low"             #not used in maemo onl for deb os
+    p.distribution="fremantle"
+    p.repository="extras-devel"
+    p.xsbc_bugtracker="https://garage.maemo.org/tracker/?group_id=1496"
+    #  p.postinstall="""#!/bin/sh
+    #  chmod +x /usr/bin/mclock.py""" #Set here your post install script
+    #  p.postremove="""#!/bin/sh
+    #  chmod +x /usr/bin/mclock.py""" #Set here your post remove script
+    #  p.preinstall="""#!/bin/sh
+    #  chmod +x /usr/bin/mclock.py""" #Set here your pre install script
+    #  p.preremove="""#!/bin/sh
+    #  chmod +x /usr/bin/mclock.py""" #Set here your pre remove script
+    version = "0.0.1"           #Version of your software, e.g. "1.2.0" or "0.8.2"
+    build = "6"                 #Build number, e.g. "1" for the first build of this version of your software. Increment for later re-builds of the same version of your software.
+                                #Text with changelog information to be displayed in the package "Details" tab of the Maemo Application Manager
+    changeloginformation = "Get app exiting cleanly, adjust threading" 
+    dir_name = "src"            #Name of the subfolder containing your package source files (e.g. usr\share\icons\hicolor\scalable\myappicon.svg, usr\lib\myapp\somelib.py). We suggest to leave it named src in all projects and will refer to that in the wiki article on maemo.org
+    #Thanks to DareTheHair from talk.maemo.org for this snippet that recursively builds the file list 
+    for root, dirs, files in os.walk(dir_name):
+        real_dir = root[len(dir_name):]
+        fake_file = []
+        for f in files:
+            fake_file.append(root + os.sep + f + "|" + f)
+        if len(fake_file) > 0:
+            p[real_dir] = fake_file
+    print p
+    r = p.generate(version,build,changelog=changeloginformation,tar=True,dsc=True,changes=True,build=False,src=True)
diff --git a/src/opt/gigfinder/LICENSE b/src/opt/gigfinder/LICENSE
new file mode 100644 (file)
index 0000000..0cff5cc
--- /dev/null
@@ -0,0 +1,23 @@
+Copyright (c) 2010 Jon Staley jmstaley@gmail.com
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/src/opt/gigfinder/events.py b/src/opt/gigfinder/events.py
new file mode 100644 (file)
index 0000000..390ccae
--- /dev/null
@@ -0,0 +1,77 @@
+import urllib
+import urllib2
+
+import location
+
+from resultsparser import  parse_json
+
+class Event:
+    def __init__(self, 
+                 title, 
+                 venue_name, 
+                 address, 
+                 lng,
+                 lat,
+                 artists, 
+                 start_date):
+        self.title = title
+        self.venue_name = venue_name
+        self.address = address
+        self.lng = lng
+        self.lat = lat
+        self.artists = artists
+        self.start_date = start_date
+
+    def get_distance_from(self, lng, lat):
+        return location.distance_between(float(lat), 
+                                         float(lng), 
+                                         float(self.lat), 
+                                         float(self.lng))
+
+class Events:
+    def __init__(self):
+        self.api_key = '1928a14bdf51369505530949d8b7e1ee'
+        self.url_base = 'http://ws.audioscrobbler.com/2.0/'
+        self.format = 'json'
+        self.method = 'geo.getevents'
+
+    def get_events(self, lat, lng, distance):
+        """ Retrieve json and parse into events list """
+        events = []
+        result = self.get_json(lat, lng, distance)
+        for event  in parse_json(result):
+            events.append(Event(event[0],
+                                event[1],
+                                event[2],
+                                event[3],
+                                event[4],
+                                event[5],
+                                event[6]))
+        return self.sort_events(events, lat, lng)
+
+    def sort_events(self, events, lat, lng):
+        """ Sort gig by distance """
+        if len(events) > 1:
+            events.sort(cmp=self.distance_cmp, key=lambda x: x.get_distance_from(lng, lat))
+        return events
+
+    def get_json(self, lat='', lng='', distance=''):
+        params = urllib.urlencode({'method': self.method,
+                                   'api_key': self.api_key,
+                                   'distance': distance,
+                                   'long': lng,
+                                   'lat': lat,
+                                   'format': self.format})
+        url = '%s?%s' % (self.url_base, params)
+        request = urllib2.Request(url, None)
+        response = urllib2.urlopen(request)
+        return response.read()
+        
+    def distance_cmp(self, x, y):
+        """ Compare distances for list sort """
+        if x > y:
+            return 1
+        elif x == y:
+            return 0
+        else:
+            return -1
diff --git a/src/opt/gigfinder/gigfinder.py b/src/opt/gigfinder/gigfinder.py
new file mode 100755 (executable)
index 0000000..c06d36f
--- /dev/null
@@ -0,0 +1,195 @@
+#!/usr/bin/env python
+
+"""Simple program to display local gigs
+
+Intended for use on the N900, uses the devices gps to find local gigs.
+"""
+
+__authors__ = ["Jon Staley"]
+__copyright__ = "Copyright 2010 Jon Staley"
+__license__ = "MIT"
+__version__ = "0.0.1"
+
+import gtk
+import hildon
+import time
+import gobject
+import os.path
+from threading import Thread
+import thread
+
+from locator import LocationUpdater
+from events import Events
+
+gtk.gdk.threads_init()
+
+# TODO: 
+# Add user settings for distance, date
+# maybe switch to json
+# maybe do km to mile conversions
+
+class GigFinder:
+
+    def __init__(self):
+        self.lat = None
+        self.long = None
+        self.distance = '10'
+        self.banner = None
+        self.location = LocationUpdater()
+        self.events = Events()
+        self.win = hildon.StackableWindow()
+        self.app_title = "Gig Finder"
+
+    def main(self):
+        """ Build the gui and start the update thread """
+       gtk.gdk.threads_enter()
+        program = hildon.Program.get_instance()
+        menu = self.create_menu()
+
+        self.win.set_title(self.app_title)
+        self.win.connect("destroy", self.quit, None)
+        self.win.set_app_menu(menu)
+
+       self.update(None, None)
+
+        self.win.show_all()
+        gtk.main()
+       gtk.gdk.threads_leave()
+
+    def quit(self, widget, data):
+        self.location.stop(widget, data)
+       thread.exit()
+       gtk.main_quit()
+       return False
+
+    def show_about(self, widget, data):
+        """ Show about dialog """
+        dialog = gtk.AboutDialog()
+        dialog.set_name('Gig Finder')
+        dialog.set_version(__version__)
+        dialog.set_authors(__authors__)
+        dialog.set_comments('Display gigs close by.\nUsing the http://www.last.fm api.')
+        dialog.set_license('Distributed under the MIT license.\nhttp://www.opensource.org/licenses/mit-license.php')
+        dialog.set_copyright(__copyright__)
+        dialog.show_all()
+
+    def show_message(self, message):
+        """ Set window progress indicator and show message """
+        hildon.hildon_gtk_window_set_progress_indicator(self.win, 1)
+        self.banner = hildon.hildon_banner_show_information(self.win,
+                                                            '', 
+                                                            message)
+
+    def hide_message(self):
+        """ Hide banner and set progress indicator """
+        self.banner.hide()
+        hildon.hildon_gtk_window_set_progress_indicator(self.win, 0)
+
+    def show_events(self, events):
+        """ Sort events, set new window title and add events to table """
+        if events:
+            self.win.set_title('%s (%s)' % (self.app_title, len(events)))
+            self.add_events(events)
+        else:
+            label = gtk.Label('No events available')
+            vbox = gtk.VBox(False, 0)
+            vbox.pack_start(label, True, True, 0)
+            vbox.show_all()
+            self.win.add(vbox)
+
+    def create_menu(self):
+        """ Build application menu """
+        update_button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
+        update_button.set_label('Update')
+        update_button.connect('clicked',
+                              self.update,
+                              None)
+
+        about_button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
+        about_button.set_label('About')
+        about_button.connect('clicked',
+                             self.show_about,
+                             None)
+
+        menu = hildon.AppMenu()
+        menu.append(update_button)
+        menu.append(about_button)
+        menu.show_all()
+        return menu
+
+    def show_details(self, widget, data):
+        """ Open new window showing gig details """
+        win = hildon.StackableWindow()
+        win.set_title(data.title)
+
+        win.vbox = gtk.VBox()
+        win.add(win.vbox)
+
+        scroll = hildon.PannableArea()
+        win.vbox.pack_start(scroll, True, True, 0)
+
+        view = hildon.TextView()
+        view.set_editable(False)
+        view.unset_flags(gtk.CAN_FOCUS)
+        view.set_wrap_mode(gtk.WRAP_WORD)
+        buffer = view.get_buffer()
+        end = buffer.get_end_iter()
+        buffer.insert(end, '%s\n' % data.title)
+        buffer.insert(end, 'Artists: %s\n' % data.artists)
+        buffer.insert(end, 'Venue: %s\n' % data.venue_name)
+        buffer.insert(end, '%s\n' % data.address)
+        buffer.insert(end, 'When: %s\n' % data.start_date.strftime('%H:%M %d/%m/%Y'))
+        buffer.insert(end, '\n')
+        scroll.add_with_viewport(view)
+
+        win.show_all()
+
+    def add_button_area(self):
+        self.box = gtk.VBox(True,0)
+        self.pannable_area = hildon.PannableArea()
+        self.pannable_area.add_with_viewport(self.box)
+        self.pannable_area.show_all()
+        self.win.add(self.pannable_area)
+        
+    def add_events(self, events):
+        """ Add a table of buttons """
+        for event in events:
+            button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, 
+                                   hildon.BUTTON_ARRANGEMENT_VERTICAL)
+            button.set_text(event.title, "distance: %0.02f km" % event.get_distance_from(self.location.long, self.location.lat))
+            button.connect("clicked", self.show_details, event)
+            self.box.pack_start(button)
+        self.box.show_all()
+            
+    def update(self, widget, data):
+        """ Start update process """
+        self.win.set_title(self.app_title)
+        self.location.reset()
+       if getattr(self, 'pannable_area', None):
+            self.win.remove(self.pannable_area)
+        self.add_button_area()
+       self.location.update_location()
+        Thread(target=self.update_gigs).start()
+
+    def update_gigs(self):
+        """ Get gig info """
+        gobject.idle_add(self.show_message, "Getting events")
+        
+        if not 'applications' in os.path.abspath(__file__):
+            # if no gps fix wait
+            while not self.location.lat or not self.location.long:
+                time.sleep(1)
+        else:
+            self.location.lat = float(51.517369)
+            self.location.long = float(-0.082998)
+         
+        events = self.events.get_events(self.location.lat, 
+                                        self.location.long, 
+                                        self.distance,)
+        gobject.idle_add(self.show_events, events)
+        gobject.idle_add(self.hide_message)
+       return True
+
+if __name__ == "__main__":
+    finder = GigFinder()
+    finder.main()
diff --git a/src/opt/gigfinder/locator.py b/src/opt/gigfinder/locator.py
new file mode 100644 (file)
index 0000000..0dc7629
--- /dev/null
@@ -0,0 +1,57 @@
+import gobject
+import location
+import thread
+
+class LocationUpdater:
+
+    def __init__(self):
+        self.lat = None
+        self.long = None
+
+        self.control = location.GPSDControl.get_default()
+        self.control.set_properties(preferred_method=location\
+                                            .METHOD_USER_SELECTED,
+                                    preferred_interval=location\
+                                            .INTERVAL_DEFAULT)
+        self.control.connect("error-verbose", self.on_error, self.control)
+        self.control.connect("gpsd-stopped", self.stop, None)
+
+        self.device = location.GPSDevice()
+        self.device.connect("changed", self.on_changed, self.control)
+
+    def update_location(self):
+        """ Run the loop and update lat and long """
+        self.reset()
+        gobject.idle_add(self.start_location, self.control)
+
+    def on_error(self, control, error, data):
+        """ Handle errors """
+        print "location error: %d... quitting" % error
+        data.quit()
+
+    def on_changed(self, device, data):
+        """ Set long and lat """
+        if not device:
+            return
+        if device.fix:
+            # once fix is found and horizontal accuracy is 1km
+            if location.GPS_DEVICE_LATLONG_SET:
+                if device.fix[6] <= 100000:
+                    self.lat, self.long = device.fix[4:6]
+                    data.stop()
+
+    def stop(self, widget, data):
+        """ Stop the location service """
+        self.control.stop()
+
+    def start_location(self, data):
+        """ Start the location service """
+        data.start()
+        return False
+
+    def reset(self):
+        """ Reset coordinates """
+        self.lat = None
+        self.long = None
+        self.device.reset_last_known()
+
diff --git a/src/opt/gigfinder/resultsparser.py b/src/opt/gigfinder/resultsparser.py
new file mode 100644 (file)
index 0000000..47deac0
--- /dev/null
@@ -0,0 +1,42 @@
+from datetime import datetime, date
+import time
+import simplejson
+
+def parse_json(json):
+    """ Parse json into usable format """
+    events_list = []
+    today = date.today()
+    json = simplejson.loads(json)
+    
+    events = json['events']['event']
+    for event in events:
+        venue_location = event['venue']['location']
+        address = '\n'.join([venue_location['street'],
+                             venue_location['city'],
+                             venue_location['country'],
+                             venue_location['postalcode']])
+        venue_geo = venue_location['geo:point']
+        if type(event['artists']['artist']) == list:
+            artist = '\n'.join(event['artists']['artist'])
+        else:
+            artist = event['artists']['artist']
+                
+        yield (event['title'], 
+               event['venue']['name'], 
+               address, 
+               venue_geo['geo:long'],
+               venue_geo['geo:lat'],
+               artist, 
+               parse_date(event['startDate']))
+        
+
+def parse_date(date_string):
+    """ Parse date string into datetime object """
+    fmt =  "%a, %d %b %Y %H:%M:%S"
+    result = time.strptime(date_string, fmt)
+    return datetime(result.tm_year, 
+                    result.tm_mon, 
+                    result.tm_mday, 
+                    result.tm_hour, 
+                    result.tm_min, 
+                    result.tm_sec)
diff --git a/src/usr/share/applications/hildon/gigfinder.desktop b/src/usr/share/applications/hildon/gigfinder.desktop
new file mode 100644 (file)
index 0000000..3fd494d
--- /dev/null
@@ -0,0 +1,12 @@
+[Desktop Entry]
+Version=1.0.0  
+Encoding=UTF-8
+Name=Gig Finder
+Comment=Gig finder
+Exec=/opt/gigfinder/gigfinder.py
+Icon=gigfinder
+X-Icon-path=/usr/share/icons
+X-Window-Icon=gigfinder
+Type=Application
+X-Osso-Type=application/x-executable
+
diff --git a/src/usr/share/icons/hicolor/48x48/hildon/gigfinder.png b/src/usr/share/icons/hicolor/48x48/hildon/gigfinder.png
new file mode 100644 (file)
index 0000000..89dfc28
Binary files /dev/null and b/src/usr/share/icons/hicolor/48x48/hildon/gigfinder.png differ