Implimenting the abstracted dispense class into the code
[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 = 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                         if self.dispense.isDisabled():
724                                 logging.info('user '+self.vstatus.cur_user+' is disabled')
725                                 self.vstatus.mk.set_messages(
726                                         [(' '*11+'ACCOUNT DISABLED'+' '*11, False, 3)])
727                                 self.vstatus.cur_user = ''
728                                 self.vstatus.cur_pin = ''
729                                 
730                                 self.reset_idler(3)
731                                 return
732
733
734                         self.vstatus.cur_pin = ''
735                         self.vstatus.mk.set_message('PIN: ')
736                         logging.info('need pin for user %s'%self.vstatus.cur_user)
737                         self.vstatus.change_state(STATE_GETTING_PIN)
738                         return
739
740         """
741         Triggered when a key is pressed and the machine is idling.
742         """
743         def handle_idle_key(self, event, params):
744                 key = params
745                 if key == 11:
746                         self.vstatus.cur_user = ''
747                         self.reset_idler()
748                         return
749                 
750                 self.vstatus.change_state(STATE_GETTING_UID)
751                 self.run_handler(event, params)
752
753         """
754         What to do when there is nothing to do.
755         """
756         def handle_idle_tick(self, event, params):
757                 ### State idling
758                 if self.vstatus.mk.done():
759                         self.idle_step()
760
761                 if self.vstatus.time_of_next_idler and time() > self.vstatus.time_of_next_idler:
762                         self.vstatus.time_of_next_idler = time() + 30
763                         self.choose_idler()
764                 
765                 ###
766
767                 self.vstatus.mk.update_display()
768
769                 self.vstatus.change_state(STATE_GRANDFATHER_CLOCK)
770                 self.run_handler(event, params)
771                 sleep(0.05)
772
773         """
774         Manages the beeps for the grandfather clock
775         """
776         def beep_on(self, when, before=0):
777                 start = int(when - before)
778                 end = int(when)
779                 now = int(time())
780
781                 if now >= start and now <= end:
782                         return 1
783                 return 0
784
785         def handle_idle_grandfather_tick(self, event, params):
786                 ### check for interesting times
787                 now = localtime()
788
789                 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
790                 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
791                 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
792                 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
793
794                 hourfromnow = localtime(time() + 3600)
795                 
796                 #onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]])
797                 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
798                         0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
799
800                 ## check for X seconds to the hour
801                 ## if case, update counter to 2
802                 if self.beep_on(onthehour,15) \
803                         or self.beep_on(halfhour,0) \
804                         or self.beep_on(quarterhour,0) \
805                         or self.beep_on(threequarterhour,0) \
806                         or self.beep_on(fivetothehour,0):
807                         self.vstatus.change_state(STATE_GRANDFATHER_CLOCK,2)
808                         self.run_handler(event, params)
809                 else:
810                         self.vstatus.change_state(STATE_IDLE)
811
812         def handle_grandfather_tick(self, event, params):
813                 go_idle = 1
814
815                 msg = []
816                 ### we live in interesting times
817                 now = localtime()
818
819                 quarterhour = mktime([now[0],now[1],now[2],now[3],15,0,now[6],now[7],now[8]])
820                 halfhour = mktime([now[0],now[1],now[2],now[3],30,0,now[6],now[7],now[8]])
821                 threequarterhour = mktime([now[0],now[1],now[2],now[3],45,0,now[6],now[7],now[8]])
822                 fivetothehour = mktime([now[0],now[1],now[2],now[3],55,0,now[6],now[7],now[8]])
823
824                 hourfromnow = localtime(time() + 3600)
825                 
826         #       onthehour = mktime([now[0],now[1],now[2],now[3],03,0,now[6],now[7],now[8]])
827                 onthehour = mktime([hourfromnow[0],hourfromnow[1],hourfromnow[2],hourfromnow[3], \
828                         0,0,hourfromnow[6],hourfromnow[7],hourfromnow[8]])
829
830
831                 #print "when it fashionable to wear a onion on your hip"
832
833                 if self.beep_on(onthehour,15):
834                         go_idle = 0
835                         next_hour=((hourfromnow[3] + 11) % 12) + 1
836                         if onthehour - time() < next_hour and onthehour - time() > 0:
837                                 self.v.beep(0, False)
838
839                                 t = int(time())
840                                 if (t % 2) == 0:
841                                         msg.append(("DING!", False, None))
842                                 else:
843                                         msg.append(("     DING!", False, None))
844                         elif int(onthehour - time()) == 0:
845                                 self.v.beep(255, False)
846                                 msg.append(("   BONG!", False, None))
847                                 msg.append(("     IT'S "+ str(next_hour) + "O'CLOCK AND ALL IS WELL .....", False, TEXT_SPEED*4))
848                 elif self.beep_on(halfhour,0):
849                         go_idle = 0
850                         self.v.beep(0, False)
851                         msg.append((" HALFHOUR ", False, 50))
852                 elif self.beep_on(quarterhour,0):
853                         go_idle = 0
854                         self.v.beep(0, False)
855                         msg.append((" QTR HOUR ", False, 50))
856                 elif self.beep_on(threequarterhour,0):
857                         go_idle = 0
858                         self.v.beep(0, False)
859                         msg.append((" 3 QTR HR ", False, 50))
860                 elif self.beep_on(fivetothehour,0):
861                         go_idle = 0
862                         self.v.beep(0, False)
863                         msg.append(("Quick run to your lectures!  Hurry! Hurry!", False, TEXT_SPEED*4))
864                 else:
865                         go_idle = 1
866                 
867                 ## check for X seconds to the hour
868
869                 if len(msg):
870                         self.vstatus.mk.set_messages(msg)
871                         sleep(1)
872
873                 self.vstatus.mk.update_display()
874                 ## if no longer case, return to idle
875
876                 ## change idler to be clock
877                 if go_idle and self.vstatus.mk.done():
878                         self.vstatus.change_state(STATE_IDLE,1)
879
880         """
881         What to do when the door is open.
882         """
883         def handle_door_idle(self, event, params):
884                 def twiddle(clock,v,wise = 2):
885                         if (clock % 4 == 0):
886                                 v.display("-FEED  ME-")
887                         elif (clock % 4 == 1+wise):
888                                 v.display("\\FEED  ME/")
889                         elif (clock % 4 == 2):
890                                 v.display("-FEED  ME-")
891                         elif (clock % 4 == 3-wise):
892                                 v.display("/FEED  ME\\")
893
894                 # don't care right now.
895                 now = int(time())
896
897                 if ((now % 60 % 2) == 0):
898                         twiddle(now, self.v)
899                 else:
900                         twiddle(now, self.v, wise=0)
901
902         """
903         What to do when the door is opened or closed.
904         """
905         def handle_door_event(self, event, params):
906                 if params == 0:  #door open
907                         self.vstatus.change_state(STATE_DOOR_OPENING)
908                         logging.warning("Entering open door mode")
909                         self.v.display("-FEED  ME-")
910                         #door_open_mode(v);
911                         self.vstatus.cur_user = ''
912                         self.vstatus.cur_pin = ''
913                 elif params == 1:  #door closed
914                         self.vstatus.change_state(STATE_DOOR_CLOSING)
915                         self.reset_idler(3)
916
917                         logging.warning('Leaving open door mode')
918                         self.v.display("-YUM YUM!-")
919
920         """
921         Triggered when a user swipes their caed, and the machine is logged out.
922         """
923         def handle_mifare_event(self, event, params):
924                 card_id = params
925                 # Translate card_id into uid.
926                 if card_id == None or card_id == self._last_card_id:
927                         return
928
929                 self._last_card_id = card_id
930                 
931                 self.dispense.authMifareCard(card_id)
932                 logging.info('Mapped card id to uid %s'%self.dispense.getUsername())
933                 if not self.dispense.isLoggedIn():
934                         self.v.beep(40, False)
935                         self.vstatus.mk.set_messages(
936                                 [(self.center('BAD CARD'), False, 1.0),
937                                  (self.center('SORRY'), False, 0.5)])
938                         self.vstatus.cur_user = ''
939                         self.vstatus.cur_pin = ''
940                         self._last_card_id = -1
941                 
942                         self.reset_idler(2)
943                         return
944                 elif self.dispense.isDisabled():
945                         self.v.beep(40, False)
946                         self.vstatus.mk.set_messages(
947                                 [(self.center('ACCT DISABLED'), False, 1.0),
948                                  (self.center('SORRY'), False, 0.5)])
949                         self.dispense.logOut()
950                         self.reset_idler(2)
951                         return
952                 else:
953                         self.vstatus.cur_selection = ''
954                         self.vstatus.change_state(STATE_GET_SELECTION)
955                         self.scroll_options(self.vstatus.username, self.vstatus.mk, True)
956                         return
957
958         """
959         Triggered when a user swipes their card and the machine is logged in.
960         """
961         def handle_mifare_add_user_event(self, event, params):
962                 card_id = params
963
964                 # Translate card_id into uid.
965                 if card_id == None or card_id == self._last_card_id:
966                         return
967
968                 self._last_card_id = card_id
969
970                 res = self.dispense.addCard(card_id)
971
972                 if get_uid(card_id) != None:
973                         self.vstatus.mk.set_messages(
974                                 [(self.center('ALREADY'), False, 0.5),
975                                  (self.center('ENROLLED'), False, 0.5)])
976                 else:
977                         logging.info('Enrolling card %s to uid %s (%s)'%(card_id, self.vstatus.cur_user, self.vstatus.username))
978                         self.set_card_id(self.vstatus.cur_user, self.card_id)
979                         self.vstatus.mk.set_messages(
980                                 [(self.center('CARD'), False, 0.5),
981                                  (self.center('ENROLLED'), False, 0.5)])
982
983         def return_to_idle(self, event, params):
984                 self.reset_idler()
985
986         """
987         Maps what to do when the state changes.
988         """
989         def create_state_table(self):
990                 self.vstatus.state_table[(STATE_IDLE,TICK,1)] = self.handle_idle_tick
991                 self.vstatus.state_table[(STATE_IDLE,KEY,1)] = self.handle_idle_key
992                 self.vstatus.state_table[(STATE_IDLE,DOOR,1)] = self.handle_door_event
993                 self.vstatus.state_table[(STATE_IDLE,MIFARE,1)] = self.handle_mifare_event
994
995                 self.vstatus.state_table[(STATE_DOOR_OPENING,TICK,1)] = self.handle_door_idle
996                 self.vstatus.state_table[(STATE_DOOR_OPENING,DOOR,1)] = self.handle_door_event
997                 self.vstatus.state_table[(STATE_DOOR_OPENING,KEY,1)] = self.do_nothing
998                 self.vstatus.state_table[(STATE_DOOR_OPENING,MIFARE,1)] = self.do_nothing
999
1000                 self.vstatus.state_table[(STATE_DOOR_CLOSING,TICK,1)] = self.return_to_idle
1001                 self.vstatus.state_table[(STATE_DOOR_CLOSING,DOOR,1)] = self.handle_door_event
1002                 self.vstatus.state_table[(STATE_DOOR_CLOSING,KEY,1)] = self.do_nothing
1003                 self.vstatus.state_table[(STATE_DOOR_CLOSING,MIFARE,1)] = self.do_nothing
1004
1005                 self.vstatus.state_table[(STATE_GETTING_UID,TICK,1)] = self.handle_getting_uid_idle
1006                 self.vstatus.state_table[(STATE_GETTING_UID,DOOR,1)] = self.handle_door_event
1007                 self.vstatus.state_table[(STATE_GETTING_UID,KEY,1)] = self.handle_getting_uid_key
1008                 self.vstatus.state_table[(STATE_GETTING_UID,MIFARE,1)] = self.handle_mifare_event
1009
1010                 self.vstatus.state_table[(STATE_GETTING_PIN,TICK,1)] = self.handle_getting_pin_idle
1011                 self.vstatus.state_table[(STATE_GETTING_PIN,DOOR,1)] = self.handle_door_event
1012                 self.vstatus.state_table[(STATE_GETTING_PIN,KEY,1)] = self.handle_getting_pin_key
1013                 self.vstatus.state_table[(STATE_GETTING_PIN,MIFARE,1)] = self.handle_mifare_event
1014
1015                 self.vstatus.state_table[(STATE_GET_SELECTION,TICK,1)] = self.handle_get_selection_idle
1016                 self.vstatus.state_table[(STATE_GET_SELECTION,DOOR,1)] = self.handle_door_event
1017                 self.vstatus.state_table[(STATE_GET_SELECTION,KEY,1)] = self.handle_get_selection_key
1018                 self.vstatus.state_table[(STATE_GET_SELECTION,MIFARE,1)] = self.handle_mifare_add_user_event
1019
1020                 self.vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,1)] = self.handle_idle_grandfather_tick
1021                 self.vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,2)] = self.handle_grandfather_tick
1022                 self.vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,1)] = self.handle_door_event
1023                 self.vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,2)] = self.handle_door_event
1024                 self.vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,1)] = self.do_nothing
1025                 self.vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,2)] = self.do_nothing
1026                 self.vstatus.state_table[(STATE_GRANDFATHER_CLOCK,MIFARE,1)] = self.handle_mifare_event
1027
1028         """
1029         Get what to do on a state change.
1030         """
1031         def get_state_table_handler(self, state, event, counter):
1032                 return self.vstatus.state_table[(state,event,counter)]
1033
1034         def time_to_next_update(self):
1035                 idle_update = self.vstatus.time_of_next_idlestep - time()
1036                 if not self.vstatus.mk.done() and self.vstatus.mk.next_update is not None:
1037                         mk_update = self.vstatus.mk.next_update - time()
1038                         if mk_update < idle_update:
1039                                 idle_update = mk_update
1040                 return idle_update
1041
1042         def run_forever(self, rfh, wfh, options, cf):
1043                 self.v = VendingMachine(rfh, wfh, USE_MIFARE)
1044                 self.dispense = Dispense()
1045                 self.vstatus = VendState(self.v)
1046                 self.create_state_table()
1047
1048                 logging.debug('PING is ' + str(self.v.ping()))
1049
1050                 if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword)
1051
1052                 self.setup_idlers()
1053                 self.reset_idler()
1054
1055                 while True:
1056                         if USE_DB:
1057                                 try:
1058                                         db.handle_events()
1059                                 except DispenseDatabaseException, e:
1060                                         logging.error('Database error: '+str(e))
1061
1062                         timeout = self.time_to_next_update()
1063                         (event, params) = self.v.next_event(timeout)
1064                         self.run_handler(event, params)
1065
1066         def run_handler(self, event, params):
1067                 handler = self.get_state_table_handler(self.vstatus.state,event,self.vstatus.counter)
1068                 if handler:
1069                         handler(event, params)
1070
1071 """
1072 Connect to the machine.
1073 """
1074 def connect_to_vend(options, cf):
1075
1076         if options.use_lat:
1077                 logging.info('Connecting to vending machine using LAT')
1078                 latclient = LATClient(service = cf.ServiceName, password = cf.ServicePassword, server_name = cf.ServerName, connect_password = cf.ConnectPassword, priv_password = cf.PrivPassword)
1079                 rfh, wfh = latclient.get_fh()
1080         elif options.use_serial:
1081                 # Open vending machine via serial.
1082                 logging.info('Connecting to vending machine using serial')
1083                 serialclient = SerialClient(port = '/dev/ttyS1', baud = 9600)
1084                 rfh,wfh = serialclient.get_fh()
1085         else:
1086                 #(rfh, wfh) = popen2('../../virtualvend/vvend.py')
1087                 logging.info('Connecting to virtual vending machine on %s:%d'%(options.host,options.port))
1088                 import socket
1089                 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
1090                 sock.connect((options.host, options.port))
1091                 rfh = sock.makefile('r')
1092                 wfh = sock.makefile('w')
1093                 global USE_MIFARE
1094                 USE_MIFARE = 0
1095                 
1096         return rfh, wfh
1097
1098 """
1099 Parse arguments from the command line
1100 """
1101 def parse_args():
1102         from optparse import OptionParser
1103
1104         op = OptionParser(usage="%prog [OPTION]...")
1105         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')
1106         op.add_option('--serial', action='store_true', default=False, dest='use_serial', help='use the serial port')
1107         op.add_option('--lat', action='store_true', default=False, dest='use_lat', help='use LAT')
1108         op.add_option('--virtualvend', action='store_false', default=True, dest='use_serial', help='use the virtual vending server instead of LAT')
1109         op.add_option('-n', '--hostname', dest='host', default='localhost', help='the hostname to connect to for virtual vending machine mode (default: localhost)')
1110         op.add_option('-p', '--port', dest='port', default=5150, type='int', help='the port number to connect to (default: 5150)')
1111         op.add_option('-l', '--log-file', metavar='FILE', dest='log_file', default='', help='log output to the specified file')
1112         op.add_option('-s', '--syslog', dest='syslog', metavar='FACILITY', default=None, help='log output to given syslog facility')
1113         op.add_option('-d', '--daemon', dest='daemon', action='store_true', default=False, help='run as a daemon')
1114         op.add_option('-v', '--verbose', dest='verbose', action='store_true', default=False, help='spit out lots of debug output')
1115         op.add_option('-q', '--quiet', dest='quiet', action='store_true', default=False, help='only report errors')
1116         op.add_option('--pid-file', dest='pid_file', metavar='FILE', default='', help='store daemon\'s pid in the given file')
1117         options, args = op.parse_args()
1118
1119         if len(args) != 0:
1120                 op.error('extra command line arguments: ' + ' '.join(args))
1121
1122         return options
1123
1124 def create_pid_file(name):
1125         try:
1126                 pid_file = file(name, 'w')
1127                 pid_file.write('%d\n'%os.getpid())
1128                 pid_file.close()
1129         except IOError, e:
1130                 logging.warning('unable to write to pid file '+name+': '+str(e))
1131
1132 def set_stuff_up():
1133         def do_nothing(signum, stack):
1134                 signal.signal(signum, do_nothing)
1135         def stop_server(signum, stack): raise KeyboardInterrupt
1136         signal.signal(signal.SIGHUP, do_nothing)
1137         signal.signal(signal.SIGTERM, stop_server)
1138         signal.signal(signal.SIGINT, stop_server)
1139
1140         options = parse_args()
1141         config_opts = VendConfigFile(options.config_file, config_options)
1142         if options.daemon: become_daemon()
1143         set_up_logging(options)
1144         if options.pid_file != '': create_pid_file(options.pid_file)
1145
1146         return options, config_opts
1147
1148 def clean_up_nicely(options, config_opts):
1149         if options.pid_file != '':
1150                 try:
1151                         os.unlink(options.pid_file)
1152                         logging.debug('Removed pid file '+options.pid_file)
1153                 except OSError: pass  # if we can't delete it, meh
1154
1155 def set_up_logging(options):
1156         logger = logging.getLogger()
1157         
1158         if not options.daemon:
1159                 stderr_logger = logging.StreamHandler(sys.stderr)
1160                 stderr_logger.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
1161                 logger.addHandler(stderr_logger)
1162         
1163         if options.log_file != '':
1164                 try:
1165                         file_logger = logging.FileHandler(options.log_file)
1166                         file_logger.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s'))
1167                         logger.addHandler(file_logger)
1168                 except IOError, e:
1169                         logger.warning('unable to write to log file '+options.log_file+': '+str(e))
1170
1171         if options.syslog != None:
1172                 sys_logger = logging.handlers.SysLogHandler('/dev/log', options.syslog)
1173                 sys_logger.setFormatter(logging.Formatter('vendserver[%d]'%(os.getpid()) + ' %(levelname)s: %(message)s'))
1174                 logger.addHandler(sys_logger)
1175
1176         if options.quiet:
1177                 logger.setLevel(logging.WARNING)
1178         elif options.verbose:
1179                 logger.setLevel(logging.DEBUG)
1180         else:
1181                 logger.setLevel(logging.INFO)
1182
1183 def become_daemon():
1184         dev_null = file('/dev/null')
1185         fd = dev_null.fileno()
1186         os.dup2(fd, 0)
1187         os.dup2(fd, 1)
1188         os.dup2(fd, 2)
1189         try:
1190                 if os.fork() != 0:
1191                         sys.exit(0)
1192                 os.setsid()
1193         except OSError, e:
1194                 raise SystemExit('failed to fork: '+str(e))
1195
1196 def do_vend_server(options, config_opts):
1197         while True:
1198                 try:
1199                         rfh, wfh = connect_to_vend(options, config_opts)
1200                 except (SerialClientException, socket.error), e:
1201                         (exc_type, exc_value, exc_traceback) = sys.exc_info()
1202                         del exc_traceback
1203                         logging.error("Connection error: "+str(exc_type)+" "+str(e))
1204                         logging.info("Trying again in 5 seconds.")
1205                         sleep(5)
1206                         continue
1207                 
1208 #               run_forever(rfh, wfh, options, config_opts)
1209                 
1210                 try:
1211                         vserver = VendServer()
1212                         vserver.run_forever(rfh, wfh, options, config_opts)
1213                 except VendingException:
1214                         logging.error("Connection died, trying again...")
1215                         logging.info("Trying again in 5 seconds.")
1216                         sleep(5)
1217
1218
1219 def main(argv=None):
1220         options, config_opts = set_stuff_up()
1221         while True:
1222                 try:
1223                         logging.warning('Starting Vend Server')
1224                         do_vend_server(options, config_opts)
1225                         logging.error('Vend Server finished unexpectedly, restarting')
1226                 except KeyboardInterrupt:
1227                         logging.info("Killed by signal, cleaning up")
1228                         clean_up_nicely(options, config_opts)
1229                         logging.warning("Vend Server stopped")
1230                         break
1231                 except SystemExit:
1232                         break
1233                 except:
1234                         (exc_type, exc_value, exc_traceback) = sys.exc_info()
1235                         tb = format_tb(exc_traceback, 20)
1236                         del exc_traceback
1237                         
1238                         logging.critical("Uh-oh, unhandled " + str(exc_type) + " exception")
1239                         logging.critical("Message: " + str(exc_value))
1240                         logging.critical("Traceback:")
1241                         for event in tb:
1242                                 for line in event.split('\n'):
1243                                         logging.critical('    '+line)
1244                         logging.critical("This message should be considered a bug in the Vend Server.")
1245                         logging.critical("Please report this to someone who can fix it.")
1246                         sleep(10)
1247                         logging.warning("Trying again anyway (might not help, but hey...)")
1248
1249 if __name__ == '__main__':
1250         sys.exit(main())

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