d6a07a04b83bf5523413f3ad274791fe2e8aa88c
[uccvend-vendserver.git] / VendServer / VendServer.py
1 #!/usr/bin/python
2 # vim:ts=4
3
4 USE_DB = 0
5 USE_MIFARE = 1
6
7 import ConfigParser
8 import sys, os, string, re, pwd, signal, math, syslog
9 import logging, logging.handlers
10 from traceback import format_tb
11 if USE_DB: import pg
12 from time import time, sleep, mktime, localtime
13 from subprocess import Popen, PIPE
14 from LATClient import LATClient, LATClientException
15 from SerialClient import SerialClient, SerialClientException
16 from VendingMachine import VendingMachine, VendingException
17 from MessageKeeper import MessageKeeper
18 from HorizScroll import HorizScroll
19 from random import random, seed
20 from Idler import GreetingIdler,TrainIdler,GrayIdler,StringIdler,ClockIdler,FortuneIdler,FileIdler,PipeIdler
21 from SnackConfig import get_snack#, get_snacks
22 import socket
23 from posix import geteuid
24 from LDAPConnector import get_uid,get_uname, set_card_id
25 from OpenDispense import OpenDispense as Dispense
26
27 CREDITS="""
28 This vending machine software brought to you by:
29 Bernard Blackham
30 Mark Tearle
31 Nick Bannon
32 Cameron Patrick
33 and a collective of hungry alpacas.
34
35 The MIFARE card reader bought to you by:
36 David Adam
37
38 Bug Hunting and hardware maintenance by:
39 Mitchell Pomery
40
41 For a good time call +61 8 6488 3901
42
43
44
45 """
46
47 PIN_LENGTH = 4
48
49 DOOR = 1
50 SWITCH = 2
51 KEY = 3
52 TICK = 4
53 MIFARE = 5
54
55
56 (
57 STATE_IDLE,
58 STATE_DOOR_OPENING,
59 STATE_DOOR_CLOSING,
60 STATE_GETTING_UID,
61 STATE_GETTING_PIN,
62 STATE_GET_SELECTION,
63 STATE_GRANDFATHER_CLOCK,
64 ) = range(1,8)
65
66 TEXT_SPEED = 0.8
67 IDLE_SPEED = 0.05
68
69
70 config_options = {
71         'DBServer': ('Database', 'Server'),
72         'DBName': ('Database', 'Name'),
73         'DBUser': ('VendingMachine', 'DBUser'),
74         'DBPassword': ('VendingMachine', 'DBPassword'),
75         
76         'ServiceName': ('VendingMachine', 'ServiceName'),
77         'ServicePassword': ('VendingMachine', 'Password'),
78         
79         'ServerName': ('DecServer', 'Name'),
80         'ConnectPassword': ('DecServer', 'ConnectPassword'),
81         'PrivPassword': ('DecServer', 'PrivPassword'),
82         }
83
84 class VendConfigFile:
85         def __init__(self, config_file, options):
86                 try:
87                         cp = ConfigParser.ConfigParser()
88                         cp.read(config_file)
89
90                         for option in options:
91                                 section, name = options[option]
92                                 value = cp.get(section, name)
93                                 self.__dict__[option] = value
94                 
95                 except ConfigParser.Error, e:
96                         raise SystemExit("Error reading config file "+config_file+": " + str(e))
97
98 class DispenseDatabaseException(Exception): pass
99
100 class DispenseDatabase:
101         def __init__(self, vending_machine, host, name, user, password):
102                 self.vending_machine = vending_machine
103                 self.db = pg.DB(dbname = name, host = host, user = user, passwd = password)
104                 self.db.query('LISTEN vend_requests')
105
106         def process_requests(self):
107                 logging.debug('database processing')
108                 query = 'SELECT request_id, request_slot FROM vend_requests WHERE request_handled = false'
109                 try:
110                         outstanding = self.db.query(query).getresult()
111                 except (pg.error,), db_err:
112                         raise DispenseDatabaseException('Failed to query database: %s\n'%(db_err.strip()))
113                 for (id, slot) in outstanding:
114                         (worked, code, string) = self.vending_machine.vend(slot)
115                         logging.debug (str((worked, code, string)))
116                         if worked:
117                                 query = 'SELECT vend_success(%s)'%id
118                                 self.db.query(query).getresult()
119                         else:
120                                 query = 'SELECT vend_failed(%s)'%id
121                                 self.db.query(query).getresult()
122
123         def handle_events(self):
124                 notifier = self.db.getnotify()
125                 while notifier is not None:
126                         self.process_requests()
127                         notify = self.db.getnotify()
128 """
129 This class manages the current state of the vending machine.
130 """
131 class VendState:
132         def __init__(self,v):
133                 self.state_table = {}
134                 self.state = STATE_IDLE
135                 self.counter = 0
136
137                 self.mk = MessageKeeper(v)
138                 self.cur_user = ''
139                 self.cur_pin = ''
140                 self.username = ''
141                 self.cur_selection = ''
142                 self.time_to_autologout = None
143
144                 self.last_timeout_refresh = None
145
146         def change_state(self,newstate,newcounter=None):
147                 if self.state != newstate:
148                         self.state = newstate
149
150                 if newcounter is not None and self.counter != newcounter:
151                         self.counter = newcounter
152
153 class VendServer():
154
155         v = None
156         vstatus = None
157         state = None
158         event = None
159         idlers = []
160         idler = None
161
162         _pin_uid = 0
163         _pin_uname = 'root'
164         _pin_pin = '----'
165         _last_card_id = -1
166
167         """
168         Show information to the user as to what can be dispensed.
169         """
170         def scroll_options(self, username, mk, welcome = False):
171                 # If the user has just logged in, show them their balance
172                 if welcome:
173                         # Balance checking
174                         acct, unused = Popen(['dispense', 'acct', username], close_fds=True, stdout=PIPE).communicate()
175                         # this is fucking appalling
176                         balance = acct[acct.find("$")+1:acct.find("(")].strip()
177                         
178                         msg = [(self.center('WELCOME'), False, TEXT_SPEED),
179                                    (self.center(username), False, TEXT_SPEED),
180                                    (self.center(balance), False, TEXT_SPEED),]
181                 else:
182                         msg = []
183                 choices = ' '*10+'CHOICES: '
184
185                 # Show what is in the coke machine
186                 cokes = []
187                 for i in range(0, 7):
188                         args = ('dispense', 'iteminfo', 'coke:%i' % i)
189                         info, unused = Popen(args, close_fds=True, stdout=PIPE).communicate()
190                         m = re.match("\s*[a-z]+:\d+\s+(\d+)\.(\d\d)\s+([^\n]+)", info)
191                         cents = int(m.group(1))*100 + int(m.group(2))
192                         cokes.append('%i %i %s' % (i, cents, m.group(3)));
193
194                 for c in cokes:
195                         c = c.strip()
196                         (slot_num, price, slot_name) = c.split(' ', 2)
197                         if slot_name == 'dead': continue
198                         choices += '%s-(%sc)-%s8 '%(slot_name, price, slot_num)
199
200                 # Show the final few options
201                 choices += '55-DOOR '
202                 choices += 'OR ANOTHER SNACK. '
203                 choices += '99 TO READ AGAIN. '
204                 choices += 'CHOICE?   '
205                 msg.append((choices, False, None))
206                 # Send it to the display
207                 mk.set_messages(msg)
208
209
210         """
211         Verify the users pin
212         """
213         def _check_pin(self, uid, pin):
214                 print "_check_pin('",uid,"',---)"
215                 if uid != self._pin_uid:
216                         try:
217                                 info = pwd.getpwuid(uid)
218                         except KeyError:
219                                 logging.info('getting pin for uid %d: user not in password file'%uid)
220                                 return None
221                         if info.pw_dir == None: return False
222                         pinfile = os.path.join(info.pw_dir, '.pin')
223                         try:
224                                 s = os.stat(pinfile)
225                         except OSError:
226                                 logging.info('getting pin for uid %d: .pin not found in home directory'%uid)
227                                 return None
228                         if s.st_mode & 077:
229                                 logging.info('getting pin for uid %d: .pin has wrong permissions. Fixing.'%uid)
230                                 os.chmod(pinfile, 0600)
231                         try:
232                                 f = file(pinfile)
233                         except IOError:
234                                 logging.info('getting pin for uid %d: I cannot read pin file'%uid)
235                                 return None
236                         pinstr = f.readline()
237                         f.close()
238                         if not re.search('^'+'[0-9]'*PIN_LENGTH+'$', pinstr):
239                                 logging.info('getting pin for uid %d: %s not a good pin'%(uid,repr(pinstr)))
240                                 return None
241                         self._pin_uid = uid
242                         self._pin_pin = pinstr
243                         self._pin_uname = info.pw_name
244                 else:
245                         pinstr = self._pin_pin
246                 if pin == int(pinstr):
247                         logging.info("Pin correct for %d",uid)
248                 else:
249                         logging.info("Pin incorrect for %d",uid)
250                 return pin == int(pinstr)
251
252         """
253         Check if the users account has been disabled
254         """
255         def acct_is_disabled(self, name=None):
256                 if name == None:
257                         name = self._pin_uname
258                 acct, unused = Popen(['dispense', 'acct', self._pin_uname], close_fds=True, stdout=PIPE).communicate()
259                 # this is fucking appalling
260                 flags = acct[acct.find("(")+1:acct.find(")")].strip()
261                 if 'disabled' in flags:
262                         return True
263                 if 'internal' in flags:
264                         return True
265                 return False
266
267         """
268         Check that the user has a valid pin set
269         """
270         def has_good_pin(self, uid):
271                 return self._check_pin(uid, None) != None
272
273         """
274         Verify the users pin.
275         """
276         def verify_user_pin(self, uid, pin, skip_pin_check=False):
277                 if skip_pin_check or self._check_pin(uid, pin) == True:
278                         info = pwd.getpwuid(uid)
279                         if skip_pin_check:
280                                 if self.acct_is_disabled(info.pw_name):
281                                         logging.info('refused mifare for disabled acct uid %d (%s)'%(uid,info.pw_name))
282                                         return '-disabled-'
283                                 logging.info('accepted mifare for uid %d (%s)'%(uid,info.pw_name))
284                         else:
285                                 logging.info('accepted pin for uid %d (%s)'%(uid,info.pw_name))
286                         return info.pw_name
287                 else:
288                         logging.info('refused pin for uid %d'%(uid))
289                         return None
290
291         """
292         In here just for fun.
293         """
294         def cookie(self):
295                 seed(time())
296                 messages = ['  WASSUP! ', 'PINK FISH ', ' SECRETS ', '  ESKIMO  ', ' FORTUNES ', 'MORE MONEY']
297                 choice = int(random()*len(messages))
298                 msg = messages[choice]
299                 left = range(len(msg))
300                 for i in range(len(msg)):
301                         if msg[i] == ' ': left.remove(i)
302                 reveal = 1
303                 while left:
304                         s = ''
305                         for i in range(0, len(msg)):
306                                 if i in left:
307                                         if reveal == 0:
308                                                 left.remove(i)
309                                                 s += msg[i]
310                                         else:
311                                                 s += chr(int(random()*26)+ord('A'))
312                                         reveal += 1
313                                         reveal %= 17
314                                 else:
315                                         s += msg[i]
316                         self.v.display(s)
317
318         """
319         Format text so it will appear centered on the screen.
320         """
321         def center(self, str):
322                 LEN = 10
323                 return ' '*((LEN-len(str))/2)+str
324
325         """
326         Configure the things that will appear on screen whil the machine is idling.
327         """
328         def setup_idlers(self):
329                 self.idlers = [
330                         #
331                         GrayIdler(self.v),
332                         GrayIdler(self.v,one="*",zero="-"),
333                         GrayIdler(self.v,one="/",zero="\\"),
334                         GrayIdler(self.v,one="X",zero="O"),
335                         GrayIdler(self.v,one="*",zero="-",reorder=1),
336                         GrayIdler(self.v,one="/",zero="\\",reorder=1),
337                         GrayIdler(self.v,one="X",zero="O",reorder=1),
338                         #
339                         ClockIdler(self.v),
340                         ClockIdler(self.v),
341                         ClockIdler(self.v),
342                         #
343                         StringIdler(self.v), # Hello Cruel World
344                         StringIdler(self.v, text="Kill 'em all", repeat=False),
345                         StringIdler(self.v, text=CREDITS),
346                         StringIdler(self.v, text=str(math.pi) + "            "),
347                         StringIdler(self.v, text=str(math.e) + "            "),
348                         StringIdler(self.v, text="    I want some pizza - please call Broadway Pizza on +61 8 9389 8500 - and order as Quinn - I am getting really hungry", repeat=False),
349                         # "Hello World" in brainfuck
350                         StringIdler(self.v, text=">+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++++>-] <.>+++++++++++[<++++++++>-]<-.--------.+++.------.--------.[-]>++++++++[<++++>- ]<+.[-]++++++++++."),
351                         #
352                         TrainIdler(self.v),
353                         #
354                         FileIdler(self.v, '/usr/share/common-licenses/GPL-2',affinity=2),
355                         #
356                         PipeIdler(self.v, "/usr/bin/getent", "passwd"),
357                         FortuneIdler(self.v,affinity=20),
358                         ]
359                 disabled = [
360                         ]
361
362         """
363         Go back to the default idler.
364         """
365         def reset_idler(self, t = None):
366                 self.idler = GreetingIdler(self.v, t)
367                 self.vstatus.time_of_next_idlestep = time()+self.idler.next()
368                 self.vstatus.time_of_next_idler = None
369                 self.vstatus.time_to_autologout = None
370                 self.vstatus.change_state(STATE_IDLE, 1)
371
372         """
373         Change to a random idler.
374         """
375         def choose_idler(self):
376
377                 # Implementation of the King Of the Hill algorithm from;
378                 # http://eli.thegreenplace.net/2010/01/22/weighted-random-generation-in-python/
379
380                 #def weighted_choice_king(weights):
381                 #       total = 0
382                 #       winner = 0
383                 #       for i, w in enumerate(weights):
384                 #               total += w
385                 #               if random.random() * total < w:
386                 #                       winner = i
387                 #       return winner
388                 #       
389
390                 total = 0
391                 winner = None
392                 
393                 for choice in self.idlers:
394                         weight = choice.affinity()
395                         total += weight
396                         if random() * total < weight:
397                                 winner = choice
398
399                 self.idler = winner
400
401                 if self.idler:
402                         self.idler.reset()
403         """
404         Run every step while the machine is idling.
405         """
406         def idle_step(self):
407                 if self.idler.finished():
408                         self.choose_idler()
409                         self.vstatus.time_of_next_idler = time() + 30
410                 nextidle = self.idler.next()
411                 if nextidle is None:
412                         nextidle = IDLE_SPEED
413                 self.vstatus.time_of_next_idlestep = time()+nextidle
414
415         """
416         These next two events trigger no response in the code.
417         """
418         def handle_tick_event(self, event, params):
419                 # don't care right now.
420                 pass
421
422         def handle_switch_event(self, event, params):
423                 # don't care right now.
424                 pass
425
426         """
427         Don't do anything for this event.
428         """
429         def do_nothing(self, event, params):
430                 print "doing nothing (s,e,p)", state, " ", event, " ", params
431                 pass
432
433         """
434         These next few entrie tell us to do nothing while we are idling
435         """
436         def handle_getting_uid_idle(self, event, params):
437                 # don't care right now.
438                 pass
439
440         def handle_getting_pin_idle(self, event, params):
441                 # don't care right now.
442                 pass
443
444         """
445         While logged in and waiting for user input, slowly get closer to logging out.
446         """
447         def handle_get_selection_idle(self, event, params):
448                 # don't care right now.
449                 ###
450                 ### State logging out ..
451                 if self.vstatus.time_to_autologout != None:
452                         time_left = self.vstatus.time_to_autologout - time()
453                         if time_left < 6 and (self.vstatus.last_timeout_refresh is None or self.vstatus.last_timeout_refresh > time_left):
454                                 self.vstatus.mk.set_message('LOGOUT: '+str(int(time_left)))
455                                 self.vstatus.last_timeout_refresh = int(time_left)
456                                 self.vstatus.cur_selection = ''
457
458                 if self.vstatus.time_to_autologout != None and self.vstatus.time_to_autologout - time() <= 0:
459                         self.vstatus.time_to_autologout = None
460                         self.vstatus.cur_user = ''
461                         self.vstatus.cur_pin = ''
462                         self.vstatus.cur_selection = ''
463                         self._last_card_id = -1
464                         self.reset_idler()
465
466                 ### State fully logged out ... reset variables
467                 if self.vstatus.time_to_autologout and not self.vstatus.mk.done(): 
468                         self.vstatus.time_to_autologout = None
469                 if self.vstatus.cur_user == '' and self.vstatus.time_to_autologout: 
470                         self.vstatus.time_to_autologout = None
471                 
472                 ### State logged in
473                 if len(self.vstatus.cur_pin) == PIN_LENGTH and self.vstatus.mk.done() and self.vstatus.time_to_autologout == None:
474                         # start autologout
475                         self.vstatus.time_to_autologout = time() + 15
476                         self.vstatus.last_timeout_refresh = None
477
478                 ## FIXME - this may need to be elsewhere.....
479                 # need to check
480                 self.vstatus.mk.update_display()
481
482         """
483         Triggered on user input while logged in.
484         """
485         def handle_get_selection_key(self, event, params):
486                 key = params
487                 if len(self.vstatus.cur_selection) == 0:
488                         if key == 11:
489                                 self.vstatus.cur_pin = ''
490                                 self.vstatus.cur_user = ''
491                                 self.vstatus.cur_selection = ''
492                                 _last_card_id = -1
493                                 self.vstatus.mk.set_messages([(self.center('BYE!'), False, 1.5)])
494                                 self.reset_idler(2)
495                                 return
496                         self.vstatus.cur_selection += chr(key + ord('0'))
497                         self.vstatus.mk.set_message('SELECT: '+self.vstatus.cur_selection)
498                         self.vstatus.time_to_autologout = None
499                 elif len(self.vstatus.cur_selection) == 1:
500                         if key == 11:
501                                 self.vstatus.cur_selection = ''
502                                 self.vstatus.time_to_autologout = None
503                                 self.scroll_options(self.vstatus.username, self.vstatus.mk)
504                                 return
505                         else:
506                                 self.vstatus.cur_selection += chr(key + ord('0'))
507                                 if self.vstatus.cur_user:
508                                         self.make_selection()
509                                         self.vstatus.cur_selection = ''
510                                         self.vstatus.time_to_autologout = time() + 8
511                                         self.vstatus.last_timeout_refresh = None
512                                 else:
513                                         # Price check mode.
514                                         self.price_check()
515                                         self.vstatus.cur_selection = ''
516                                         self.vstatus.time_to_autologout = None
517                                         self.vstatus.last_timeout_refresh = None
518
519         """
520         Triggered when the user has entered the id of something they would like to purchase.
521         """
522         def make_selection(self):
523                 # should use sudo here
524                 if self.vstatus.cur_selection == '55':
525                         self.vstatus.mk.set_message('OPENSESAME')
526                         logging.info('dispensing a door for %s'%self.vstatus.username)
527                         if geteuid() == 0:
528                                 ret = os.system('dispense -u "%s" door'%self.vstatus.username)
529                         else:
530                                 ret = os.system('dispense door')
531                         if ret == 0:
532                                 logging.info('door opened')
533                                 self.vstatus.mk.set_message(self.center('DOOR OPEN'))
534                         else:
535                                 logging.warning('user %s tried to dispense a bad door'%self.vstatus.username)
536                                 self.vstatus.mk.set_message(self.center('BAD DOOR'))
537                         sleep(1)
538                 elif self.vstatus.cur_selection == '81':
539                         self.cookie()
540                 elif self.vstatus.cur_selection == '99':
541                         self.scroll_options(self.vstatus.username, self.vstatus.mk)
542                         self.vstatus.cur_selection = ''
543                         return
544                 elif self.vstatus.cur_selection[1] == '8':
545                         self.v.display('GOT DRINK?')
546                         if ((os.system('dispense -u "%s" coke:%s'%(self.vstatus.username, self.vstatus.cur_selection[0])) >> 8) != 0):
547                                 self.v.display('SEEMS NOT')
548                         else:
549                                 self.v.display('GOT DRINK!')
550                 else:
551                         # first see if it's a named slot
552                         try:
553                                 price, shortname, name = get_snack( self.vstatus.cur_selection )
554                         except:
555                                 price, shortname, name = get_snack( '--' )
556                         dollarprice = "$%.2f" % ( price / 100.0 )
557                         self.v.display(self.vstatus.cur_selection+' - %s'%dollarprice)
558                         exitcode = os.system('dispense -u "%s" snack:%s'%(self.vstatus.username, self.vstatus.cur_selection)) >> 8
559                         if (exitcode == 0):
560                                 # magic dispense syslog service
561                                 syslog.syslog(syslog.LOG_INFO | syslog.LOG_LOCAL4, "vended %s (slot %s) for %s" % (name, self.vstatus.cur_selection, self.vstatus.username))
562                                 (worked, code, string) = self.v.vend(self.vstatus.cur_selection)
563                                 if worked:
564                                         self.v.display('THANK YOU')
565                                 else:
566                                         print "Vend Failed:", code, string
567                                         self.v.display('VEND FAIL')
568                         elif (exitcode == 5):   # RV_BALANCE
569                                 self.v.display('NO MONEY?')
570                         elif (exitcode == 4):   # RV_ARGUMENTS (zero give causes arguments)
571                                 self.v.display('EMPTY SLOT')
572                         elif (exitcode == 1):   # RV_BADITEM (Dead slot)
573                                 self.v.display('EMPTY SLOT')
574                         else:
575                                 syslog.syslog(syslog.LOG_INFO | syslog.LOG_LOCAL4, "failed vending %s (slot %s) for %s (code %d)" % (name, self.vstatus.cur_selection, self.vstatus.username, exitcode))
576                                 self.v.display('UNK ERROR')
577                 sleep(1)
578
579         """
580         Find the price of an item.
581         """
582         def price_check():
583                 if self.vstatus.cur_selection[1] == '8':
584                         args = ('dispense', 'iteminfo', 'coke:' + self.vstatus.cur_selection[0])
585                         info, unused = Popen(args, close_fds=True, stdout=PIPE).communicate()
586                         dollarprice = re.match("\s*[a-z]+:\d+\s+(\d+\.\d\d)\s+([^\n]+)", info).group(1)
587                 else:
588                         # first see if it's a named slot
589                         try:
590                                 price, shortname, name = get_snack( self.vstatus.cur_selection )
591                         except:
592                                 price, shortname, name = get_snack( '--' )
593                         dollarprice = "$%.2f" % ( price / 100.0 )
594                 self.v.display(self.vstatus.cur_selection+' - %s'%dollarprice)
595
596         """
597         Triggered when the user presses a button while entering their pin.
598         """
599         def handle_getting_pin_key(self, event, params):
600                 key = params
601                 if len(self.vstatus.cur_pin) < PIN_LENGTH:
602                         if key == 11:
603                                 if self.vstatus.cur_pin == '':
604                                         self.vstatus.cur_user = ''
605                                         slef.reset_idler()
606
607                                         return
608                                 self.vstatus.cur_pin = ''
609                                 self.vstatus.mk.set_message('PIN: ')
610                                 return
611                         self.vstatus.cur_pin += chr(key + ord('0'))
612                         self.vstatus.mk.set_message('PIN: '+'X'*len(self.vstatus.cur_pin))
613                         if len(self.vstatus.cur_pin) == PIN_LENGTH:
614                                 self.vstatus.username = self.verify_user_pin(int(self.vstatus.cur_user), int(self.vstatus.cur_pin))
615                                 if self.vstatus.username:
616                                         self.v.beep(0, False)
617                                         self.vstatus.cur_selection = ''
618                                         self.vstatus.change_state(STATE_GET_SELECTION)
619                                         self.scroll_options(self.vstatus.username, self.vstatus.mk, True)
620                                         return
621                                 else:
622                                         self.v.beep(40, False)
623                                         self.vstatus.mk.set_messages(
624                                                 [(self.center('BAD PIN'), False, 1.0),
625                                                  (self.center('SORRY'), False, 0.5)])
626                                         self.vstatus.cur_user = ''
627                                         self.vstatus.cur_pin = ''
628                                 
629                                         self.reset_idler(2)
630
631                                         return
632
633         """
634         Triggered when the user presses a button while entering their user id.
635         """
636         def handle_getting_uid_key(self, event, params):
637                 key = params
638                 # complicated key handling here:
639
640                 if len(self.vstatus.cur_user) == 0 and key == 9:
641                         self.vstatus.cur_selection = ''
642                         self.vstatus.time_to_autologout = None
643                         self.vstatus.mk.set_message('PRICECHECK')
644                         sleep(0.5)
645                         self.scroll_options('', vstatus.mk)
646                         self.vstatus.change_state(STATE_GET_SELECTION)
647                         return
648
649                 if len(self.vstatus.cur_user) <8:
650                         if key == 11:
651                                 self.vstatus.cur_user = ''
652
653                                 self.reset_idler()
654                                 return
655                         self.vstatus.cur_user += chr(key + ord('0'))
656                         #logging.info('dob: '+vstatus.cur_user)
657                         if len(self.vstatus.cur_user) > 5:
658                                 self.vstatus.mk.set_message('>'+self.vstatus.cur_user)
659                         else:
660                                 self.vstatus.mk.set_message('UID: '+self.vstatus.cur_user)
661                 
662                 if len(self.vstatus.cur_user) == 5:
663                         uid = int(self.vstatus.cur_user)
664
665                         if uid == 0:
666                                 logging.info('user '+self.vstatus.cur_user+' has a bad PIN')
667                                 pfalken="""
668         CARRIER DETECTED
669
670         CONNECT 128000
671
672         Welcome to Picklevision Sytems, Sunnyvale, CA
673
674         Greetings Professor Falken.
675
676
677
678
679         Shall we play a game?
680
681
682         Please choose from the following menu:
683
684         1. Tic-Tac-Toe
685         2. Chess
686         3. Checkers
687         4. Backgammon
688         5. Poker
689         6. Toxic and Biochemical Warfare
690         7. Global Thermonuclear War
691
692         7 [ENTER]
693
694         Wouldn't you prefer a nice game of chess?
695
696         """.replace('\n','    ')
697                                 self.vstatus.mk.set_messages([(pfalken, False, 10)])
698                                 self.vstatus.cur_user = ''
699                                 self.vstatus.cur_pin = ''
700                                 
701                                 self.reset_idler(10)
702
703                                 return
704
705                         if not self.has_good_pin(uid):
706                                 logging.info('user '+self.vstatus.cur_user+' has a bad PIN')
707                                 self.vstatus.mk.set_messages(
708                                         [(' '*10+'INVALID PIN SETUP'+' '*11, False, 3)])
709                                 self.vstatus.cur_user = ''
710                                 self.vstatus.cur_pin = ''
711                                 
712                                 self.reset_idler(3)
713
714                                 return
715                         
716                         if self.acct_is_disabled():
717                                 logging.info('user '+self.vstatus.cur_user+' is disabled')
718                                 self.vstatus.mk.set_messages(
719                                         [(' '*11+'ACCOUNT DISABLED'+' '*11, False, 3)])
720                                 self.vstatus.cur_user = ''
721                                 self.vstatus.cur_pin = ''
722                                 
723                                 self.reset_idler(3)
724                                 return
725
726
727                         self.vstatus.cur_pin = ''
728                         self.vstatus.mk.set_message('PIN: ')
729                         logging.info('need pin for user %s'%self.vstatus.cur_user)
730                         self.vstatus.change_state(STATE_GETTING_PIN)
731                         return
732
733         """
734         Triggered when a key is pressed and the machine is idling.
735         """
736         def handle_idle_key(self, event, params):
737                 key = params
738                 if key == 11:
739                         self.vstatus.cur_user = ''
740                         self.reset_idler()
741                         return
742                 
743                 self.vstatus.change_state(STATE_GETTING_UID)
744                 self.run_handler(event, params)
745
746         """
747         What to do when there is nothing to do.
748         """
749         def handle_idle_tick(self, event, params):
750                 ### State idling
751                 if self.vstatus.mk.done():
752                         self.idle_step()
753
754                 if self.vstatus.time_of_next_idler and time() > self.vstatus.time_of_next_idler:
755                         self.vstatus.time_of_next_idler = time() + 30
756                         self.choose_idler()
757                 
758                 ###
759
760                 self.vstatus.mk.update_display()
761
762                 self.vstatus.change_state(STATE_GRANDFATHER_CLOCK)
763                 self.run_handler(event, params)
764                 sleep(0.05)
765
766         """
767         Manages the beeps for the grandfather clock
768         """
769         def beep_on(self, when, before=0):
770                 start = int(when - before)
771                 end = int(when)
772                 now = int(time())
773
774                 if now >= start and now <= end:
775                         return 1
776                 return 0
777
778         def handle_idle_grandfather_tick(self, event, params):
779                 ### check for interesting times
780                 now = localtime()
781
782                 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
783                 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
784                 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
785                 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
786
787                 hourfromnow = localtime(time() + 3600)
788                 
789                 #onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]])
790                 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
791                         0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
792
793                 ## check for X seconds to the hour
794                 ## if case, update counter to 2
795                 if self.beep_on(onthehour,15) \
796                         or self.beep_on(halfhour,0) \
797                         or self.beep_on(quarterhour,0) \
798                         or self.beep_on(threequarterhour,0) \
799                         or self.beep_on(fivetothehour,0):
800                         self.vstatus.change_state(STATE_GRANDFATHER_CLOCK,2)
801                         self.run_handler(event, params)
802                 else:
803                         self.vstatus.change_state(STATE_IDLE)
804
805         def handle_grandfather_tick(self, event, params):
806                 go_idle = 1
807
808                 msg = []
809                 ### we live in interesting times
810                 now = localtime()
811
812                 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
813                 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
814                 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
815                 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
816
817                 hourfromnow = localtime(time() + 3600)
818                 
819         #       onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]])
820                 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
821                         0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
822
823
824                 #print "when it fashionable to wear a onion on your hip"
825
826                 if self.beep_on(onthehour,15):
827                         go_idle = 0
828                         next_hour=((hourfromnow[3] + 11) % 12) + 1
829                         if onthehour - time() < next_hour and onthehour - time() > 0:
830                                 self.v.beep(0, False)
831
832                                 t = int(time())
833                                 if (t % 2) == 0:
834                                         msg.append(("DING!", False, None))
835                                 else:
836                                         msg.append(("     DING!", False, None))
837                         elif int(onthehour - time()) == 0:
838                                 self.v.beep(255, False)
839                                 msg.append(("   BONG!", False, None))
840                                 msg.append(("     IT'S "+ str(next_hour) + "O'CLOCK AND ALL IS WELL .....", False, TEXT_SPEED*4))
841                 elif self.beep_on(halfhour,0):
842                         go_idle = 0
843                         self.v.beep(0, False)
844                         msg.append((" HALFHOUR ", False, 50))
845                 elif self.beep_on(quarterhour,0):
846                         go_idle = 0
847                         self.v.beep(0, False)
848                         msg.append((" QTR HOUR ", False, 50))
849                 elif self.beep_on(threequarterhour,0):
850                         go_idle = 0
851                         self.v.beep(0, False)
852                         msg.append((" 3 QTR HR ", False, 50))
853                 elif self.beep_on(fivetothehour,0):
854                         go_idle = 0
855                         self.v.beep(0, False)
856                         msg.append(("Quick run to your lectures!  Hurry! Hurry!", False, TEXT_SPEED*4))
857                 else:
858                         go_idle = 1
859                 
860                 ## check for X seconds to the hour
861
862                 if len(msg):
863                         self.vstatus.mk.set_messages(msg)
864                         sleep(1)
865
866                 self.vstatus.mk.update_display()
867                 ## if no longer case, return to idle
868
869                 ## change idler to be clock
870                 if go_idle and self.vstatus.mk.done():
871                         self.vstatus.change_state(STATE_IDLE,1)
872
873         """
874         What to do when the door is open.
875         """
876         def handle_door_idle(self, event, params):
877                 def twiddle(clock,v,wise = 2):
878                         if (clock % 4 == 0):
879                                 v.display("-FEED  ME-")
880                         elif (clock % 4 == 1+wise):
881                                 v.display("\\FEED  ME/")
882                         elif (clock % 4 == 2):
883                                 v.display("-FEED  ME-")
884                         elif (clock % 4 == 3-wise):
885                                 v.display("/FEED  ME\\")
886
887                 # don't care right now.
888                 now = int(time())
889
890                 if ((now % 60 % 2) == 0):
891                         twiddle(now, self.v)
892                 else:
893                         twiddle(now, self.v, wise=0)
894
895         """
896         What to do when the door is opened or closed.
897         """
898         def handle_door_event(self, event, params):
899                 if params == 0:  #door open
900                         self.vstatus.change_state(STATE_DOOR_OPENING)
901                         logging.warning("Entering open door mode")
902                         self.v.display("-FEED  ME-")
903                         #door_open_mode(v);
904                         self.vstatus.cur_user = ''
905                         self.vstatus.cur_pin = ''
906                 elif params == 1:  #door closed
907                         self.vstatus.change_state(STATE_DOOR_CLOSING)
908                         self.reset_idler(3)
909
910                         logging.warning('Leaving open door mode')
911                         self.v.display("-YUM YUM!-")
912
913         """
914         Triggered when a user swipes their caed, and the machine is logged out.
915         """
916         def handle_mifare_event(self, event, params):
917                 card_id = params
918                 # Translate card_id into uid.
919                 if card_id == None or card_id == self._last_card_id:
920                         return
921
922                 self._last_card_id = card_id
923                 
924                 try:
925                         self.vstatus.cur_user = get_uid(card_id)
926                         logging.info('Mapped card id to uid %s'%vstatus.cur_user)
927                         self.vstatus.username = get_uname(vstatus.cur_user)
928                         if self.acct_is_disabled(self.vstatus.username):
929                                 self.vstatus.username = '-disabled-'
930                 except ValueError:
931                         self.vstatus.username = None
932                 if self.vstatus.username == '-disabled-':
933                         self.v.beep(40, False)
934                         self.vstatus.mk.set_messages(
935                                 [(self.center('ACCT DISABLED'), False, 1.0),
936                                  (self.center('SORRY'), False, 0.5)])
937                         self.vstatus.cur_user = ''
938                         self.vstatus.cur_pin = ''
939                         self.vstatus.username = None
940                 
941                         self.reset_idler(2)
942                         return
943                 elif self.vstatus.username:
944                         self.v.beep(0, False)
945                         self.vstatus.cur_selection = ''
946                         self.vstatus.change_state(STATE_GET_SELECTION)
947                         self.scroll_options(self.vstatus.username, self.vstatus.mk, True)
948                         return
949                 else:
950                         self.v.beep(40, False)
951                         self.vstatus.mk.set_messages(
952                                 [(self.center('BAD CARD'), False, 1.0),
953                                  (self.center('SORRY'), False, 0.5)])
954                         self.vstatus.cur_user = ''
955                         self.vstatus.cur_pin = ''
956                         self._last_card_id = -1
957                 
958                         self.reset_idler(2)
959                         return
960
961         """
962         Triggered when a user swipes their card and the machine is logged in.
963         """
964         def handle_mifare_add_user_event(self, evnt, params):
965                 card_id = params
966
967                 # Translate card_id into uid.
968                 if card_id == None or card_id == self._last_card_id:
969                         return
970
971                 self._last_card_id = card_id
972                 
973                 try:
974                         if get_uid(card_id) != None:
975                                 self.vstatus.mk.set_messages(
976                                         [(self.center('ALREADY'), False, 0.5),
977                                          (self.center('ENROLLED'), False, 0.5)])
978
979                                 # scroll_options(vstatus.username, vstatus.mk)
980                                 return
981                 except ValueError:
982                         pass
983
984                 logging.info('Enrolling card %s to uid %s (%s)'%(card_id, vstatus.cur_user, vstatus.username))
985                 self.set_card_id(self.vstatus.cur_user, card_id)
986                 self.vstatus.mk.set_messages(
987                         [(self.center('CARD'), False, 0.5),
988                          (self.center('ENROLLED'), False, 0.5)])
989
990                 # scroll_options(vstatus.username, vstatus.mk)
991
992         def return_to_idle(self, event, params):
993                 self.reset_idler()
994
995         """
996         Maps what to do when the state changes.
997         """
998         def create_state_table(self):
999                 self.vstatus.state_table[(STATE_IDLE,TICK,1)] = self.handle_idle_tick
1000                 self.vstatus.state_table[(STATE_IDLE,KEY,1)] = self.handle_idle_key
1001                 self.vstatus.state_table[(STATE_IDLE,DOOR,1)] = self.handle_door_event
1002                 self.vstatus.state_table[(STATE_IDLE,MIFARE,1)] = self.handle_mifare_event
1003
1004                 self.vstatus.state_table[(STATE_DOOR_OPENING,TICK,1)] = self.handle_door_idle
1005                 self.vstatus.state_table[(STATE_DOOR_OPENING,DOOR,1)] = self.handle_door_event
1006                 self.vstatus.state_table[(STATE_DOOR_OPENING,KEY,1)] = self.do_nothing
1007                 self.vstatus.state_table[(STATE_DOOR_OPENING,MIFARE,1)] = self.do_nothing
1008
1009                 self.vstatus.state_table[(STATE_DOOR_CLOSING,TICK,1)] = self.return_to_idle
1010                 self.vstatus.state_table[(STATE_DOOR_CLOSING,DOOR,1)] = self.handle_door_event
1011                 self.vstatus.state_table[(STATE_DOOR_CLOSING,KEY,1)] = self.do_nothing
1012                 self.vstatus.state_table[(STATE_DOOR_CLOSING,MIFARE,1)] = self.do_nothing
1013
1014                 self.vstatus.state_table[(STATE_GETTING_UID,TICK,1)] = self.handle_getting_uid_idle
1015                 self.vstatus.state_table[(STATE_GETTING_UID,DOOR,1)] = self.handle_door_event
1016                 self.vstatus.state_table[(STATE_GETTING_UID,KEY,1)] = self.handle_getting_uid_key
1017                 self.vstatus.state_table[(STATE_GETTING_UID,MIFARE,1)] = self.handle_mifare_event
1018
1019                 self.vstatus.state_table[(STATE_GETTING_PIN,TICK,1)] = self.handle_getting_pin_idle
1020                 self.vstatus.state_table[(STATE_GETTING_PIN,DOOR,1)] = self.handle_door_event
1021                 self.vstatus.state_table[(STATE_GETTING_PIN,KEY,1)] = self.handle_getting_pin_key
1022                 self.vstatus.state_table[(STATE_GETTING_PIN,MIFARE,1)] = self.handle_mifare_event
1023
1024                 self.vstatus.state_table[(STATE_GET_SELECTION,TICK,1)] = self.handle_get_selection_idle
1025                 self.vstatus.state_table[(STATE_GET_SELECTION,DOOR,1)] = self.handle_door_event
1026                 self.vstatus.state_table[(STATE_GET_SELECTION,KEY,1)] = self.handle_get_selection_key
1027                 self.vstatus.state_table[(STATE_GET_SELECTION,MIFARE,1)] = self.handle_mifare_add_user_event
1028
1029                 self.vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,1)] = self.handle_idle_grandfather_tick
1030                 self.vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,2)] = self.handle_grandfather_tick
1031                 self.vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,1)] = self.handle_door_event
1032                 self.vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,2)] = self.handle_door_event
1033                 self.vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,1)] = self.do_nothing
1034                 self.vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,2)] = self.do_nothing
1035                 self.vstatus.state_table[(STATE_GRANDFATHER_CLOCK,MIFARE,1)] = self.handle_mifare_event
1036
1037         """
1038         Get what to do on a state change.
1039         """
1040         def get_state_table_handler(self, state, event, counter):
1041                 return self.vstatus.state_table[(state,event,counter)]
1042
1043         def time_to_next_update(self):
1044                 idle_update = self.vstatus.time_of_next_idlestep - time()
1045                 if not self.vstatus.mk.done() and self.vstatus.mk.next_update is not None:
1046                         mk_update = self.vstatus.mk.next_update - time()
1047                         if mk_update < idle_update:
1048                                 idle_update = mk_update
1049                 return idle_update
1050
1051         def run_forever(self, rfh, wfh, options, cf):
1052                 self.v = VendingMachine(rfh, wfh, USE_MIFARE)
1053                 self.vstatus = VendState(self.v)
1054                 self.create_state_table()
1055
1056                 logging.debug('PING is ' + str(self.v.ping()))
1057
1058                 if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword)
1059
1060                 self.setup_idlers()
1061                 self.reset_idler()
1062
1063                 while True:
1064                         if USE_DB:
1065                                 try:
1066                                         db.handle_events()
1067                                 except DispenseDatabaseException, e:
1068                                         logging.error('Database error: '+str(e))
1069
1070                         timeout = self.time_to_next_update()
1071                         (event, params) = self.v.next_event(timeout)
1072                         self.run_handler(event, params)
1073
1074         def run_handler(self, event, params):
1075                 handler = self.get_state_table_handler(self.vstatus.state,event,self.vstatus.counter)
1076                 if handler:
1077                         handler(event, params)
1078
1079 """
1080 Connect to the machine.
1081 """
1082 def connect_to_vend(options, cf):
1083
1084         if options.use_lat:
1085                 logging.info('Connecting to vending machine using LAT')
1086                 latclient = LATClient(service = cf.ServiceName, password = cf.ServicePassword, server_name = cf.ServerName, connect_password = cf.ConnectPassword, priv_password = cf.PrivPassword)
1087                 rfh, wfh = latclient.get_fh()
1088         #elif options.use_serial:
1089         #       # Open vending machine via serial.
1090         #       logging.info('Connecting to vending machine using serial')
1091         #       serialclient = SerialClient(port = '/dev/ttyS1', baud = 9600)
1092         #       rfh,wfh = serialclient.get_fh()
1093         else:
1094                 #(rfh, wfh) = popen2('../../virtualvend/vvend.py')
1095                 logging.info('Connecting to virtual vending machine on %s:%d'%(options.host,options.port))
1096                 import socket
1097                 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
1098                 sock.connect((options.host, options.port))
1099                 rfh = sock.makefile('r')
1100                 wfh = sock.makefile('w')
1101                 global USE_MIFARE
1102                 USE_MIFARE = 0
1103                 
1104         return rfh, wfh
1105
1106 """
1107 Parse arguments from the command line
1108 """
1109 def parse_args():
1110         from optparse import OptionParser
1111
1112         op = OptionParser(usage="%prog [OPTION]...")
1113         op.add_option('-f', '--config-file', default='./servers.conf', metavar='FILE', dest='config_file', help='use the specified config file instead of /etc/dispense/servers.conf')
1114         op.add_option('--serial', action='store_true', default=False, dest='use_serial', help='use the serial port')
1115         op.add_option('--lat', action='store_true', default=False, dest='use_lat', help='use LAT')
1116         op.add_option('--virtualvend', action='store_false', default=True, dest='use_serial', help='use the virtual vending server instead of LAT')
1117         op.add_option('-n', '--hostname', dest='host', default='localhost', help='the hostname to connect to for virtual vending machine mode (default: localhost)')
1118         op.add_option('-p', '--port', dest='port', default=5150, type='int', help='the port number to connect to (default: 5150)')
1119         op.add_option('-l', '--log-file', metavar='FILE', dest='log_file', default='', help='log output to the specified file')
1120         op.add_option('-s', '--syslog', dest='syslog', metavar='FACILITY', default=None, help='log output to given syslog facility')
1121         op.add_option('-d', '--daemon', dest='daemon', action='store_true', default=False, help='run as a daemon')
1122         op.add_option('-v', '--verbose', dest='verbose', action='store_true', default=False, help='spit out lots of debug output')
1123         op.add_option('-q', '--quiet', dest='quiet', action='store_true', default=False, help='only report errors')
1124         op.add_option('--pid-file', dest='pid_file', metavar='FILE', default='', help='store daemon\'s pid in the given file')
1125         options, args = op.parse_args()
1126
1127         if len(args) != 0:
1128                 op.error('extra command line arguments: ' + ' '.join(args))
1129
1130         return options
1131
1132 def create_pid_file(name):
1133         try:
1134                 pid_file = file(name, 'w')
1135                 pid_file.write('%d\n'%os.getpid())
1136                 pid_file.close()
1137         except IOError, e:
1138                 logging.warning('unable to write to pid file '+name+': '+str(e))
1139
1140 def set_stuff_up():
1141         def do_nothing(signum, stack):
1142                 signal.signal(signum, do_nothing)
1143         def stop_server(signum, stack): raise KeyboardInterrupt
1144         signal.signal(signal.SIGHUP, do_nothing)
1145         signal.signal(signal.SIGTERM, stop_server)
1146         signal.signal(signal.SIGINT, stop_server)
1147
1148         options = parse_args()
1149         config_opts = VendConfigFile(options.config_file, config_options)
1150         if options.daemon: become_daemon()
1151         set_up_logging(options)
1152         if options.pid_file != '': create_pid_file(options.pid_file)
1153
1154         return options, config_opts
1155
1156 def clean_up_nicely(options, config_opts):
1157         if options.pid_file != '':
1158                 try:
1159                         os.unlink(options.pid_file)
1160                         logging.debug('Removed pid file '+options.pid_file)
1161                 except OSError: pass  # if we can't delete it, meh
1162
1163 def set_up_logging(options):
1164         logger = logging.getLogger()
1165         
1166         if not options.daemon:
1167                 stderr_logger = logging.StreamHandler(sys.stderr)
1168                 stderr_logger.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
1169                 logger.addHandler(stderr_logger)
1170         
1171         if options.log_file != '':
1172                 try:
1173                         file_logger = logging.FileHandler(options.log_file)
1174                         file_logger.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s'))
1175                         logger.addHandler(file_logger)
1176                 except IOError, e:
1177                         logger.warning('unable to write to log file '+options.log_file+': '+str(e))
1178
1179         if options.syslog != None:
1180                 sys_logger = logging.handlers.SysLogHandler('/dev/log', options.syslog)
1181                 sys_logger.setFormatter(logging.Formatter('vendserver[%d]'%(os.getpid()) + ' %(levelname)s: %(message)s'))
1182                 logger.addHandler(sys_logger)
1183
1184         if options.quiet:
1185                 logger.setLevel(logging.WARNING)
1186         elif options.verbose:
1187                 logger.setLevel(logging.DEBUG)
1188         else:
1189                 logger.setLevel(logging.INFO)
1190
1191 def become_daemon():
1192         dev_null = file('/dev/null')
1193         fd = dev_null.fileno()
1194         os.dup2(fd, 0)
1195         os.dup2(fd, 1)
1196         os.dup2(fd, 2)
1197         try:
1198                 if os.fork() != 0:
1199                         sys.exit(0)
1200                 os.setsid()
1201         except OSError, e:
1202                 raise SystemExit('failed to fork: '+str(e))
1203
1204 def do_vend_server(options, config_opts):
1205         while True:
1206                 try:
1207                         rfh, wfh = connect_to_vend(options, config_opts)
1208                 except (SerialClientException, socket.error), e:
1209                         (exc_type, exc_value, exc_traceback) = sys.exc_info()
1210                         del exc_traceback
1211                         logging.error("Connection error: "+str(exc_type)+" "+str(e))
1212                         logging.info("Trying again in 5 seconds.")
1213                         sleep(5)
1214                         continue
1215                 
1216 #               run_forever(rfh, wfh, options, config_opts)
1217                 
1218                 try:
1219                         vserver = VendServer()
1220                         vserver.run_forever(rfh, wfh, options, config_opts)
1221                 except VendingException:
1222                         logging.error("Connection died, trying again...")
1223                         logging.info("Trying again in 5 seconds.")
1224                         sleep(5)
1225
1226
1227 def main(argv=None):
1228         options, config_opts = set_stuff_up()
1229         while True:
1230                 try:
1231                         logging.warning('Starting Vend Server')
1232                         do_vend_server(options, config_opts)
1233                         logging.error('Vend Server finished unexpectedly, restarting')
1234                 except KeyboardInterrupt:
1235                         logging.info("Killed by signal, cleaning up")
1236                         clean_up_nicely(options, config_opts)
1237                         logging.warning("Vend Server stopped")
1238                         break
1239                 except SystemExit:
1240                         break
1241                 except:
1242                         (exc_type, exc_value, exc_traceback) = sys.exc_info()
1243                         tb = format_tb(exc_traceback, 20)
1244                         del exc_traceback
1245                         
1246                         logging.critical("Uh-oh, unhandled " + str(exc_type) + " exception")
1247                         logging.critical("Message: " + str(exc_value))
1248                         logging.critical("Traceback:")
1249                         for event in tb:
1250                                 for line in event.split('\n'):
1251                                         logging.critical('    '+line)
1252                         logging.critical("This message should be considered a bug in the Vend Server.")
1253                         logging.critical("Please report this to someone who can fix it.")
1254                         sleep(10)
1255                         logging.warning("Trying again anyway (might not help, but hey...)")
1256
1257 if __name__ == '__main__':
1258         sys.exit(main())

UCC git Repository :: git.ucc.asn.au