File: cs_helpers.pyin

package info (click to toggle)
capisuite 0.4.5-3
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 4,588 kB
  • ctags: 1,050
  • sloc: cpp: 3,979; sh: 3,465; python: 446; makefile: 263
file content (436 lines) | stat: -rw-r--r-- 17,648 bytes parent folder | download | duplicates (2)
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
#         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

# 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.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,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

	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
#