VendServer - Work around display bug
[uccvend-vendserver.git] / VendServer / VendServer.py
index 6a7af6b..3ac2b81 100755 (executable)
@@ -1,14 +1,12 @@
 #!/usr/bin/python
 # vim:ts=4
 
-USE_DB = 0
 USE_MIFARE = 1
 
 import ConfigParser
 import sys, os, string, re, pwd, signal, math, syslog
 import logging, logging.handlers
 from traceback import format_tb
-if USE_DB: import pg
 from time import time, sleep, mktime, localtime
 from subprocess import Popen, PIPE
 from LATClient import LATClient, LATClientException
@@ -21,7 +19,6 @@ from Idler import GreetingIdler,TrainIdler,GrayIdler,StringIdler,ClockIdler,Fort
 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="""
@@ -95,36 +92,6 @@ class VendConfigFile:
                except ConfigParser.Error, e:
                        raise SystemExit("Error reading config file "+config_file+": " + str(e))
 
-class DispenseDatabaseException(Exception): pass
-
-class DispenseDatabase:
-       def __init__(self, vending_machine, host, name, user, password):
-               self.vending_machine = vending_machine
-               self.db = pg.DB(dbname = name, host = host, user = user, passwd = password)
-               self.db.query('LISTEN vend_requests')
-
-       def process_requests(self):
-               logging.debug('database processing')
-               query = 'SELECT request_id, request_slot FROM vend_requests WHERE request_handled = false'
-               try:
-                       outstanding = self.db.query(query).getresult()
-               except (pg.error,), db_err:
-                       raise DispenseDatabaseException('Failed to query database: %s\n'%(db_err.strip()))
-               for (id, slot) in outstanding:
-                       (worked, code, string) = self.vending_machine.vend(slot)
-                       logging.debug (str((worked, code, string)))
-                       if worked:
-                               query = 'SELECT vend_success(%s)'%id
-                               self.db.query(query).getresult()
-                       else:
-                               query = 'SELECT vend_failed(%s)'%id
-                               self.db.query(query).getresult()
-
-       def handle_events(self):
-               notifier = self.db.getnotify()
-               while notifier is not None:
-                       self.process_requests()
-                       notify = self.db.getnotify()
 """
 This class manages the current state of the vending machine.
 """
@@ -164,38 +131,34 @@ class VendServer():
        _pin_pin = '----'
        _last_card_id = -1
 
+       dispense = None
+
        """
        Show information to the user as to what can be dispensed.
        """
        def scroll_options(self, 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()
-                       # this is fucking appalling
-                       balance = acct[acct.find("$")+1:acct.find("(")].strip()
+                       balance = self.dispense.getBalance()
+                       balance = balance[:-4] + '.' + balance[-4] + balance[-2:]   # Work around display bug
                        
                        msg = [(self.center('WELCOME'), False, TEXT_SPEED),
-                                  (self.center(username), False, TEXT_SPEED),
+                                  (self.center(self.dispense.getUsername()), False, TEXT_SPEED),
                                   (self.center(balance), False, TEXT_SPEED),]
                else:
                        msg = []
                choices = ' '*10+'CHOICES: '
 
                # Show what is in the coke machine
+               # Need to update this so it uses the abstracted system
                cokes = []
-               for i in range(0, 7):
-                       args = ('dispense', 'iteminfo', 'coke:%i' % i)
-                       info, unused = Popen(args, close_fds=True, stdout=PIPE).communicate()
-                       m = re.match("\s*[a-z]+:\d+\s+(\d+)\.(\d\d)\s+([^\n]+)", info)
-                       cents = int(m.group(1))*100 + int(m.group(2))
-                       cokes.append('%i %i %s' % (i, cents, m.group(3)));
+               for i in ['08', '18', '28', '38', '48', '58', '68']:
+                       cokes.append((i, self.dispense.getItemInfo(i)))
 
                for c in cokes:
-                       c = c.strip()
-                       (slot_num, price, slot_name) = c.split(' ', 2)
-                       if slot_name == 'dead': continue
-                       choices += '%s-(%sc)-%s8 '%(slot_name, price, slot_num)
+                       if c[1][0] == 'dead':
+                               continue
+                       choices += '%s-(%sc)-%s8 '%(c[1][0], c[1][1], c[0])
 
                # Show the final few options
                choices += '55-DOOR '
@@ -206,88 +169,6 @@ class VendServer():
                # Send it to the display
                mk.set_messages(msg)
 
-
-       """
-       Verify the users pin
-       """
-       def _check_pin(self, uid, pin):
-               print "_check_pin('",uid,"',---)"
-               if uid != self._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. Fixing.'%uid)
-                               os.chmod(pinfile, 0600)
-                       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
-                       self._pin_uid = uid
-                       self._pin_pin = pinstr
-                       self._pin_uname = info.pw_name
-               else:
-                       pinstr = self._pin_pin
-               if pin == int(pinstr):
-                       logging.info("Pin correct for %d",uid)
-               else:
-                       logging.info("Pin incorrect for %d",uid)
-               return pin == int(pinstr)
-
-       """
-       Check if the users account has been disabled
-       """
-       def acct_is_disabled(self, name=None):
-               if name == None:
-                       name = self._pin_uname
-               acct, unused = Popen(['dispense', 'acct', self._pin_uname], close_fds=True, stdout=PIPE).communicate()
-               # this is fucking appalling
-               flags = acct[acct.find("(")+1:acct.find(")")].strip()
-               if 'disabled' in flags:
-                       return True
-               if 'internal' in flags:
-                       return True
-               return False
-
-       """
-       Check that the user has a valid pin set
-       """
-       def has_good_pin(self, uid):
-               return self._check_pin(uid, None) != None
-
-       """
-       Verify the users pin.
-       """
-       def verify_user_pin(self, uid, pin, skip_pin_check=False):
-               if skip_pin_check or self._check_pin(uid, pin) == True:
-                       info = pwd.getpwuid(uid)
-                       if skip_pin_check:
-                               if self.acct_is_disabled(info.pw_name):
-                                       logging.info('refused mifare for disabled acct uid %d (%s)'%(uid,info.pw_name))
-                                       return '-disabled-'
-                               logging.info('accepted mifare for uid %d (%s)'%(uid,info.pw_name))
-                       else:
-                               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
-
        """
        In here just for fun.
        """
@@ -511,7 +392,11 @@ class VendServer():
                                        self.vstatus.last_timeout_refresh = None
                                else:
                                        # Price check mode.
-                                       self.price_check()
+                                       (name,price) = self.dispense.getItemInfo(self.vstatus.cur_selection)
+                                       dollarprice = "$%.2f" % ( price / 100.0 )
+                                       dollarprice = dollarprice[:-4] + '.' + dollarprice[-4] + dollarprice[-2:]   # Work around display bug
+                                       self.v.display( self.vstatus.cur_selection+' - %s'%dollarprice)
+
                                        self.vstatus.cur_selection = ''
                                        self.vstatus.time_to_autologout = None
                                        self.vstatus.last_timeout_refresh = None
@@ -576,23 +461,6 @@ class VendServer():
                                self.v.display('UNK ERROR')
                sleep(1)
 
-       """
-       Find the price of an item.
-       """
-       def price_check():
-               if self.vstatus.cur_selection[1] == '8':
-                       args = ('dispense', 'iteminfo', 'coke:' + self.vstatus.cur_selection[0])
-                       info, unused = Popen(args, close_fds=True, stdout=PIPE).communicate()
-                       dollarprice = re.match("\s*[a-z]+:\d+\s+(\d+\.\d\d)\s+([^\n]+)", info).group(1)
-               else:
-                       # first see if it's a named slot
-                       try:
-                               price, shortname, name = get_snack( self.vstatus.cur_selection )
-                       except:
-                               price, shortname, name = get_snack( '--' )
-                       dollarprice = "$%.2f" % ( price / 100.0 )
-               self.v.display(self.vstatus.cur_selection+' - %s'%dollarprice)
-
        """
        Triggered when the user presses a button while entering their pin.
        """
@@ -611,8 +479,8 @@ class VendServer():
                        self.vstatus.cur_pin += chr(key + ord('0'))
                        self.vstatus.mk.set_message('PIN: '+'X'*len(self.vstatus.cur_pin))
                        if len(self.vstatus.cur_pin) == PIN_LENGTH:
-                               self.vstatus.username = self.verify_user_pin(int(self.vstatus.cur_user), int(self.vstatus.cur_pin))
-                               if self.vstatus.username:
+                               self.dispense.authUserIdPin(self.vstatus.cur_user, self.vstatus.cur_pin)
+                               if self.dispense.getUsername():
                                        self.v.beep(0, False)
                                        self.vstatus.cur_selection = ''
                                        self.vstatus.change_state(STATE_GET_SELECTION)
@@ -702,18 +570,9 @@ class VendServer():
 
                                return
 
-                       if not self.has_good_pin(uid):
-                               logging.info('user '+self.vstatus.cur_user+' has a bad PIN')
-                               self.vstatus.mk.set_messages(
-                                       [(' '*10+'INVALID PIN SETUP'+' '*11, False, 3)])
-                               self.vstatus.cur_user = ''
-                               self.vstatus.cur_pin = ''
-                               
-                               self.reset_idler(3)
-
-                               return
-                       
-                       if self.acct_is_disabled():
+                       # TODO Fix this up do we can check before logging in
+                       """
+                       if self.dispense.isDisabled():
                                logging.info('user '+self.vstatus.cur_user+' is disabled')
                                self.vstatus.mk.set_messages(
                                        [(' '*11+'ACCOUNT DISABLED'+' '*11, False, 3)])
