base version
[pystan] / src / pystan / lib / stan.py
1 # -*- coding: UTF-8 -*-
2
3 import os, sys, time, re
4 import urllib, urllib2, urlparse, cgi
5 from urllib2 import URLError
6
7
8 from timetable_parser import StanTimetableParser
9
10 class STAN:
11
12         RSS_URL =  'http://www.reseau-stan.com/rss'
13
14         get_params = [ 'rub_code', 'gpl_id', 'thm_id', 'lineid', 'date', 'hour', 'direction' ]
15
16         def lines(self):
17
18                 return [
19                            { 'id': 144,
20                              'name': u"Nancy République / Bouxières-aux-Dames",
21                              'numbers': ["322"],
22                              'fg-color': '#fff',
23                              'bg-color': '#6ecbf1'
24                            },
25                            { 'id': 145,
26                              'name': u"Vandoeuvre Vᅢᄅlodrome / Chaligny",
27                              'numbers': ["511"],
28                              'fg-color': '#fff',
29                              'bg-color': '#a3b909'
30                            },
31                            { 'id': 146,
32                              'name': u"Nancy République / Frouard Le Nid",
33                              'numbers': ["323"],
34                              'fg-color': '#fff',
35                              'bg-color': '#f5a3c8'
36                            },
37                            { 'id': 147,
38                              'name': u"Vandoeuvre Vélodrome / Pont Saint-Vincent",
39                              'numbers': ["512"],
40                              'fg-color': '#fff',
41                              'bg-color': '#c00266'
42                            },
43                            { 'id': 148,
44                              'name': u"Nancy République / Pompey Les Vannes",
45                              'numbers': ["324"],
46                              'fg-color': '#fff',
47                              'bg-color': '#d80b8c'
48                            },
49                            { 'id': 149,
50                              'name': u"Vandoeuvre Vélodrome / Messein",
51                              'numbers': ["514"],
52                              'fg-color': '#fff',
53                              'bg-color': '#006d2c'
54                            },
55                            { 'id': 150,
56                              'name': u"Nancy République / Pompey Fonds de Lavaux",
57                              'numbers': ["325"],
58                              'fg-color': '#fff',
59                              'bg-color': '#b4d8be'
60                            },
61                            { 'id': 151,
62                             'name': u"Nancy Place Carnot / Chaligny",
63                              'numbers': ["521"],
64                              'fg-color': '#a3b909',
65                              'bg-color': '#fff'
66                            },
67                            { 'id': 152,
68                              'name': u"Nancy République / Saint-Nicolas Le Nid",
69                              'numbers': ["621"],
70                              'fg-color': '#fff',
71                              'bg-color': '#0b0a4b'
72                            },
73                            { 'id': 153,
74                              'name': u"Nancy Place Carnot / Pont Saint-Vincent",
75                              'numbers': ["522"],
76                              'fg-color': '#c00266',
77                              'bg-color': '#fff'
78                            },
79                            { 'id': 154,
80                              'name': u"Nancy République / Dombasle Maroc",
81                              'numbers': ["624"],
82                              'fg-color': '#fff',
83                              'bg-color': '#fb1518'
84                            },
85                            { 'id': 155,
86                              'name': u"Nancy Place Carnot / Bainville-sur-Madon",
87                              'numbers': ["523"],
88                              'fg-color': '#81d0e3',
89                              'bg-color': '#fff'
90                            },
91                            { 'id': 157,
92                              'name': u"Nancy République / Saint-Nicolas République",
93                              'numbers': ["622"],
94                              'fg-color': '#fff',
95                              'bg-color': '#0d97a8'
96                            },
97                            { 'id': 156,
98                              'name': u"Nancy République / Dombasle Stand",
99                              'numbers': ["623"],
100                              'fg-color': '#fff',
101                              'bg-color': '#fe6612'
102                            },
103                            { 'id': 158,
104                              'name': u"Nancy République / Champigneulles",
105                              'numbers': ["321"],
106                              'fg-color': '#fff',
107                              'bg-color': '#d3b6d1'
108                            },
109                            { 'id': 126,
110                              'name': u"Vandoeuvre Parc des Expositions / Nancy République",
111                              'numbers': ["129"],
112                              'fg-color': '#fff',
113                              'bg-color': '#92bbad'
114                            },
115                            { 'id': 133,
116                              'name': u"Ligne 80",
117                              'numbers': ["80"],
118                              'bg-color': 'yellow'
119                            },
120                            { 'id': 138,
121                              'name': u"Vandoeuvre CHU Brabois / Essey Mouzimpré",
122                              'numbers': ["Tram 1"],
123                              'fg-color': '#fff',
124                              'bg-color': '#ff0000'
125                            },
126                            { 'id': 141,
127                              'name': u"Vandoeuvre CHU Brabois / Centre d\'accueil - Villers les Essarts",
128                              'numbers': ["Ptit Stan Vilers"],
129                              'bg-color': 'yellow'
130                            },
131                            { 'id': 143,
132                              'name': u"Vandoeuvre CHU Brabois / Vandoeuvre Nations - Médecine préventive ",
133                              'numbers': ["Ptit Stan Vandoeuvre"],
134                              'bg-color': 'yellow'
135                            },
136                            { 'id': 140,
137                              'name': u"Laxou Sapinière / Laxou Provinces",
138                              'numbers': ["Ptit Stan Laxou"],
139                              'bg-color': 'yellow'
140                            },
141                            { 'id': 142,
142                              'name': u"Nancy Carnot",
143                              'numbers': ["Ptit Stan Colline"],
144                              'bg-color': 'yellow'
145                            },
146                            { 'id': 134,
147                              'name': u"Malzéville Sadi Carnot - Nancy Cimetière du Sud",
148                              'numbers': ["Minibus"],
149                              'bg-color': 'yellow'
150                            },
151                            { 'id': 159,
152                              'name': u"StanU",
153                              'numbers': ["110"],
154                              'bg-color': 'yellow'
155                            },
156                            { 'id': 112,
157                              'name': u"Maxéville Champ le Boeuf - Nancy Haut du Lièvre / Jarville Sion",
158                              'numbers': ["111", "112"],
159                              'fg-color': '#fff',
160                              'bg-color': '#71228d'
161                            },
162                            { 'id': 96,
163                              'name': u"Houdemont / Vandoeuvre Charmois",
164                              'numbers': ["114"],
165                              'fg-color': '#000',
166                              'bg-color': '#b6a6d3'
167                            },
168                            { 'id': 98,
169                              'name': u"Ludres / Villers Lycée Stanislas",
170                              'numbers': ["115"],
171                              'fg-color': '#fff',
172                              'bg-color': '#705aac'
173                            },
174                            { 'id': 101,
175                              'name': u"Fléville Fleurychamp - Heillecourt / Villers Lycée Stanislas",
176                              'numbers': ["116"],
177                              'fg-color': '#fff',
178                              'bg-color': '#755eac'
179                            },
180                            { 'id': 103,
181                              'name': u"Nancy Beauregard / Nancy République",
182                              'numbers': ["121"],
183                              'fg-color': '#fff',
184                              'bg-color': '#339d2f'
185                            },
186                            { 'id': 135,
187                              'name': u"Villers Clairlieu / Nancy République",
188                              'numbers': ["122", "126"],
189                              'fg-color': '#000',
190                              'bg-color': '#9cca1f'
191                            },
192                            { 'id': 106,
193                              'name': u"Vandoeuvre Nations / Nancy République",
194                              'numbers': ["123"],
195                              'fg-color': '#000',
196                              'bg-color': '#a9d72f'
197                            },
198                            { 'id': 109,
199                              'name': u"Laxou Mouzon / Nancy République",
200                              'numbers': ["124"],
201                              'fg-color': '#fff',
202                              'bg-color': '#39a63b'
203                            },
204
205                            { 'id': 118,
206                              'name': u"Laxou Provinces / Seichamps",
207                              'numbers': ["130"],
208                              'fg-color': '#fff',
209                              'bg-color': '#048a96'
210                            },
211                            { 'id': 95,
212                              'name': u"Maxéville Mairie / Jarville Sion - Heillecourt - Fléville De la Noue",
213                              'numbers': ["131", "132", "133"],
214                              'fg-color': '#000',
215                              'bg-color': '#82cee6'
216                            },
217                            { 'id': 97,
218                              'name': u"Villers Lycée Stanislas / Maxéville Lafayette - Malzéville Savlons",
219                              'numbers': ["134", "135"],
220                              'fg-color': '#fff',
221                              'bg-color': '#1693cb'
222                            },
223                            { 'id': 100,
224                              'name': u"Nancy Cours Léopold / Laneuveville - La Madeleine",
225                              'numbers': ["136", "137"],
226                              'fg-color': '#fff',
227                              'bg-color': '#6f88c1'
228                            },
229                            { 'id': 102,
230                              'name': u"Laxou Champ le Boeuf / Vandoeurvre Roberval",
231                              'numbers': ["138"],
232                              'fg-color': '#fff',
233                              'bg-color': '#123e97'
234                            },
235                            { 'id': 104,
236                              'name': u"Nancy Cours Léopold / Ludres",
237                              'numbers': ["139"],
238                              'fg-color': '#fff',
239                              'bg-color': '#6f87c7'
240                            },
241                            { 'id': 114,
242                              'name': u"Malzéville / Saulxures Forêt - Art sur Meurthe",
243                              'numbers': ["141", "142"],
244                              'fg-color': '#fff',
245                              'bg-color': '#ff750e'
246                            },
247                            { 'id': 108,
248                              'name': u"Malzéville Pixérécourt / Saulxures",
249                              'numbers': ["161"],
250                              'fg-color': '#000',
251                              'bg-color': '#ffff33'
252                            },
253                            { 'id': 116,
254                              'name': u"Essey C.E.S / Gérard Barrois Stade Marcel Picot",
255                              'numbers': ["162"],
256                              'fg-color': '#000',
257                              'bg-color': '#f59638'
258                            },
259                            { 'id': 107,
260                              'name': u"Dommartemont / Jarville Sion",
261                              'numbers': ["163"],
262                              'fg-color': '#000',
263                              'bg-color': '#fad956'
264                            },
265                            { 'id': 110,
266                              'name': u"Essey Mouzimpré / Seichamps - Pulnoy (Lignes accessibles PMR)",
267                              'numbers': ["171", "172"],
268                              'bg-color': '#f38ab3'
269                             }
270                 ]
271
272         def schedules_cache_dirname(self):
273                 pystan_schedules = os.path.expanduser('~/.pystan/cache/schedules')
274                 if not os.path.isdir(pystan_schedules):
275                          #os.makedirs('~/.pystan/cache/schedules')
276                          pass
277                 return pystan_schedules
278
279         def filter_params(self, params):
280                 only_wanted = {}
281                 for key in params:
282                         if key in self.get_params:
283                                 only_wanted[key] = params[key]
284                 return only_wanted
285
286         def schedule_cache_filename(self, params):
287                 params['date'] = self.schedules_cache_dateformat(params['date'])
288                 params = [ str(key)+"="+str(params[key]) for key in iter(params)]
289                 params.sort()
290                 filename = '_'.join(params)
291                 filename = filename.replace('/', '-')
292                 return "%s/%s" % (self.schedules_cache_dirname(), filename)
293
294         def schedules_cache_dateformat(self, date):
295                 date = time.strptime(date, "%d/%m/%Y")
296                 day_n = int(time.strftime("%w", date))
297                 # saturday / sunday
298                 if day_n == 0 or day_n == 6:
299                         date = time.strftime("%a/%m/%Y", date)
300                 # week
301                 else:
302                         date = time.strftime("week/%m/%Y", date)
303                 return date
304
305
306         def default_timetable_params(self, params):
307                 for param, default in { 'rub_code':6, 'thm_id':6, 'gpl_id':0 }.items(): #{'sens':1, 'date':'01/01/01', 'hour':'12', 'index':'1'}:
308                         if not params.has_key(param) or params[param] is None:
309                                 params[param] = default
310                 return params
311
312         def search(self, from_location, from_city, to_location, to_city, datetime, restrict=68):
313                 cached_result = self.cached_search(from_location, from_city, to_location, to_city, datetime, restrict)
314                 if cached_search is not None:
315                         return cached_result
316
317                 '''
318                 POST to http://www.reseau-stan.com/ri/index.asp
319                 from_location:  keywordsDep
320                 from_city:      comDep
321                 to_location:    keywordsArr
322                 to_city:        comArr
323                 date:           laDate (11/03/2009)
324                 hour:           lHeure (13 = 13h)
325                 minute:         laMinute
326                 restrict:       typeDate (68 = partir apres, 65 = arriver avant)
327                 '''
328                 pass
329
330         def cached_search(self, from_location, from_city, to_location, to_city, datetime, restrict=68):
331                 cache_id = '-'.join([ from_location, from_city, to_location, to_city, datetime, restrict ])
332                 pass
333
334         def load_timetable(self, lineid, params=None, bypass=False):
335                 params = self.default_timetable_params(params)
336                 params['lineid'] = lineid
337                 params = self.filter_params(params)
338
339                 timetable_raw_html_file = None
340
341                 if timetable_raw_html_file is None:
342                         timetable_raw_html_file = self.load_timetable_local(params, bypass)
343
344                 if timetable_raw_html_file is None:
345                         timetable_raw_html_file = self.load_timetable_remote(params)
346
347                 if timetable_raw_html_file is None:
348                         print "WARNING:\tNo cache file and no internet connection"
349
350                 if timetable_raw_html_file:
351                         parser = StanTimetableParser()
352                         parser.feed(timetable_raw_html_file)
353                         parser.close()
354                         parsed = parser.result
355
356                         if parsed['navigation'].has_key('next'):
357                                 index_next = urlparse.urlsplit(parsed['navigation']['next'])[3]
358                                 index_next = cgi.parse_qs(index_next)
359                                 parsed['navigation']['next'] = index_next['index'][0]
360
361                         if parsed['navigation'].has_key('prev'):
362                                 index_prev = urlparse.urlsplit(parsed['navigation']['prev'])[3]
363                                 index_prev = cgi.parse_qs(index_prev)
364                                 parsed['navigation']['prev'] = index_prev['index'][0]
365
366                         if len(parsed['stations']) > 0:
367                                 parsed['stations'] = self.extract_stations_infos(parsed['stations'])
368                                 pass
369
370                         return parsed
371
372                 else:
373                         return timetable_raw_html_file
374
375         def load_timetable_remote(self, params):
376                 url = 'http://www.reseau-stan.com/horaires_ligne/index.asp'
377
378                 get_params = params.copy()
379                 get_params['lign_id'] = get_params['lineid']; del(get_params['lineid'])
380
381                 if get_params.has_key('direction') and int(get_params['direction']) > 0:
382                         get_params['sens'] = 2
383                         del(get_params['direction'])
384                 else:
385                         get_params['sens'] = 1
386
387                 if get_params.has_key('hour'):
388                         get_params['index'] = get_params['hour']
389                         del(get_params['hour'])
390
391                 req = urllib2.Request(url, urllib.urlencode(get_params))
392
393                 print "GET FROM:\t%s?%s" % (url, urllib.urlencode(get_params))
394
395                 file_data = None
396
397                 try:
398                         response = urllib2.urlopen(req)
399                         file_data = response.read()
400                 except URLError, e:
401                         print "WARNING:\t%s" % e
402
403                 if file_data is not None:
404                         self.save_timetable_local(file_data, params)
405                 return file_data
406
407         def load_timetable_local(self, params, bypass=False):
408                 file = self.schedule_cache_filename(params)
409                 file_data = None
410                 max_age = 999L
411
412                 print "GET FROM:\t%s" % file
413
414                 if not os.path.isfile(file):
415                         #print "WARNING: %s does not exists" % file
416                         file_data = None
417
418                 elif bypass or os.stat(file).st_mtime <= int(time.time()) + max_age:
419                         response = open(file)
420                         try:
421                                 file_data = response.read()
422                         finally:
423                                 response.close()
424                 else:
425                         print "WARNING:\t%s exists but is too old" % file
426                         file_data = False
427
428                 return file_data
429
430         def save_timetable_local(self, file_data, params):
431
432                 file_data = re.compile('(<div id="goatResult">.*)<div id="footer">', re.DOTALL).findall(file_data)
433
434                 if len(file_data) != 1:
435                         print "ERROR:\tCannot store a valid html section (%s occurences found)" % len(file_data)
436                         return False
437                 else:
438                   file_data = file_data[0]
439
440                 file = self.schedule_cache_filename(params)
441                 print "WRITING:\t%s" % file
442                 file_dest = open(file, 'w')
443                 file_dest.write(file_data)
444                 file_dest.close()
445                 return True
446
447         def extract_stations_infos(self, stations_urls):
448                 stations_ids = []
449                 for station_url in stations_urls:
450                         station_id = urlparse.urlsplit(station_url)[3]
451                         station_id = cgi.parse_qs(station_id)
452
453                         if not station_id.has_key('pa_id1') or len(station_id['pa_id1']) != 1:
454                                 print "ERROR:\tCannot extract pa_id1 from url %s " % station_url
455                                 continue
456
457                         station_id = station_id['pa_id1'][0]
458
459                         url = 'http://www.reseau-stan.com/ri/cartegoogle.asp'
460
461                         if False:
462                                 req = urllib2.Request(url, urllib.urlencode({ 'pa_iad1':station_id }))
463                                 print "GET FROM:\t%s?%s" % (url, urllib.urlencode(get_params))
464
465                                 file_data = None
466                                 try:
467                                         response = urllib2.urlopen(req)
468                                         file_data = response.read()
469                                 except URLError, e:
470                                         print "WARNING: %s" % e
471
472                         stations_ids.append({
473                                 'id': station_id,
474                                 'href': "http://www.reseau-stan.com/ri/cartegoogle.asp?%s" % urllib.urlencode({ 'pa_iad1':station_id })
475                         })
476
477                 return stations_ids
478