base version
[pystan] / src / pystan / lib / stan.py
diff --git a/src/pystan/lib/stan.py b/src/pystan/lib/stan.py
new file mode 100644 (file)
index 0000000..337672d
--- /dev/null
@@ -0,0 +1,478 @@
+# -*- coding: UTF-8 -*-
+
+import os, sys, time, re
+import urllib, urllib2, urlparse, cgi
+from urllib2 import URLError
+
+
+from timetable_parser import StanTimetableParser
+
+class STAN:
+
+       RSS_URL =  'http://www.reseau-stan.com/rss'
+
+       get_params = [ 'rub_code', 'gpl_id', 'thm_id', 'lineid', 'date', 'hour', 'direction' ]
+
+       def lines(self):
+
+               return [
+                          { 'id': 144,
+                            'name': u"Nancy République / Bouxières-aux-Dames",
+                            'numbers': ["322"],
+                            'fg-color': '#fff',
+                            'bg-color': '#6ecbf1'
+                          },
+                          { 'id': 145,
+                            'name': u"Vandoeuvre Vᅢᄅlodrome / Chaligny",
+                            'numbers': ["511"],
+                            'fg-color': '#fff',
+                            'bg-color': '#a3b909'
+                          },
+                          { 'id': 146,
+                            'name': u"Nancy République / Frouard Le Nid",
+                            'numbers': ["323"],
+                            'fg-color': '#fff',
+                            'bg-color': '#f5a3c8'
+                          },
+                          { 'id': 147,
+                            'name': u"Vandoeuvre Vélodrome / Pont Saint-Vincent",
+                            'numbers': ["512"],
+                            'fg-color': '#fff',
+                            'bg-color': '#c00266'
+                          },
+                          { 'id': 148,
+                            'name': u"Nancy République / Pompey Les Vannes",
+                            'numbers': ["324"],
+                            'fg-color': '#fff',
+                            'bg-color': '#d80b8c'
+                          },
+                          { 'id': 149,
+                            'name': u"Vandoeuvre Vélodrome / Messein",
+                            'numbers': ["514"],
+                            'fg-color': '#fff',
+                            'bg-color': '#006d2c'
+                          },
+                          { 'id': 150,
+                            'name': u"Nancy République / Pompey Fonds de Lavaux",
+                            'numbers': ["325"],
+                            'fg-color': '#fff',
+                            'bg-color': '#b4d8be'
+                          },
+                          { 'id': 151,
+                           'name': u"Nancy Place Carnot / Chaligny",
+                            'numbers': ["521"],
+                            'fg-color': '#a3b909',
+                            'bg-color': '#fff'
+                          },
+                          { 'id': 152,
+                            'name': u"Nancy République / Saint-Nicolas Le Nid",
+                            'numbers': ["621"],
+                            'fg-color': '#fff',
+                            'bg-color': '#0b0a4b'
+                          },
+                          { 'id': 153,
+                            'name': u"Nancy Place Carnot / Pont Saint-Vincent",
+                            'numbers': ["522"],
+                            'fg-color': '#c00266',
+                            'bg-color': '#fff'
+                          },
+                          { 'id': 154,
+                            'name': u"Nancy République / Dombasle Maroc",
+                            'numbers': ["624"],
+                            'fg-color': '#fff',
+                            'bg-color': '#fb1518'
+                          },
+                          { 'id': 155,
+                            'name': u"Nancy Place Carnot / Bainville-sur-Madon",
+                            'numbers': ["523"],
+                            'fg-color': '#81d0e3',
+                            'bg-color': '#fff'
+                          },
+                          { 'id': 157,
+                            'name': u"Nancy République / Saint-Nicolas République",
+                            'numbers': ["622"],
+                            'fg-color': '#fff',
+                            'bg-color': '#0d97a8'
+                          },
+                          { 'id': 156,
+                            'name': u"Nancy République / Dombasle Stand",
+                            'numbers': ["623"],
+                            'fg-color': '#fff',
+                            'bg-color': '#fe6612'
+                          },
+                          { 'id': 158,
+                            'name': u"Nancy République / Champigneulles",
+                            'numbers': ["321"],
+                            'fg-color': '#fff',
+                            'bg-color': '#d3b6d1'
+                          },
+                          { 'id': 126,
+                            'name': u"Vandoeuvre Parc des Expositions / Nancy République",
+                            'numbers': ["129"],
+                            'fg-color': '#fff',
+                            'bg-color': '#92bbad'
+                          },
+                          { 'id': 133,
+                            'name': u"Ligne 80",
+                            'numbers': ["80"],
+                            'bg-color': 'yellow'
+                          },
+                          { 'id': 138,
+                            'name': u"Vandoeuvre CHU Brabois / Essey Mouzimpré",
+                            'numbers': ["Tram 1"],
+                            'fg-color': '#fff',
+                            'bg-color': '#ff0000'
+                          },
+                          { 'id': 141,
+                            'name': u"Vandoeuvre CHU Brabois / Centre d\'accueil - Villers les Essarts",
+                            'numbers': ["Ptit Stan Vilers"],
+                            'bg-color': 'yellow'
+                          },
+                          { 'id': 143,
+                            'name': u"Vandoeuvre CHU Brabois / Vandoeuvre Nations - Médecine préventive ",
+                            'numbers': ["Ptit Stan Vandoeuvre"],
+                            'bg-color': 'yellow'
+                          },
+                          { 'id': 140,
+                            'name': u"Laxou Sapinière / Laxou Provinces",
+                            'numbers': ["Ptit Stan Laxou"],
+                            'bg-color': 'yellow'
+                          },
+                          { 'id': 142,
+                            'name': u"Nancy Carnot",
+                            'numbers': ["Ptit Stan Colline"],
+                            'bg-color': 'yellow'
+                          },
+                          { 'id': 134,
+                            'name': u"Malzéville Sadi Carnot - Nancy Cimetière du Sud",
+                            'numbers': ["Minibus"],
+                            'bg-color': 'yellow'
+                          },
+                          { 'id': 159,
+                            'name': u"StanU",
+                            'numbers': ["110"],
+                            'bg-color': 'yellow'
+                          },
+                          { 'id': 112,
+                            'name': u"Maxéville Champ le Boeuf - Nancy Haut du Lièvre / Jarville Sion",
+                            'numbers': ["111", "112"],
+                            'fg-color': '#fff',
+                            'bg-color': '#71228d'
+                          },
+                          { 'id': 96,
+                            'name': u"Houdemont / Vandoeuvre Charmois",
+                            'numbers': ["114"],
+                            'fg-color': '#000',
+                            'bg-color': '#b6a6d3'
+                          },
+                          { 'id': 98,
+                            'name': u"Ludres / Villers Lycée Stanislas",
+                            'numbers': ["115"],
+                            'fg-color': '#fff',
+                            'bg-color': '#705aac'
+                          },
+                          { 'id': 101,
+                            'name': u"Fléville Fleurychamp - Heillecourt / Villers Lycée Stanislas",
+                            'numbers': ["116"],
+                            'fg-color': '#fff',
+                            'bg-color': '#755eac'
+                          },
+                          { 'id': 103,
+                            'name': u"Nancy Beauregard / Nancy République",
+                            'numbers': ["121"],
+                            'fg-color': '#fff',
+                            'bg-color': '#339d2f'
+                          },
+                          { 'id': 135,
+                            'name': u"Villers Clairlieu / Nancy République",
+                            'numbers': ["122", "126"],
+                            'fg-color': '#000',
+                            'bg-color': '#9cca1f'
+                          },
+                          { 'id': 106,
+                            'name': u"Vandoeuvre Nations / Nancy République",
+                            'numbers': ["123"],
+                            'fg-color': '#000',
+                            'bg-color': '#a9d72f'
+                          },
+                          { 'id': 109,
+                            'name': u"Laxou Mouzon / Nancy République",
+                            'numbers': ["124"],
+                            'fg-color': '#fff',
+                            'bg-color': '#39a63b'
+                          },
+
+                          { 'id': 118,
+                            'name': u"Laxou Provinces / Seichamps",
+                            'numbers': ["130"],
+                            'fg-color': '#fff',
+                            'bg-color': '#048a96'
+                          },
+                          { 'id': 95,
+                            'name': u"Maxéville Mairie / Jarville Sion - Heillecourt - Fléville De la Noue",
+                            'numbers': ["131", "132", "133"],
+                            'fg-color': '#000',
+                            'bg-color': '#82cee6'
+                          },
+                          { 'id': 97,
+                            'name': u"Villers Lycée Stanislas / Maxéville Lafayette - Malzéville Savlons",
+                            'numbers': ["134", "135"],
+                            'fg-color': '#fff',
+                            'bg-color': '#1693cb'
+                          },
+                          { 'id': 100,
+                            'name': u"Nancy Cours Léopold / Laneuveville - La Madeleine",
+                            'numbers': ["136", "137"],
+                            'fg-color': '#fff',
+                            'bg-color': '#6f88c1'
+                          },
+                          { 'id': 102,
+                            'name': u"Laxou Champ le Boeuf / Vandoeurvre Roberval",
+                            'numbers': ["138"],
+                            'fg-color': '#fff',
+                            'bg-color': '#123e97'
+                          },
+                          { 'id': 104,
+                            'name': u"Nancy Cours Léopold / Ludres",
+                            'numbers': ["139"],
+                            'fg-color': '#fff',
+                            'bg-color': '#6f87c7'
+                          },
+                          { 'id': 114,
+                            'name': u"Malzéville / Saulxures Forêt - Art sur Meurthe",
+                            'numbers': ["141", "142"],
+                            'fg-color': '#fff',
+                            'bg-color': '#ff750e'
+                          },
+                          { 'id': 108,
+                            'name': u"Malzéville Pixérécourt / Saulxures",
+                            'numbers': ["161"],
+                            'fg-color': '#000',
+                            'bg-color': '#ffff33'
+                          },
+                          { 'id': 116,
+                            'name': u"Essey C.E.S / Gérard Barrois Stade Marcel Picot",
+                            'numbers': ["162"],
+                            'fg-color': '#000',
+                            'bg-color': '#f59638'
+                          },
+                          { 'id': 107,
+                            'name': u"Dommartemont / Jarville Sion",
+                            'numbers': ["163"],
+                            'fg-color': '#000',
+                            'bg-color': '#fad956'
+                          },
+                          { 'id': 110,
+                            'name': u"Essey Mouzimpré / Seichamps - Pulnoy (Lignes accessibles PMR)",
+                            'numbers': ["171", "172"],
+                            'bg-color': '#f38ab3'
+                           }
+               ]
+
+       def schedules_cache_dirname(self):
+               pystan_schedules = os.path.expanduser('~/.pystan/cache/schedules')
+               if not os.path.isdir(pystan_schedules):
+                        #os.makedirs('~/.pystan/cache/schedules')
+                        pass
+               return pystan_schedules
+
+       def filter_params(self, params):
+               only_wanted = {}
+               for key in params:
+                       if key in self.get_params:
+                               only_wanted[key] = params[key]
+               return only_wanted
+
+       def schedule_cache_filename(self, params):
+               params['date'] = self.schedules_cache_dateformat(params['date'])
+               params = [ str(key)+"="+str(params[key]) for key in iter(params)]
+               params.sort()
+               filename = '_'.join(params)
+               filename = filename.replace('/', '-')
+               return "%s/%s" % (self.schedules_cache_dirname(), filename)
+
+       def schedules_cache_dateformat(self, date):
+               date = time.strptime(date, "%d/%m/%Y")
+               day_n = int(time.strftime("%w", date))
+               # saturday / sunday
+               if day_n == 0 or day_n == 6:
+                       date = time.strftime("%a/%m/%Y", date)
+               # week
+               else:
+                       date = time.strftime("week/%m/%Y", date)
+               return date
+
+
+       def default_timetable_params(self, params):
+               for param, default in { 'rub_code':6, 'thm_id':6, 'gpl_id':0 }.items(): #{'sens':1, 'date':'01/01/01', 'hour':'12', 'index':'1'}:
+                       if not params.has_key(param) or params[param] is None:
+                               params[param] = default
+               return params
+
+       def search(self, from_location, from_city, to_location, to_city, datetime, restrict=68):
+               cached_result = self.cached_search(from_location, from_city, to_location, to_city, datetime, restrict)
+               if cached_search is not None:
+                       return cached_result
+
+               '''
+               POST to http://www.reseau-stan.com/ri/index.asp
+               from_location:  keywordsDep
+               from_city:      comDep
+               to_location:    keywordsArr
+               to_city:        comArr
+               date:           laDate (11/03/2009)
+               hour:           lHeure (13 = 13h)
+               minute:         laMinute
+               restrict:       typeDate (68 = partir apres, 65 = arriver avant)
+               '''
+               pass
+
+       def cached_search(self, from_location, from_city, to_location, to_city, datetime, restrict=68):
+               cache_id = '-'.join([ from_location, from_city, to_location, to_city, datetime, restrict ])
+               pass
+
+       def load_timetable(self, lineid, params=None, bypass=False):
+               params = self.default_timetable_params(params)
+               params['lineid'] = lineid
+               params = self.filter_params(params)
+
+               timetable_raw_html_file = None
+
+               if timetable_raw_html_file is None:
+                       timetable_raw_html_file = self.load_timetable_local(params, bypass)
+
+               if timetable_raw_html_file is None:
+                       timetable_raw_html_file = self.load_timetable_remote(params)
+
+               if timetable_raw_html_file is None:
+                       print "WARNING:\tNo cache file and no internet connection"
+
+               if timetable_raw_html_file:
+                       parser = StanTimetableParser()
+                       parser.feed(timetable_raw_html_file)
+                       parser.close()
+                       parsed = parser.result
+
+                       if parsed['navigation'].has_key('next'):
+                               index_next = urlparse.urlsplit(parsed['navigation']['next'])[3]
+                               index_next = cgi.parse_qs(index_next)
+                               parsed['navigation']['next'] = index_next['index'][0]
+
+                       if parsed['navigation'].has_key('prev'):
+                               index_prev = urlparse.urlsplit(parsed['navigation']['prev'])[3]
+                               index_prev = cgi.parse_qs(index_prev)
+                               parsed['navigation']['prev'] = index_prev['index'][0]
+
+                       if len(parsed['stations']) > 0:
+                               parsed['stations'] = self.extract_stations_infos(parsed['stations'])
+                               pass
+
+                       return parsed
+
+               else:
+                       return timetable_raw_html_file
+
+       def load_timetable_remote(self, params):
+               url = 'http://www.reseau-stan.com/horaires_ligne/index.asp'
+
+               get_params = params.copy()
+               get_params['lign_id'] = get_params['lineid']; del(get_params['lineid'])
+
+               if get_params.has_key('direction') and int(get_params['direction']) > 0:
+                       get_params['sens'] = 2
+                       del(get_params['direction'])
+               else:
+                       get_params['sens'] = 1
+
+               if get_params.has_key('hour'):
+                       get_params['index'] = get_params['hour']
+                       del(get_params['hour'])
+
+               req = urllib2.Request(url, urllib.urlencode(get_params))
+
+               print "GET FROM:\t%s?%s" % (url, urllib.urlencode(get_params))
+
+               file_data = None
+
+               try:
+                       response = urllib2.urlopen(req)
+                       file_data = response.read()
+               except URLError, e:
+                       print "WARNING:\t%s" % e
+
+               if file_data is not None:
+                       self.save_timetable_local(file_data, params)
+               return file_data
+
+       def load_timetable_local(self, params, bypass=False):
+               file = self.schedule_cache_filename(params)
+               file_data = None
+               max_age = 999L
+
+               print "GET FROM:\t%s" % file
+
+               if not os.path.isfile(file):
+                       #print "WARNING: %s does not exists" % file
+                       file_data = None
+
+               elif bypass or os.stat(file).st_mtime <= int(time.time()) + max_age:
+                       response = open(file)
+                       try:
+                               file_data = response.read()
+                       finally:
+                               response.close()
+               else:
+                       print "WARNING:\t%s exists but is too old" % file
+                       file_data = False
+
+               return file_data
+
+       def save_timetable_local(self, file_data, params):
+
+               file_data = re.compile('(<div id="goatResult">.*)<div id="footer">', re.DOTALL).findall(file_data)
+
+               if len(file_data) != 1:
+                       print "ERROR:\tCannot store a valid html section (%s occurences found)" % len(file_data)
+                       return False
+               else:
+                 file_data = file_data[0]
+
+               file = self.schedule_cache_filename(params)
+               print "WRITING:\t%s" % file
+               file_dest = open(file, 'w')
+               file_dest.write(file_data)
+               file_dest.close()
+               return True
+
+       def extract_stations_infos(self, stations_urls):
+               stations_ids = []
+               for station_url in stations_urls:
+                       station_id = urlparse.urlsplit(station_url)[3]
+                       station_id = cgi.parse_qs(station_id)
+
+                       if not station_id.has_key('pa_id1') or len(station_id['pa_id1']) != 1:
+                               print "ERROR:\tCannot extract pa_id1 from url %s " % station_url
+                               continue
+
+                       station_id = station_id['pa_id1'][0]
+
+                       url = 'http://www.reseau-stan.com/ri/cartegoogle.asp'
+
+                       if False:
+                               req = urllib2.Request(url, urllib.urlencode({ 'pa_iad1':station_id }))
+                               print "GET FROM:\t%s?%s" % (url, urllib.urlencode(get_params))
+
+                               file_data = None
+                               try:
+                                       response = urllib2.urlopen(req)
+                                       file_data = response.read()
+                               except URLError, e:
+                                       print "WARNING: %s" % e
+
+                       stations_ids.append({
+                               'id': station_id,
+                               'href': "http://www.reseau-stan.com/ri/cartegoogle.asp?%s" % urllib.urlencode({ 'pa_iad1':station_id })
+                       })
+
+               return stations_ids
+