Rearranging file and adding comments
[uccvend-vendserver.git] / VendServer / VendServer.py
index 2a9a55a..860c1c8 100755 (executable)
@@ -22,6 +22,7 @@ from SnackConfig import get_snack#, get_snacks
 import socket
 from posix import geteuid
 from LDAPConnector import get_uid,get_uname, set_card_id
+from OpenDispense import OpenDispense as Dispense
 
 CREDITS="""
 This vending machine software brought to you by:
@@ -31,7 +32,11 @@ Nick Bannon
 Cameron Patrick
 and a collective of hungry alpacas.
 
+The MIFARE card reader bought to you by:
+David Adam
 
+Bug Hunting and hardware maintenance by:
+Mitchell Pomery
 
 For a good time call +61 8 6488 3901
 
@@ -61,6 +66,43 @@ STATE_GRANDFATHER_CLOCK,
 TEXT_SPEED = 0.8
 IDLE_SPEED = 0.05
 
+_pin_uid = 0
+_pin_uname = 'root'
+_pin_pin = '----'
+
+_last_card_id = -1
+
+idlers = []
+idler = None
+
+config_options = {
+       'DBServer': ('Database', 'Server'),
+       'DBName': ('Database', 'Name'),
+       'DBUser': ('VendingMachine', 'DBUser'),
+       'DBPassword': ('VendingMachine', 'DBPassword'),
+       
+       'ServiceName': ('VendingMachine', 'ServiceName'),
+       'ServicePassword': ('VendingMachine', 'Password'),
+       
+       'ServerName': ('DecServer', 'Name'),
+       'ConnectPassword': ('DecServer', 'ConnectPassword'),
+       'PrivPassword': ('DecServer', 'PrivPassword'),
+       }
+
+class VendConfigFile:
+       def __init__(self, config_file, options):
+               try:
+                       cp = ConfigParser.ConfigParser()
+                       cp.read(config_file)
+
+                       for option in options:
+                               section, name = options[option]
+                               value = cp.get(section, name)
+                               self.__dict__[option] = value
+               
+               except ConfigParser.Error, e:
+                       raise SystemExit("Error reading config file "+config_file+": " + str(e))
+
 class DispenseDatabaseException(Exception): pass
 
 class DispenseDatabase:
@@ -91,8 +133,36 @@ class DispenseDatabase:
                while notifier is not None:
                        self.process_requests()
                        notify = self.db.getnotify()
+"""
+This class manages the current state of the vending machine.
+"""
+class VendState:
+       def __init__(self,v):
+               self.state_table = {}
+               self.state = STATE_IDLE
+               self.counter = 0
+
+               self.mk = MessageKeeper(v)
+               self.cur_user = ''
+               self.cur_pin = ''
+               self.username = ''
+               self.cur_selection = ''
+               self.time_to_autologout = None
+
+               self.last_timeout_refresh = None
+
+       def change_state(self,newstate,newcounter=None):
+               if self.state != newstate:
+                       self.state = newstate
+
+               if newcounter is not None and self.counter != newcounter:
+                       self.counter = newcounter
 
+"""
+Show information to the user as to what can be dispensed.
+"""
 def scroll_options(username, mk, welcome = False):
+       # If the user has just logged in, show them their balance
        if welcome:
                # Balance checking
                acct, unused = Popen(['dispense', 'acct', username], close_fds=True, stdout=PIPE).communicate()
@@ -106,7 +176,7 @@ def scroll_options(username, mk, welcome = False):
                msg = []
        choices = ' '*10+'CHOICES: '
 
-       # Get coke contents
+       # Show what is in the coke machine
        cokes = []
        for i in range(0, 7):
                args = ('dispense', 'iteminfo', 'coke:%i' % i)
@@ -121,27 +191,19 @@ def scroll_options(username, mk, welcome = False):
                if slot_name == 'dead': continue
                choices += '%s-(%sc)-%s8 '%(slot_name, price, slot_num)
 
-#      we don't want to print snacks for now since it'll be too large
-#      and there's physical bits of paper in the machine anyway - matt
-#      try:
-#              snacks = get_snacks()
-#      except:
-#              snacks = {}
-#
-#      for slot, ( name, price ) in snacks.items():
-#              choices += '%s8-%s (%sc) ' % ( slot, name, price )
-
+       # Show the final few options
        choices += '55-DOOR '
        choices += 'OR ANOTHER SNACK. '
        choices += '99 TO READ AGAIN. '
        choices += 'CHOICE?   '
        msg.append((choices, False, None))
+       # Send it to the display
        mk.set_messages(msg)
 
-_pin_uid = 0
-_pin_uname = 'root'
-_pin_pin = '----'
 
+"""
+Verify the users pin
+"""
 def _check_pin(uid, pin):
        global _pin_uid
        global _pin_uname
@@ -184,6 +246,9 @@ def _check_pin(uid, pin):
                logging.info("Pin incorrect for %d",uid)
        return pin == int(pinstr)
 
+"""
+Check if the users account has been disabled
+"""
 def acct_is_disabled(name=None):
        global _pin_uname
        if name == None:
@@ -197,9 +262,15 @@ def acct_is_disabled(name=None):
                return True
        return False
 
+"""
+Check that the user has a valid pin set
+"""
 def has_good_pin(uid):
        return _check_pin(uid, None) != None
 
+"""
+Verify the users pin.
+"""
 def verify_user_pin(uid, pin, skip_pin_check=False):
        if skip_pin_check or _check_pin(uid, pin) == True:
                info = pwd.getpwuid(uid)
@@ -215,7 +286,9 @@ def verify_user_pin(uid, pin, skip_pin_check=False):
                logging.info('refused pin for uid %d'%(uid))
                return None
 
-
+"""
+In here just for fun.
+"""
 def cookie(v):
        seed(time())
        messages = ['  WASSUP! ', 'PINK FISH ', ' SECRETS ', '  ESKIMO  ', ' FORTUNES ', 'MORE MONEY']
@@ -240,44 +313,54 @@ def cookie(v):
                                s += msg[i]
                v.display(s)
 
+"""
+Format text so it will appear centered on the screen.
+"""
 def center(str):
        LEN = 10
        return ' '*((LEN-len(str))/2)+str
 
-
-
-idlers = []
-idler = None
-
+"""
+Configure the things that will appear on screen whil the machine is idling.
+"""
 def setup_idlers(v):
        global idlers, idler
        idlers = [
-                GrayIdler(v),
+               #
+               GrayIdler(v),
+               GrayIdler(v,one="*",zero="-"),
+               GrayIdler(v,one="/",zero="\\"),
+               GrayIdler(v,one="X",zero="O"),
+               GrayIdler(v,one="*",zero="-",reorder=1),
+               GrayIdler(v,one="/",zero="\\",reorder=1),
+               GrayIdler(v,one="X",zero="O",reorder=1),
+               #
+               ClockIdler(v),
+               ClockIdler(v),
+               ClockIdler(v),
+               #
+               StringIdler(v), # Hello Cruel World
                StringIdler(v, text="Kill 'em all", repeat=False),
-                GrayIdler(v,one="*",zero="-"),
                StringIdler(v, text=CREDITS),
-                GrayIdler(v,one="/",zero="\\"),
-               ClockIdler(v),
-                GrayIdler(v,one="X",zero="O"),
-               FileIdler(v, '/usr/share/common-licenses/GPL-2',affinity=2),
-                GrayIdler(v,one="*",zero="-",reorder=1),
                StringIdler(v, text=str(math.pi) + "            "),
-               ClockIdler(v),
-                GrayIdler(v,one="/",zero="\\",reorder=1),
                StringIdler(v, text=str(math.e) + "            "),
-                GrayIdler(v,one="X",zero="O",reorder=1),
-               StringIdler(v, text="    I want some pizza - please call Pizza Hut Shenton Park on +61 8 9381 9979 [now closed? - MSH] - and order as Quinn - I am getting really hungry", repeat=False),
-               PipeIdler(v, "/usr/bin/getent", "passwd"),
-               FortuneIdler(v),
-               ClockIdler(v),
-               StringIdler(v),
-               TrainIdler(v),
+               StringIdler(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),
                # "Hello World" in brainfuck
                StringIdler(v, text=">+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++++>-] <.>+++++++++++[<++++++++>-]<-.--------.+++.------.--------.[-]>++++++++[<++++>- ]<+.[-]++++++++++."),
+               #
+               TrainIdler(v),
+               #
+               FileIdler(v, '/usr/share/common-licenses/GPL-2',affinity=2),
+               #
+               PipeIdler(v, "/usr/bin/getent", "passwd"),
+               FortuneIdler(v,affinity=20),
                ]
        disabled = [
                ]
 
+"""
+Go back to the default idler.
+"""
 def reset_idler(v, vstatus, t = None):
        global idlers, idler
        idler = GreetingIdler(v, t)
@@ -286,26 +369,41 @@ def reset_idler(v, vstatus, t = None):
        vstatus.time_to_autologout = None
        vstatus.change_state(STATE_IDLE, 1)
 
+"""
+Change to a random idler.
+"""
 def choose_idler():
        global idlers, idler
-       iiindex = 0
-       average_affinity = 10 # guessing here...
-
-       if idler and idler.__class__ != GreetingIdler:
-               iiindex = idlers.index(idler)
-
-       iilen = len(idlers)
-
-       move = int(random()*len(idlers)*average_affinity) + 1
 
-       while move >= 0:
-               iiindex += 1
-               iiindex %= iilen
-               idler = idlers[iiindex]
-               move -= idler.affinity()
+       # Implementation of the King Of the Hill algorithm from;
+       # http://eli.thegreenplace.net/2010/01/22/weighted-random-generation-in-python/
+
+       #def weighted_choice_king(weights):
+       #       total = 0
+       #       winner = 0
+       #       for i, w in enumerate(weights):
+       #               total += w
+       #               if random.random() * total < w:
+       #                       winner = i
+       #       return winner
+       #       
+
+       total = 0
+       winner = None
+       
+       for choice in idlers:
+               weight = choice.affinity()
+               total += weight
+               if random() * total < weight:
+                       winner = choice
 
-       idler.reset()
+       idler = winner
 
+       if idler:
+               idler.reset()
+"""
+Run every step while the machine is idling.
+"""
 def idle_step(vstatus):
        global idler
        if idler.finished():
@@ -316,38 +414,9 @@ def idle_step(vstatus):
                nextidle = IDLE_SPEED
        vstatus.time_of_next_idlestep = time()+nextidle
 
-class VendState:
-       def __init__(self,v):
-               self.state_table = {}
-               self.state = STATE_IDLE
-               self.counter = 0
-
-               self.mk = MessageKeeper(v)
-               self.cur_user = ''
-               self.cur_pin = ''
-               self.username = ''
-               self.cur_selection = ''
-               self.time_to_autologout = None
-
-               self.last_timeout_refresh = None
-
-       def change_state(self,newstate,newcounter=None):
-               if self.state != newstate:
-                       #print "Changing state from: ", 
-                       #print self.state,
-                       #print " to ", 
-                       #print newstate 
-                       self.state = newstate
-
-               if newcounter is not None and self.counter != newcounter:
-                       #print "Changing counter from: ", 
-                       #print self.counter,
-                       #print " to ", 
-                       #print newcounter 
-                       self.counter = newcounter
-
-
-
+"""
+These next two events trigger no response in the code.
+"""
 def handle_tick_event(event, params, v, vstatus):
        # don't care right now.
        pass
@@ -356,11 +425,16 @@ def handle_switch_event(event, params, v, vstatus):
        # don't care right now.
        pass
 
-
+"""
+Don't do anything for this event.
+"""
 def do_nothing(state, event, params, v, vstatus):
        print "doing nothing (s,e,p)", state, " ", event, " ", params
        pass
 
+"""
+These next few entrie tell us to do nothing while we are idling
+"""
 def handle_getting_uid_idle(state, event, params, v, vstatus):
        # don't care right now.
        pass
@@ -369,7 +443,11 @@ def handle_getting_pin_idle(state, event, params, v, vstatus):
        # don't care right now.
        pass
 
+"""
+While logged in and waiting for user input, slowly get closer to logging out.
+"""
 def handle_get_selection_idle(state, event, params, v, vstatus):
+       global _last_card_id
        # don't care right now.
        ###
        ### State logging out ..
@@ -385,7 +463,7 @@ def handle_get_selection_idle(state, event, params, v, vstatus):
                vstatus.cur_user = ''
                vstatus.cur_pin = ''
                vstatus.cur_selection = ''
-                       
+               _last_card_id = -1
                reset_idler(v, vstatus)
 
        ### State fully logged out ... reset variables
@@ -404,16 +482,18 @@ def handle_get_selection_idle(state, event, params, v, vstatus):
        # need to check
        vstatus.mk.update_display()
 
-
-
+"""
+Triggered on user input while logged in.
+"""
 def handle_get_selection_key(state, event, params, v, vstatus):
+       global _last_card_id
        key = params
        if len(vstatus.cur_selection) == 0:
                if key == 11:
                        vstatus.cur_pin = ''
                        vstatus.cur_user = ''
                        vstatus.cur_selection = ''
-                       
+                       _last_card_id = -1
                        vstatus.mk.set_messages([(center('BYE!'), False, 1.5)])
                        reset_idler(v, vstatus, 2)
                        return
@@ -440,13 +520,15 @@ def handle_get_selection_key(state, event, params, v, vstatus):
                                vstatus.time_to_autologout = None
                                vstatus.last_timeout_refresh = None
 
+"""
+Triggered when the user has entered the id of something they would like to purchase.
+"""
 def make_selection(v, vstatus):
        # should use sudo here
        if vstatus.cur_selection == '55':
                vstatus.mk.set_message('OPENSESAME')
                logging.info('dispensing a door for %s'%vstatus.username)
                if geteuid() == 0:
-                       #ret = os.system('su - "%s" -c "dispense door"'%vstatus.username)
                        ret = os.system('dispense -u "%s" door'%vstatus.username)
                else:
                        ret = os.system('dispense door')
@@ -477,8 +559,6 @@ def make_selection(v, vstatus):
                        price, shortname, name = get_snack( '--' )
                dollarprice = "$%.2f" % ( price / 100.0 )
                v.display(vstatus.cur_selection+' - %s'%dollarprice)
-#              exitcode = os.system('dispense -u "%s" give \>snacksales %d "%s"'%(vstatus.username, price, name)) >> 8
-#              exitcode = os.system('dispense -u "%s" give \>sales\:snack %d "%s"'%(vstatus.username, price, name)) >> 8
                exitcode = os.system('dispense -u "%s" snack:%s'%(vstatus.username, vstatus.cur_selection)) >> 8
                if (exitcode == 0):
                        # magic dispense syslog service
@@ -500,7 +580,9 @@ def make_selection(v, vstatus):
                        v.display('UNK ERROR')
        sleep(1)
 
-
+"""
+Find the price of an item.
+"""
 def price_check(v, vstatus):
        if vstatus.cur_selection[1] == '8':
                args = ('dispense', 'iteminfo', 'coke:' + vstatus.cur_selection[0])
@@ -515,7 +597,9 @@ def price_check(v, vstatus):
                dollarprice = "$%.2f" % ( price / 100.0 )
        v.display(vstatus.cur_selection+' - %s'%dollarprice)
 
-
+"""
+Triggered when the user presses a button while entering their pin.
+"""
 def handle_getting_pin_key(state, event, params, v, vstatus):
        #print "handle_getting_pin_key (s,e,p)", state, " ", event, " ", params
        key = params
@@ -551,7 +635,9 @@ def handle_getting_pin_key(state, event, params, v, vstatus):
 
                                return
 
-
+"""
+Triggered when the user presses a button while entering their user id.
+"""
 def handle_getting_uid_key(state, event, params, v, vstatus):
        #print "handle_getting_uid_key (s,e,p)", state, " ", event, " ", params
        key = params
@@ -651,7 +737,9 @@ Wouldn't you prefer a nice game of chess?
                vstatus.change_state(STATE_GETTING_PIN)
                return
 
-
+"""
+Triggered when a key is pressed and the machine is idling.
+"""
 def handle_idle_key(state, event, params, v, vstatus):
        #print "handle_idle_key (s,e,p)", state, " ", event, " ", params
 
@@ -665,7 +753,9 @@ def handle_idle_key(state, event, params, v, vstatus):
        vstatus.change_state(STATE_GETTING_UID)
        run_handler(event, key, v, vstatus)
 
-
+"""
+What to do when there is nothing to do.
+"""
 def handle_idle_tick(state, event, params, v, vstatus):
        ### State idling
        if vstatus.mk.done():
@@ -683,6 +773,9 @@ def handle_idle_tick(state, event, params, v, vstatus):
        run_handler(event, params, v, vstatus)
        sleep(0.05)
 
+"""
+Manages the beeps for the grandfather clock
+"""
 def beep_on(when, before=0):
        start = int(when - before)
        end = int(when)
@@ -787,6 +880,9 @@ def handle_grandfather_tick(state, event, params, v, vstatus):
        if go_idle and vstatus.mk.done():
                vstatus.change_state(STATE_IDLE,1)
 
+"""
+What to do when the door is open.
+"""
 def handle_door_idle(state, event, params, v, vstatus):
        def twiddle(clock,v,wise = 2):
                if (clock % 4 == 0):
@@ -806,7 +902,9 @@ def handle_door_idle(state, event, params, v, vstatus):
        else:
                twiddle(now, v, wise=0)
 
-
+"""
+What to do when the door is opened or closed.
+"""
 def handle_door_event(state, event, params, v, vstatus):
        if params == 0:  #door open
                vstatus.change_state(STATE_DOOR_OPENING)
@@ -822,12 +920,18 @@ def handle_door_event(state, event, params, v, vstatus):
                logging.warning('Leaving open door mode')
                v.display("-YUM YUM!-")
 
+"""
+Triggered when a user swipes their caed, and the machine is logged out.
+"""
 def handle_mifare_event(state, event, params, v, vstatus):
+       global _last_card_id
        card_id = params
        # Translate card_id into uid.
-       if card_id == None:
+       if card_id == None or card_id == _last_card_id:
                return
 
+       _last_card_id = card_id
+       
        try:
                vstatus.cur_user = get_uid(card_id)
                logging.info('Mapped card id to uid %s'%vstatus.cur_user)
@@ -860,17 +964,24 @@ def handle_mifare_event(state, event, params, v, vstatus):
                         (center('SORRY'), False, 0.5)])
                vstatus.cur_user = ''
                vstatus.cur_pin = ''
+               _last_card_id = -1
        
                reset_idler(v, vstatus, 2)
                return
 
+"""
+Triggered when a user swipes their card and the machine is logged in.
+"""
 def handle_mifare_add_user_event(state, event, params, v, vstatus):
-       card_id = params
+       global _last_card_id
+       card_id = params
 
        # Translate card_id into uid.
-       if card_id == None:
+       if card_id == None or card_id == _last_card_id:
                return
 
+       _last_card_id = card_id
+       
        try:
                if get_uid(card_id) != None:
                        vstatus.mk.set_messages(
@@ -893,6 +1004,9 @@ def handle_mifare_add_user_event(state, event, params, v, vstatus):
 def return_to_idle(state,event,params,v,vstatus):
        reset_idler(v, vstatus)
 
+"""
+Maps what to do when the state changes.
+"""
 def create_state_table(vstatus):
        vstatus.state_table[(STATE_IDLE,TICK,1)] = handle_idle_tick
        vstatus.state_table[(STATE_IDLE,KEY,1)] = handle_idle_key
@@ -910,28 +1024,31 @@ def create_state_table(vstatus):
        vstatus.state_table[(STATE_DOOR_CLOSING,MIFARE,1)] = do_nothing
 
        vstatus.state_table[(STATE_GETTING_UID,TICK,1)] = handle_getting_uid_idle
-       vstatus.state_table[(STATE_GETTING_UID,DOOR,1)] = do_nothing
+       vstatus.state_table[(STATE_GETTING_UID,DOOR,1)] = handle_door_event
        vstatus.state_table[(STATE_GETTING_UID,KEY,1)] = handle_getting_uid_key
        vstatus.state_table[(STATE_GETTING_UID,MIFARE,1)] = handle_mifare_event
 
        vstatus.state_table[(STATE_GETTING_PIN,TICK,1)] = handle_getting_pin_idle
-       vstatus.state_table[(STATE_GETTING_PIN,DOOR,1)] = do_nothing
+       vstatus.state_table[(STATE_GETTING_PIN,DOOR,1)] = handle_door_event
        vstatus.state_table[(STATE_GETTING_PIN,KEY,1)] = handle_getting_pin_key
        vstatus.state_table[(STATE_GETTING_PIN,MIFARE,1)] = handle_mifare_event
 
        vstatus.state_table[(STATE_GET_SELECTION,TICK,1)] = handle_get_selection_idle
-       vstatus.state_table[(STATE_GET_SELECTION,DOOR,1)] = do_nothing
+       vstatus.state_table[(STATE_GET_SELECTION,DOOR,1)] = handle_door_event
        vstatus.state_table[(STATE_GET_SELECTION,KEY,1)] = handle_get_selection_key
        vstatus.state_table[(STATE_GET_SELECTION,MIFARE,1)] = handle_mifare_add_user_event
 
        vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,1)] = handle_idle_grandfather_tick
        vstatus.state_table[(STATE_GRANDFATHER_CLOCK,TICK,2)] = handle_grandfather_tick
-       vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,1)] = do_nothing
-       vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,2)] = do_nothing
+       vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,1)] = handle_door_event
+       vstatus.state_table[(STATE_GRANDFATHER_CLOCK,DOOR,2)] = handle_door_event
        vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,1)] = do_nothing
        vstatus.state_table[(STATE_GRANDFATHER_CLOCK,KEY,2)] = do_nothing
        vstatus.state_table[(STATE_GRANDFATHER_CLOCK,MIFARE,1)] = handle_mifare_event
 
+"""
+Get what to do on a state change.
+"""
 def get_state_table_handler(vstatus, state, event, counter):
        return vstatus.state_table[(state,event,counter)]
 
@@ -955,17 +1072,6 @@ def run_forever(rfh, wfh, options, cf):
        setup_idlers(v)
        reset_idler(v, vstatus)
 
-       # This main loop was hideous and the work of the devil.
-       # This has now been fixed (mostly) - mtearle
-       #
-       #
-       # notes for later surgery
-       #   (event, counter, ' ')
-       #        V
-       #   d[      ] = (method)
-       #
-       # ( return state - not currently implemented )
-
        while True:
                if USE_DB:
                        try:
@@ -979,14 +1085,14 @@ def run_forever(rfh, wfh, options, cf):
 
                run_handler(event, params, v, vstatus)
 
-#              logging.debug('Got event: ' + repr(e))
-
-
 def run_handler(event, params, v, vstatus):
        handler = get_state_table_handler(vstatus,vstatus.state,event,vstatus.counter)
        if handler:
                handler(vstatus.state, event, params, v, vstatus)
 
+"""
+Connect to the machine.
+"""
 def connect_to_vend(options, cf):
 
        if options.use_lat:
@@ -1011,6 +1117,9 @@ def connect_to_vend(options, cf):
                
        return rfh, wfh
 
+"""
+Parse arguments from the command line
+"""
 def parse_args():
        from optparse import OptionParser
 
@@ -1034,34 +1143,6 @@ def parse_args():
 
        return options
 
-config_options = {
-       'DBServer': ('Database', 'Server'),
-       'DBName': ('Database', 'Name'),
-       'DBUser': ('VendingMachine', 'DBUser'),
-       'DBPassword': ('VendingMachine', 'DBPassword'),
-       
-       'ServiceName': ('VendingMachine', 'ServiceName'),
-       'ServicePassword': ('VendingMachine', 'Password'),
-       
-       'ServerName': ('DecServer', 'Name'),
-       'ConnectPassword': ('DecServer', 'ConnectPassword'),
-       'PrivPassword': ('DecServer', 'PrivPassword'),
-       }
-
-class VendConfigFile:
-       def __init__(self, config_file, options):
-               try:
-                       cp = ConfigParser.ConfigParser()
-                       cp.read(config_file)
-
-                       for option in options:
-                               section, name = options[option]
-                               value = cp.get(section, name)
-                               self.__dict__[option] = value
-               
-               except ConfigParser.Error, e:
-                       raise SystemExit("Error reading config file "+config_file+": " + str(e))
-
 def create_pid_file(name):
        try:
                pid_file = file(name, 'w')
@@ -1155,7 +1236,8 @@ def do_vend_server(options, config_opts):
                        logging.info("Trying again in 5 seconds.")
                        sleep(5)
 
-if __name__ == '__main__':
+
+def main(argv=None):
        options, config_opts = set_stuff_up()
        while True:
                try:
@@ -1185,3 +1267,5 @@ if __name__ == '__main__':
                        sleep(10)
                        logging.warning("Trying again anyway (might not help, but hey...)")
 
+if __name__ == '__main__':
+       sys.exit(main())

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