okay... still bugs with StringIdler
[zanchey/dispense2.git] / sql-edition / servers / VendServer.py
index 31365f9..7c4a278 100755 (executable)
@@ -12,12 +12,22 @@ from time import time, sleep
 from popen2 import popen2
 from LATClient import LATClient, LATClientException
 from VendingMachine import VendingMachine, VendingException
 from popen2 import popen2
 from LATClient import LATClient, LATClientException
 from VendingMachine import VendingMachine, VendingException
+from MessageKeeper import MessageKeeper
 from HorizScroll import HorizScroll
 from random import random, seed
 from HorizScroll import HorizScroll
 from random import random, seed
-from Idler import TrainIdler,GrayIdler
+from Idler import TrainIdler,GrayIdler,StringIdler
 import socket
 from posix import geteuid
 
 import socket
 from posix import geteuid
 
+CREDITS="""
+This vending machine software brought to you by:
+Bernard Blackham
+Mark Tearle
+Nick Bannon
+Cameron Patrick
+and a collective of hungry alpacas.
+"""
+
 GREETING = 'UCC SNACKS'
 PIN_LENGTH = 4
 
 GREETING = 'UCC SNACKS'
 PIN_LENGTH = 4
 
@@ -86,22 +96,27 @@ def get_pin(uid):
        try:
                info = pwd.getpwuid(uid)
        except KeyError:
        try:
                info = pwd.getpwuid(uid)
        except KeyError:
+               logging.info('getting pin for uid %d: user not in password file'%uid)
                return None
        if info.pw_dir == None: return False
        pinfile = os.path.join(info.pw_dir, '.pin')
        try:
                s = os.stat(pinfile)
        except OSError:
                return None
        if info.pw_dir == None: return False
        pinfile = os.path.join(info.pw_dir, '.pin')
        try:
                s = os.stat(pinfile)
        except OSError:
+               logging.info('getting pin for uid %d: .pin not found in home directory'%uid)
                return None
        if s.st_mode & 077:
                return None
        if s.st_mode & 077:
+               logging.info('getting pin for uid %d: .pin has wrong permissions'%uid)
                return None
        try:
                f = file(pinfile)
        except IOError:
                return None
        try:
                f = file(pinfile)
        except IOError:
+               logging.info('getting pin for uid %d: I cannot read pin file'%uid)
                return None
        pinstr = f.readline()
        f.close()
        if not re.search('^'+'[0-9]'*PIN_LENGTH+'$', pinstr):
                return None
        pinstr = f.readline()
        f.close()
        if not re.search('^'+'[0-9]'*PIN_LENGTH+'$', pinstr):
+               logging.info('getting pin for uid %d: %s not a good pin'%(uid,repr(pinstr)))
                return None
        return int(pinstr)
 
                return None
        return int(pinstr)
 
@@ -111,8 +126,10 @@ def has_good_pin(uid):
 def verify_user_pin(uid, pin):
        if get_pin(uid) == pin:
                info = pwd.getpwuid(uid)
 def verify_user_pin(uid, pin):
        if get_pin(uid) == pin:
                info = pwd.getpwuid(uid)
+               logging.info('accepted pin for uid %d (%s)'%(uid,info.pw_name))
                return info.pw_name
        else:
                return info.pw_name
        else:
+               logging.info('refused pin for uid %d'%(uid))
                return None
 
 def door_open_mode(v):
                return None
 
 def door_open_mode(v):
@@ -157,54 +174,38 @@ def center(str):
        LEN = 10
        return ' '*((LEN-len(str))/2)+str
 
        LEN = 10
        return ' '*((LEN-len(str))/2)+str
 