@@ -722,7 +581,7 @@ class VendServer():
                                
                                self.reset_idler(3)
                                return
-
+                       """
 
                        self.vstatus.cur_pin = ''
                        self.vstatus.mk.set_message('PIN: ')
@@ -921,47 +780,37 @@ class VendServer():
 
                self._last_card_id = card_id
                
-               try:
-                       self.vstatus.cur_user = get_uid(card_id)
-                       logging.info('Mapped card id to uid %s'%self.vstatus.cur_user)
-                       self.vstatus.username = get_uname(self.vstatus.cur_user)
-                       if self.acct_is_disabled(self.vstatus.username):
-                               self.vstatus.username = '-disabled-'
-               except ValueError:
-                       self.vstatus.username = None
-               if self.vstatus.username == '-disabled-':
+               self.dispense.authMifareCard(card_id)
+               logging.info('Mapped card id to uid %s'%self.dispense.getUsername())
+               if not self.dispense.isLoggedIn():
                        self.v.beep(40, False)
                        self.vstatus.mk.set_messages(
-                               [(self.center('ACCT DISABLED'), False, 1.0),
+                               [(self.center('BAD CARD'), False, 1.0),
                                 (self.center('SORRY'), False, 0.5)])
                        self.vstatus.cur_user = ''
                        self.vstatus.cur_pin = ''
-                       self.vstatus.username = None
+                       self._last_card_id = -1
                
                        self.reset_idler(2)
                        return
-               elif self.vstatus.username:
-                       self.v.beep(0, False)
-                       self.vstatus.cur_selection = ''
-                       self.vstatus.change_state(STATE_GET_SELECTION)
-                       self.scroll_options(self.vstatus.username, self.vstatus.mk, True)
-                       return
-               else:
+               elif self.dispense.isDisabled():
                        self.v.beep(40, False)
                        self.vstatus.mk.set_messages(
-                               [(self.center('BAD CARD'), False, 1.0),
+                               [(self.center('ACCT DISABLED'), False, 1.0),
                                 (self.center('SORRY'), False, 0.5)])
-                       self.vstatus.cur_user = ''
-                       self.vstatus.cur_pin = ''
-                       self._last_card_id = -1
-               
+                       self.dispense.logOut()
                        self.reset_idler(2)
                        return
+               else:
+                       self.vstatus.cur_selection = ''
+                       self.vstatus.change_state(STATE_GET_SELECTION)
+                       self.scroll_options(self.vstatus.username, self.vstatus.mk, True)
+                       return
 
        """
        Triggered when a user swipes their card and the machine is logged in.
        """
-       def handle_mifare_add_user_event(self, evnt, params):
+       def handle_mifare_add_user_event(self, event, params):
                card_id = params
 
                # Translate card_id into uid.
@@ -969,25 +818,15 @@ class VendServer():
                        return
 
                self._last_card_id = card_id
-               
-               try:
-                       if get_uid(card_id) != None:
-                               self.vstatus.mk.set_messages(
-                                       [(self.center('ALREADY'), False, 0.5),
-                                        (self.center('ENROLLED'), False, 0.5)])
-
-                               # scroll_options(vstatus.username, vstatus.mk)
-                               return
-               except ValueError:
-                       pass
 
-               logging.info('Enrolling card %s to uid %s (%s)'%(card_id, self.vstatus.cur_user, self.vstatus.username))
-               self.set_card_id(self.vstatus.cur_user, self.card_id)
-               self.vstatus.mk.set_messages(
-                       [(self.center('CARD'), False, 0.5),
-                        (self.center('ENROLLED'), False, 0.5)])
-
-               # scroll_options(vstatus.username, vstatus.mk)
+               if not self.dispense.addCard(card_id):
+                       self.vstatus.mk.set_messages(
+                               [(self.center('ALREADY'), False, 0.5),
+                                (self.center('ENROLLED'), False, 0.5)])
+               else:
+                       self.vstatus.mk.set_messages(
+                               [(self.center('CARD'), False, 0.5),
+                                (self.center('ENROLLED'), False, 0.5)])
 
        def return_to_idle(self, event, params):
                self.reset_idler()
@@ -1050,23 +889,16 @@ class VendServer():
 
        def run_forever(self, rfh, wfh, options, cf):
                self.v = VendingMachine(rfh, wfh, USE_MIFARE)
+               self.dispense = Dispense()
                self.vstatus = VendState(self.v)
                self.create_state_table()
 
                logging.debug('PING is ' + str(self.v.ping()))
 
-               if USE_DB: db = DispenseDatabase(v, cf.DBServer, cf.DBName, cf.DBUser, cf.DBPassword)
-
                self.setup_idlers()
                self.reset_idler()
 
                while True:
-                       if USE_DB:
-                               try:
-                                       db.handle_events()
-                               except DispenseDatabaseException, e:
-                                       logging.error('Database error: '+str(e))
-
                        timeout = self.time_to_next_update()
                        (event, params) = self.v.next_event(timeout)
                        self.run_handler(event, params)

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