From 444b524c3a26d8dd83fae2a074d3d13b10ba17f7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 5 Jan 2004 22:26:34 +0000 Subject: forgot some files preliminary client implementation git-svn-id: file:///home/lennart/svn/public/ivam2/trunk@15 dbf6933d-3bce-0310-9bcc-ed052ba35b35 --- client/ivam-autobox | 2 + client/ivam-createvb | 2 + client/ivam-echo | 3 + client/ivam-play | 3 + client/ivam-voicebox | 48 ++++++++ client/ivamApi.py | 44 +++++++ client/ivamCore.py | 13 ++ client/ivamDefs.py | 2 + client/ivamShbufConnector.py | 26 ++++ client/ivamUtil.py | 13 ++ client/ivamVoiceBox.py | 275 +++++++++++++++++++++++++++++++++++++++++++ src/dtmffifo.c | 82 +++++++++++++ src/dtmffifo.h | 13 ++ 13 files changed, 526 insertions(+) create mode 100755 client/ivam-autobox create mode 100755 client/ivam-createvb create mode 100755 client/ivam-echo create mode 100755 client/ivam-play create mode 100755 client/ivam-voicebox create mode 100644 client/ivamApi.py create mode 100644 client/ivamCore.py create mode 100644 client/ivamDefs.py create mode 100644 client/ivamShbufConnector.py create mode 100644 client/ivamUtil.py create mode 100644 client/ivamVoiceBox.py create mode 100644 src/dtmffifo.c create mode 100644 src/dtmffifo.h diff --git a/client/ivam-autobox b/client/ivam-autobox new file mode 100755 index 0000000..8d5063a --- /dev/null +++ b/client/ivam-autobox @@ -0,0 +1,2 @@ +#!/usr/bin/python + diff --git a/client/ivam-createvb b/client/ivam-createvb new file mode 100755 index 0000000..8d5063a --- /dev/null +++ b/client/ivam-createvb @@ -0,0 +1,2 @@ +#!/usr/bin/python + diff --git a/client/ivam-echo b/client/ivam-echo new file mode 100755 index 0000000..b0f3e40 --- /dev/null +++ b/client/ivam-echo @@ -0,0 +1,3 @@ +#!/bin/sh + +exec cat diff --git a/client/ivam-play b/client/ivam-play new file mode 100755 index 0000000..043ffbe --- /dev/null +++ b/client/ivam-play @@ -0,0 +1,3 @@ +#!/bin/sh + +exec zcat -f "$1" diff --git a/client/ivam-voicebox b/client/ivam-voicebox new file mode 100755 index 0000000..fc7b45a --- /dev/null +++ b/client/ivam-voicebox @@ -0,0 +1,48 @@ +#!/usr/bin/python + +import sys, os + +from ivamCore import log +import ivamCore, ivamVoiceBox + +def usage(self): + log("%s [--record-time=SECS] [--pin=PIN] [--pin-file=PINFILE] [--debug]", sys.argv[0]) + +def parseArgs(vb, argv): + + try: + opts, args = getopt.getopt(argv[1:], "ht:p:P:de:", ["help", "record-time=", "pin=", "pin-file=", "debug", "message"]) + except getopt.GetoptError: + usage() + sys.exit(1) + + vb.setDirectory(args[0]) + + for o, a in opts: + if o in ("-d", "--debug"): + DEBUG = True + + elif o in ("-h", "--help"): + usage() + sys.exit() + + elif o in ("-t", "--record-time"): + vb.recordTime = int(a) + + elif o in ("-p", "--pin"): + vb.setPin(a) + + elif o in ("-P", "--pin-file"): + vb.setPin(getContents(a)) + + elif o in ("-e", "--message"): + vb.messageProgram = a + +def main(): + vb = ivamVoiceBox.VoiceBox() + parseArgs(vb, sys.argv) + ivamCore.newConnector(vb).run() + sys.exit() + +if __name__ == "__main__": + main() diff --git a/client/ivamApi.py b/client/ivamApi.py new file mode 100644 index 0000000..3cf5651 --- /dev/null +++ b/client/ivamApi.py @@ -0,0 +1,44 @@ + +class Processor: + + def onConnect(self, c): + pass + + def onHangup(self, c): + pass + + def onDtmfEvent(self, c, event): + pass + + def onClipFinish(self, c, fname): + pass + + def onTimeout(self, c): + pass + + def onRecordFinish(self, c, fname): + pass + +class Connector: + + def run(self): + pass + + def playClip(self, fname): + pass + + def stopPlayback(self): + pass + + def softStopPlayback(self): + pass + + def recordClip(self, fname, gzip = 0): + pass + + def stopRecording(self): + pass + + def timeout(self, t): + pass + diff --git a/client/ivamCore.py b/client/ivamCore.py new file mode 100644 index 0000000..08d8771 --- /dev/null +++ b/client/ivamCore.py @@ -0,0 +1,13 @@ + +import os, ivamShbufConnector, ivamPipeConnector; + +def newConnector(c): + + if getenv("SHBUF") is None: + return ivamPipeConnector(c) + else: + return ivamShbufConnector(c) + +def log(s): + sys.stderr.write("%s\n" % s) + diff --git a/client/ivamDefs.py b/client/ivamDefs.py new file mode 100644 index 0000000..45c5087 --- /dev/null +++ b/client/ivamDefs.py @@ -0,0 +1,2 @@ + +spoolDirectory = "/var/spool/ivam" diff --git a/client/ivamShbufConnector.py b/client/ivamShbufConnector.py new file mode 100644 index 0000000..feef521 --- /dev/null +++ b/client/ivamShbufConnector.py @@ -0,0 +1,26 @@ + +from ivamApi import ivamConnector + +class ivamShbufConnector(ivamAnsweringMachine): + + def run(self): + pass + + def playClip(self): + pass + + def stopPlayback(self): + pass + + def softStopPlayback(self): + pass + + def recordClip(self): + pass + + def stopRecording(self): + pass + + def timeout(self): + pass + diff --git a/client/ivamUtil.py b/client/ivamUtil.py new file mode 100644 index 0000000..17e9801 --- /dev/null +++ b/client/ivamUtil.py @@ -0,0 +1,13 @@ + +def getContents(fn): + f = file(a, "r") + r = f.readline().strip(" \t\r\n")) + f.close() + return r + + +def setContents(fn, s): + f = file(a, "w") + f.write("%s\n" % s) + f.close() + diff --git a/client/ivamVoiceBox.py b/client/ivamVoiceBox.py new file mode 100644 index 0000000..3cafab1 --- /dev/null +++ b/client/ivamVoiceBox.py @@ -0,0 +1,275 @@ + +import getopt, sys +import ivamApi, ivamCore, ivamDefs + +from ivamCore import log +from ivamUtil import getContents, setContents + +DEBUG = False + +class VoiceBox(ivamApi.Processor): + + # Constants + STATE_INVALID, STATE_WELCOME, STATE_WELCOME_BEEP, STATE_RECORD, STATE_PRE_FINISH, STATE_FINISH, STATE_AUTH, STATE_MESSAGE, STATE_AUTH_OK, STATE_NO_MORE_MESSAGES, STATE_MESSAGE_BEEP, STATE_REMOVED, STATE_EMPTY = range(13) + AUTH_TIMEOUT = 20 + + # State + currentState = STATE_INVALID + + # Use GZIP compression? + useGzip = 1 + + def onConnect(self, c, callerNumber, calleeNumber): + + if DEBUG: + log("onConnect()") + + self.callerNumber = callerNumber + self.calleeNumber = calleeNumber + + self.currentState = self.STATE_WELCOME + c.playClip("welcome") + + def loginComplete(self): + self.messages = self.getMessageNames() + self.currentMessage = 0 + + if len(self.messages) == 0: + self.currentState = self.STATE_EMPTY + c.playClip("empty") + else: + self.currentState = self.STATE_MESSAGE_BEEP + c.playClip("beep") + + def onClipFinish(self, c, fname): + + if DEBUG: + log("onClipFinish('%s')" % fname) + + if self.currentState == self.STATE_WELCOME: + self.currentState = self.STATE_WELCOME_BEEP; + c.playClip("beep") + + elif self.currentState == self.STATE_WELCOME_BEEP: + self.currentState = self.STATE_RECORD + c.recordClip(self.nextMessageName(), self.useGzip) + c.setTimeout(self.recordTime) + + elif self.currentState == self.STATE_PRE_FINISH: + self.currentState = self.STATE_FINISH + c.hangup() + + elif self.currentState == self.STATE_AUTH: + + # Silence ... + pass + + elif self.currentState == self.STATE_AUTH_OK: + + self.loginComplete() + + elif self.currentState == self.STATE_MESSAGE_BEEP: + + while True: + + if self.currentMessage >= len(self.messages): + self.currentMessage = self.STATE_NO_MORE_MESSAGES + c.playClip("nomoremessages") + else: + self.currentState = self.STATE_MESSAGE_BEEP + + try: + c.playClip(self.messages[self.currentMessage]) + break + + except IOError, e: + del(self.messages[self.currentMessage]) + + if len(self.messages) == 0: + self.currentState = self.STATE_EMPTY + c.playClip("empty") + + continue + + elif self.currentState == self.STATE_MESSAGE: + + self.currentMessage +=1 + self.currentState = self.STATE_MESSAGE_BEEP + c.playClip("beep") + + elif self.currentState == self.STATE_REMOVE: + + if len(self.messages) == 0: + self.currentState = self.STATE_EMPTY + c.playClip("empty") + else: + self.currentState = self.STATE_MESSAGE_BEEP + c.playClip("beep") + + elif self.currentState == self.STATE_NO_MORE_MESSAGES: + + # Silence ... + pass + + elif self.currentState == self.STATE_EMPTY: + self.currentState = self.STATE_FINISH + c.hangup() + + def onDtmfEvent(self, c, event): + + if DEBUG: + ivamCore.log("onDtmfEvent(%c)" % event) + + if ((self.currentState == self.STATE_WELCOME) or (self.currentState == self.STATE_WELCOME_BEEP)) and (event == '0'): + + if self.pin == "": + self.loginComplete() + + elif self.pin == "-": + self.currentState = self.STATE_FINISH + c.hangup() + + else: + self.currentState = self.STATE_AUTH + self.inputPin = "" + c.stopPlayback() + c.playClip("auth") + c.setTimeout(AUTH_TIMEOUT) + + elif self.currentState == self.STATE_AUTH: + c.stopPlayback() + self.inputPin += event + + if len(self.inputPin) >= len(self.pin): + c.setTimeout(0) + + if self.inputPin == self.pin: + self.currentState = self.STATE_AUTH_OK + c.playClip("authok") + else: + self.currentState = self.STATE_FINISH + c.hangup() + + elif self.currentState in [self.STATE_MESSAGE, self.STATE_MESSAGE_BEEP, self.STATE_NO_MORE_MESSAGES] : + + changed = 0 + + if event == '0': + + if self.currentMessage >= len(self.messages): + self.currentMessage = len(self.messages)-1 + + os.remove("%s.ulaw" % self.messages[self.currentMessage]) + del(self.messages[self.currentMessage]) + + self.currentState = self.STATE_REMOVED + c.playClip("removed") + + else: + if event in ['4', '2']: + self.currentMessage -= 1 + changed = 1 + + if event in ['6', '8']: + self.currentMessage += 1 + changed = 1 + + if event == '5': + changed = 1 + + if event == '1': + self.currentMessage = 0 + changed = 1 + + if event == '7': + self.currentMessage = len(self.messages)-1 + changed = 1 + + if self.currentMessage < 0: + self.currentMessage = 0 + + if self.currentMessage >= len(self.messages): + self.currentMessage = len(self.messages)-1 + + if changed: + self.currentState = self.STATE_MESSAGE_BEEP + c.playClip("beep") + + else: + c.stopPlayback() + + def onTimeout(self, c): + + if DEBUG: + ivamCore.log("onTimeout()") + + if self.currentState == self.STATE_RECORD: + self.currentState = self.STATE_PRE_FINISH + c.stopRecording() + c.playClip("beep") + + elif self.currentState in [self.STATE_AUTH, self.STATE_PIN2, self.STATE_PIN3, self.STATE_PIN4]: + self.currentState = self.STATE_FINISH + c.hangup() + + def onRecordFinish(self, c, fname): + + if DEBUG: + ivamCore.log("onRecordFinish(%s)", fname) + + log("Starting new message notification program.") + r = os.spawnvp(os.P_WAIT, self.messageProgram, [self.messageProgram, "%s.ulaw" % fname]) + log("Program finished (return value is %i)." % r) + + def getMessageNames(self): + + f = filter(lambda e: e.startswith("message-"), os.listdir(self.directory)) + f.sort() + f.reverse() + return f + + def nextMessageName(self): + + fn = "%s/message-%010i-%s-%s.ulaw" % (self.directory, time.time(), self.callerNumber, self.callerNumber) + + if self.useGzip: + return fn+".gz" + + return fn + + def setPin(self, pin): + + if re.match('^([0-9#*]*|-)$', pin).end() is None: + log("Invalid PIN. PIN has to consist of 0-9#*. Use '-' for always denying access.") + raise Exception, "Invalid PIN" + + self.pin = pin + + def setDirectory(self, path): + + self.directory = path + + try: + self.setPin(getContents("%s/PIN" % path)) + except Exception: + self.pin = "-" + + try: + self.recordTime = int(getContents("%s/RECORD_TIME" % path))) + except Exception: + self.recordTime = 60 + + messageProgram = "%s/NEWMESSAGE" % path + +def setupVoiceBox(dname, pin, recordTime): + if dname.find("/") == -1: + dname = "%s/%s" % (ivamDefs.spoolDirectory, dname) + + os.mkdir(dname, 0770) + os.mkdir("%s/messages" % dname, 0770) + + setContents("%s/PIN" % dname, pin) + setContents("%s/RECORD_TIME" % dname, `recordTime`) + + os.symlink + diff --git a/src/dtmffifo.c b/src/dtmffifo.c new file mode 100644 index 0000000..54a1f62 --- /dev/null +++ b/src/dtmffifo.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dtmffifo.h" + +struct dtmf_fifo* dtmf_fifo_new(void) { + struct dtmf_fifo *d = NULL; + char p[PATH_MAX]; + + d = malloc(sizeof(struct dtmf_fifo)); + assert(d); + memset(d, 0, sizeof(struct dtmf_fifo)); + d->fd = -1; + + d->dir = strdup("/tmp/ivamd.XXXXXX"); + assert(d->dir); + + if (!mkdtemp(d->dir)) { + daemon_log(LOG_ERR, "Failed to create temporary directory '%s': %s", d->dir, strerror(errno)); + goto fail; + } + + snprintf(p, sizeof(p), "%s/%s", d->dir, "dtmf"); + d->fname = strdup(p); + assert(d->fname); + + if (mkfifo(d->fname, 0700) != 0) { + daemon_log(LOG_ERR, "Failed to create FIFO '%s': %s", d->fname, strerror(errno)); + goto fail; + } + + if ((d->fd = open(d->fname, O_RDWR|O_NDELAY)) < 0) { + daemon_log(LOG_ERR, "Failed to open FIFO '%s': %s", d->fname, strerror(errno)); + goto fail; + } + + daemon_log(LOG_INFO, "Sucessfully opened DTMF FIFO '%s'.", d->fname); + + return d; + +fail: + if (d) + dtmf_fifo_free(d); + + return NULL; +} + +void dtmf_fifo_free(struct dtmf_fifo *d) { + assert(d); + + if (d->fd >= 0) + close(d->fd); + + if (d->fname) { + unlink(d->fname); + free(d->fname); + } + + if (d->dir) { + rmdir(d->dir); + free(d->dir); + } + + free(d); +} + +void dtmf_fifo_pass(struct dtmf_fifo *d, char c) { + assert(d && d->fd >= 0); + + daemon_log(LOG_INFO, "Recieved DTMF character '%c'", c); + + if (write(d->fd, &c, 1) != 1) + daemon_log(LOG_ERR, "Failed to write to DTMF FIFO: %s", strerror(errno)); +} diff --git a/src/dtmffifo.h b/src/dtmffifo.h new file mode 100644 index 0000000..39e39c9 --- /dev/null +++ b/src/dtmffifo.h @@ -0,0 +1,13 @@ +#ifndef foodtmffifohfoo +#define foodtmffifohfoo + +struct dtmf_fifo { + int fd; + char *dir, *fname; +}; + +struct dtmf_fifo* dtmf_fifo_new(void); +void dtmf_fifo_free(struct dtmf_fifo *d); +void dtmf_fifo_pass(struct dtmf_fifo *d, char c); + +#endif -- cgit