-class MessageKeeper:
-       def __init__(self, vendie):
-               # Each element of scrolling_message should be a 3-tuple of
-               # ('message', True/False if it is to be repeated, time to display)
-               self.scrolling_message = []
-               self.v = vendie
-               self.next_update = None
-
-       def set_message(self, string):
-               self.scrolling_message = [(string, False, None)]
-               self.update_display(True)
-
-       def set_messages(self, strings):
-               self.scrolling_message = strings
-               self.update_display(True)
-
-       def update_display(self, forced = False):
-               if not forced and self.next_update != None and time() < self.next_update:
-                       return
-               if len(self.scrolling_message) > 0:
-                       if len(self.scrolling_message[0][0]) > 10:
-                               (m, r, t) = self.scrolling_message[0]
-                               a = []
-                               exp = HorizScroll(m).expand(padding = 0, wraparound = True)
-                               if t == None:
-                                       t = 0.1
-                               else:
-                                       t = t / len(exp)
-                               for x in exp:
-                                       a.append((x, r, t))
-                               del self.scrolling_message[0]
-                               self.scrolling_message = a + self.scrolling_message
-                       newmsg = self.scrolling_message[0]
-                       if newmsg[2] != None:
-                               self.next_update = time() + newmsg[2]
-                       else:
-                               self.next_update = None
-                       self.v.display(self.scrolling_message[0][0])
-                       if self.scrolling_message[0][1]:
-                               self.scrolling_message.append(self.scrolling_message[0])
-                       del self.scrolling_message[0]
 
 
-       def done(self):
-               return len(self.scrolling_message) == 0
+
+idlers = []
+idler = None
+def setup_idlers(v):
+       global idlers, idler
+       idlers = [
+               TrainIdler(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),
+               ]
+               #StringIdler(v),
+               #StringIdler(v, text=CREDITS),
+       idler = choose_idler()
+
+def choose_idler():
+       global idler
+       idler = idlers[int(random()*len(idlers))]
+       idler.reset()
+
+def idle_step():
+       global idler
+       idler.next()
 
 def run_forever(rfh, wfh, options, cf):
        v = VendingMachine(rfh, wfh)
 
 def run_forever(rfh, wfh, options, cf):
        v = VendingMachine(rfh, wfh)
-       logging.debug('PING is' + str(v.ping()))
+       logging.debug('PING is ' + str(v.ping()))
 
        if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword)
        cur_user = ''
 
        if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword)
        cur_user = ''
@@ -214,9 +215,7 @@ def run_forever(rfh, wfh, options, cf):
        mk = MessageKeeper(v)
        mk.set_message(GREETING)
        time_to_autologout = None
        mk = MessageKeeper(v)
        mk.set_message(GREETING)
        time_to_autologout = None
-       #idler = TrainIdler(v)
-       #idler = GrayIdler(v)
-       idler = GrayIdler(v,one="*",zero="-")
+       setup_idlers(v)
        time_to_idle = None
        last_timeout_refresh = None
 
        time_to_idle = None
        last_timeout_refresh = None
 
@@ -247,15 +246,20 @@ def run_forever(rfh, wfh, options, cf):
                        # start autologout
                        time_to_autologout = time() + 15
 
                        # start autologout
                        time_to_autologout = time() + 15
 
-               if time_to_idle == None and cur_user == '': time_to_idle = time() + 60
-               if time_to_idle != None and cur_user != '': time_to_idle = None
-               if time_to_idle is not None and time() > time_to_idle: idler.next()
+               if time_to_idle == None and cur_user == '':
+                       time_to_idle = time() + 5
+                       choose_idler()
+               if time_to_idle is not None and cur_user != '': time_to_idle = None
+               if time_to_idle is not None and time() > time_to_idle: idle_step()
+               if time_to_idle is not None and time() > time_to_idle + 300:
+                       time_to_idle = time()
+                       choose_idler()
 
                mk.update_display()
 
                e = v.next_event(0)
                if e == None:
 
                mk.update_display()
 
                e = v.next_event(0)
                if e == None:
-                       e = v.next_event(0.1)
+                       e = v.next_event(0.05)
                        if e == None:
                                continue
                time_to_idle = None
                        if e == None:
                                continue
                time_to_idle = None
@@ -283,6 +287,7 @@ def run_forever(rfh, wfh, options, cf):
                                if len(cur_user) == 5:
                                        uid = int(cur_user)
                                        if not has_good_pin(uid):
                                if len(cur_user) == 5:
                                        uid = int(cur_user)
                                        if not has_good_pin(uid):
+                                               logging.info('user '+cur_user+' has a bad PIN')
                                                #mk.set_messages(
                                                        #[(center('INVALID'), False, 0.7),
                                                         #(center('PIN'), False, 0.7),
                                                #mk.set_messages(
                                                        #[(center('INVALID'), False, 0.7),
                                                         #(center('PIN'), False, 0.7),
