capisuite/scripts/cs_helpers.pyin

430 lines
17 KiB
Plaintext

# cs_helpers.py - some helper functions for CapiSuite scripts
# -----------------------------------------------------------
# copyright : (C) 2002 by Gernot Hillier
# email : gernot@hillier.de
# version : $Revision: 1.15 $
#
# 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 config file, section GLOBAL missing")
return config
# @brief escape a filename to include it savely in a shell command
#
# The filename is enclosed in single quotation marks and quotation
# marks therein are quoted
#
# @return the escaped filename
def escape(filename):
return "'"+filename.replace("'","'\\''")+"'"
# @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,encodings.ascii,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"): # normal fax file
# 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","Error while converting sff to tif. File damaged or sfftobmp not installed?"
# tif -> ps -> pdf
# the first pipe must be handled by the shell so that the output of
# of ps2pdf can be read immediately. Handling this shell in Python
# leads to an overflow of the ps2pdf output pipe...
command="tiff2ps -a "+escape(basename+"tif")+" | ps2pdf - -"
tiff2pdf=popen2.Popen3(command)
if (tiff2pdf.poll()!=-1):
raise "conv-error","Error while calling tiff2ps or ps2pdf. Not installed?"
tiff2pdf.tochild.close() # we don't need the input pipe
# 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.set_payload(tiff2pdf.fromchild.read())
tiff2pdf.fromchild.close()
ret=tiff2pdf.wait()
if (ret!=0):
raise "conv-error","Error "+str(ret)+" occured during tiff2ps or ps2pdf"
os.unlink(basename+"tif")
email.Encoders.encode_base64(filepart)
elif (mail_type=="cff"): # color fax file
# cff -> ps
ret=os.spawnlp(os.P_WAIT,"jpeg2ps","jpeg2ps","-m",attachment,"-o",basename+"ps")
if (ret or not os.access(basename+"ps",os.F_OK)):
raise "conv-error","Can't convert cff to ps. File damaged or jpeg2ps not installed?"
# tif -> ps -> pdf
# the first pipe must be handled by the shell so that the output of
# of ps2pdf can be read immediately. Handling this shell in Python
# leads to an overflow of the ps2pdf output pipe...
command="ps2pdf "+escape(basename+"ps")+" -"
ps2pdf=popen2.Popen3(command)
if (ps2pdf.poll()!=-1):
raise "conv-error","Error while calling ps2pdf. Not installed?"
ps2pdf.tochild.close() # we don't need the input pipe
# 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.set_payload(ps2pdf.fromchild.read())
ps2pdf.fromchild.close()
ret=ps2pdf.wait()
if (ret!=0):
raise "conv-error","Error "+str(ret)+" occured during ps2pdf"
os.unlink(basename+"ps")
email.Encoders.encode_base64(filepart)
elif (mail_type=="la"): # voice file
# 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. File damaged or 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 "+escape(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,encodings.ascii,popen2,sys,capisuite
# Create a text/plain message. Don't forget to change charset here
# if you want to use non-us-ascii characters in the mail!
msg = email.MIMEText.MIMEText(text)
msg['Subject'] = mail_subject
msg['From'] = mail_from
msg['To'] = mail_to
sendmail = popen2.Popen3("sendmail -t -f "+escape(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)
# Old Log (for new changes see ChangeLog):
# Revision 1.14 2003/10/19 20:17:54 gernot
# - sendMIMEMail: better wording for some error messages during file conversion
#
# Revision 1.13 2003/07/21 17:44:07 gernot
# - forgot one import in last commit :-|
#
# Revision 1.12 2003/07/20 10:27:51 gernot
# - workaround for Python RuntimeError "cannot unmarshal code objects in
# restricted execution mode", thx to Sander Roest for finally finding
# this solution
#
# Revision 1.11 2003/06/16 10:20:36 gernot
# - use new multipage feature of jpeg2ps (requires special jpeg2ps version!)
#
# Revision 1.10 2003/05/25 13:38:30 gernot
# - support reception of color fax documents
#
# Revision 1.9 2003/04/24 21:04:20 gernot
# - replace functions deprecated in Python 2.2.2 (mainly related to the email
# module)
#
# Revision 1.8 2003/04/24 14:03:18 gernot
# - shortened some long lines
# - added function escape which escapes a string for shell usage
# - escape mail addresses given to sendmail
#
# Revision 1.7 2003/04/16 07:16:35 gernot
# - fixed pipe buffer overflow for conversion of long fax documents to PDF
#
# 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
#