1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
|
# cs_helpers.py - some helper functions for CapiSuite scripts
# -----------------------------------------------------------
# copyright : (C) 2002 by Gernot Hillier
# email : gernot@hillier.de
# version : $Revision: 1.11.2.3 $
#
# 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.
import os
import time
# 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"
# Convert sff files to tiff files. This is placed in an extra function
# because the sfftobmp tool used for this conversion has changed its
# parameters recently. So this function can be changed to fit your needs.
# Normally, the make environment of CapiSuite should do this automatically
# but if you change your sfftobmp version after building CapiSuite you have
# to adapt this manually.
def sfftotiff(fromfile,tofile):
# for sfftobmp 2.x: remove the "#2" characters of the following line
#2 parameters=("-tif",fromfile,tofile)
# for sfftobmp 3.x: remove the "#3" characters of the following line
#3 parameters=("-tif",fromfile,"-o",tofile)
return os.spawnlp(os.P_WAIT,"sfftobmp","sfftobmp",*parameters)
# @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 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,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,smtplib,popen2,capisuite
msg = email.MIMEBase.MIMEBase("multipart","mixed")
msg['Subject']=mail_subject
msg['From']=mail_from
msg['To']=mail_to
msg['Date']=time.strftime('%a, %d %b %Y %H:%M:%S %z')
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=sfftotiff(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
# 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 -h11 -H12 -L.5 -w8.5 -a "+escape(basename+"tif")+" | ps2pdf -sPAPERSIZE=a4 - -"
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. 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 -sPAPERSIZE=a4 "+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,"-w",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)
try:
server = smtplib.SMTP('localhost')
server.sendmail(mail_from, mail_to.replace('"', '').split(',') ,msg.as_string())
server.quit()
except Exception,e:
capisuite.error("Error while trying to send mail: %s" % e)
else:
capisuite.log("mail sent 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,smtplib,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
msg['Date'] = time.strftime('%a, %d %b %Y %H:%M:%S %z')
try:
server = smtplib.SMTP('localhost')
server.sendmail(mail_from,mail_to,msg.as_string())
server.quit()
except Exception,e:
capisuite.error("Error while trying to send mail: %s" % e)
else:
capisuite.log("mail sent 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.11.2.3 2004/01/18 09:24:32 gernot
# - use 16 bit mode when converting .la to WAV. Thx to Holger Krull for
# the fix (closes bug #51)!
#
# Revision 1.11.2.2 2003/07/21 17:43:26 gernot
# - forgot one import in last commit :-|
#
# Revision 1.11.2.1 2003/07/20 10:28:01 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
#
|