e89692073a717c60810eebf8bfc64720f65ffaeb
[uccvend-vendserver.git] / sql-edition / servers / VendServer.py
1 #!/usr/bin/python
2 # vim:ts=4
3
4 USE_DB = 0
5
6 import ConfigParser
7 import sys, os, string, re, pwd, signal, math
8 import logging, logging.handlers
9 from traceback import format_tb
10 if USE_DB: import pg
11 from time import time, sleep
12 from popen2 import popen2
13 from LATClient import LATClient, LATClientException
14 from SerialClient import SerialClient, SerialClientException
15 from VendingMachine import VendingMachine, VendingException
16 from MessageKeeper import MessageKeeper
17 from HorizScroll import HorizScroll
18 from random import random, seed
19 from Idler import TrainIdler,GrayIdler,StringIdler,ClockIdler,FortuneIdler,FileIdler,PipeIdler
20 import socket
21 from posix import geteuid
22
23 CREDITS="""
24 This vending machine software brought to you by:
25 Bernard Blackham
26 Mark Tearle
27 Nick Bannon
28 Cameron Patrick
29 and a collective of hungry alpacas.
30
31
32
33 For a good time call +61 8 6488 3901
34
35
36
37 """
38
39 GREETING = 'UCC SNACKS'
40 PIN_LENGTH = 4
41
42 DOOR = 1
43 SWITCH = 2
44 KEY = 3
45
46 class DispenseDatabaseException(Exception): pass
47
48 class DispenseDatabase:
49         def __init__(self, vending_machine, host, name, user, password):
50                 self.vending_machine = vending_machine
51                 self.db = pg.DB(dbname = name, host = host, user = user, passwd = password)
52                 self.db.query('LISTEN vend_requests')
53
54         def process_requests(self):
55                 logging.debug('database processing')
56                 query = 'SELECT request_id, request_slot FROM vend_requests WHERE request_handled = false'
57                 try:
58                         outstanding = self.db.query(query).getresult()
59                 except (pg.error,), db_err:
60                         raise DispenseDatabaseException('Failed to query database: %s\n'%(db_err.strip()))
61                 for (id, slot) in outstanding:
62                         (worked, code, string) = self.vending_machine.vend(slot)
63                         logging.debug (str((worked, code, string)))
64                         if worked:
65                                 query = 'SELECT vend_success(%s)'%id
66                                 self.db.query(query).getresult()
67                         else:
68                                 query = 'SELECT vend_failed(%s)'%id
69                                 self.db.query(query).getresult()
70
71         def handle_events(self):
72                 notifier = self.db.getnotify()
73                 while notifier is not None:
74                         self.process_requests()
75                         notify = self.db.getnotify()
76
77 def scroll_options(username, mk, welcome = False):
78         if welcome:
79                 msg = [(center('WELCOME'), False, 0.8),
80                            (center(username), False, 0.8)]
81         else:
82                 msg = []
83         choices = ' '*10+'CHOICES: '
84         try:
85                 coke_machine = file('/home/other/coke/coke_contents')
86                 cokes = coke_machine.readlines()
87                 coke_machine.close()
88         except:
89                 cokes = []
90                 pass
91         for c in cokes:
92                 c = c.strip()
93                 (slot_num, price, slot_name) = c.split(' ', 2)
94                 if slot_name == 'dead': continue
95                 choices += '%s8-%s (%sc) '%(slot_num, slot_name, price)
96         choices += '55-DOOR '
97         choices += 'OR A SNACK. '
98         choices += '99 TO READ AGAIN. '
99         choices += 'CHOICE?   '
100         msg.append((choices, False, None))
101         mk.set_messages(msg)
102
103 def get_pin(uid):
104         try:
105                 info = pwd.getpwuid(uid)
106         except KeyError:
107                 logging.info('getting pin for uid %d: user not in password file'%uid)
108                 return None
109         if info.pw_dir == None: return False
110         pinfile = os.path.join(info.pw_dir, '.pin')
111         try:
112                 s = os.stat(pinfile)
113         except OSError:
114                 logging.info('getting pin for uid %d: .pin not found in home directory'%uid)
115                 return None
116         if s.st_mode & 077:
117                 logging.info('getting pin for uid %d: .pin has wrong permissions'%uid)
118                 return None
119         try:
120                 f = file(pinfile)
121         except IOError:
122                 logging.info('getting pin for uid %d: I cannot read pin file'%uid)
123                 return None
124         pinstr = f.readline()
125         f.close()
126         if not re.search('^'+'[0-9]'*PIN_LENGTH+'$', pinstr):
127                 logging.info('getting pin for uid %d: %s not a good pin'%(uid,repr(pinstr)))
128                 return None
129         return int(pinstr)
130
131 def has_good_pin(uid):
132         return get_pin(uid) != None
133
134 def verify_user_pin(uid, pin):
135         if get_pin(uid) == pin:
136                 info = pwd.getpwuid(uid)
137                 logging.info('accepted pin for uid %d (%s)'%(uid,info.pw_name))
138                 return info.pw_name
139         else:
140                 logging.info('refused pin for uid %d'%(uid))
141                 return None
142
143 def door_open_mode(v):
144         logging.warning("Entering open door mode")
145         v.display("-FEED  ME-")
146         while True:
147                 e = v.next_event()
148                 if e == None: break
149                 (event, params) = e
150                 if event == DOOR:
151                         if params == 1: # door closed
152                                 logging.warning('Leaving open door mode')
153                                 v.display("-YUM YUM!-")
154                                 sleep(1)
155                                 return
156
157 def cookie(v):
158         seed(time())
159         messages = ['  WASSUP! ', 'PINK FISH ', ' SECRETS ', '  ESKIMO  ', ' FORTUNES ', 'MORE MONEY']
160         choice = int(random()*len(messages))
161         msg = messages[choice]
162         left = range(len(msg))
163         for i in range(len(msg)):
164                 if msg[i] == ' ': left.remove(i)
165         reveal = 1
166         while left:
167                 s = ''
168                 for i in range(0, len(msg)):
169                         if i in left:
170                                 if reveal == 0:
171                                         left.remove(i)
172                                         s += msg[i]
173                                 else:
174                                         s += chr(int(random()*26)+ord('A'))
175                                 reveal += 1
176                                 reveal %= 17
177                         else:
178                                 s += msg[i]
179                 v.display(s)
180
181 def center(str):
182         LEN = 10
183         return ' '*((LEN-len(str))/2)+str
184
185
186
187 idlers = []
188 idler = None
189
190 def setup_idlers(v):
191         global idlers, idler
192         idlers = [
193                  GrayIdler(v),
194                 StringIdler(v, text="Kill 'em all", repeat=False),
195                  GrayIdler(v,one="*",zero="-"),
196                 StringIdler(v, text=CREDITS),
197                  GrayIdler(v,one="/",zero="\\"),
198                 ClockIdler(v),
199                  GrayIdler(v,one="X",zero="O"),
200                 FileIdler(v, '/usr/share/common-licenses/GPL-2'),
201                  GrayIdler(v,one="*",zero="-",reorder=1),
202                 StringIdler(v, text=str(math.pi) + "            "),
203                 ClockIdler(v),
204                  GrayIdler(v,one="/",zero="\\",reorder=1),
205                 StringIdler(v, text=str(math.e) + "            "),
206                  GrayIdler(v,one="X",zero="O",reorder=1),
207                 StringIdler(v, text="    I want some pizza - please call Pizza Hut Shenton Park on +61 8 9381 9979 - and order as Quinn - I am getting really hungry", repeat=False),
208                 PipeIdler(v, "/usr/bin/ypcat", "passwd"),
209                 FortuneIdler(v),
210                 ClockIdler(v),
211                 StringIdler(v),
212                 TrainIdler(v),
213                 ]
214     disabled = [
215                 ]
216         idler = choose_idler()
217
218 def choose_idler():
219         global idler
220         iiindex = 0
221
222         if idler:
223                 iiindex = idlers.index(idler)
224
225         iilen = len(idlers)
226
227         move = int(random()*len(idlers)) + 1
228
229         while move >= 0:
230                 idler = idlers[( (iiindex + 1) % iilen)]
231                 move = move - idler.affinity()
232
233         idler.reset()
234
235 def idle_step():
236         global idler
237         if idler.finished():
238                 choose_idler()
239         idler.next()
240
241 class VendState:
242         def __init__(self,v):
243                 self.mk = MessageKeeper(v)
244                 self.cur_user = ''
245                 self.cur_pin = ''
246                 self.cur_selection = ''
247                 self.time_to_autologout = None
248                 self.time_to_idle = None
249                 self.last_timeout_refresh = None
250
251
252 def handle_door_event(event, params, v, vstatus):
253         if params == 0:
254                 door_open_mode(v);
255                 vstatus.cur_user = ''
256                 vstatus.cur_pin = ''
257                 vstatus.mk.set_message(GREETING)
258
259 def handle_switch_event(event, params, v, vstatus):
260         # don't care right now.
261         pass
262
263 def handle_key_event(event, params, v, vstatus):
264         key = params
265         # complicated key handling here:
266         if len(vstatus.cur_user) < 5:
267                 if key == 11:
268                         vstatus.cur_user = ''
269                         vstatus.mk.set_message(GREETING)
270                         return
271                 vstatus.cur_user += chr(key + ord('0'))
272                 vstatus.mk.set_message('UID: '+vstatus.cur_user)
273                 if len(vstatus.cur_user) == 5:
274                         uid = int(vstatus.cur_user)
275                         if not has_good_pin(uid):
276                                 logging.info('user '+vstatus.cur_user+' has a bad PIN')
277                                 #mk.set_messages(
278                                         #[(center('INVALID'), False, 0.7),
279                                          #(center('PIN'), False, 0.7),
280                                          #(center('SETUP'), False, 1.0),
281                                          #(GREETING, False, None)])
282                                 vstatus.mk.set_messages(
283                                         [(' '*10+'INVALID PIN SETUP'+' '*10, False, 3),
284                                          (GREETING, False, None)])
285                                 vstatus.cur_user = ''
286                                 vstatus.cur_pin = ''
287                                 return
288                         vstatus.cur_pin = ''
289                         vstatus.mk.set_message('PIN: ')
290                         logging.info('need pin for user %s'%vstatus.cur_user)
291                         return
292         elif len(vstatus.cur_pin) < PIN_LENGTH:
293                 if key == 11:
294                         if vstatus.cur_pin == '':
295                                 vstatus.cur_user = ''
296                                 vstatus.mk.set_message(GREETING)
297                                 return
298                         vstatus.cur_pin = ''
299                         vstatus.mk.set_message('PIN: ')
300                         return
301                 vstatus.cur_pin += chr(key + ord('0'))
302                 vstatus.mk.set_message('PIN: '+'X'*len(vstatus.cur_pin))
303                 if len(vstatus.cur_pin) == PIN_LENGTH:
304                         username = verify_user_pin(int(vstatus.cur_user), int(vstatus.cur_pin))
305                         if username:
306                                 v.beep(0, False)
307                                 vstatus.cur_selection = ''
308                                 scroll_options(username, vstatus.mk, True)
309                                 return
310                         else:
311                                 v.beep(40, False)
312                                 vstatus.mk.set_messages(
313                                         [(center('BAD PIN'), False, 1.0),
314                                          (center('SORRY'), False, 0.5),
315                                          (GREETING, False, None)])
316                                 vstatus.cur_user = ''
317                                 vstatus.cur_pin = ''
318                                 return
319         elif len(vstatus.cur_selection) == 0:
320                 if key == 11:
321                         vstatus.cur_pin = ''
322                         vstatus.cur_user = ''
323                         vstatus.cur_selection = ''
324                         vstatus.mk.set_messages(
325                                 [(center('BYE!'), False, 1.5),
326                                  (GREETING, False, None)])
327                         return
328                 vstatus.cur_selection += chr(key + ord('0'))
329                 vstatus.mk.set_message('SELECT: '+vstatus.cur_selection)
330                 vstatus.time_to_autologout = None
331         elif len(vstatus.cur_selection) == 1:
332                 if key == 11:
333                         vstatus.cur_selection = ''
334                         vstatus.time_to_autologout = None
335                         scroll_options(username, vstatus.mk)
336                         return
337                 else:
338                         vstatus.cur_selection += chr(key + ord('0'))
339                         #make_selection(cur_selection)
340                         # XXX this should move somewhere else:
341                         if vstatus.cur_selection == '55':
342                                 vstatus.mk.set_message('OPENSESAME')
343                                 logging.info('dispensing a door for %s'%username)
344                                 if geteuid() == 0:
345                                         ret = os.system('su - "%s" -c "dispense door"'%username)
346                                 else:
347                                         ret = os.system('dispense door')
348                                 if ret == 0:
349                                         logging.info('door opened')
350                                         vstatus.mk.set_message(center('DOOR OPEN'))
351                                 else:
352                                         logging.warning('user %s tried to dispense a bad door'%username)
353                                         vstatus.mk.set_message(center('BAD DOOR'))
354                                 sleep(1)
355                         elif vstatus.cur_selection == '91':
356                                 cookie(v)
357                         elif vstatus.cur_selection == '99':
358                                 scroll_options(username, vstatus.mk)
359                                 vstatus.cur_selection = ''
360                                 return
361                         elif vstatus.cur_selection[1] == '8':
362                                 v.display('GOT COKE?')
363                                 if ((os.system('su - "%s" -c "dispense %s"'%(username, vstatus.cur_selection[0])) >> 8) != 0):
364                                         v.display('SEEMS NOT')
365                                 else:
366                                         v.display('GOT COKE!')
367                         else:
368                                 v.display(vstatus.cur_selection+' - $1.00')
369                                 if ((os.system('su - "%s" -c "dispense snack"'%(username)) >> 8) == 0):
370                                         v.vend(vstatus.cur_selection)
371                                         v.display('THANK YOU')
372                                 else:
373                                         v.display('NO MONEY?')
374                         sleep(1)
375                         vstatus.cur_selection = ''
376                         vstatus.time_to_autologout = time() + 8
377                         vstatus.last_timeout_refresh = None
378
379
380 def run_forever(rfh, wfh, options, cf):
381         v = VendingMachine(rfh, wfh)
382         vstatus = VendState(v)
383
384         logging.debug('PING is ' + str(v.ping()))
385
386         if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword)
387
388         vstatus.mk.set_message(GREETING)
389         setup_idlers(v)
390
391
392         # This main loop is hideous and the work of the devil - mtearle
393         #
394         #
395         # notes for later surgery
396         #   (event, counter, ' ')
397         #        V
398         #   d[      ] = (method)
399         #
400         #  return state
401
402         while True:
403                 if USE_DB:
404                         try:
405                                 db.handle_events()
406                         except DispenseDatabaseException, e:
407                                 logging.error('Database error: '+str(e))
408
409                 if vstatus.time_to_autologout != None:
410                         time_left = vstatus.time_to_autologout - time()
411                         if time_left < 6 and (vstatus.last_timeout_refresh is None or vstatus.last_timeout_refresh > time_left):
412                                 vstatus.mk.set_message('LOGOUT: '+str(int(time_left)))
413                                 vstatus.last_timeout_refresh = int(time_left)
414                                 vstatus.cur_selection = ''
415
416                 if vstatus.time_to_autologout != None and vstatus.time_to_autologout - time() <= 0:
417                         vstatus.time_to_autologout = None
418                         vstatus.cur_user = ''
419                         vstatus.cur_pin = ''
420                         vstatus.cur_selection = ''
421                         vstatus.mk.set_message(GREETING)
422
423                 if vstatus.time_to_autologout and not vstatus.mk.done(): vstatus.time_to_autologout = None
424                 if vstatus.cur_user == '' and vstatus.time_to_autologout: vstatus.time_to_autologout = None
425                 if len(vstatus.cur_pin) == PIN_LENGTH and vstatus.mk.done() and vstatus.time_to_autologout == None:
426                         # start autologout
427                         vstatus.time_to_autologout = time() + 15
428                         vstatus.last_timeout_refresh = None
429
430                 if vstatus.time_to_idle == None and vstatus.cur_user == '':
431                         vstatus.time_to_idle = time() + 5
432                         choose_idler()
433                 if vstatus.time_to_idle is not None and vstatus.cur_user != '': vstatus.time_to_idle = None
434                 if vstatus.time_to_idle is not None and time() > vstatus.time_to_idle: idle_step()
435                 if vstatus.time_to_idle is not None and time() > vstatus.time_to_idle + 300:
436                         vstatus.time_to_idle = time()
437                         choose_idler()
438
439                 vstatus.mk.update_display()
440
441                 e = v.next_event(0)
442                 if e == None:
443                         e = v.next_event(0.05)
444                         if e == None:
445                                 continue
446                 vstatus.time_to_idle = None
447                 (event, params) = e
448                 logging.debug('Got event: ' + repr(e))
449
450                 if event == DOOR:
451                         handle_door_event(event, params, v, vstatus)
452                 elif event == SWITCH:
453                         handle_switch_event(event, params, v, vstatus)
454                 elif event == KEY:
455                         handle_key_event(event, params, v, vstatus)
456
457 def connect_to_vend(options, cf):
458
459         if options.use_lat:
460                 logging.info('Connecting to vending machine using LAT')
461                 latclient = LATClient(service = cf.ServiceName, password = cf.ServicePassword, server_name = cf.ServerName, connect_password = cf.ConnectPassword, priv_password = cf.PrivPassword)
462                 rfh, wfh = latclient.get_fh()
463         elif options.use_serial:
464                 # Open vending machine via serial.
465                 logging.info('Connecting to vending machine using serial')
466                 serialclient = SerialClient(port = '/dev/ttyS1', baud = 9600)
467                 rfh,wfh = serialclient.get_fh()
468         else:
469                 #(rfh, wfh) = popen2('../../virtualvend/vvend.py')
470                 logging.info('Connecting to virtual vending machine on %s:%d'%(options.host,options.port))
471                 import socket
472                 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
473                 sock.connect((options.host, options.port))
474                 rfh = sock.makefile('r')
475                 wfh = sock.makefile('w')
476                 
477         return rfh, wfh
478
479 def parse_args():
480         from optparse import OptionParser
481
482         op = OptionParser(usage="%prog [OPTION]...")
483         op.add_option('-f', '--config-file', default='/etc/dispense/servers.conf', metavar='FILE', dest='config_file', help='use the specified config file instead of /etc/dispense/servers.conf')
484         op.add_option('--serial', action='store_true', default=True, dest='use_serial', help='use the serial port')
485         op.add_option('--lat', action='store_true', default=False, dest='use_lat', help='use LAT')
486         op.add_option('--virtualvend', action='store_false', default=True, dest='use_serial', help='use the virtual vending server instead of LAT')
487         op.add_option('-n', '--hostname', dest='host', default='localhost', help='the hostname to connect to for virtual vending machine mode (default: localhost)')
488         op.add_option('-p', '--port', dest='port', default=5150, type='int', help='the port number to connect to (default: 5150)')
489         op.add_option('-l', '--log-file', metavar='FILE', dest='log_file', default='', help='log output to the specified file')
490         op.add_option('-s', '--syslog', dest='syslog', metavar='FACILITY', default=None, help='log output to given syslog facility')
491         op.add_option('-d', '--daemon', dest='daemon', action='store_true', default=False, help='run as a daemon')
492         op.add_option('-v', '--verbose', dest='verbose', action='store_true', default=False, help='spit out lots of debug output')
493         op.add_option('-q', '--quiet', dest='quiet', action='store_true', default=False, help='only report errors')
494         op.add_option('--pid-file', dest='pid_file', metavar='FILE', default='', help='store daemon\'s pid in the given file')
495         options, args = op.parse_args()
496
497         if len(args) != 0:
498                 op.error('extra command line arguments: ' + ' '.join(args))
499
500         return options
501
502 config_options = {
503         'DBServer': ('Database', 'Server'),
504         'DBName': ('Database', 'Name'),
505         'DBUser': ('VendingMachine', 'DBUser'),
506         'DBPassword': ('VendingMachine', 'DBPassword'),
507         
508         'ServiceName': ('VendingMachine', 'ServiceName'),
509         'ServicePassword': ('VendingMachine', 'Password'),
510         
511         'ServerName': ('DecServer', 'Name'),
512         'ConnectPassword': ('DecServer', 'ConnectPassword'),
513         'PrivPassword': ('DecServer', 'PrivPassword'),
514         }
515
516 class VendConfigFile:
517         def __init__(self, config_file, options):
518                 try:
519                         cp = ConfigParser.ConfigParser()
520                         cp.read(config_file)
521
522                         for option in options:
523                                 section, name = options[option]
524                                 value = cp.get(section, name)
525                                 self.__dict__[option] = value
526                 
527                 except ConfigParser.Error, e:
528                         raise SystemExit("Error reading config file "+config_file+": " + str(e))
529
530 def create_pid_file(name):
531         try:
532                 pid_file = file(name, 'w')
533                 pid_file.write('%d\n'%os.getpid())
534                 pid_file.close()
535         except IOError, e:
536                 logging.warning('unable to write to pid file '+name+': '+str(e))
537
538 def set_stuff_up():
539         def do_nothing(signum, stack):
540                 signal.signal(signum, do_nothing)
541         def stop_server(signum, stack): raise KeyboardInterrupt
542         signal.signal(signal.SIGHUP, do_nothing)
543         signal.signal(signal.SIGTERM, stop_server)
544         signal.signal(signal.SIGINT, stop_server)
545
546         options = parse_args()
547         config_opts = VendConfigFile(options.config_file, config_options)
548         if options.daemon: become_daemon()
549         set_up_logging(options)
550         if options.pid_file != '': create_pid_file(options.pid_file)
551
552         return options, config_opts
553
554 def clean_up_nicely(options, config_opts):
555         if options.pid_file != '':
556                 try:
557                         os.unlink(options.pid_file)
558                         logging.debug('Removed pid file '+options.pid_file)
559                 except OSError: pass  # if we can't delete it, meh
560
561 def set_up_logging(options):
562         logger = logging.getLogger()
563         
564         if not options.daemon:
565                 stderr_logger = logging.StreamHandler(sys.stderr)
566                 stderr_logger.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
567                 logger.addHandler(stderr_logger)
568         
569         if options.log_file != '':
570                 try:
571                         file_logger = logging.FileHandler(options.log_file)
572                         file_logger.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s'))
573                         logger.addHandler(file_logger)
574                 except IOError, e:
575                         logger.warning('unable to write to log file '+options.log_file+': '+str(e))
576
577         if options.syslog != None:
578                 sys_logger = logging.handlers.SysLogHandler('/dev/log', options.syslog)
579                 sys_logger.setFormatter(logging.Formatter('vendserver[%d]'%(os.getpid()) + ' %(levelname)s: %(message)s'))
580                 logger.addHandler(sys_logger)
581
582         if options.quiet:
583                 logger.setLevel(logging.WARNING)
584         elif options.verbose:
585                 logger.setLevel(logging.DEBUG)
586         else:
587                 logger.setLevel(logging.INFO)
588
589 def become_daemon():
590         dev_null = file('/dev/null')
591         fd = dev_null.fileno()
592         os.dup2(fd, 0)
593         os.dup2(fd, 1)
594         os.dup2(fd, 2)
595         try:
596                 if os.fork() != 0:
597                         sys.exit(0)
598                 os.setsid()
599         except OSError, e:
600                 raise SystemExit('failed to fork: '+str(e))
601
602 def do_vend_server(options, config_opts):
603         while True:
604                 try:
605                         rfh, wfh = connect_to_vend(options, config_opts)
606                 except (SerialClientException, socket.error), e:
607                         (exc_type, exc_value, exc_traceback) = sys.exc_info()
608                         del exc_traceback
609                         logging.error("Connection error: "+str(exc_type)+" "+str(e))
610                         logging.info("Trying again in 5 seconds.")
611                         sleep(5)
612                         continue
613                 
614                 try:
615                         run_forever(rfh, wfh, options, config_opts)
616                 except VendingException:
617                         logging.error("Connection died, trying again...")
618                         logging.info("Trying again in 5 seconds.")
619                         sleep(5)
620
621 if __name__ == '__main__':
622         options, config_opts = set_stuff_up()
623         while True:
624                 try:
625                         logging.warning('Starting Vend Server')
626                         do_vend_server(options, config_opts)
627                         logging.error('Vend Server finished unexpectedly, restarting')
628                 except KeyboardInterrupt:
629                         logging.info("Killed by signal, cleaning up")
630                         clean_up_nicely(options, config_opts)
631                         logging.warning("Vend Server stopped")
632                         break
633                 except SystemExit:
634                         break
635                 except:
636                         (exc_type, exc_value, exc_traceback) = sys.exc_info()
637                         tb = format_tb(exc_traceback, 20)
638                         del exc_traceback
639                         
640                         logging.critical("Uh-oh, unhandled " + str(exc_type) + " exception")
641                         logging.critical("Message: " + str(exc_value))
642                         logging.critical("Traceback:")
643                         for event in tb:
644                                 for line in event.split('\n'):
645                                         logging.critical('    '+line)
646                         logging.critical("This message should be considered a bug in the Vend Server.")
647                         logging.critical("Please report this to someone who can fix it.")
648                         sleep(10)
649                         logging.warning("Trying again anyway (might not help, but hey...)")
650

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