@@ -296,6 +301,7 @@ def run_forever(rfh, wfh, options, cf):
                                                continue
                                        cur_pin = ''
                                        mk.set_message('PIN: ')
                                                continue
                                        cur_pin = ''
                                        mk.set_message('PIN: ')
+                                       logging.info('need pin for user %s'%cur_user)
                                        continue
                        elif len(cur_pin) < PIN_LENGTH:
                                if key == 11:
                                        continue
                        elif len(cur_pin) < PIN_LENGTH:
                                if key == 11:
@@ -348,13 +354,16 @@ def run_forever(rfh, wfh, options, cf):
                                        # XXX this should move somewhere else:
                                        if cur_selection == '55':
                                                mk.set_message('OPENSESAME')
                                        # XXX this should move somewhere else:
                                        if cur_selection == '55':
                                                mk.set_message('OPENSESAME')
+                                               logging.info('dispensing a door for %s'%username)
                                                if geteuid() == 0:
                                                        ret = os.system('su - "%s" -c "dispense door"'%username)
                                                else:
                                                        ret = os.system('dispense door')
                                                if ret == 0:
                                                if geteuid() == 0:
                                                        ret = os.system('su - "%s" -c "dispense door"'%username)
                                                else:
                                                        ret = os.system('dispense door')
                                                if ret == 0:
+                                                       logging.info('door opened')
                                                        mk.set_message(center('DOOR OPEN'))
                                                else:
                                                        mk.set_message(center('DOOR OPEN'))
                                                else:
+                                                       logging.warning('user %s tried to dispense a bad door'%username)
                                                        mk.set_message(center('BAD DOOR'))
                                                sleep(1)
                                        elif cur_selection == '91':
                                                        mk.set_message(center('BAD DOOR'))
                                                sleep(1)
                                        elif cur_selection == '91':
@@ -378,10 +387,12 @@ def run_forever(rfh, wfh, options, cf):
 def connect_to_vend(options, cf):
        # Open vending machine via LAT?
        if options.use_lat:
 def connect_to_vend(options, cf):
        # Open vending machine via LAT?
        if options.use_lat:
+               logging.info('Connecting to vending machine using LAT')
                latclient = LATClient(service = cf.ServiceName, password = cf.ServicePassword, server_name = cf.ServerName, connect_password = cf.ConnectPassword, priv_password = cf.PrivPassword)
                rfh, wfh = latclient.get_fh()
        else:
                #(rfh, wfh) = popen2('../../virtualvend/vvend.py')
                latclient = LATClient(service = cf.ServiceName, password = cf.ServicePassword, server_name = cf.ServerName, connect_password = cf.ConnectPassword, priv_password = cf.PrivPassword)
                rfh, wfh = latclient.get_fh()
        else:
                #(rfh, wfh) = popen2('../../virtualvend/vvend.py')
+               logging.info('Connecting to virtual vending machine on %s:%d'%(options.host,options.port))
                import socket
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
                sock.connect((options.host, options.port))
                import socket
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
                sock.connect((options.host, options.port))
@@ -399,7 +410,7 @@ def parse_args():
        op.add_option('-n', '--hostname', dest='host', default='localhost', help='the hostname to connect to for virtual vending machine mode (default: localhost)')
        op.add_option('-p', '--port', dest='port', default=5150, type='int', help='the port number to connect to (default: 5150)')
        op.add_option('-l', '--log-file', metavar='FILE', dest='log_file', default='', help='log output to the specified file')
        op.add_option('-n', '--hostname', dest='host', default='localhost', help='the hostname to connect to for virtual vending machine mode (default: localhost)')
        op.add_option('-p', '--port', dest='port', default=5150, type='int', help='the port number to connect to (default: 5150)')
        op.add_option('-l', '--log-file', metavar='FILE', dest='log_file', default='', help='log output to the specified file')
-       op.add_option('-s', '--syslog', dest='syslog', action='store_true', default=False, help='log output to syslog')
+       op.add_option('-s', '--syslog', dest='syslog', metavar='FACILITY', default=None, help='log output to given syslog facility')
        op.add_option('-d', '--daemon', dest='daemon', action='store_true', default=False, help='run as a daemon')
        op.add_option('-v', '--verbose', dest='verbose', action='store_true', default=False, help='spit out lots of debug output')
        op.add_option('-q', '--quiet', dest='quiet', action='store_true', default=False, help='only report errors')
        op.add_option('-d', '--daemon', dest='daemon', action='store_true', default=False, help='run as a daemon')
        op.add_option('-v', '--verbose', dest='verbose', action='store_true', default=False, help='spit out lots of debug output')
        op.add_option('-q', '--quiet', dest='quiet', action='store_true', default=False, help='only report errors')
