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

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