X-Git-Url: https://git.ucc.asn.au/?p=uccvend-vendserver.git;a=blobdiff_plain;f=sql-edition%2Fservers%2FVendServer.py;h=7ac707120632c0b660477f43fa4cd9c9d2f17730;hp=31365f9bccd373124ecbea805721ac52ef578393;hb=06e9a8f89f0ca91cf3300483b61ccab29f70cf7a;hpb=3332a98fa5f734d3319d17d4b772148400ce6700 diff --git a/sql-edition/servers/VendServer.py b/sql-edition/servers/VendServer.py index 31365f9..7ac7071 100755 --- a/sql-edition/servers/VendServer.py +++ b/sql-edition/servers/VendServer.py @@ -4,20 +4,38 @@ USE_DB = 0 import ConfigParser -import sys, os, string, re, pwd, signal +import sys, os, string, re, pwd, signal, math import logging, logging.handlers from traceback import format_tb if USE_DB: import pg from time import time, sleep from popen2 import popen2 from LATClient import LATClient, LATClientException +from SerialClient import SerialClient, SerialClientException from VendingMachine import VendingMachine, VendingException +from MessageKeeper import MessageKeeper from HorizScroll import HorizScroll from random import random, seed -from Idler import TrainIdler,GrayIdler +from Idler import TrainIdler,GrayIdler,StringIdler,ClockIdler,FortuneIdler,FileIdler,PipeIdler 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. + + + +For a good time call +61 8 6488 3901 + + + +""" + GREETING = 'UCC SNACKS' PIN_LENGTH = 4 @@ -86,22 +104,27 @@ def get_pin(uid): 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: + logging.info('getting pin for uid %d: .pin not found in home directory'%uid) 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: + 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): + logging.info('getting pin for uid %d: %s not a good pin'%(uid,repr(pinstr))) return None return int(pinstr) @@ -111,8 +134,10 @@ def has_good_pin(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: + logging.info('refused pin for uid %d'%(uid)) return None def door_open_mode(v): @@ -157,54 +182,65 @@ def center(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 = [ + GrayIdler(v), + 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'), + 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 - and order as Quinn - I am getting really hungry", repeat=False), + PipeIdler(v, "/usr/bin/ypcat", "passwd"), + FortuneIdler(v), + ClockIdler(v), + StringIdler(v), + TrainIdler(v), + ] + disabled = [ + ] + idler = choose_idler() + +def choose_idler(): + global idler + iiindex = 0 + + if idler: + iiindex = idlers.index(idler) + + iilen = len(idlers) + + move = int(random()*len(idlers)) + 1 + + while move >= 0: + idler = idlers[( (iiindex + 1) % iilen)] + move = move - idler.affinity() + + idler.reset() + +def idle_step(): + global idler + if idler.finished(): + choose_idler() + idler.next() 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 = '' @@ -214,12 +250,21 @@ def run_forever(rfh, wfh, options, cf): 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 + + # This main loop is hideous and the work of the devil - mtearle + # + # + # notes for later surgery + # (event, counter, ' ') + # V + # d[ ] = (method) + # + # return state + while True: if USE_DB: try: @@ -246,16 +291,22 @@ def run_forever(rfh, wfh, options, cf): if len(cur_pin) == PIN_LENGTH and mk.done() and time_to_autologout == None: # start autologout time_to_autologout = time() + 15 + last_timeout_refresh = None - 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: - e = v.next_event(0.1) + e = v.next_event(0.05) if e == None: continue time_to_idle = None @@ -283,6 +334,7 @@ def run_forever(rfh, wfh, options, cf): 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), @@ -296,6 +348,7 @@ def run_forever(rfh, wfh, options, cf): 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: @@ -348,13 +401,16 @@ def run_forever(rfh, wfh, options, cf): # 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: + logging.info('door opened') 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': @@ -365,23 +421,36 @@ def run_forever(rfh, wfh, options, cf): continue elif cur_selection[1] == '8': v.display('GOT COKE?') - os.system('su - "%s" -c "dispense %s"'%(username, cur_selection[0])) + if ((os.system('su - "%s" -c "dispense %s"'%(username, cur_selection[0])) >> 8) != 0): + v.display('SEEMS NOT') + else: + v.display('GOT COKE!') else: - v.display('HERES A '+cur_selection) - v.vend(cur_selection) - sleep(0.5) - v.display('THANK YOU') - sleep(0.5) + v.display(cur_selection+' - $1.00') + if ((os.system('su - "%s" -c "dispense snack"'%(username)) >> 8) == 0): + v.vend(cur_selection) + v.display('THANK YOU') + else: + v.display('NO MONEY?') + sleep(1) cur_selection = '' time_to_autologout = time() + 8 + last_timeout_refresh = None 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() + elif options.use_serial: + # Open vending machine via serial. + logging.info('Connecting to vending machine using serial') + serialclient = SerialClient(port = '/dev/ttyS1', baud = 9600) + rfh,wfh = serialclient.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)) @@ -395,11 +464,13 @@ def parse_args(): op = OptionParser(usage="%prog [OPTION]...") 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') - op.add_option('--virtualvend', action='store_false', default=True, dest='use_lat', help='use the virtual vending server instead of LAT') + op.add_option('--serial', action='store_true', default=True, dest='use_serial', help='use the serial port') + op.add_option('--lat', action='store_true', default=False, dest='use_lat', help='use LAT') + op.add_option('--virtualvend', action='store_false', default=True, dest='use_serial', help='use the virtual vending server instead of LAT') 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') @@ -437,9 +508,7 @@ class VendConfigFile: 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: @@ -458,9 +527,9 @@ def set_stuff_up(): 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() + set_up_logging(options) if options.pid_file != '': create_pid_file(options.pid_file) return options, config_opts @@ -475,9 +544,10 @@ def clean_up_nicely(options, config_opts): 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: @@ -487,8 +557,8 @@ def set_up_logging(options): 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) @@ -505,14 +575,18 @@ def become_daemon(): 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: try: rfh, wfh = connect_to_vend(options, config_opts) - except (LATClientException, socket.error), e: + except (SerialClientException, socket.error), e: (exc_type, exc_value, exc_traceback) = sys.exc_info() del exc_traceback logging.error("Connection error: "+str(exc_type)+" "+str(e)) @@ -539,6 +613,8 @@ if __name__ == '__main__': 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)