372 lines
14 KiB
Plaintext
372 lines
14 KiB
Plaintext
# cs_helpers.py - some helper functions for CapiSuite scripts
|
|
# -----------------------------------------------------------
|
|
# copyright : (C) 2002 by Gernot Hillier
|
|
# email : gernot@hillier.de
|
|
# version : $Revision: 1.6 $
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
|
|
# the name of the config file read by the scripts; see there for options and
|
|
# descriptions
|
|
configfile_fax="@pkgsysconfdir@/fax.conf"
|
|
configfile_voice="@pkgsysconfdir@/answering_machine.conf"
|
|
|
|
# @brief read configuration file and return a ConfigParser object
|
|
#
|
|
# The configfile is read from the path given above and the surrounding
|
|
# quotation marks from the values are removed
|
|
#
|
|
# @return the constructed config file object
|
|
def readConfig(file=""):
|
|
import ConfigParser
|
|
config=ConfigParser.ConfigParser()
|
|
if (file==""):
|
|
config.readfp(open(configfile_fax))
|
|
config.readfp(open(configfile_voice))
|
|
else:
|
|
config.readfp(open(file))
|
|
for s in config.sections():
|
|
for o in config.options(s):
|
|
value=config.get(s,o)
|
|
if (len(value)>1 and value[0]=='"'):
|
|
config.set(s,o,value[1:-1])
|
|
if (not config.has_section('GLOBAL')):
|
|
raise IOError("invalid configuration file - section GLOBAL is missing")
|
|
return config
|
|
|
|
# @brief get an option from the user or global section
|
|
#
|
|
# The option is searched in the users section and if not found
|
|
# in the global section.
|
|
#
|
|
# @param config the ConfigParser object containing the values
|
|
# @param user user section to use, if empty only global section is read
|
|
# @param option the name of the option to search for
|
|
#
|
|
# @return the value for this option or None if it's not found
|
|
def getOption(config,user,option,default=None):
|
|
if config.has_option(user,option):
|
|
return config.get(user,option)
|
|
elif config.has_option('GLOBAL',option):
|
|
return config.get('GLOBAL',option)
|
|
else:
|
|
return default
|
|
|
|
# @brief Search for an audio file first in user_dir, than in audio_dir
|
|
#
|
|
# @param config the ConfigParser object containing the configuration
|
|
# @param user the name of the user
|
|
# @param filename the filename of the wave file
|
|
#
|
|
# @return the found file with full path
|
|
def getAudio(config,user,filename):
|
|
import os,capisuite
|
|
systemdir=getOption(config,"","audio_dir")
|
|
if (systemdir==None):
|
|
raise IOError("option audio_dir not found.")
|
|
userdir=getOption(config,"","voice_user_dir")
|
|
if (userdir==None):
|
|
raise IOError("option voice_user_dir not found.")
|
|
userdir=os.path.join(userdir,user)+"/"
|
|
if (int(getOption(config,"","user_audio_files","0")) and os.access(userdir+filename,os.R_OK)):
|
|
return userdir+filename
|
|
else:
|
|
return systemdir+filename
|
|
|
|
# @brief thread-safe creation of a unique filename in a directory
|
|
#
|
|
# This function reads the nextnumber from then "nextnr"-file in the given
|
|
# directory and updates it. It holds the next free file number.
|
|
#
|
|
# If nextnr doesn't exist, it's created.
|
|
#
|
|
# The filenames created will have the format
|
|
#
|
|
# basename-number.suffix
|
|
#
|
|
# @param directory name of the directory to work in
|
|
# @param basename the basename of the filename
|
|
# @param suffix the suffix of the filename (without ".")
|
|
#
|
|
# @return new file name
|
|
def uniqueName(directory,basename,suffix):
|
|
import fcntl,os,re
|
|
# acquire lock
|
|
lockfile=open(directory+"cs_lock","w")
|
|
fcntl.lockf(lockfile,fcntl.LOCK_EX)
|
|
|
|
try:
|
|
countfile=open(directory+basename+"-nextnr","r")
|
|
nextnr=int(countfile.readline())
|
|
countfile.close()
|
|
except IOError:
|
|
# search for next free sequence number
|
|
files=os.listdir(directory)
|
|
files=filter (lambda s: re.match(re.escape(basename)+"-.*\."+re.escape(suffix),s),files)
|
|
if (len(files)):
|
|
files=map(lambda s: int(s[len(basename)+1:-len(suffix)-1]),files)
|
|
nextnr=max(files)+1 # take nr of last file and increase it by one
|
|
else:
|
|
nextnr=0
|
|
files.sort()
|
|
|
|
newname=directory+basename+"-"+str(nextnr)+"."+suffix
|
|
|
|
countfile=open(directory+basename+"-nextnr","w")
|
|
countfile.write(str(nextnr+1)+'\n')
|
|
countfile.close()
|
|
|
|
# unlock
|
|
fcntl.lockf(lockfile,fcntl.LOCK_UN)
|
|
lockfile.close()
|
|
os.unlink(directory+"cs_lock")
|
|
return newname
|
|
|
|
# @brief send email with text and attachment of type sff or la converted to pdf/wav
|
|
#
|
|
# This function creates a multipart MIME-message containing a text/plain
|
|
# part with a string and one attachment of type application/pdf or audio/wav.
|
|
#
|
|
# The given attachment is automatically converted from Structured Fax File
|
|
# (.sff) or inversed A-Law (.la) to the well known PDF or WAV format.
|
|
#
|
|
# @param mail_from the From: address for the mail
|
|
# @param mail_to the To: address for the mail
|
|
# @param mail_subject the subject of the mail
|
|
# @param mail_type containing either "sff" or "la"
|
|
# @param text a string containing the text of the first part of the mail
|
|
# @param attachment name of the file to send as attachment
|
|
def sendMIMEMail(mail_from,mail_to,mail_subject,mail_type,text,attachment):
|
|
import email.MIMEBase,email.MIMEText,email.MIMEAudio,email.Encoders,os,sys,popen2,capisuite
|
|
msg = email.MIMEBase.MIMEBase("multipart","mixed")
|
|
msg['Subject']=mail_subject
|
|
msg['From']=mail_from
|
|
msg['To']=mail_to
|
|
|
|
msg.preamble = 'This is a Multipart-MIME-message. Please use a capable mailer.\n'
|
|
msg.epilogue = '' # To guarantee the message ends with a newline
|
|
|
|
basename=attachment[:attachment.rindex('.')+1]
|
|
try:
|
|
if (mail_type=="sff"):
|
|
# sff -> tif
|
|
ret=os.spawnlp(os.P_WAIT,"sfftobmp","sfftobmp","-tif",attachment,basename+"tif")
|
|
if (ret or not os.access(basename+"tif",os.F_OK)):
|
|
raise "conv-error","Can't convert sff to tif. sfftobmp not installed?"
|
|
# tif -> ps -> pdf
|
|
tiff2ps=popen2.Popen3("tiff2ps -a "+basename+"tif")
|
|
if (tiff2ps.poll()!=-1):
|
|
raise "conv-error","Error while calling tiff2ps. Not installed?"
|
|
tiff2ps.tochild.close() # we don't need the input pipe
|
|
ps2pdf=popen2.Popen3("ps2pdf - -")
|
|
if (ps2pdf.poll()!=-1):
|
|
raise "conv-error","Error while calling ps2pdf. Not installed?\n"
|
|
ps2pdf.tochild.write(tiff2ps.fromchild.read())
|
|
tiff2ps.fromchild.close()
|
|
ret=tiff2ps.wait()
|
|
if (ret!=0):
|
|
raise "conv-error","Error "+str(ret)+" occured during tiff2ps"
|
|
os.unlink(basename+"tif")
|
|
ps2pdf.tochild.close() # send EOF, so that it starts to convert
|
|
# create attachment with pdf stream
|
|
filepart = email.MIMEBase.MIMEBase("application","pdf",name=os.path.basename(basename)+"pdf")
|
|
filepart.add_header('Content-Disposition','attachment',filename=os.path.basename(basename)+"pdf")
|
|
filepart.add_payload(ps2pdf.fromchild.read())
|
|
ps2pdf.fromchild.close()
|
|
ret=ps2pdf.wait()
|
|
if (ret!=0):
|
|
raise "conv-error","Error "+str(ret)+" occured during ps2pdf"
|
|
email.Encoders.encode_base64(filepart)
|
|
elif (mail_type=="la"):
|
|
# la -> wav
|
|
# don't use stdout as sox needs a file to be able to seek in it otherwise the header will be incomplete
|
|
ret = os.spawnlp(os.P_WAIT,"sox","sox",attachment,basename+"wav")
|
|
if (ret or not os.access(basename+"wav",os.R_OK)):
|
|
raise "conv-error","Error while calling sox. Not installed?"
|
|
filepart = email.MIMEAudio.MIMEAudio(open(basename+"wav").read(),"x-wav",email.Encoders.encode_base64,name=os.path.basename(basename)+"wav")
|
|
filepart.add_header('Content-Disposition','attachment',filename=os.path.basename(basename)+"wav")
|
|
os.unlink(basename+"wav")
|
|
textpart = email.MIMEText.MIMEText(text)
|
|
msg.attach(textpart)
|
|
msg.attach(filepart)
|
|
except "conv-error",errormessage:
|
|
text+="\n\nERROR occured while converting file: "+errormessage+"\nPlease talk to your friendly administrator.\n"
|
|
textpart = email.MIMEText.MIMEText(text)
|
|
msg.attach(textpart)
|
|
|
|
sendmail = popen2.Popen3("sendmail -t -f "+mail_from)
|
|
if (sendmail.poll()!=-1):
|
|
capisuite.error("Error while calling sendmail. Not installed?\n")
|
|
return
|
|
sendmail.tochild.write(msg.as_string())
|
|
sendmail.tochild.close()
|
|
sendmail.fromchild.close()
|
|
ret=sendmail.wait()
|
|
if (ret!=0):
|
|
capisuite.error("Error while calling sendmail, return code="+str(ret))
|
|
else:
|
|
capisuite.log("sendmail finished successful",3)
|
|
|
|
# @brief send a simple text email
|
|
#
|
|
# This function creates a simple mail
|
|
#
|
|
# @param mail_from the From: address for the mail
|
|
# @param mail_to the To: address for the mail
|
|
# @param mail_subject the subject of the mail
|
|
# @param text a string containing the text of the first part of the mail
|
|
def sendSimpleMail(mail_from,mail_to,mail_subject,text):
|
|
import email.Encoders, email.MIMEText, popen2, sys,capisuite
|
|
# Create a text/plain message, using Quoted-Printable encoding for non-ASCII
|
|
# characters.
|
|
msg = email.MIMEText.MIMEText(text, _encoder=email.Encoders.encode_quopri)
|
|
|
|
msg['Subject'] = mail_subject
|
|
msg['From'] = mail_from
|
|
msg['To'] = mail_to
|
|
|
|
sendmail = popen2.Popen3("sendmail -t -f "+mail_from)
|
|
if (sendmail.poll()!=-1):
|
|
capisuite.error("Error while calling sendmail. Not installed?\n")
|
|
return
|
|
sendmail.tochild.write(msg.as_string())
|
|
sendmail.tochild.close()
|
|
sendmail.fromchild.close()
|
|
ret=sendmail.wait()
|
|
if (ret!=0):
|
|
capisuite.error("Error while calling sendmail, return code="+str(ret))
|
|
else:
|
|
capisuite.log("sendmail finished successful",3)
|
|
|
|
|
|
# @brief write description file for received fax or voice
|
|
#
|
|
# This function writes an INI-style description file for the given data file
|
|
# which can later on be read by a ConfigParser instance. The data file name
|
|
# is used, the extension stripped and replaced by .txt
|
|
#
|
|
# @param filename the data filename (with extension!)
|
|
# @param content the content as string
|
|
def writeDescription(filename,content):
|
|
descr=open(filename[:filename.rindex('.')+1]+"txt","w")
|
|
descr.write("# Description file for "+filename+"\n")
|
|
descr.write("# This if for internal use of CapiSuite.\n")
|
|
descr.write("# Only change if you know what you do!!\n")
|
|
descr.write("[GLOBAL]\n")
|
|
descr.write("filename=\""+filename+"\"\n")
|
|
descr.write(content)
|
|
descr.close()
|
|
|
|
# @brief say a german number
|
|
#
|
|
# All numbers from 0 to 99 are said correctly, while all larger ones are
|
|
# split into numbers and only the numbers are said one after another.
|
|
# An input of "-" produces the word "unbekannt" (unknown)
|
|
#
|
|
# @param call reference to the call
|
|
# @param number the number to say
|
|
# @param curr_user the current user named
|
|
# @param config the ConfigParser instance holding the configuration info
|
|
def sayNumber(call,number,curr_user,config):
|
|
import capisuite
|
|
if (number=="-" or number=="??"): # "??" is needed for backward compatibility to versions <= 0.4.1a
|
|
capisuite.audio_send(call,getAudio(config,curr_user,"unbekannt.la"),1)
|
|
elif (len(number)==2 and number[0]!="0"):
|
|
if (number[0]=="1"):
|
|
if (number[1]=="0"):
|
|
capisuite.audio_send(call,getAudio(config,curr_user,"10.la"),1)
|
|
elif (number[1]=="1"):
|
|
capisuite.audio_send(call,getAudio(config,curr_user,"11.la"),1)
|
|
elif (number[1]=="2"):
|
|
capisuite.audio_send(call,getAudio(config,curr_user,"12.la"),1)
|
|
elif (number[1]=="3"):
|
|
capisuite.audio_send(call,getAudio(config,curr_user,"13.la"),1)
|
|
elif (number[1]=="4"):
|
|
capisuite.audio_send(call,getAudio(config,curr_user,"14.la"),1)
|
|
elif (number[1]=="5"):
|
|
capisuite.audio_send(call,getAudio(config,curr_user,"15.la"),1)
|
|
elif (number[1]=="6"):
|
|
capisuite.audio_send(call,getAudio(config,curr_user,"16.la"),1)
|
|
elif (number[1]=="7"):
|
|
capisuite.audio_send(call,getAudio(config,curr_user,"17.la"),1)
|
|
elif (number[1]=="8"):
|
|
capisuite.audio_send(call,getAudio(config,curr_user,"18.la"),1)
|
|
elif (number[1]=="9"):
|
|
capisuite.audio_send(call,getAudio(config,curr_user,"19.la"),1)
|
|
else:
|
|
if (number[1]=="0"):
|
|
capisuite.audio_send(call,getAudio(config,curr_user,number+".la"),1)
|
|
elif (number[1]=="1"):
|
|
capisuite.audio_send(call,getAudio(config,curr_user,"ein.la"),1)
|
|
capisuite.audio_send(call,getAudio(config,curr_user,"und.la"),1)
|
|
capisuite.audio_send(call,getAudio(config,curr_user,number[0]+"0.la"),1)
|
|
else:
|
|
capisuite.audio_send(call,getAudio(config,curr_user,number[1]+".la"),1)
|
|
capisuite.audio_send(call,getAudio(config,curr_user,"und.la"),1)
|
|
capisuite.audio_send(call,getAudio(config,curr_user,number[0]+"0.la"),1)
|
|
else:
|
|
for i in number:
|
|
capisuite.audio_send(call,getAudio(config,curr_user,i+".la"),1)
|
|
|
|
# $Log: cs_helpers.pyin,v $
|
|
# Revision 1.6 2003/04/10 21:29:51 gernot
|
|
# - support empty destination number for incoming calls correctly (austrian
|
|
# telecom does this (sic))
|
|
# - core now returns "-" instead of "??" for "no number available" (much nicer
|
|
# in my eyes)
|
|
# - new wave file used in remote inquiry for "unknown number"
|
|
#
|
|
# Revision 1.5 2003/04/10 20:54:44 gernot
|
|
# - allow multiple mail addresses to be set as fax_email or voice_email
|
|
#
|
|
# Revision 1.4 2003/04/08 07:59:56 gernot
|
|
# - replace some wrong space indentations by tabs...
|
|
#
|
|
# Revision 1.3 2003/04/07 15:58:37 gernot
|
|
# - attachments to sent e-mails now get a valid filename
|
|
#
|
|
# Revision 1.2 2003/03/20 09:12:42 gernot
|
|
# - error checking for reading of configuration improved, many options got
|
|
# optional, others produce senseful error messages now if not found,
|
|
# fixes bug# 531, thx to Dieter Pelzel for reporting
|
|
#
|
|
# Revision 1.1.1.1 2003/02/19 08:19:54 gernot
|
|
# initial checkin of 0.4
|
|
#
|
|
# Revision 1.8 2003/02/10 14:03:34 ghillie
|
|
# - cosmetical fixes in sendMIMEMail
|
|
# - added wait() calls to popen objects, otherwise processes will hang
|
|
# after CapiSuite has run them (i.e. sendmail stays as Zombie)
|
|
#
|
|
# Revision 1.7 2003/02/03 14:47:49 ghillie
|
|
# - sayNumber now works correctly for all numbers between 0 and 99
|
|
# (in german). Added the necessary voice files and improved "1"-"9"
|
|
#
|
|
# Revision 1.6 2003/01/27 21:55:10 ghillie
|
|
# - getOption returns now None if option isn't found at all (no exception)
|
|
# - removed capisuite.log from uniqueName() (not possible in capisuitefax!)
|
|
# - added some missing "import capisuite" statements
|
|
#
|
|
# Revision 1.5 2003/01/27 19:24:29 ghillie
|
|
# - updated to use new configuration files for fax & answering machine
|
|
#
|
|
# Revision 1.4 2003/01/19 12:02:40 ghillie
|
|
# - use capisuite log functions instead of stdout/stderr
|
|
#
|
|
# Revision 1.3 2003/01/17 15:08:17 ghillie
|
|
# - typos as usual...
|
|
# - added sendSimpleMail for normal text messages
|
|
#
|
|
# Revision 1.2 2003/01/15 15:52:49 ghillie
|
|
# - readConfig now takes filename as parameter
|
|
# - uniqueName: countfile now has basename as prefix, fixed small bug
|
|
# in countfile creation
|
|
# - sendMail: added .la->.wav convertion, error messages now included
|
|
# in messages to user
|
|
# - writeDescription: [data] renamed to [global]
|
|
# - sayNumber: small fixes
|
|
#
|