@@ -437,9 +448,7 @@ class VendConfigFile:
                                self.__dict__[option] = value
                
                except ConfigParser.Error, e:
                                self.__dict__[option] = value
                
                except ConfigParser.Error, e:
-                       logging.critical("Error reading config file "+config_file+": " + str(e))
-                       logging.critical("Bailing out")
-                       sys.exit(1)
+                       raise SystemExit("Error reading config file "+config_file+": " + str(e))
 
 def create_pid_file(name):
        try:
 
 def create_pid_file(name):
        try:
@@ -458,9 +467,9 @@ def set_stuff_up():
        signal.signal(signal.SIGINT, stop_server)
 
        options = parse_args()
        signal.signal(signal.SIGINT, stop_server)
 
        options = parse_args()
-       set_up_logging(options)
        config_opts = VendConfigFile(options.config_file, config_options)
        if options.daemon: become_daemon()
        config_opts = VendConfigFile(options.config_file, config_options)
        if options.daemon: become_daemon()
+       set_up_logging(options)
        if options.pid_file != '': create_pid_file(options.pid_file)
 
        return options, config_opts
        if options.pid_file != '': create_pid_file(options.pid_file)
 
        return options, config_opts
@@ -475,9 +484,10 @@ def clean_up_nicely(options, config_opts):
 def set_up_logging(options):
        logger = logging.getLogger()
        
 def set_up_logging(options):
        logger = logging.getLogger()
        
-       stderr_logger = logging.StreamHandler(sys.stderr)
-       stderr_logger.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
-       logger.addHandler(stderr_logger)
+       if not options.daemon:
+               stderr_logger = logging.StreamHandler(sys.stderr)
+               stderr_logger.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
+               logger.addHandler(stderr_logger)
        
        if options.log_file != '':
                try:
        
        if options.log_file != '':
                try:
@@ -487,8 +497,8 @@ def set_up_logging(options):
                except IOError, e:
                        logger.warning('unable to write to log file '+options.log_file+': '+str(e))
 
                except IOError, e:
                        logger.warning('unable to write to log file '+options.log_file+': '+str(e))
 
-       if options.syslog:
-               sys_logger = logging.handlers.SysLogHandler('/dev/log', 'daemon')
+       if options.syslog != None:
+               sys_logger = logging.handlers.SysLogHandler('/dev/log', options.syslog)
                sys_logger.setFormatter(logging.Formatter('vendserver[%d]'%(os.getpid()) + ' %(levelname)s: %(message)s'))
                logger.addHandler(sys_logger)
 
                sys_logger.setFormatter(logging.Formatter('vendserver[%d]'%(os.getpid()) + ' %(levelname)s: %(message)s'))
                logger.addHandler(sys_logger)
 
@@ -505,8 +515,12 @@ def become_daemon():
        os.dup2(fd, 0)
        os.dup2(fd, 1)
        os.dup2(fd, 2)
        os.dup2(fd, 0)
        os.dup2(fd, 1)
        os.dup2(fd, 2)
-       if os.fork() != 0:
-               sys.exit(0)
+       try:
+               if os.fork() != 0:
+                       sys.exit(0)
+               os.setsid()
+       except OSError, e:
+               raise SystemExit('failed to fork: '+str(e))
 
 def do_vend_server(options, config_opts):
        while True:
 
 def do_vend_server(options, config_opts):
        while True:
@@ -539,6 +553,8 @@ if __name__ == '__main__':
                        clean_up_nicely(options, config_opts)
                        logging.warning("Vend Server stopped")
                        break
                        clean_up_nicely(options, config_opts)
                        logging.warning("Vend Server stopped")
                        break
+               except SystemExit:
+                       break
                except:
                        (exc_type, exc_value, exc_traceback) = sys.exc_info()
                        tb = format_tb(exc_traceback, 20)
                except:
                        (exc_type, exc_value, exc_traceback) = sys.exc_info()
                        tb = format_tb(exc_traceback, 20)

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