diff --git a/AUTHORS b/AUTHORS index 223932c..24b906c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1 +1,2 @@ Gernot Hillier +Hartmut Goebel diff --git a/ChangeLog b/ChangeLog index e9a4973..a502cfd 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,9 +1,32 @@ +2004-10-25 Gernot Hillier + * src/capisuite-py/config.py.in: minor documentation changes + * src/capisuite-py/__init__.py: Likewise. + * src/capisuite-py/config.py.in (class NoGlobalSectionError): moved + to exceptions.py + * src/capisuite-py/exceptions.py: Likewise. + +2004-09-04 Gernot Hillier + * scripts/cs_helpers.pyin (__sendmail, __call, sff2tif, cff2ps, + la2wav, sendMIMEMail): fix several bugs, answering machine now seems + to work basically + * scripts/answering_machine.confin: update description of parameter + voice_email to new behaviour (empty string not allowed any more) + * NEWS: Likewise. + 2004-07-15 Gernot Hillier * configure.in: support new parameter in ALERT_REQ of newer capi4linux versions, thx to Steffen Barszus for reporting and the fix * src/backend/capi.cpp (Capi::alert_req): Likewise. * acinclude.m4 (CS_TEST_CAPI4LINUX): Likewise. +2004-06-12 Gernot Hillier + * scripts/incoming.py (voiceIncoming): create file in received queue + only if necessary (i.e. as late as possible) + * src/capisuite-py/fileutils.py (_releaseLock): ignore insignificant + error resulting from race condition + * src/capisuite-py/voice.py (createReceivedJob): fix argument passing + * scripts/cs_helpers.pyin (sendMIMEMail): stringify exception instance + 2004-06-10 Gernot Hillier * src/backend/connection.cpp (info_ind_called_party_nr): prevent CapiSuite from crashing when a too high DDIBaseLength was configured, @@ -17,9 +40,33 @@ to avoid using phony targets in man page creation * docs/manual.docbook.in: see above. +2004-05-10 Gernot Hillier + * src/capisuite-py/core.py (Call.log,Call.__init__,Call.reject, + Call.disconnect): fix minor bugs in function calls, import some + additional symbols from _capisuite to our namespace, rename call_from + and call_to attributes of class Call to from_nr and to_nr, don't set + _handle to None after disconnect/reject + * scripts/incoming.py (callIncoming,faxIncoming,voiceIncoming): rename + call_from and call_to attributes of class Call to from_nr and to_nr; + use them everywhere instead of old global variables + * src/application/capisuitemodule.cpp (convertConnRef,convertCapiRef): + improve misleading error message + 2004-04-18 Gernot Hillier * scripts/capisuitefax.in: allow * and # in destination numbers +2004-04-09 Gernot Hillier + * src/capisuite-py/core.py (Capi.call_voice,Capi.call_faxG3): fix + some namespace problems and typos + +2004-04-04 Gernot Hillier + * configure.in: add support to install new capisuite Python module + * src/Makefile.am: Likewise + * src/capisuite-py/Makefile.am: Likewise (new file) + * src/capisuite-py/core.py (class Call): new method Call.log + * scripts/incoming.py (callIncoming,faxIncoming,voiceIncoming): use + Call.log instead of core.log where appropriate + 2004-03-24 Gernot Hillier * docs/Makefile.am: change pathes to Docbook stylesheets to reflect changes in SUSE 9.1 @@ -31,16 +78,72 @@ 2004-03-23 Gernot Hillier * docs/manual-de.docbook (capicodes_protocol): fix small typo +2004-03-21 Hartmut Goebel + * scripts/fax.confin: Removed spaces from section names "Mail ...". + * scripts/answering_machine.confin: Likewise. + * scripts/incoming.py: Likewise. + * scripts/idle.py: Likewise. + * rc.capisuite.in: Likewise within a comment. + * scripts/incoming.py: Removed some outdated comments. + 2004-03-20 Gernot Hillier - * src/application/pythonscript.h: extend prefix() so that it can create - a short prefix, too; use short prefix for Python traceback (fixes bug + * src/application/pythonscript.h (prefix): add support for short + logging prefixes; use them for Python tracebacks (fixes bug #63, reported anonymously) - * src/application/pythonscript.cpp: Likewise. + * src/application/pythonscript.cpp (prefix,run): Likewise. * scripts/incoming.py: Fix typo. +2004-03-19 Gernot Hillier + * scripts/idle.py: Remove old CVS history + * scripts/incoming.py: Likewise. + * scripts/incoming.py: changed some comments, moved connection + accept code to voiceIncoming + +2004-03-14 Hartmut Goebel + * scripts/incoming.py: Use capisuite.core instead of _capisuite. + * src/capisuite-py/fax.py: Removed faxInfo2dict. + * src/capisuite-py/core.py: Added OO layer for _capisuite + functions and classes Capi and Call. + * src/capisuite-py/core.py: New class FaxInfo. + 2004-03-09 Hartmut Goebel - * src/application/idlescript.cpp: Reduced delay until + * SConstruct: new file for building with SCons build-system. + * SConscript: Likewise. + * SConscript-Config: Likewise. + * SConscript-Options: Likewise. + * docs/SConscript: Likewise. + * scripts/SConscript: Likewise. + * scripts/waves/SConscript: Likewise. + * src/SConscript: Likewise. + * src/application/SConscript: Likewise. + * src/backend/SConscript: Likewise. + * src/modules/SConscript: Likewise. + * src/application/capisuitemodule.cpp (capisuitemodule_init): Renamed + built-in module to '_capisuite' + * src/application/idlescript.cpp (run): Reduced delay until idlescript.py is called the first time after startup. + * src/capisuite-py: Added python library modules. + * src/capisuite-py/SConscript: Likewise. + * src/capisuite-py/__init__.py: Likewise. + * src/capisuite-py/config.py.in: Likewise. + * src/capisuite-py/consts.py: Likewise. + * src/capisuite-py/core.py: Likewise. + * src/capisuite-py/exceptions.py: Likewise. + * src/capisuite-py/fax.py: Likewise. + * src/capisuite-py/fileutils.py: Likewise. + * src/capisuite-py/pychecker.rc: Likewise. + * src/capisuite-py/voice.py: Likewise. + * scripts/fax.confin: Added sections 'Mail Fax|Voice ...'. + * scripts/answering_machine.confin: Likewise. + * rc.capisuite.in: Added 'todo' note. + * scripts/capisuite-checkconfig: new file (thx to + carsten@capimin.cbclass.net for the idea and a first version). + * scripts/cs_helper.pyin: Changed to use new python library. + * scripts/capisuitefax.in: Reworked to use new python library. + * scripts/idle.py: Likewise. + * scripts/incoming.py: Likewise. + * scripts/remote-connect.py: Likewise. + * ChangeLog, TODO, AUTHORS: updated. 2004-03-07 Gernot Hillier * docs/manual.docbook: update links to new web site structure @@ -69,7 +172,7 @@ confused the config file parser when not deleted by the user * scripts/fax.confin: Likewise. -2004-02-20 Hartmut Goebel +2004-02-20 Hartmut Goebel * docs/Doxyfile.in: modified variable replacement for possible change of build system * docs/Makefile.am: Likewise. @@ -216,13 +319,13 @@ 2003-10-19 Gernot Hillier * docs/manual.docbook (creating_alaw): add note how to convert vbox - to inversed A-Law files (thx to Thomas Niesel) + to inversed A-Law files (thx to Thomas Niesel) * docs/manual-de.docbook (creating_alaw): see above * docs/manual.docbook (require_soft): updated link to jpeg2ps (thx to - Achim Bohnet) + Achim Bohnet) * docs/manual-de.docbook (require_soft): see above * scripts/cs_helpers.pyin (sendMIMEMail): better wording for some error - messages + messages 2003-10-05 Gernot Hillier diff --git a/Makefile.in b/Makefile.in index c62da8b..7238614 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.8.3 from Makefile.am. +# Makefile.in generated by automake 1.9.1 from Makefile.am. # @configure_input@ # Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, @@ -38,7 +38,7 @@ subdir = . DIST_COMMON = README $(am__configure_deps) $(srcdir)/Makefile.am \ $(srcdir)/Makefile.in $(srcdir)/config.h.in \ $(top_srcdir)/configure AUTHORS COPYING ChangeLog INSTALL NEWS \ - TODO depcomp install-sh missing mkinstalldirs + TODO depcomp install-sh missing mkinstalldirs py-compile ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/acinclude.m4 \ $(top_srcdir)/configure.in @@ -57,6 +57,12 @@ RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \ install-recursive installcheck-recursive installdirs-recursive \ pdf-recursive ps-recursive uninstall-info-recursive \ uninstall-recursive +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = `echo $$p | sed -e 's|^.*/||'`; am__installdirs = "$(DESTDIR)$(docdir)" docDATA_INSTALL = $(INSTALL_DATA) DATA = $(doc_DATA) @@ -136,6 +142,8 @@ am__fastdepCXX_TRUE = @am__fastdepCXX_TRUE@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ bindir = @bindir@ build_alias = @build_alias@ datadir = @datadir@ @@ -235,7 +243,7 @@ install-docDATA: $(doc_DATA) test -z "$(docdir)" || $(mkdir_p) "$(DESTDIR)$(docdir)" @list='$(doc_DATA)'; for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ - f="`echo $$p | sed -e 's|^.*/||'`"; \ + f=$(am__strip_dir) \ echo " $(docDATA_INSTALL) '$$d$$p' '$(DESTDIR)$(docdir)/$$f'"; \ $(docDATA_INSTALL) "$$d$$p" "$(DESTDIR)$(docdir)/$$f"; \ done @@ -243,7 +251,7 @@ install-docDATA: $(doc_DATA) uninstall-docDATA: @$(NORMAL_UNINSTALL) @list='$(doc_DATA)'; for p in $$list; do \ - f="`echo $$p | sed -e 's|^.*/||'`"; \ + f=$(am__strip_dir) \ echo " rm -f '$(DESTDIR)$(docdir)/$$f'"; \ rm -f "$(DESTDIR)$(docdir)/$$f"; \ done @@ -321,14 +329,16 @@ TAGS: tags-recursive $(HEADERS) $(SOURCES) config.h.in $(TAGS_DEPENDENCIES) \ $(TAGS_FILES) $(LISP) tags=; \ here=`pwd`; \ - if (etags --etags-include --version) >/dev/null 2>&1; then \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ include_option=--etags-include; \ + empty_fix=.; \ else \ include_option=--include; \ + empty_fix=; \ fi; \ list='$(SUBDIRS)'; for subdir in $$list; do \ if test "$$subdir" = .; then :; else \ - test -f $$subdir/TAGS && \ + test ! -f $$subdir/TAGS || \ tags="$$tags $$include_option=$$here/$$subdir/TAGS"; \ fi; \ done; \ @@ -338,9 +348,11 @@ TAGS: tags-recursive $(HEADERS) $(SOURCES) config.h.in $(TAGS_DEPENDENCIES) \ done | \ $(AWK) ' { files[$$0] = 1; } \ END { for (i in files) print i; }'`; \ - test -z "$(ETAGS_ARGS)$$tags$$unique" \ - || $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ - $$tags $$unique + if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique; \ + fi ctags: CTAGS CTAGS: ctags-recursive $(HEADERS) $(SOURCES) config.h.in $(TAGS_DEPENDENCIES) \ $(TAGS_FILES) $(LISP) @@ -393,15 +405,17 @@ distdir: $(DISTFILES) || exit 1; \ fi; \ done - list='$(SUBDIRS)'; for subdir in $$list; do \ + list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ if test "$$subdir" = .; then :; else \ test -d "$(distdir)/$$subdir" \ - || mkdir "$(distdir)/$$subdir" \ + || $(mkdir_p) "$(distdir)/$$subdir" \ || exit 1; \ + distdir=`$(am__cd) $(distdir) && pwd`; \ + top_distdir=`$(am__cd) $(top_distdir) && pwd`; \ (cd $$subdir && \ $(MAKE) $(AM_MAKEFLAGS) \ - top_distdir="../$(top_distdir)" \ - distdir="../$(distdir)/$$subdir" \ + top_distdir="$$top_distdir" \ + distdir="$$distdir/$$subdir" \ distdir) \ || exit 1; \ fi; \ @@ -412,15 +426,15 @@ distdir: $(DISTFILES) ! -type d ! -perm -444 -exec $(SHELL) $(install_sh) -c -m a+r {} {} \; \ || chmod -R a+r $(distdir) dist-gzip: distdir - $(AMTAR) chof - $(distdir) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz + tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz $(am__remove_distdir) dist-bzip2: distdir - $(AMTAR) chof - $(distdir) | bzip2 -9 -c >$(distdir).tar.bz2 + tardir=$(distdir) && $(am__tar) | bzip2 -9 -c >$(distdir).tar.bz2 $(am__remove_distdir) dist-tarZ: distdir - $(AMTAR) chof - $(distdir) | compress -c >$(distdir).tar.Z + tardir=$(distdir) && $(am__tar) | compress -c >$(distdir).tar.Z $(am__remove_distdir) dist-shar: distdir @@ -433,7 +447,7 @@ dist-zip: distdir $(am__remove_distdir) dist dist-all: distdir - $(AMTAR) chof - $(distdir) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz + tardir=$(distdir) && $(am__tar) | GZIP=$(GZIP_ENV) gzip -c >$(distdir).tar.gz $(am__remove_distdir) # This target untars the dist file and tries a VPATH configuration. Then @@ -442,13 +456,13 @@ dist dist-all: distdir distcheck: dist case '$(DIST_ARCHIVES)' in \ *.tar.gz*) \ - GZIP=$(GZIP_ENV) gunzip -c $(distdir).tar.gz | $(AMTAR) xf - ;;\ + GZIP=$(GZIP_ENV) gunzip -c $(distdir).tar.gz | $(am__untar) ;;\ *.tar.bz2*) \ - bunzip2 -c $(distdir).tar.bz2 | $(AMTAR) xf - ;;\ + bunzip2 -c $(distdir).tar.bz2 | $(am__untar) ;;\ *.tar.Z*) \ - uncompress -c $(distdir).tar.Z | $(AMTAR) xf - ;;\ + uncompress -c $(distdir).tar.Z | $(am__untar) ;;\ *.shar.gz*) \ - GZIP=$(GZIP_ENV) gunzip -c $(distdir).tar.gz | unshar ;;\ + GZIP=$(GZIP_ENV) gunzip -c $(distdir).shar.gz | unshar ;;\ *.zip*) \ unzip $(distdir).zip ;;\ esac @@ -530,7 +544,7 @@ mostlyclean-generic: clean-generic: distclean-generic: - -rm -f $(CONFIG_CLEAN_FILES) + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" diff --git a/NEWS b/NEWS index 9ec2f7d..f3137ed 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,11 @@ 0.5 (CVS nn): ============= + * scripts: removed the special support for giving an empty voice_email/ + fax_email parameter. This is NOT allowed any more and will result in + an error when sending the mail. Simply don't set these parameters + any more if you don't need them! + * core: use shorter, more readable format for Python traceback logging * documentation: 5 man pages are now also created: capisuite(8), @@ -13,6 +18,22 @@ * scripts: capisuitefax now also accepts PDF files (thx to Jochen Meyer and Eckhard Rüggeberg for reporting) + * scripts: capisuitefax now submits a single job if several + files are given (implemented by Hartmut Goebel, fixes bug #38) + + * scripts: New script capisuite-checkconfig for checking the + configuration (thx to carsten@capimin.cbclass.net for the + idea and a first version, improved by Hartmut Goebel). + + * core: major refactoring of the Python based interface has + been done. Now a complete new Python module library + implements a lot of useful functions and offers handy + abstractions. (planned and implemented by Hartmut Goebel) + + * scripts: the mails sent can now be configured in the special sections + MailFaxReceived, MailFaxSent, MailFaxFailed and MailVoiceReceived in + answering_machine.conf and fax.conf (implemented by Hartmut Goebel) + * core: fixed a bug which could lead to a crash when some unexpected CAPI messages arrived (thx to Karsten Keil for analyzing) @@ -42,8 +63,8 @@ should only contain important changes from a user's point of view, while ChangeLog records all changes in detail. -0.4.4 (CVS tag NN): -=================== +0.4.4 +===== * Makefiles: fixed "make" to not trying to create directories any more - this prevented normal users to call "make" sometimes (thx to Steffen diff --git a/SConscript b/SConscript new file mode 100644 index 0000000..7ef4b82 --- /dev/null +++ b/SConscript @@ -0,0 +1,17 @@ +# -*- python -*- + +# build top level files +# this is a SConscript, too, to support really build-dirs + +Import('env') + +cronfile = env.FileSubst('capisuite.cron', 'capisuite.cronin') +rcfile = env.FileSubst('rc.capisuite', 'rc.capisuite.in') +env.AddPostAction([cronfile, rcfile], 'chmod gu+x $TARGET') + +Alias('install', + env.Install('$docdir', Split('COPYING NEWS README')), + env.Install('$sysconfdir/init.d/capisuite', rcfile), + env.InstallAs('$sysconfdir/cron.daily/capisuite', cronfile), + env.InstallAs('$pkgsysconfdir/cronjob.conf','cronjob.conf'), + ) diff --git a/SConscript-Config b/SConscript-Config new file mode 100644 index 0000000..0c6a877 --- /dev/null +++ b/SConscript-Config @@ -0,0 +1,192 @@ +# -*- python -*- +""" +Check the build environment for CapiSuite + +(c) Copyright 2004 by Hartmut Goebel +""" + +Import(['env']) + +headerfilename = 'config.h' +havedict = {} + +open(headerfilename, 'w') # clear file contents + +###--- hack SCons.SConftest ---### + +import SCons.Conftest, string +from types import IntType +def _Have(context, key, have): + """ + Slightly modified version of SCons.Conftest._Have which uses + global havedict and headerfilename instead of context.*. This is + necessary until SCons support a way to actually use this feature. + In addition this writes '#define ... 1'. + """ + key_up = string.upper(key) + key_up = string.replace(key_up, ':', '_') + key_up = string.replace(key_up, '.', '_') + key_up = string.replace(key_up, '/', '_') + key_up = string.replace(key_up, ' ', '_') + havedict[key_up] = have + if headerfilename: + f = open(headerfilename, "a") + if have == 1: + f.write("#define %s 1\n" % key_up) + elif have == 0: + f.write("/* #undef %s */\n" % key_up) + elif type(have) == IntType: + f.write("#define %s %d\n" % (key_up, have)) + else: + f.write('#define %s "%s"\n' % (key_up, str(have))) + f.close() + +# need to monkey-patch it into SCons.Conftest :-( +SCons.Conftest._Have = _Have + +_Have(None, 'PACKAGE', env['PACKAGE']) +_Have(None, 'VERSION', env['VERSION']) + +## these were defined by auto-rools but are unused: +## #define PACKAGE_BUGREPORT "" +## #define PACKAGE_NAME "" +## #define PACKAGE_STRING "" +## #define PACKAGE_TARNAME "" +## #define PACKAGE_VERSION "" + +###--- autoconf-like checks ---### +### these tests are build like the corresponding autoconf tests + +def CheckHeadersStdC(context): + context.Message("Checking for ANSI C header files ... ") + text = """ + #include + #include + #include + #include + \n""" + # Some tests for SunOS 4.x, ISC 2.0.2 and Irix-4.0.5 skipped here, + # since CapiSuite requires Linux anway. + ret = context.CompileProg(text, '.cpp') + SCons.Conftest._YesNoResult(context, ret, "STDC_HEADERS", text) + context.did_show_result = 1 + return ret + +def CheckHeaderTime(context): + context.Display("Checking whether time.h and sys/time.h may both be " + "included ... ") + text = """ + #include + #include + #include + + int main() { + if ((struct tm *) 0) return 0; + return 0; + } + \n""" + ret = context.CompileProg(text, '.cpp') + SCons.Conftest._YesNoResult(context, ret, "TIME_WITH_SYS_TIME", text) + context.did_show_result = 1 + return ret + +###--- custom checks for CapiSuite ---### + +def CheckStringClear(context): + context.Display('Checking for string::clear method ... ') + text = """ + #include + int main() { + std::string a; a.clear(); + } + \n""" + ret = context.CompileProg(text, '.cpp') + SCons.Conftest._YesNoResult(context, ret, "HAVE_STRING_CLEAR", text) + context.did_show_result = 1 + return ret + + +conf = Configure(env, + custom_tests = {'CheckHeadersStdC': CheckHeadersStdC, + 'CheckHeaderTime': CheckHeaderTime, + 'CheckStringClear': CheckStringClear, + }, + conf_dir = '.sconf_temp', # no '#' to build in build-dir + log_file = 'config.log' # no '#' to build in build-dir + ) + +conf.CheckHeadersStdC() + +missing = 0 +for h in Split("sys/types.h sys/stat.h stdlib.h string.h " + "memory.h strings.h inttypes.h stdint.h unistd.h"): + missing += not conf.CheckHeader(h, language='C++') +if missing: + print 'Required headers missing - aborting.' + Exit(5) + +conf.CheckFunc('gettimeofday', language='C++') +conf.CheckHeader('sys/time.h', language='C++') +conf.CheckHeaderTime() + +###--- CapiSuite specials ---### + +# new gcc3 feature: we can #include instead of ostream.h +conf.CheckHeader('ostream', language='C++') +conf.CheckStringClear() # checking for string::clear method + + +###--- libs required by CapiSuite ---### + +# checking for capi20_register in -lcapi20... yes +if not conf.CheckLib('capi20', 'capi20_register', language='C++'): + print 'libcapi not found - aborting' + Exit(5) + +# checking for pthread_create in -lpthread... yes +if not conf.CheckLib('pthread', 'pthread_create', language='C++'): + print 'lipthread not found - aborting' + Exit(5) + +# todo +# Which(doxygen) +# Which(pychecker) + +conf.Finish() + + +""" +These are the checks 'configure' does + +Legende: +- not neccessary ++ Scons build-in +? unsure +~ implemented in SConscript/SConstruct + +-checking for a BSD-compatible install... /usr//bin/install -c +-checking whether build environment is sane... yes +-checking for gawk... gawk +-checking whether make sets $(MAKE)... yes ++checking for gcc... gcc ++checking for C compiler default output... a.out ++checking whether the C compiler works... yes +?checking whether we are cross compiling... no ++checking for suffix of executables... ++checking for suffix of object files... o +?checking whether we are using the GNU C compiler... yes +?checking whether gcc accepts -g... yes +?checking for gcc option to accept ANSI C... none needed +-checking for style of include used by make... GNU +-checking dependency style of gcc... gcc3 + ++checking for g++... g++ +checking whether we are using the GNU C++ compiler... yes +checking whether g++ accepts -g... yes +-checking dependency style of g++... gcc3 +-checking for a BSD-compatible install... /usr//bin/install -c ++checking for ranlib... ranlib +-checking whether make sets $(MAKE)... (cached) yes ++checking how to run the C++ preprocessor... g++ -E +-checking for egrep... grep -E +""" diff --git a/SConscript-Options b/SConscript-Options new file mode 100644 index 0000000..0a0d354 --- /dev/null +++ b/SConscript-Options @@ -0,0 +1,43 @@ +# -*- python -*- +""" +Options for building CapiSuite + +(c) Copyright 2004 by Hartmut Goebel +""" + +# default prefix +prefix = '/usr/local' +oldincludedir = '/usr/include' + +opts = Options('options.cache', ARGUMENTS) +opts.AddOptions( + # Installation directories: + ('prefix', 'install architecture-independent files in prefix', prefix), + ('execprefix','install architecture-dependent files in execprefix','$prefix'), + + # Fine tuning of the installation directories: + ('bindir', 'user executables', '$execprefix/bin'), + ('sbindir', 'system admin executables', '$execprefix/sbin'), + ('libexecdir', 'program executables', '$execprefix/libexec'), + ('datadir', 'read-only architecture-independent data','$prefix/share'), + ('sysconfdir', 'read-only single-machine data', '$prefix/etc',), + ('sharedstatedir','modifiable architecture-independent data','$prefix/com',), + ('localstatedir', 'modifiable single-machine data', '$prefix/var'), + ('libdir', 'object code libraries', '$execprefix/lib'), + ('includedir', 'C header files', '$prefix/include'), + ('oldincludedir', 'C header files for non-gcc', oldincludedir), + ('infodir', 'info documentation', '$prefix/info'), + ('mandir', 'man documentation', '$prefix/man'), + + # install into another filesystem base + ('INSTALL_BASE', 'base dir for installation (userfull for RPMs)', + '#/dist'), + + # this is a capisuite-special + ('docdir', 'other documentation', '$datadir/doc/capisuite'), + ) + +Import(['env', '__targets__']) +opts.Update(env) +Help(opts.GenerateHelpText(env) + __targets__) +opts.Save('options.cache', env) diff --git a/SConstruct b/SConstruct new file mode 100644 index 0000000..2ef700d --- /dev/null +++ b/SConstruct @@ -0,0 +1,246 @@ +# -*- python -*- +""" +Main SCons build script for CapiSuite + +(c) Copyright 2004 by Hartmut Goebel + +CapiSuite is (c) Copyright by Gernot Hiller + +Use 'scons --help' for a list of available options. + +Options have only to be given once, since they are cached in a file +'options.cache'. To change an option, simply pass it again with a +different value. + +Example: + + scons prefix=/ # build for prefix=/ + scons # prefix=/ is taken from 'options.cache' + scons prefix=/usr/local # build for prefix=/usr/local + # since this is the default, the entry in options.cache + # will be renmoved + scons # default prefix is used +""" + +__targets__ = """ +Additional targets: + configure : build the 'configure' script + (this is automatically done if 'config.h' is missing) + pycheck : check Python sources with PyChecker (not yet implemented) + + install : install all files + install-pylib : install only the python library + install-scripts : install only the python scripts + install-exec : install only the executables + + For all install-targets base may be set with INSTALL_BASE=... +""" + + +# File-Content Substitution will (hopefully ) be part of SCons 0.95 +def _file_subst(target, source, env): + import os, re + import SCons.Util + + def _substitute(matchobj, env=env): + sym = matchobj.group(1) + try: + return env.subst(str(env[sym])) + except: # TypeError: # sym not a string + print 'Not substituting', sym + return matchobj.group(0) # matched + + delim = re.escape(env.get('SUBST_DELIM', '@')) + subst_pattern = re.compile('%s(.*?)%s' % (delim, delim)) + for t, s in zip(target, source): + t = str(t) + s = s.rstr() + text = open(s, 'rb').read() + text = subst_pattern.sub(_substitute, text) + open(t, 'wb').write(text) + os.chmod(t, os.stat(s)[0]) + return None + +def _fs_strfunc(target, source, env): + return "generating '%s' from '%s'" % (target[0], source[0]) + +_fs_builder = Builder(action = Action(_file_subst, strfunction = _fs_strfunc)) + +import sys, os, os.path +import SCons.Util +import SCons.Node.FS + +EnsurePythonVersion(2,2) # capisuite requires this +#EnsureSConsVersion(0,94) + +build_dir = Dir('#/build') + +class InstallableEnv(Environment): + def Install(self, dir, source): + """Install specified files in the given directory.""" + if self.has_key('INSTALL_BASE'): + def _Dir(name): + return SCons.Node.FS.Dir(name, parent, self.fs) + parent = self['INSTALL_BASE'] + dir = self.arg2nodes(dir, _Dir) + return Environment.Install(self, dir, source) + + def InstallAs(self, target, source): + """Install sources as targets.""" + def _File(name): + return SCons.Node.FS.File(name, dir, self.fs) + + if self.has_key('INSTALL_BASE'): + dir = self['INSTALL_BASE'] + targets = self.arg2nodes(target, _File) + return Environment.InstallAs(self, target, source) + +env = InstallableEnv() +env.Append( + BUILDERS={'FileSubst' : _fs_builder}, + PACKAGE = 'capisuite', + VERSION = '0.5.cvs', + srcdir = build_dir, + + pkgdatadir = '${datadir}/${PACKAGE}', + pkglibdir = '${libdir}/${PACKAGE}', + pkgincludedir = '${includedir}/${PACKAGE}', + + pkgbindir = '${bindir}', + pkgsbindir = '${sbindir}', + pkgsysconfdir = '${sysconfdir}/${PACKAGE}', + #pkglibdir = '${prefix}/lib', + spooldir = '${localstatedir}/spool/${PACKAGE}', + docdir = '${pkgdatadir}/doc/${PACKAGE}', + ) +env.SConscript('SConscript-Options', exports=['env', '__targets__']) + +if env.has_key('INSTALL_BASE'): + env.Replace(INSTALL_BASE=env.Dir('$INSTALL_BASE')) +Export('env') + +env.BuildDir(build_dir=build_dir, src_dir='.') + + +###---####---###---####---###---####---###---####---###---####---###---### +# call configure if required + +# if config.h does not exist, build it using Scons' conftest +if not os.path.exists(str(File('config.h', build_dir))) \ + or 'configure' in COMMAND_LINE_TARGETS: + env.SConscript('SConscript-Config', build_dir=build_dir) + +###---####---###---####---###---####---###---####---###---####---###---### + +def GetPythonModuleSetup(env): + """ + Get configuration for linking with the Python library. + """ + import distutils.sysconfig + from distutils.sysconfig import get_python_lib, get_config_vars, \ + get_config_var + env.Append( + python_version = distutils.sysconfig.get_python_version(), + python_prefix = distutils.sysconfig.PREFIX, + python_execprefix = distutils.sysconfig.EXEC_PREFIX, + python_libdir = get_python_lib(plat_specific=1, standard_lib=1), + python_moduledir = get_python_lib(plat_specific=0, standard_lib=0), + python_moduleexecdir = get_python_lib(plat_specific=1, standard_lib=0), + python_includespec = get_config_vars('INCLUDEPY', 'CONFINCLUDEPY'), + python_linkforshared = get_config_var('LINKFORSHARED'), + + pkgpython_moduledir = '${python_moduledir}/${PACKAGE}', + pkgpython_moduleexecdir = '${python_moduleexecdir}/${PACKAGE}', + + LINKFLAGS = ['${python_linkforshared}'], + LIBS = ['python${python_version}'], + ) + env.Append( + CPPPATH = env['python_includespec'], + PYTHON = sys.executable, + ) + +def GetPythonEmbeddedSetup(env): + """ + Get configuration for linking an embedded Python application. + """ + # Base idea on which variable to check are from the INN 2.3.1 package + import distutils.sysconfig + print 'Checking how to link an embedded Python application:', + libvars = Split("LIBS LIBC LIBM LOCALMODLIBS BASEMODLIBS") + python_libspec = [] + for libvar in distutils.sysconfig.get_config_vars(*libvars): + libvar = libvar.split() + for lib in libvar: + # sanity check + if not lib.startswith('-l'): + print "Error in Python Makefile: shared lib spec does ", \ + "not start with '-l':", lib + python_libspec.append(lib[2:]) + env.Append(python_libspec = python_libspec, + python_configdir = distutils.sysconfig.get_config_var('LIBPL'), + # preferable these should be '$python..', but SCons + # doe not yet support this (SCons 0.94) + LIBS = python_libspec, + LIBPATH = ['${python_configdir}'], + ) + print ' '.join(python_libspec) + #print '>>>', env['LIBS'] + +GetPythonModuleSetup(env) +GetPythonEmbeddedSetup(env) + +env.Append( + CCFLAGS = Split('-g -O2'), + LIBS = Split('pthread capi20'), + CPPDEFINES={'LOCALSTATEDIR': r'\"${localstatedir}\"', + 'PKGDATADIR' : r'\"${pkgdatadir}\"', + 'PKGSYSCONFDIR': r'\"${pkgsysconfdir}\"', + 'PKGLIBDIR' : r'\"${pkglibdir}\"', + }, + ) + +## # build debug? +## if ARGUMENTS.get('debug', 0): +## env.Append(CXXFLAGS = ['-g', '-DDEBUG']) +## else: +## env.Append(CXXFLAGS = ['-O2']) +## +## if env['CXX'] in ('g++', 'c++'): +## env.Append(CXXFLAGS = ['-Wall', '-Wno-non-virtual-dtor']) + + +###---####---###---####---###---####---###---####---###---####---###---### + +# snippet for unittest +#mytest = env.program(...) +#Alias( 'unittest', mytest ) +#Alias( 'all', 'unittest' ) +#unittetsInstall = env.Install(...) +#Alias('unittest', unitteststInstall) + +# now build the subdirectories' stuff +env.SConscript(dirs=[Dir('.', build_dir), + Dir('src', build_dir), + Dir('scripts', build_dir), + Dir('scripts/waves', build_dir), + Dir('docs', build_dir), + ]) + +#env.SourceCode('.', +# env.CVS('pserver:anonymous@cvs.capisuite.berlios.de:/cvsroot/capisuite', +# 'capisuite')) + +""" +make DESTDIR=$RPM_BUILD_ROOT install +mkdir -p $RPM_BUILD_ROOT/usr/sbin + +ln -sf ../../etc/init.d/capisuite $RPM_BUILD_ROOT/usr/sbin/rccapisuite +""" +#EXTRA_DIST = rc.capisuite.in capisuite.cronin cronjob.conf +#install-data-local: +# mkdir -p $(DESTDIR)$(localstatedir)/log +# $(mkinstalldirs) $(DESTDIR)$(spooldir)/sendq +# $(mkinstalldirs) $(DESTDIR)$(spooldir)/done +# $(mkinstalldirs) $(DESTDIR)$(spooldir)/failed +# $(mkinstalldirs) $(DESTDIR)$(spooldir)/users diff --git a/TODO b/TODO index e7a9984..a6154d8 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,18 @@ +Important for 0.5.0/capisuite-py: +* Check and fix all 'todo:'s in the python library and scripts. +* Update documentation to include + - capisuite-checkconfig + - the python library modules. + - 'Mail Fax|Voice ...' sections in config files +* Add update instructions for 0.5.0/capisuite-py + - 'Mail Fax|Voice ...' sections in config files + - new modulename '_capisuite' (but use capisuite.core instead) +* Add option '--is-configured' to capisuite-checkconfig to be used + by rc-file. +* Add 'scons-local' to distribution. + NICE: +- more checks/options for capisuite-checkconfig - ?valgrind-clean the used libs and Python? - support more than one send_controller - don't use 34xx codes, define constants instead and print meaningful messages @@ -7,4 +21,3 @@ FUTURE PLANS: - setuid away from root (problem: chown of recorded file to user) - test-implement the whole application part in Python - rewrite capisuitefax and idle.py to use named socket communication - diff --git a/configure b/configure index a18f3e7..f560ab4 100755 --- a/configure +++ b/configure @@ -309,7 +309,7 @@ ac_includes_default="\ # include #endif" -ac_subst_vars='SHELL PATH_SEPARATOR PACKAGE_NAME PACKAGE_TARNAME PACKAGE_VERSION PACKAGE_STRING PACKAGE_BUGREPORT exec_prefix prefix program_transform_name bindir sbindir libexecdir datadir sysconfdir sharedstatedir localstatedir libdir includedir oldincludedir infodir mandir build_alias host_alias target_alias DEFS ECHO_C ECHO_N ECHO_T LIBS INSTALL_PROGRAM INSTALL_SCRIPT INSTALL_DATA CYGPATH_W PACKAGE VERSION ACLOCAL AUTOCONF AUTOMAKE AUTOHEADER MAKEINFO AMTAR install_sh STRIP ac_ct_STRIP INSTALL_STRIP_PROGRAM mkdir_p AWK SET_MAKE am__leading_dot CC CFLAGS LDFLAGS CPPFLAGS ac_ct_CC EXEEXT OBJEXT DEPDIR am__include am__quote AMDEP_TRUE AMDEP_FALSE AMDEPBACKSLASH CCDEPMODE am__fastdepCC_TRUE am__fastdepCC_FALSE CXX CXXFLAGS ac_ct_CXX CXXDEPMODE am__fastdepCXX_TRUE am__fastdepCXX_FALSE RANLIB ac_ct_RANLIB doxygen CXXCPP EGREP docdir PYTHON PYTHON_VERSION PYTHON_PREFIX PYTHON_EXEC_PREFIX PYTHON_PLATFORM pythondir pkgpythondir pyexecdir pkgpyexecdir python_version python_prefix python_execprefix python_configdir python_moduledir python_moduleexecdir python_includespec python_linkforshared LIBOBJS LTLIBOBJS' +ac_subst_vars='SHELL PATH_SEPARATOR PACKAGE_NAME PACKAGE_TARNAME PACKAGE_VERSION PACKAGE_STRING PACKAGE_BUGREPORT exec_prefix prefix program_transform_name bindir sbindir libexecdir datadir sysconfdir sharedstatedir localstatedir libdir includedir oldincludedir infodir mandir build_alias host_alias target_alias DEFS ECHO_C ECHO_N ECHO_T LIBS INSTALL_PROGRAM INSTALL_SCRIPT INSTALL_DATA CYGPATH_W PACKAGE VERSION ACLOCAL AUTOCONF AUTOMAKE AUTOHEADER MAKEINFO install_sh STRIP ac_ct_STRIP INSTALL_STRIP_PROGRAM mkdir_p AWK SET_MAKE am__leading_dot AMTAR am__tar am__untar CC CFLAGS LDFLAGS CPPFLAGS ac_ct_CC EXEEXT OBJEXT DEPDIR am__include am__quote AMDEP_TRUE AMDEP_FALSE AMDEPBACKSLASH CCDEPMODE am__fastdepCC_TRUE am__fastdepCC_FALSE CXX CXXFLAGS ac_ct_CXX CXXDEPMODE am__fastdepCXX_TRUE am__fastdepCXX_FALSE RANLIB ac_ct_RANLIB doxygen CXXCPP EGREP docdir PYTHON PYTHON_VERSION PYTHON_PREFIX PYTHON_EXEC_PREFIX PYTHON_PLATFORM pythondir pkgpythondir pyexecdir pkgpyexecdir python_version python_prefix python_execprefix python_configdir python_moduledir python_moduleexecdir python_includespec python_linkforshared LIBOBJS LTLIBOBJS' ac_subst_files='' # Initialize some variables set by options. @@ -1311,7 +1311,7 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu -am__api_version="1.8" +am__api_version="1.9" ac_aux_dir= for ac_dir in $srcdir $srcdir/.. $srcdir/../..; do if test -f $ac_dir/install-sh; then @@ -1488,13 +1488,21 @@ echo "$as_me: WARNING: \`missing' script is too old or missing" >&2;} fi if mkdir -p --version . >/dev/null 2>&1 && test ! -d ./--version; then - # Keeping the `.' argument allows $(mkdir_p) to be used without - # argument. Indeed, we sometimes output rules like + # We used to keeping the `.' as first argument, in order to + # allow $(mkdir_p) to be used without argument. As in # $(mkdir_p) $(somedir) - # where $(somedir) is conditionally defined. - # (`test -n '$(somedir)' && $(mkdir_p) $(somedir)' is a more - # expensive solution, as it forces Make to start a sub-shell.) - mkdir_p='mkdir -p -- .' + # where $(somedir) is conditionally defined. However this is wrong + # for two reasons: + # 1. if the package is installed by a user who cannot write `.' + # make install will fail, + # 2. the above comment should most certainly read + # $(mkdir_p) $(DESTDIR)$(somedir) + # so it does not work when $(somedir) is undefined and + # $(DESTDIR) is not. + # To support the latter case, we have to write + # test -z "$(somedir)" || $(mkdir_p) $(DESTDIR)$(somedir), + # so the `.' trick is pointless. + mkdir_p='mkdir -p --' else # On NextStep and OpenStep, the `mkdir' command does not # recognize any option. It will interpret all options as @@ -1638,9 +1646,6 @@ AUTOHEADER=${AUTOHEADER-"${am_missing_run}autoheader"} MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"} - -AMTAR=${AMTAR-"${am_missing_run}tar"} - install_sh=${install_sh-"$am_aux_dir/install-sh"} # Installed binaries are usually stripped using `strip' when the user @@ -1733,6 +1738,13 @@ INSTALL_STRIP_PROGRAM="\${SHELL} \$(install_sh) -c -s" # We need awk for the "check" target. The system "awk" is bad on # some platforms. +# Always define AMTAR for backward compatibility. + +AMTAR=${AMTAR-"${am_missing_run}tar"} + +am__tar='${AMTAR} chof - "$$tardir"'; am__untar='${AMTAR} xf -' + + @@ -2811,9 +2823,14 @@ else grep sub/conftest.${OBJEXT-o} sub/conftest.Po > /dev/null 2>&1 && ${MAKE-make} -s -f confmf > /dev/null 2>&1; then # icc doesn't choke on unknown options, it will just issue warnings - # (even with -Werror). So we grep stderr for any message - # that says an option was ignored. - if grep 'ignoring option' conftest.err >/dev/null 2>&1; then :; else + # or remarks (even with -Werror). So we grep stderr for any message + # that says an option was ignored or not supported. + # When given -MP, icc 7.0 and 7.1 complain thusly: + # icc: Command line warning: ignoring option '-M'; no argument required + # The diagnosis changed in icc 8.0: + # icc: Command line remark: option '-MP' not supported + if (grep 'ignoring option' conftest.err || + grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else am_cv_CC_dependencies_compiler_type=$depmode break fi @@ -3270,9 +3287,14 @@ else grep sub/conftest.${OBJEXT-o} sub/conftest.Po > /dev/null 2>&1 && ${MAKE-make} -s -f confmf > /dev/null 2>&1; then # icc doesn't choke on unknown options, it will just issue warnings - # (even with -Werror). So we grep stderr for any message - # that says an option was ignored. - if grep 'ignoring option' conftest.err >/dev/null 2>&1; then :; else + # or remarks (even with -Werror). So we grep stderr for any message + # that says an option was ignored or not supported. + # When given -MP, icc 7.0 and 7.1 complain thusly: + # icc: Command line warning: ignoring option '-M'; no argument required + # The diagnosis changed in icc 8.0: + # icc: Command line remark: option '-MP' not supported + if (grep 'ignoring option' conftest.err || + grep 'not supported' conftest.err) >/dev/null 2>&1; then :; else am_cv_CXX_dependencies_compiler_type=$depmode break fi @@ -4692,6 +4714,7 @@ echo "$as_me: error: too old" >&2;} { (exit 1); exit 1; }; } fi + am_display_PYTHON=$PYTHON else # Otherwise, try each interpreter until we find one that satisfies # VERSION. @@ -4902,7 +4925,7 @@ echo "${ECHO_T}${python_libspec}" >&6 CPPFLAGS='-DLOCALSTATEDIR=\"$(localstatedir)\" -DPKGDATADIR=\"$(pkgdatadir)\" -DPKGSYSCONFDIR=\"$(sysconfdir)/capisuite\" -DPKGLIBDIR=\"$(pkglibdir)\" $(python_includespec)' - ac_config_files="$ac_config_files Makefile src/Makefile src/backend/Makefile src/modules/Makefile src/application/Makefile scripts/Makefile scripts/waves/Makefile docs/Makefile" + ac_config_files="$ac_config_files Makefile src/Makefile src/backend/Makefile src/modules/Makefile src/application/Makefile src/capisuite-py/Makefile scripts/Makefile scripts/waves/Makefile docs/Makefile" cat >confcache <<\_ACEOF # This file is a shell script that caches the results of configure # tests run on this system so they can be shared between configure @@ -5464,6 +5487,7 @@ do "src/backend/Makefile" ) CONFIG_FILES="$CONFIG_FILES src/backend/Makefile" ;; "src/modules/Makefile" ) CONFIG_FILES="$CONFIG_FILES src/modules/Makefile" ;; "src/application/Makefile" ) CONFIG_FILES="$CONFIG_FILES src/application/Makefile" ;; + "src/capisuite-py/Makefile" ) CONFIG_FILES="$CONFIG_FILES src/capisuite-py/Makefile" ;; "scripts/Makefile" ) CONFIG_FILES="$CONFIG_FILES scripts/Makefile" ;; "scripts/waves/Makefile" ) CONFIG_FILES="$CONFIG_FILES scripts/waves/Makefile" ;; "docs/Makefile" ) CONFIG_FILES="$CONFIG_FILES docs/Makefile" ;; @@ -5565,7 +5589,6 @@ s,@AUTOCONF@,$AUTOCONF,;t t s,@AUTOMAKE@,$AUTOMAKE,;t t s,@AUTOHEADER@,$AUTOHEADER,;t t s,@MAKEINFO@,$MAKEINFO,;t t -s,@AMTAR@,$AMTAR,;t t s,@install_sh@,$install_sh,;t t s,@STRIP@,$STRIP,;t t s,@ac_ct_STRIP@,$ac_ct_STRIP,;t t @@ -5574,6 +5597,9 @@ s,@mkdir_p@,$mkdir_p,;t t s,@AWK@,$AWK,;t t s,@SET_MAKE@,$SET_MAKE,;t t s,@am__leading_dot@,$am__leading_dot,;t t +s,@AMTAR@,$AMTAR,;t t +s,@am__tar@,$am__tar,;t t +s,@am__untar@,$am__untar,;t t s,@CC@,$CC,;t t s,@CFLAGS@,$CFLAGS,;t t s,@LDFLAGS@,$LDFLAGS,;t t @@ -6243,27 +6269,21 @@ echo X"$mf" | else continue fi - grep '^DEP_FILES *= *[^ #]' < "$mf" > /dev/null || continue - # Extract the definition of DEP_FILES from the Makefile without - # running `make'. + # Extract the definition of DEPDIR, am__include, and am__quote + # from the Makefile without running `make'. DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"` test -z "$DEPDIR" && continue + am__include=`sed -n 's/^am__include = //p' < "$mf"` + test -z "am__include" && continue + am__quote=`sed -n 's/^am__quote = //p' < "$mf"` # When using ansi2knr, U may be empty or an underscore; expand it U=`sed -n 's/^U = //p' < "$mf"` - test -d "$dirpart/$DEPDIR" || mkdir "$dirpart/$DEPDIR" - # We invoke sed twice because it is the simplest approach to - # changing $(DEPDIR) to its actual value in the expansion. - for file in `sed -n ' - /^DEP_FILES = .*\\\\$/ { - s/^DEP_FILES = // - :loop - s/\\\\$// - p - n - /\\\\$/ b loop - p - } - /^DEP_FILES = / s/^DEP_FILES = //p' < "$mf" | \ + # Find all dependency output files, they are included files with + # $(DEPDIR) in their names. We invoke sed twice because it is the + # simplest approach to changing $(DEPDIR) to its actual value in the + # expansion. + for file in `sed -n " + s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \ sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g' -e 's/\$U/'"$U"'/g'`; do # Make sure the directory exists. test -f "$dirpart/$file" && continue diff --git a/configure.in b/configure.in index c5cd123..c69b358 100644 --- a/configure.in +++ b/configure.in @@ -26,4 +26,4 @@ AM_PATH_PYTHON(2.2) PGAC_CHECK_PYTHON_EMBED_SETUP CPPFLAGS='-DLOCALSTATEDIR=\"$(localstatedir)\" -DPKGDATADIR=\"$(pkgdatadir)\" -DPKGSYSCONFDIR=\"$(sysconfdir)/capisuite\" -DPKGLIBDIR=\"$(pkglibdir)\" $(python_includespec)' -AC_OUTPUT(Makefile src/Makefile src/backend/Makefile src/modules/Makefile src/application/Makefile scripts/Makefile scripts/waves/Makefile docs/Makefile) +AC_OUTPUT(Makefile src/Makefile src/backend/Makefile src/modules/Makefile src/application/Makefile src/capisuite-py/Makefile scripts/Makefile scripts/waves/Makefile docs/Makefile) diff --git a/docs/Makefile.in b/docs/Makefile.in index a858304..a3caae8 100644 --- a/docs/Makefile.in +++ b/docs/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.8.3 from Makefile.am. +# Makefile.in generated by automake 1.9.1 from Makefile.am. # @configure_input@ # Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, @@ -46,7 +46,8 @@ CONFIG_CLEAN_FILES = SOURCES = DIST_SOURCES = man1dir = $(mandir)/man1 -am__installdirs = "$(DESTDIR)$(man1dir)" "$(DESTDIR)$(man5dir)" "$(DESTDIR)$(man8dir)" +am__installdirs = "$(DESTDIR)$(man1dir)" "$(DESTDIR)$(man5dir)" \ + "$(DESTDIR)$(man8dir)" man5dir = $(mandir)/man5 man8dir = $(mandir)/man8 NROFF = nroff @@ -114,6 +115,8 @@ am__fastdepCXX_TRUE = @am__fastdepCXX_TRUE@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ bindir = @bindir@ build_alias = @build_alias@ datadir = @datadir@ @@ -381,7 +384,7 @@ mostlyclean-generic: clean-generic: distclean-generic: - -rm -f $(CONFIG_CLEAN_FILES) + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @@ -435,9 +438,9 @@ uninstall-am: uninstall-info-am uninstall-local uninstall-man uninstall-man: uninstall-man1 uninstall-man5 uninstall-man8 -.PHONY: all all-am check check-am clean clean-generic distclean \ - distclean-generic distdir dvi dvi-am html html-am info info-am \ - install install-am install-data install-data-am \ +.PHONY: all all-am check check-am clean clean-generic dist-hook \ + distclean distclean-generic distdir dvi dvi-am html html-am \ + info info-am install install-am install-data install-data-am \ install-data-local install-exec install-exec-am install-info \ install-info-am install-man install-man1 install-man5 \ install-man8 install-strip installcheck installcheck-am \ diff --git a/docs/SConscript b/docs/SConscript new file mode 100644 index 0000000..65156d9 --- /dev/null +++ b/docs/SConscript @@ -0,0 +1,66 @@ +# -*- python -*- + +Import('env') +#EXTRA_DIST = Doxyfile.in mainpage.doxy manual.docbook manual.README + + +## dist-hook: manual-html manual-pdf reference-html +## mkdir $(distdir)/manual +## cp -r $(srcdir)/manual/* $(distdir)/manual/ +## mkdir $(distdir)/reference +## cp $(srcdir)/reference/* $(distdir)/reference/ +## cp manual.pdf $(distdir)/ + +import re + +def patch_version(target, source, env): + """ + Change version contained in the tag. + This is done in-place to avoid yet another .in file. + """ + text = source[0].get_contents() + text = re.sub(r'<title>CapiSuite [\w.]*', + r'CapiSuite %s' % env['VERSION'], + text) + open(source[0].abspath, 'w').write(text) + + +env.Append(docbuilddir=Dir('.')) + +manualdir = Dir('manual') +stylesheetdir = Dir('/usr/share/sgml/docbook/xsl-stylesheets') + +#images = Install(Dir('images', manualdir), images) + +# create HTML manual +manual_html = env.Command(File('index.html', manualdir), + 'manual.docbook', [ + patch_version, + ['xmllint', '--noout', '--valid', '$SOURCE'], + ['xsltproc', '-o', '${TARGET.dir}/', + File('xhtml/chunk.xsl', stylesheetdir), '$SOURCE'] + ]) +#env.Depends(manual_html, images) + +# copy missing images +env.AddPostAction(manual_html, [ \ + ['rm', '-fr', Dir('images', manualdir)], + ['cp', '-r', Dir('images', stylesheetdir), Dir(manualdir)] + ]) + +# create PDF manual +manual_pdf = env.Command('manual.pdf', 'manual.docbook', + 'db2pdf -o ${TARGET.dir} $SOURCE' + ) + +# substitute version, capisuite_sources, srcdir +doxyfile = env.FileSubst('Doxyfile', 'Doxyfile.in') +ref_html = env.Command('reference/index.html', doxyfile, [ \ + ['doxygen', doxyfile] + ]) + +Alias('install', + env.Install('$docdir', manual_pdf), + #env.Install('$docdir', 'manual'), + #env.Install('$docdir', 'reference'), + ) diff --git a/rc.capisuite.in b/rc.capisuite.in index 3cc6862..accfdc2 100755 --- a/rc.capisuite.in +++ b/rc.capisuite.in @@ -82,6 +82,8 @@ case "$1" in # answering machine. Otherwise exit. # IMPORTANT: Change this or comment it out if you want to use # your own CapiSuite scripts. + # todo: change these tests since they are not longer valid due to + # [Mail...] sections. Consider using capisuite-checkconfig! while read -r sec rest ; do if [ "${sec:0:1}" = "[" -a "$sec" != "[GLOBAL]" ]; then configured_fax=yes diff --git a/scripts/Makefile.in b/scripts/Makefile.in index 859aa0f..d2ae804 100644 --- a/scripts/Makefile.in +++ b/scripts/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.8.3 from Makefile.am. +# Makefile.in generated by automake 1.9.1 from Makefile.am. # @configure_input@ # Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, @@ -46,7 +46,8 @@ am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = -am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(pkglibdir)" "$(DESTDIR)$(pkgsysconfdir)" "$(DESTDIR)$(python_moduledir)" +am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(pkglibdir)" \ + "$(DESTDIR)$(pkgsysconfdir)" "$(DESTDIR)$(python_moduledir)" binSCRIPT_INSTALL = $(INSTALL_SCRIPT) SCRIPTS = $(bin_SCRIPTS) SOURCES = @@ -57,6 +58,12 @@ RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \ install-recursive installcheck-recursive installdirs-recursive \ pdf-recursive ps-recursive uninstall-info-recursive \ uninstall-recursive +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = `echo $$p | sed -e 's|^.*/||'`; dist_pkglibDATA_INSTALL = $(INSTALL_DATA) pkgsysconfDATA_INSTALL = $(INSTALL_DATA) python_moduleDATA_INSTALL = $(INSTALL_DATA) @@ -127,6 +134,8 @@ am__fastdepCXX_TRUE = @am__fastdepCXX_TRUE@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ bindir = @bindir@ build_alias = @build_alias@ datadir = @datadir@ @@ -227,7 +236,7 @@ install-dist_pkglibDATA: $(dist_pkglib_DATA) test -z "$(pkglibdir)" || $(mkdir_p) "$(DESTDIR)$(pkglibdir)" @list='$(dist_pkglib_DATA)'; for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ - f="`echo $$p | sed -e 's|^.*/||'`"; \ + f=$(am__strip_dir) \ echo " $(dist_pkglibDATA_INSTALL) '$$d$$p' '$(DESTDIR)$(pkglibdir)/$$f'"; \ $(dist_pkglibDATA_INSTALL) "$$d$$p" "$(DESTDIR)$(pkglibdir)/$$f"; \ done @@ -235,7 +244,7 @@ install-dist_pkglibDATA: $(dist_pkglib_DATA) uninstall-dist_pkglibDATA: @$(NORMAL_UNINSTALL) @list='$(dist_pkglib_DATA)'; for p in $$list; do \ - f="`echo $$p | sed -e 's|^.*/||'`"; \ + f=$(am__strip_dir) \ echo " rm -f '$(DESTDIR)$(pkglibdir)/$$f'"; \ rm -f "$(DESTDIR)$(pkglibdir)/$$f"; \ done @@ -244,7 +253,7 @@ install-pkgsysconfDATA: $(pkgsysconf_DATA) test -z "$(pkgsysconfdir)" || $(mkdir_p) "$(DESTDIR)$(pkgsysconfdir)" @list='$(pkgsysconf_DATA)'; for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ - f="`echo $$p | sed -e 's|^.*/||'`"; \ + f=$(am__strip_dir) \ echo " $(pkgsysconfDATA_INSTALL) '$$d$$p' '$(DESTDIR)$(pkgsysconfdir)/$$f'"; \ $(pkgsysconfDATA_INSTALL) "$$d$$p" "$(DESTDIR)$(pkgsysconfdir)/$$f"; \ done @@ -252,7 +261,7 @@ install-pkgsysconfDATA: $(pkgsysconf_DATA) uninstall-pkgsysconfDATA: @$(NORMAL_UNINSTALL) @list='$(pkgsysconf_DATA)'; for p in $$list; do \ - f="`echo $$p | sed -e 's|^.*/||'`"; \ + f=$(am__strip_dir) \ echo " rm -f '$(DESTDIR)$(pkgsysconfdir)/$$f'"; \ rm -f "$(DESTDIR)$(pkgsysconfdir)/$$f"; \ done @@ -261,7 +270,7 @@ install-python_moduleDATA: $(python_module_DATA) test -z "$(python_moduledir)" || $(mkdir_p) "$(DESTDIR)$(python_moduledir)" @list='$(python_module_DATA)'; for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ - f="`echo $$p | sed -e 's|^.*/||'`"; \ + f=$(am__strip_dir) \ echo " $(python_moduleDATA_INSTALL) '$$d$$p' '$(DESTDIR)$(python_moduledir)/$$f'"; \ $(python_moduleDATA_INSTALL) "$$d$$p" "$(DESTDIR)$(python_moduledir)/$$f"; \ done @@ -269,7 +278,7 @@ install-python_moduleDATA: $(python_module_DATA) uninstall-python_moduleDATA: @$(NORMAL_UNINSTALL) @list='$(python_module_DATA)'; for p in $$list; do \ - f="`echo $$p | sed -e 's|^.*/||'`"; \ + f=$(am__strip_dir) \ echo " rm -f '$(DESTDIR)$(python_moduledir)/$$f'"; \ rm -f "$(DESTDIR)$(python_moduledir)/$$f"; \ done @@ -347,14 +356,16 @@ TAGS: tags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ $(TAGS_FILES) $(LISP) tags=; \ here=`pwd`; \ - if (etags --etags-include --version) >/dev/null 2>&1; then \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ include_option=--etags-include; \ + empty_fix=.; \ else \ include_option=--include; \ + empty_fix=; \ fi; \ list='$(SUBDIRS)'; for subdir in $$list; do \ if test "$$subdir" = .; then :; else \ - test -f $$subdir/TAGS && \ + test ! -f $$subdir/TAGS || \ tags="$$tags $$include_option=$$here/$$subdir/TAGS"; \ fi; \ done; \ @@ -364,9 +375,11 @@ TAGS: tags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ done | \ $(AWK) ' { files[$$0] = 1; } \ END { for (i in files) print i; }'`; \ - test -z "$(ETAGS_ARGS)$$tags$$unique" \ - || $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ - $$tags $$unique + if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique; \ + fi ctags: CTAGS CTAGS: ctags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ $(TAGS_FILES) $(LISP) @@ -417,15 +430,17 @@ distdir: $(DISTFILES) || exit 1; \ fi; \ done - list='$(SUBDIRS)'; for subdir in $$list; do \ + list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ if test "$$subdir" = .; then :; else \ test -d "$(distdir)/$$subdir" \ - || mkdir "$(distdir)/$$subdir" \ + || $(mkdir_p) "$(distdir)/$$subdir" \ || exit 1; \ + distdir=`$(am__cd) $(distdir) && pwd`; \ + top_distdir=`$(am__cd) $(top_distdir) && pwd`; \ (cd $$subdir && \ $(MAKE) $(AM_MAKEFLAGS) \ - top_distdir="../$(top_distdir)" \ - distdir="../$(distdir)/$$subdir" \ + top_distdir="$$top_distdir" \ + distdir="$$distdir/$$subdir" \ distdir) \ || exit 1; \ fi; \ @@ -457,7 +472,7 @@ mostlyclean-generic: clean-generic: distclean-generic: - -rm -f $(CONFIG_CLEAN_FILES) + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @@ -522,15 +537,16 @@ uninstall-info: uninstall-info-recursive distclean-recursive distclean-tags distdir dvi dvi-am html \ html-am info info-am install install-am install-binSCRIPTS \ install-data install-data-am install-dist_pkglibDATA \ - install-exec install-exec-am install-info install-info-am \ - install-man install-pkgsysconfDATA install-python_moduleDATA \ - install-strip installcheck installcheck-am installdirs \ - installdirs-am maintainer-clean maintainer-clean-generic \ - maintainer-clean-recursive mostlyclean mostlyclean-generic \ - mostlyclean-recursive pdf pdf-am ps ps-am tags tags-recursive \ - uninstall uninstall-am uninstall-binSCRIPTS \ - uninstall-dist_pkglibDATA uninstall-info-am \ - uninstall-pkgsysconfDATA uninstall-python_moduleDATA + install-exec install-exec-am install-exec-hook install-info \ + install-info-am install-man install-pkgsysconfDATA \ + install-python_moduleDATA install-strip installcheck \ + installcheck-am installdirs installdirs-am maintainer-clean \ + maintainer-clean-generic maintainer-clean-recursive \ + mostlyclean mostlyclean-generic mostlyclean-recursive pdf \ + pdf-am ps ps-am tags tags-recursive uninstall uninstall-am \ + uninstall-binSCRIPTS uninstall-dist_pkglibDATA uninstall-hook \ + uninstall-info-am uninstall-pkgsysconfDATA \ + uninstall-python_moduleDATA capisuitefax: capisuitefax.in diff --git a/scripts/SConscript b/scripts/SConscript new file mode 100644 index 0000000..77b9576 --- /dev/null +++ b/scripts/SConscript @@ -0,0 +1,53 @@ +# -*- python -*- + +Import('env') + +def py_compile(target, source, env): + """compile python modules for .../python2.x/site-packages/""" + # Note: this differs from #/capisuite/SConscript.py_compile in 'dfile' + import py_compile, os.path + py_compile.compile(source[0].abspath, + #cfile=target[0].abspath, + dfile = os.path.join(env.subst('$python_moduledir'), + env.subst('$SOURCE.file')), + ) + +# these ar meant to be used by users +user_scripts = [env.FileSubst('capisuitefax', 'capisuitefax.in'),] +env.AddPostAction(user_scripts, 'chmod 755 $TARGETS') + +# these are meant to be used by the admin +sbin_scripts = [File('capisuite-checkconfig'),] +#env.AddPostAction(sbin_scripts, 'chmod 755 $TARGETS') + +# config files +configs = [ + env.FileSubst('fax.conf', 'fax.confin'), + env.FileSubst('answering_machine.conf', 'answering_machine.confin') + ] + +# this is no longer needed +# todo: check cs_helper.py into cvs instead of cs_helper.pyin +env.FileSubst('cs_helpers.py', 'cs_helpers.pyin') + +pymodules = [] +for mod in Split('cs_helpers'): + pymodules.append(mod+'.py') + pymodules.append(env.Command(mod + '.pyc', mod+'.py', py_compile)) + +#--- install --- + +install_pylib = env.Install('$python_moduledir', pymodules) +Alias('install-pylib',install_pylib) + +for i in [env.Install('$pkgbindir', user_scripts), + env.Install('$pkgsbindir', sbin_scripts), + env.Install('$pkglibdir', Split('idle.py incoming.py'))]: + Alias('install-scripts', i) + Alias('install', i) + +Alias('install', + env.Install('$python_moduledir', pymodules), + env.Install('$pkgsysconfdir', configs), + env.Install('$pkglibdir', 'README'), + ) diff --git a/scripts/answering_machine.confin b/scripts/answering_machine.confin index b2772cd..853d471 100644 --- a/scripts/answering_machine.confin +++ b/scripts/answering_machine.confin @@ -85,6 +85,24 @@ record_silence_timeout="5" # header field. voice_email_from="capisuite daemon " +############################################################################### +############################# Mail settings ################################### +############################################################################### + +# defined for voice receive: call_from, call_to, date, +# msg_length, filename, hostname + +[MailVoiceReceived] +subject = Received a voice call from %(call_from)s to %(call_to)s +text = + You got a voice call from %(call_from)s to %(call_to)s + Date: %(date)s + Length: %(msg_length)i seconds + + See attached file. + The original file was saved to file://%(filename)s on host "%(hostname)s". + + ############################################################################### ############################# user settings ################################### ############################################################################### @@ -108,12 +126,12 @@ voice_email_from="capisuite daemon " # is necessary for example for the austrian "Global Call" where no number is # signalled when the main MSN is called (sic). # -# voice_email=",,..." (optional, defaults to empty string) +# voice_email=",,..." (optional) # # If given, this string indicates email-addresses where the received faxes -# and voice calls will be sent to. If it is empty, the recorded calls and +# and voice calls will be sent to. If it is not given, the recorded calls and # faxes will be sent to the user on the current system. If you don't want to -# get emails, see the "action" option below +# get emails, see the "action" option below. # # pin="" (optional, defaults to empty) # @@ -142,6 +160,5 @@ voice_email_from="capisuite daemon " #voice_action="MailAndSave" #voice_delay="10" #record_length="60" -#voice_email="" #pin="99*45" diff --git a/scripts/capisuite-checkconfig b/scripts/capisuite-checkconfig new file mode 100755 index 0000000..88da219 --- /dev/null +++ b/scripts/capisuite-checkconfig @@ -0,0 +1,131 @@ +#!/usr/bin/python + +import sys, os, os.path + +def error(*msgs): + for m in msgs: + print >>sys.stderr, m, + print >>sys.stderr + sys.exit(10) + +try: + import capisuite.config + import capisuite.fileutils + from capisuite.consts import * +except Exception, e: + error("failed to import capisuite's python modules", str(e)) + + +# Sompe helper functions, if you don't know what they do, just skip to +# the easy part + +def test_nextfile(user, path, queue, prefix): + path = os.path.join(path, user, queue) + if not os.path.exists(path): + print "Directory", path, "does not exist" + print " ==> this is ok, if this queue has never been", + print "used before (e.g. never send a fax)" + print + return + file = os.path.join(path, prefix+"-nextnr") + if not os.path.exists(file): + print "File", file, "does not exist" + print " ==> this is ok, if this queue has never been", + print "used before (e.g. never send a fax)" + print + return + + print "Trying to read file:", file + num = None + try: + num = capisuite.fileutils.readCounter(file, default=None) + except IOError, err: + print >>sys.stderr, "****Failed to read/parse the *nextnr file:", err + except ValueError: + print >>sys.stderr, "****Failed to convert file content to int number" + else: + if num is None: + print " ", file, 'is unset' + else: + print " ", file, 'contains number', num + + +def checkQdirs(user, basedir, queuedir): + path = os.path.join(basedir, user, queuedir) + if not os.path.exists(path): + if os.getuid() == 0: + print 'creating user queue dir', path + capisuite.fileutils._mkuserdir(user, basedir, user, queuedir) + return 1 + else: + print >>sys.stderr, 'missing user queue dir', path + return 0 + return 1 + +# END helper functions ================ + +def checkGlobalConfig(file=None): + print "Reading global configuration ...", + try: + config = capisuite.config.readGlobalConfig(file) + except IOError, err: + print + error("Failed to read/parse config file:", err) + print 'okay' + + print + print "========= current configuration ===================" + for section in config.sections(): + print "----------------", '['+section+']', "-------------" + options = config.options(section) + options.sort() + for option in options: + print option, '=', config.get(section, option) + print + + + print + print "=========== Testing User Queues and Sequence Files =====" + print + + basedir = config.get('GLOBAL', 'fax_user_dir') + userdirs_missing = 0 + for user in config.listUsers(): + print "----------------", user, "-------------" + for queue in (SEND_Q, RECEIVED_Q): + if not checkQdirs(user, basedir, queue): + userdirs_missing += 1 + else: + test_nextfile(user, basedir, queue, "fax") + if queue == RECEIVED_Q: + test_nextfile(user, basedir, queue, "voice") + + if userdirs_missing: + print + print 'Some user queue dirs are missing.' + print "Rerun as 'root' to create them." + print + +#----------- main -----------# + +if len(sys.argv) == 1: + checkGlobalConfig() +elif 0: + checkGlobalConfig(sys.argv[1]) +else: + config = capisuite.config.readGlobalConfig(sys.argv[1]) + sections = config.sections() + sections.sort() + for section in sections: + print "----------------", '['+section+']', "-------------" + options = config.options(section) + options.sort() + for option in options: + print option, '=', config.get(section, option) + print + print 'known users:', + print config.listUsers() +# todo: these checks: +# - are all users valid system users? +# - do all voice-users have 'voice_delay' set? +# - option --configured to chekc if any fax or voice user is configured diff --git a/scripts/capisuitefax.in b/scripts/capisuitefax.in index eb5b256..7084c65 100755 --- a/scripts/capisuitefax.in +++ b/scripts/capisuitefax.in @@ -1,4 +1,5 @@ #!@PYTHON@ +# -*- mode: python -*- # # capisuitefax - capisuite tool for enqueuing faxes # --------------------------------------------------- @@ -11,18 +12,10 @@ # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. -import getopt,os,sys,re,time,pwd,errno,fcntl,string +import getopt, os, sys, pwd, string, commands # capisuite stuff -import cs_helpers - -dialstring="" -addressee="" -subject="" -abort="" -user="" -quiet=0 -listqueue=0 -useprefix=1 +import capisuite.fax +import capisuite.config def usage(error=""): print """capisuitefax - capisuite tool for enqueueing faxes @@ -50,165 +43,163 @@ other options: -l, --list print jobs in the send queue The given files must be in Adobe PostScript or PDF format""" - if (error!=""): + if error: print print "ERROR:",error sys.exit(1) -def showlist(config,user): - sendq=cs_helpers.getOption(config,"","fax_user_dir") - if (sendq==None): - print "ERROR: option fax_user_dir not set in fax configuration" - sys.exit(1) - sendq=os.path.join(sendq,user,"sendq")+"/" - print "ID Nr./Addressee Tries Next try Subject" +def convert2Fax(filenames, faxname): + # todo: clean this up, maybe rewrite it + """ + convert file 'filename' to a SFF fax file 'faxfile'. + """ + def checkFile(fname, fname_): + if not os.access(fname, os.R_OK): + print >> sys.stderr, "can't open", fname + return 0 + status, filetype = commands.getstatusoutput( + "file -b -i %s 2>/dev/null" % fname_) + if status: + usage("Error when executing command 'file'") + return 0 + if filetype.find("application/postscript") < 0 \ + and filetype.find("application/pdf") < 0: + print >> sys.stderr, arg, "is not a PostScript/PDF file" + return 0 + return 1 - files=os.listdir(sendq) - files=filter (lambda s: re.match("fax-.*\.txt",s),files) - if (not len(files)): - print "--- queue empty ---" + # todo: catch errors! + files = [] + for fname in filenames: + fname_ = commands.mkarg(fname) + if not checkFile(fname, fname_): + return 0 + files.append(fname_) + command = "gs -dNOPAUSE -dQUIET -dBATCH -sDEVICE=cfax " \ + "-sOutputFile=%s %s" % (faxname, ' '.join(files)) + ret = os.system(command) >> 8 + if ret: + print >> sys.stderr, "error during SFF-conversion ot file", arg + print >> sys.stderr, "Ghostscript not installed?" + sys.exit() - for job in files: - control=cs_helpers.readConfig(sendq+job) - sys.stdout.write(re.match("fax-([0-9]+)\.txt",job).group(1)) - sys.stdout.write("\t") - dest=cs_helpers.getOption(control,"GLOBAL","addressee","") - if (dest==""): - dest=control.get("GLOBAL","dialstring") - sys.stdout.write(dest+"\t") - if (len(dest)<8): - sys.stdout.write("\t") - sys.stdout.write(control.get("GLOBAL","tries")+"\t") - sys.stdout.write(control.get("GLOBAL","starttime")+"\t") - sys.stdout.write(cs_helpers.getOption(control,"GLOBAL","subject","")+"\n") - - sys.exit(0) - -def abortjob(config,user,job): - sendq=cs_helpers.getOption(config,"","fax_user_dir") - if (sendq==None): - print "ERROR: option fax_user_dir not set in fax configuration" - sys.exit(1) - sendq=os.path.join(sendq,user,"sendq")+"/" - job="fax-"+job+".txt" - - if (not os.access(sendq+job,os.W_OK)): - print "job to abort not valid" - sys.exit(1) +def showQueue(config, user): try: - lockfile=open(sendq+job[:-3]+"lock","w") - # lock so that it isn't deleted while sending - fcntl.lockf(lockfile,fcntl.LOCK_EX | fcntl.LOCK_NB) - os.unlink(sendq+job) - os.unlink(sendq+job[:-3]+"sff") - fcntl.lockf(lockfile,fcntl.LOCK_UN) - os.unlink(sendq+job[:-3]+"lock") - except IOError,err: - if (err.errno in (errno.EACCES,errno.EAGAIN)): - print "Job is currently in transmission. Can't abort." + jobs = capisuite.fax.getQueueDetails(config, user) + except capisuite.config.NoOptionError: + print "ERROR: option fax_user_dir not set in fax configuration" + return 10 + if not jobs: + print "--- queue empty ---" + else: + format = "%s\t%s %2s\t%-24s %s" + print format % ('ID', 'Numb./Addr.', 'Try', 'Next try', 'Subject') + for entry in jobs: + print format % entry + return 0 + + +def abortJob(config, user, job): + # todo: test whether 'job' is an integer + try: + capisuite.fax.abortUserJob(config, user, jobnum=job) + except capisuite.fax.InvalidJob: + print "job to abort not valid" + return 1 + except capisuite.fax.JobLockedError: + print "Job is currently in transmission. Can't abort." + return 1 + return 0 try: - optlist,args = getopt.getopt(sys.argv[1:], "d:a:u:lhqnA:S:" - ,['dialstring=','noprefix','help',"abort=","list","quiet","user=", - 'addressee=','subject=']) + optlist,args = getopt.getopt(sys.argv[1:], "d:a:u:lhqnA:S:", + ['dialstring=','noprefix','help',"abort=","list","quiet","user=", + 'addressee=','subject=']) except getopt.GetoptError, e: usage(e.msg) + sys.exit(1) # read options -for option,param in optlist: - if option in ('-d','--dialstring'): dialstring=param - if option in ('-A','--addressee'): addressee=param - if option in ('-S','--subject'): subject=param - if option in ('-n','--noprefix'): useprefix=0 +dialstring = addressee = subject = abort = user = "" +quiet = listqueue = 0 +useprefix = 1 + +for option, param in optlist: + # commands if option in ('-h','--help'): usage() - if option in ('-l','--list'): listqueue=1 - if option in ('-a','--abort'): abort=param - if option in ('-q','--quiet'): quiet=1 - if option in ('-u','--user'): - if (os.getuid()==0): - user=param + elif option in ('-l','--list'): listqueue = 1 + elif option in ('-a','--abort'): abort = param + # send options + elif option in ('-d','--dialstring'): dialstring = param + elif option in ('-A','--addressee'): addressee = param + elif option in ('-S','--subject'): subject = param + elif option in ('-n','--noprefix'): useprefix = 0 + elif option in ('-q','--quiet'): quiet = 1 + elif option in ('-u','--user'): + if not os.getuid(): + user = param else: usage("--user may only used as root!") -if (not abort and not listqueue and not dialstring): +if not abort and not listqueue and not dialstring: usage("No usable command given.") -# filter out common separators from dialstring, check it -dialstring=dialstring.translate(string.maketrans("",""),"-/ ()") -for i in dialstring: - if ((i>'9' or i<'0') and i not in ('+*#')): - usage("Invalid dialstring given.") +config = capisuite.config.readGlobalConfig() +if not user: + user = pwd.getpwuid(os.getuid()).pw_name -if (dialstring and len(args)==0): - usage("No fax files given") +if listqueue: + sys.exit(showQueue(config, user)) +elif abort: + try: + jobnum = int(abort) + except ValueError: + print "Error: job id has to be a number, but is %r" % abort + sys.exit(1) + sys.exit(abortJob(config, user, jobnum)) +else: + # queue fax for sending -# test if this user is allowed to send faxes -config=cs_helpers.readConfig() -if (user==""): - user=pwd.getpwuid(os.getuid())[0] -if (not config.has_section(user)): - print "Sorry, you're no valid user for CapiSuite" - sys.exit(1) + # filter out common separators from dialstring, check it + dialstring = dialstring.translate(string.maketrans("",""),"-/ ()") + for i in dialstring: + if not i in '+0123456789*#': + usage("Invalid dialstring given, character %r is not allowed." % i) -if ((cs_helpers.getOption(config,user,"outgoing_MSN","")=="") and (config.get(user,"fax_numbers","")=="")): - print "Sorry, you're not allowed to use fax services" - sys.exit(1) + if dialstring and len(args)==0: + usage("No fax files given") -# test environment -sendq=cs_helpers.getOption(config,"","fax_user_dir") -if (sendq==None): - print "ERROR: option fax_user_dir not set in fax configuration" - sys.exit(1) -sendq=os.path.join(sendq,user,"sendq")+"/" -if (not os.access(sendq,os.W_OK)): - print "can't write to queue dir" - sys.exit(1) + # test if this user is allowed to send faxes + if not config.has_section(user): + print "Sorry, you're no valid user for CapiSuite" + sys.exit(1) -if (listqueue): - showlist(config,user) + if not config.has_option(user, "outgoing_MSN") and \ + not config.has_option(user, "fax_numbers"): + print "Sorry, you're not allowed to use fax services" + sys.exit(1) -if (abort): - abortjob(config,user,abort) + if useprefix: + dialstring = config.getUser(user, "dial_prefix", '') + dialstring -prefix=cs_helpers.getOption(config,user,"dial_prefix","") -if (useprefix): - dialstring=prefix+dialstring - -# convert and enqueue files -for i in args: - if (not os.access(i,os.R_OK)): - sys.stderr.write("can't open "+i+'\n') - continue - t=os.popen("file -b -i "+cs_helpers.escape(i)+" 2>/dev/null") - filetype=t.read() - if (t.close()): - usage("can't execute \"file\"") - if (not re.search("application/postscript",filetype) \ - and not re.search("application/pdf",filetype)): - sys.stderr.write(i+" is not a PostScript/PDF file\n") - continue - - newname=cs_helpers.uniqueName(sendq,"fax","sff") - - command="gs -dNOPAUSE -dQUIET -dBATCH -sDEVICE=cfax -sOutputFile=" \ - + newname+" "+cs_helpers.escape(i) - ret=(os.system(command))>>8 - if (ret): - sys.stderr.write("error during SFF-conversion at file "+i+'. \ - Ghostscript not installed?\n') - sys.exit() - - cs_helpers.writeDescription(newname,"dialstring=\""+dialstring+"\"\n" - +"starttime=\""+time.ctime()+"\"\ntries=\"0\"\n" - +"user=\""+user+"\"\naddressee=\""+addressee+"\"\nsubject=\"" - +subject+"\"\n") - os.chmod(newname,0600) - os.chmod(newname[:-3]+"txt",0600) - if (os.getuid()==0): - user_entry=pwd.getpwnam(user) - os.chown(newname,user_entry[2],user_entry[3]) - os.chown(newname[:-3]+"txt",user_entry[2],user_entry[3]) - print i,"successful enqueued as",newname,"for",dialstring + # convert and enqueue files + jobDesc = { + "dialstring": dialstring, + #"user": user, + "addressee": addressee, + "subject": subject, + # todo: allow these to be specified for this jobs + #'stationID': ... + #'headline': ... + } + try: + jobnum = capisuite.fax.enqueueJob(config, user, args, + convert2Fax, **jobDesc) + except IOError, e: + print e + print "can't write to queue dir" + sys.exit(1) + print "Successful enqueued as job", jobnum, "for", dialstring diff --git a/scripts/cs_helpers.pyin b/scripts/cs_helpers.pyin index 6d43b38..46830ea 100644 --- a/scripts/cs_helpers.pyin +++ b/scripts/cs_helpers.pyin @@ -1,3 +1,4 @@ +# -*- mode: python -*- # cs_helpers.py - some helper functions for CapiSuite scripts # ----------------------------------------------------------- # copyright : (C) 2002 by Gernot Hillier @@ -9,131 +10,84 @@ # 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" +import os, commands -# @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 +from capisuite.config import * +from capisuite.voice import sayNumber, getAudio -# @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 "'%s'" % filename.replace("'","'\\''") +# Note: readConfig is now imported from capisuite.config -# @brief get an option from the user or global section +# @brief escape a argument to include it savely in a shell command # -# The option is searched in the users section and if not found -# in the global section. +# This is just a wrapper to commands.mkarg which strips the leading +# space which mkarg() adds. # -# @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 +# @return the escaped argument +def escape(arg): + arg = commands.mkarg(arg) + if arg[0] == ' ': + arg = arg[1:] + return arg -# @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(os.path.join(userdir,filename),os.R_OK)): - return os.path.join(userdir,filename) - else: - return os.path.join(systemdir,filename) +def getOption(config, user, option, default=None): + return config.getUser(user, option, default) -# @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(os.path.join(directory,"cs_lock"),"w") - fcntl.lockf(lockfile,fcntl.LOCK_EX) +# Note: getAudio is now imported from capisuite.voice +# Note: uniqueName is now imported from capisuite.fileutils +def uniqueName(*args, **kwargs): + return capisuite.fileutils.uniqueName(*args, **kwargs)[1] - try: - countfile=open("%s-nextnr" % os.path.join(directory,basename),"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("%s-.*\.%s" % (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="%s-%i.%s" % (os.path.join(directory,basename),nextnr,suffix) +def __sendmail(mail_from, mail_to, msg): + import popen2, capisuite.core - countfile=open("%s-nextnr" % os.path.join(directory,basename),"w") - countfile.write('%i\n' % (nextnr+1)) - countfile.close() + r,w = popen2.popen2("{ /usr/sbin/sendmail -t -f %s } 2>&1" % escape(mail_from)) + try: + w.write(msg.as_string()) + except IOError: #Errno 32: Broken Pipe + capisuite.core.error("Error while calling sendmail. Not installed?\n") + return 0 + w.close() + #ret = sendmail.wait() + text = r.read() + r.close() + if text: + capisuite.core.error("Error while calling sendmail")#, return code=%i" % ret) + capisuite.core.error(text) + return 0 + capisuite.core.log("sendmail finished successful",3) + return 1 - # unlock - fcntl.lockf(lockfile,fcntl.LOCK_UN) - lockfile.close() - os.unlink(os.path.join(directory,"cs_lock")) - return newname +def __sendmail(fromaddr, toaddr, msg): + import smtplib, capisuite.core + smtpserver, port = 'localhost', 25 + server = smtplib.SMTP(smtpserver, port) + server.sendmail(fromaddr, (toaddr), msg.as_string()) + server.quit() + capisuite.core.log("mail sent successfully",3) + return 1 + + +def __call(msg, cmd, *args): + """ + outfile MUST be last parameter! + """ + # todo: think about using commands.getstatusoutput() here + ret = os.spawnlp(os.P_WAIT, cmd, cmd, *(args)) + if ret or not os.access(args[-1], os.F_OK): + raise ConvertionError("Error while converting %s. " + "File damaged or %s not installed?" %(msg, cmd)) + +def sff2tif(infile, outfile): + __call('sff to tif', "sfftobmp", "-tif", infile, outfile) + +def cff2ps(infile, outfile): + __call("cff to ps", "jpeg2ps", "-m", infile , "-o", outfile) + +def la2wav(infile, outfile): + __call('la to wav', "sox", infile, '-w', outfile) + +class ConvertionError(Exception): pass # @brief send email with text and attachment of type sff or la converted to pdf/wav # @@ -149,95 +103,84 @@ def uniqueName(directory,basename,suffix): # @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 +def sendMIMEMail(mail_from, mail_to, mail_subject, mail_type, + text, attachment): + import email.MIMEBase, email.MIMEText, email.MIMEAudio, email.Encoders + import encodings.ascii, os - msg.preamble = 'This is a Multipart-MIME-message. Please use a capable mailer.\n' - msg.epilogue = '' # To guarantee the message ends with a newline + msg = email.MIMEBase.MIMEBase("multipart","mixed") + msg['Subject']=mail_subject + msg['From']=mail_from + msg['To']=mail_to - 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,"%stif" % basename) - if (ret or not os.access("%stif" % basename,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 %s | ps2pdf - -" % escape("%stif" % basename) - 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="%spdf" % os.path.basename(basename)) - filepart.add_header('Content-Disposition','attachment',filename="%spdf" % os.path.basename(basename)) - filepart.set_payload(tiff2pdf.fromchild.read()) - tiff2pdf.fromchild.close() - ret=tiff2pdf.wait() - if (ret!=0): - raise "conv-error","Error %i occured during tiff2ps or ps2pdf" % ret - os.unlink("%stif" % basename) - 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","%sps" % basename) - if (ret or not os.access("%sps" % basename,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 %s -" % escape("%sps" % basename) - 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="%spdf" % os.path.basename(basename)) - filepart.add_header('Content-Disposition','attachment',filename="%spdf" % os.path.basename(basename)) - filepart.set_payload(ps2pdf.fromchild.read()) - ps2pdf.fromchild.close() - ret=ps2pdf.wait() - if (ret!=0): - raise "conv-error","Error %i occured during ps2pdf" % ret - os.unlink("%sps" % basename) - 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","%swav" % basename) - if (ret or not os.access("%swav" % basename,os.R_OK)): - raise "conv-error","Error while calling sox. File damaged or sox not installed?" - filepart = email.MIMEAudio.MIMEAudio(open("%swav" % basename).read(),"x-wav",email.Encoders.encode_base64,name="%swav" % os.path.basename(basename)) - filepart.add_header('Content-Disposition','attachment',filename="%swav" % os.path.basename(basename)) - os.unlink("%swav" % basename) - textpart = email.MIMEText.MIMEText(text) - msg.attach(textpart) - msg.attach(filepart) - except "conv-error",errormessage: - text="%s\n\nERROR occured while converting file: %s\nPlease talk to your friendly administrator.\n" % (text,errormessage) - textpart = email.MIMEText.MIMEText(text) - msg.attach(textpart) + msg.preamble = 'This is a Multipart-MIME-message. Please use a capable mailer.\n' + msg.epilogue = '' # To guarantee the message ends with a newline + + basepath = os.path.splitext(attachment)[0] + basename = os.path.basename(basepath) + try: + if mail_type == "sff": # normal fax file + # convert sff -> tif + sff2tif(attachment, "%s.tif" % basepath) + # convert tif -> ps -> pdf + cmd = "tiff2ps -a %s | ps2pdf - -" % escape("%s.tif" %basepath) + try: + status, content = commands.getstatusoutput(cmd) + finally: + os.unlink("%s.tif" % basepath) + if status: + raise ConvertionError("Error while calling tiff2ps or ps2pdf. " + "Not installed?") + filepart = email.MIMEBase.MIMEBase("application","pdf", + name = "%s.pdf" % basename) + filepart.add_header('Content-Disposition','attachment', + filename = "%s.pdf" % basename) + filepart.set_payload(content) + email.Encoders.encode_base64(filepart) + elif mail_type == "cff": # color fax file + # convert cff -> ps + cff2ps(attachment, "%s.ps" % basepath) + # convert ps -> pdf + cmd = "ps2pdf %s -" % escape("%s.ps" % basepath) + try: + status, content = commands.getstatusoutput(cmd) + finally: + os.unlink("%s.ps" % basepath) + if status: + raise ConvertionError("Error while calling ps2pdf. " + "Not installed?") + filepart = email.MIMEBase.MIMEBase("application", "pdf", + name = "%s.pdf" % basename) + filepart.add_header('Content-Disposition', 'attachment', + filename="%s.pdf" % basename) + filepart.set_payload(content) + email.Encoders.encode_base64(filepart) + elif mail_type == "la": # voice file + # la -> wav + la2wav(attachment, "%s.wav" % basepath) + content = open("%s.wav" % basepath).read() + os.unlink("%s.wav" % basepath) + filepart = email.MIMEAudio.MIMEAudio(content, "x-wav", + email.Encoders.encode_base64, + name = "%s.wav" % basename) + filepart.add_header('Content-Disposition', 'attachment', + filename = "%s.wav" % basename) + textpart = email.MIMEText.MIMEText(text) + msg.attach(textpart) + msg.attach(filepart) + except ConvertionError, errormessage: + text = [text, '' + 'The following error occured while converting file:', + str(errormessage), '' + 'Please talk to your friendly administrator.', '' + ] + # todo: add encoding (at least as example) + # todo: use latin-1 as std-encoding instead of ascii + textpart = email.MIMEText.MIMEText('\n'.join(text)) + msg.attach(textpart) + + return __sendmail(mail_from, mail_to, msg) - sendmail = popen2.Popen3("sendmail -t -f %s" % 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=%i" % ret) - else: - capisuite.log("sendmail finished successful",3) # @brief send a simple text email # @@ -247,28 +190,21 @@ def sendMIMEMail(mail_from,mail_to,mail_subject,mail_type,text,attachment): # @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) +# +# @result returns true on success +def sendSimpleMail(mail_from, mail_to, mail_subject, text): + #import email.Encoders, email.MIMEText, encodings.ascii - msg['Subject'] = mail_subject - msg['From'] = mail_from - msg['To'] = mail_to - - sendmail = popen2.Popen3("sendmail -t -f %s" % 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=%i" % ret) - else: - capisuite.log("sendmail finished successful",3) + # Create a text/plain message. Don't forget to change charset here + # if you want to use non-us-ascii characters in the mail! + # todo: add encoding anyway (at least as example) + # todo: use latin-1 as std-encoding instead of ascii + import email.MIMEText + msg = email.MIMEText.MIMEText(text) + msg['Subject'] = mail_subject + msg['From'] = mail_from + msg['To'] = mail_to + return __sendmail(mail_from, mail_to, msg) # @brief write description file for received fax or voice @@ -279,157 +215,19 @@ def sendSimpleMail(mail_from,mail_to,mail_subject,text): # # @param filename the data filename (with extension!) # @param content the content as string -def writeDescription(filename,content): - descr=open("%stxt" % filename[:filename.rindex('.')+1],"w") - descr.write("# Description file for %s\n" % filename) - 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=\"%s\"\n" % filename) - descr.write(content) - descr.close() +def writeDescription(filename, content): + from types import StringType + assert isinstance(content, StringType) + import capisuite.fileutils + descrname = capisuite.fileutils.controlname(filename) + descr = open(descrname, "w") + print >> descr, "# Description file for", filename + print >> descr, "# This if for internal use of CapiSuite." + print >> descr, "# Only change if you know what you do!" + print >> descr, "[GLOBAL]" + print >> descr, 'filename="%s"' % filename + print >> descr, 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 -# @param gender if the number is used in connection with a singular noun ("f" --> "eine Nachricht") -def sayNumber(call,number,curr_user,config,gender="-"): - 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 (gender!="-" and number in ("1","01")): - if (gender in ("n","m")): - capisuite.audio_send(call,getAudio(config,curr_user,"ein.la"),1) - else: - capisuite.audio_send(call,getAudio(config,curr_user,"eine.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,"%s.la" % number),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,"%s0.la" % number[0]),1) - else: - capisuite.audio_send(call,getAudio(config,curr_user,"%s.la" % number[1]),1) - capisuite.audio_send(call,getAudio(config,curr_user,"und.la"),1) - capisuite.audio_send(call,getAudio(config,curr_user,"%s0.la" % number[0]),1) - else: - for i in number: - capisuite.audio_send(call,getAudio(config,curr_user,"%s.la" % i),1) +# sayNumber is now imported from capisuite.voice -# 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 -# diff --git a/scripts/fax.confin b/scripts/fax.confin index 5e233a1..799951c 100644 --- a/scripts/fax.confin +++ b/scripts/fax.confin @@ -114,6 +114,55 @@ fax_headline="Sent by CapiSuite (www.CapiSuite.de)" # messages. fax_email_from="capisuite daemon " +############################################################################### +############################# Mail settings ################################### +############################################################################### + +######### +# defined for fax receive: call_from, call_to, stationID, bitRate, +# resolution color, numPages, filename, hostname + +[MailFaxReceived] +subject = Received a fax from %(call_from)s to %(call_to)s +text = + You got a fax from %(call_from)s to %(call_to)s + Station ID: %(stationID)s + Transmission Details: bit rate %(bitRate)i %(resolution)s %(color)s + Pages: %(numPages)i + + See attached file. + The original file was saved to file://%(filename)s on host "%(hostname)s". + +######### +# defined for fax send: addressee, dialstring, subject, filename, tries, +# result, resultB3, filename, hostname + +[MailFaxSent] +subject = Fax to %(addressee)s (%(dialstring)s) sent successfully. +text = + Your fax job to %(addressee)s (%(dialstring)s) was sent successfully. + + Subject: %(subject)s + Filename: %(filename)s + Number of tries: %(tries)i + Last result: 0x%(result)x/0x%(resultB3)x + + It was moved to file://%(filename)s on host %(hostname)s. + + +[MailFaxFailed] +subject = Fax to %(addressee)s (%(dialstring)s) FAILED. +text = + I'm sorry, but your fax job to %(addressee)s (%(dialstring)s) failed finally. + + Subject: %(subject)s + Filename: %(filename)s + Number of tries: %(tries)i + Last result: 0x%(result)x/0x%(resultB3)x + + It was moved to file://%(filename)s on host %(hostname)s. + + ############################################################################### ############################# user settings ################################### ############################################################################### diff --git a/scripts/idle.py b/scripts/idle.py index 7d50ecc..fd4458e 100644 --- a/scripts/idle.py +++ b/scripts/idle.py @@ -1,3 +1,5 @@ +# -*- mode: python ; coding: iso_8859_15 -*- +# # idle.py - default script for capisuite # --------------------------------------------- # copyright : (C) 2002 by Gernot Hillier @@ -10,247 +12,140 @@ # (at your option) any later version. # -import os,re,time,pwd,fcntl +import os, time, pwd, fcntl + # capisuite stuff -import capisuite,cs_helpers +#import capisuite +import capisuite.fax +from capisuite.config import NoOptionError +import capisuite.core as core + +# todo: eliminate this +import cs_helpers + +# sendfax is now imported from capisuite.fax + +from capisuite.fileutils import _releaseLock, _getLock, LockTakenError def idle(capi): - config=cs_helpers.readConfig() - spool=cs_helpers.getOption(config,"","spool_dir") - if (spool==None): - capisuite.error("global option spool_dir not found.") - return - - done=os.path.join(spool,"done") - failed=os.path.join(spool,"failed") - if (not os.access(done,os.W_OK) or not os.access(failed,os.W_OK)): - capisuite.error("Can't read/write to the necessary spool dirs") - return + config = capisuite.config.readGlobalConfig() + try: + spool = config.get('GLOBAL', "spool_dir") + max_tries = config.getint('GLOBAL', "send_tries") + delays = config.getList('GLOBAL', "send_delays") + except NoOptionError, err: + core.error("global option %s not found." % err.option) + return - userlist=config.sections() - userlist.remove('GLOBAL') + # todo: implement config.getQueue(queue,user=None) + doneQ = os.path.join(spool, "done") + failedQ = os.path.join(spool, "failed") - for user in userlist: # search in all user-specified sendq's - userdata=pwd.getpwnam(user) - outgoing_nr=cs_helpers.getOption(config,user,"outgoing_MSN","") - if (outgoing_nr==""): - incoming_nrs=cs_helpers.getOption(config,user,"fax_numbers","") - if (incoming_nrs==""): - continue - else: - outgoing_nr=(incoming_nrs.split(','))[0] + if not os.access(doneQ, os.W_OK) or not os.access(failedQ, os.W_OK): + core.error("Can't read/write to the necessary spool dirs") + return - udir=cs_helpers.getOption(config,"","fax_user_dir") - if (udir==None): - capisuite.error("global option fax_user_dir not found.") - return - udir=os.path.join(udir,user) - sendq=os.path.join(udir,"sendq") - if (not os.access(udir,os.F_OK)): - os.mkdir(udir,0700) - os.chown(udir,userdata[2],userdata[3]) - if (not os.access(sendq,os.F_OK)): - os.mkdir(sendq,0700) - os.chown(sendq,userdata[2],userdata[3]) + # search in all user-specified sendq's + for user in config.listUsers(): + outgoing_num = config.getUser(user, "outgoing_msn") + if not outgoing_num: + incoming_nums = config.getUser(user, "fax_numbers") + if not incoming_nums: + continue + outgoing_num = incoming_nums.split(',')[0].strip() - files=os.listdir(sendq) - files=filter (lambda s: re.match("fax-.*\.txt",s),files) + mailaddress = config.getUser(user, "fax_email", user) + fromaddress = config.getUser(user, "fax_email_from", user) - for job in files: - job_fax="%ssff" % job[:-3] - real_user_c=os.stat(os.path.join(sendq,job)).st_uid - real_user_j=os.stat(os.path.join(sendq,job_fax)).st_uid - if (real_user_j!=pwd.getpwnam(user)[2] or real_user_c!=pwd.getpwnam(user)[2]): - capisuite.error("job %s seems to be manipulated (wrong uid)! Ignoring..." % os.path.join(sendq,job_fax)) - continue + for jobnum, controlfile in capisuite.fax.getQueueFiles(config, user): + assert controlfile == os.path.abspath(controlfile) + try: + # lock the job so that it isn't deleted while sending + lock = _getLock(forfile=controlfile, blocking=0) + except LockTakenError: + # if we didn't get the lock, continue with next job + continue + try: + control = capisuite.config.JobDescription(controlfile) - lockfile=open(os.path.join(sendq,"%slock" % job[:-3]),"w") - # read directory contents - fcntl.lockf(lockfile,fcntl.LOCK_EX) # lock so that it isn't deleted while sending + fax_file = control.get('filename') + assert fax_file == os.path.abspath(fax_file) - if (not os.access(os.path.join(sendq,job),os.W_OK)): # perhaps it was cancelled? - fcntl.lockf(lockfile,fcntl.LOCK_UN) - lockfile.close() - os.unlink(os.path.join(sendq,"%slock" % job[:-3])) - continue + # both the job control file and the fax file must have + # the users uid + uid = pwd.getpwnam(user).pw_uid + if os.stat(controlfile).st_uid != uid or \ + os.stat(fax_file).st_uid != uid: + core.error("job %s seems to be manipulated (wrong uid)! " + "Ignoring..." % controlfile) + _releaseLock(lock) + continue - control=cs_helpers.readConfig(os.path.join(sendq,job)) - # set DST value to -1 (unknown), as strptime sets it wrong for some reason - starttime=(time.strptime(control.get("GLOBAL","starttime")))[0:8]+(-1,) - starttime=time.mktime(starttime) - if (starttime>time.time()): - fcntl.lockf(lockfile,fcntl.LOCK_UN) - lockfile.close() - os.unlink(os.path.join(sendq,"%slock" % job[:-3])) - continue + # todo: describe what is tested here + # perhaps it was cancelled? + if not os.access(controlfile, os.W_OK): + _releaseLock(lock) + continue - tries=control.getint("GLOBAL","tries") - dialstring=control.get("GLOBAL","dialstring") - addressee=cs_helpers.getOption(control,"GLOBAL","addressee","") - subject=cs_helpers.getOption(control,"GLOBAL","subject","") - mailaddress=cs_helpers.getOption(config,user,"fax_email","") - if (mailaddress==""): - mailaddress=user - fromaddress=cs_helpers.getOption(config,user,"fax_email_from","") - if (fromaddress==""): - fromaddress=user + # set DST value to -1 (unknown), as strptime sets it wrong + # for some reason + starttime = time.strptime(control.get("starttime"))[:-1]+(-1, ) + starttime = time.mktime(starttime) + if starttime > time.time(): + _releaseLock(lock) + continue - capisuite.log("job %s from %s to %s initiated" % (job_fax,user,dialstring),1) - result,resultB3 = sendfax(capi,os.path.join(sendq,job_fax),outgoing_nr,dialstring,user,config) - tries+=1 - capisuite.log("job %s: result was %x,%x" % (job_fax,result,resultB3),1) + sendinfo = { + 'outgoing_num': outgoing_num, + 'dialstring': control.get("dialstring") + } + # these options may overwrite the global settings per job + for n in ('stationID', 'headline'): + if control.has_option(n): + sendinfo[n] = control.get(n) - if (result in (0,0x3400,0x3480,0x3490,0x349f) and resultB3==0): - movejob(job_fax,sendq,done,user) - capisuite.log("job %s: finished successfully" % job_fax,1) - mailtext="Your fax job to %s (%s) was sent successfully.\n\n" \ - "Subject: %s\nFilename: %s\nNeeded tries: %i\n" \ - "Last result: 0x%x/0x%x\n\nIt was moved to " \ - "file://%s on host \"%s\"" % (addressee,dialstring, \ - subject,job_fax,tries,result,resultB3, \ - os.path.join(done,"%s-%s" % (user,job_fax)), \ - os.uname()[1]) - cs_helpers.sendSimpleMail(fromaddress,mailaddress, - "Fax to %s (%s) sent successfully." % (addressee,dialstring), - mailtext) - else: - max_tries=int(cs_helpers.getOption(config,"","send_tries","10")) - delays=cs_helpers.getOption(config,"","send_delays","60,60,60,300,300,3600,3600,18000,36000").split(",") - delays=map(int,delays) - if ((tries-1)=max_tries): - movejob(job_fax,sendq,failed,user) - capisuite.log("job %s: failed finally" % job_fax,1) - mailtext="I'm sorry, but your fax job to %s (%s) " \ - "failed finally.\n\nSubject: %s\n" \ - "Filename: %s\nTries: %i\n" \ - "Last result: 0x%x/0x%x\n\n" \ - "It was moved to file://%s-%s on host %s.\n\n" \ - % (addressee,dialstring,subject,job_fax,tries,result, \ - resultB3,os.path.join(failed,user),job_fax,os.uname()[1]) - cs_helpers.sendSimpleMail(fromaddress,mailaddress, - "Fax to %s (%s) FAILED." % (addressee,dialstring), - mailtext) - - fcntl.lockf(lockfile,fcntl.LOCK_UN) - lockfile.close() - os.unlink("%slock" % os.path.join(sendq,job[:-3])) - -def sendfax(capi,job,outgoing_nr,dialstring,user,config): - try: - controller=int(cs_helpers.getOption(config,"","send_controller","1")) - timeout=int(cs_helpers.getOption(config,user,"outgoing_timeout","60")) - stationID=cs_helpers.getOption(config,user,"fax_stationID") - if (stationID==None): - capisuite.error("Warning: fax_stationID for user %s not set" % user) - stationID="" - headline=cs_helpers.getOption(config,user,"fax_headline","") - (call,result)=capisuite.call_faxG3(capi,controller,outgoing_nr,dialstring,timeout,stationID,headline) - if (result!=0): - return(result,0) - capisuite.fax_send(call,job) - return(capisuite.disconnect(call)) - except capisuite.CallGoneError: - return(capisuite.disconnect(call)) - -def movejob(job,olddir,newdir,user): - os.rename(os.path.join(olddir,job),os.path.join(newdir,"%s-%s" % (user,job))) - os.rename(os.path.join(olddir,"%stxt" % job[:-3]),os.path.join(newdir, "%s-%stxt" % (user,job[:-3]))) - -# -# History: -# -# Old Log (for new changes see ChangeLog): -# Revision 1.11 2003/12/02 18:50:09 gernot -# - fax_numbers is really allowed to be empty now... -# -# Revision 1.10 2003/10/03 13:42:09 gernot -# - added new options "fax_email_from" and "voice_email_from" -# -# Revision 1.9 2003/09/21 12:34:37 gernot -# - add 0x349f to list of normal results -# -# Revision 1.8 2003/06/26 11:53:17 gernot -# - fax jobs can be given an addressee and a subject now (resolves #18, reported -# by Achim Bohnet) -# -# Revision 1.7 2003/06/19 14:58:43 gernot -# - fax_numbers is now really optional (bug #23) -# - tries counter was wrongly reported (bug #29) -# -# Revision 1.6 2003/04/06 11:07:40 gernot -# - fix for 1-hour-delayed sending of fax (DST problem) -# -# Revision 1.5 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.4 2003/03/13 11:09:58 gernot -# - use stricted permissions for saved files and created userdirs. Fixes -# bug #544 -# -# Revision 1.3 2003/03/09 11:48:10 gernot -# - removed wrong unlock operation (lock not acquired at this moment!) -# -# Revision 1.2 2003/03/06 09:59:11 gernot -# - added "file://" as prefix to filenames in sent mails, thx to -# Achim Bohnet for this suggestion -# -# Revision 1.1.1.1 2003/02/19 08:19:54 gernot -# initial checkin of 0.4 -# -# Revision 1.12 2003/02/18 09:54:22 ghillie -# - added missing lockfile deletions, corrected locking protocol -# -> fixes Bugzilla 23731 -# -# Revision 1.11 2003/02/17 16:48:43 ghillie -# - do locking, so that jobs can be deleted -# -# Revision 1.10 2003/02/10 14:50:52 ghillie -# - revert logic of outgoing_MSN: it's overriding the first number of -# fax_numbers now -# -# Revision 1.9 2003/02/05 15:59:11 ghillie -# - search for *.txt instead of *.sff so no *.sff which is currently created -# by capisuitefax will be found! -# -# Revision 1.8 2003/01/31 11:22:00 ghillie -# - use different sendq's for each user (in his user_dir). -# - use prefix user- for names in done and failed -# -# Revision 1.7 2003/01/27 21:56:46 ghillie -# - mailaddress may be not set, that's the same as "" -# - use first entry of fax_numbers as outgoing MSN if it exists -# -# Revision 1.6 2003/01/27 19:24:29 ghillie -# - updated to use new configuration files for fax & answering machine -# -# Revision 1.5 2003/01/19 12:03:27 ghillie -# - use capisuite log functions instead of stdout/stderr -# -# Revision 1.4 2003/01/17 15:09:26 ghillie -# - updated to use new configuration file capisuite-script.conf -# -# Revision 1.3 2003/01/13 16:12:00 ghillie -# - renamed from idle.pyin to idle.py as all previously processed variables -# stay in the config file and cs_helpers.pyin now -# -# Revision 1.2 2002/12/16 13:07:22 ghillie -# - finished queue processing -# -# Revision 1.1 2002/12/14 13:53:19 ghillie -# - idle.py and incoming.py are now auto-created from *.pyin -# + core.log("job %s from %s to %s initiated" % + (jobnum, user, sendinfo['dialstring']), 1) + result, resultB3 = capisuite.fax.sendfax(config, user, capi, + fax_file, **sendinfo) + core.log("job %s: result was %x, %x" % \ + (jobnum, result, resultB3), 1) + sendinfo['result'] = result + sendinfo['resultB3'] = resultB3 + + # todo: use symbolic names for these results to be more + # meaningfull + send_ok = resultB3 == 0 \ + and result in (0, 0x3400, 0x3480, 0x3490, 0x349f) + tries = control.getint("tries") +1 + if send_ok: + core.log("job %s: finished successfully" % jobnum, 1) + control = capisuite.fax.moveJob(controlfile, doneQ, user) + sendinfo.update(control.items()) + sendinfo['hostname'] = os.uname()[1] + cs_helpers.sendSimpleMail( + fromaddress, mailaddress, + config.get('MailFaxSent', 'subject') % sendinfo, + config.get('MailFaxSent', 'text') % sendinfo) + elif tries >= max_tries: + # too many ties, send failed + core.log("job %s: failed finally" % jobnum, 1) + control = capisuite.fax.moveJob(controlfile, failedQ, user) + sendinfo.update(control.items()) + cs_helpers.sendSimpleMail( + fromaddress, mailaddress, + config.get('MailFaxFailed', 'subject') % sendinfo, + config.get('MailFaxFailed', 'text') % sendinfo) + else: + # delay next try + next_delay = int(delays[ min(len(delays),tries) -1 ]) + core.log("job %s: delayed for %i seconds" % \ + (jobnum, next_delay), 2) + starttime = time.time() + next_delay + control.set('starttime', time.ctime(starttime)) + control.set('tries', tries) + control.write(controlfile) + finally: + _releaseLock(lock) diff --git a/scripts/incoming.py b/scripts/incoming.py index a05b27c..bbfcb92 100644 --- a/scripts/incoming.py +++ b/scripts/incoming.py @@ -1,3 +1,5 @@ +# -*- mode: python ; coding: iso_8859_15 -*- +# # incoming.py - standard incoming script for capisuite # ---------------------------------------------------- # copyright : (C) 2002 by Gernot Hillier @@ -11,581 +13,419 @@ # # general imports -import time,os,re,string,pwd +import time, os + # CapiSuite imports -import capisuite,cs_helpers +import capisuite, cs_helpers +from capisuite.config import NoOptionError +import capisuite.fileutils as fileutils +import capisuite.fax +import capisuite.voice +from capisuite import core +say = capisuite.voice.say # shortcut -# @brief main function called by CapiSuite when an incoming call is received -# -# It will decide if this call should be accepted, with which service and for -# which user. The real call handling is done in faxIncoming and voiceIncoming. -# -# @param call reference to the call. Needed by all capisuite functions -# @param service one of SERVICE_FAXG3, SERVICE_VOICE, SERVICE_OTHER -# @param call_from string containing the number of the calling party -# @param call_to string containing the number of the called party -def callIncoming(call,service,call_from,call_to): - # read config file and search for call_to in the user sections - try: - config=cs_helpers.readConfig() - userlist=config.sections() - userlist.remove('GLOBAL') - curr_user="" +def callIncoming(call, service, call_from, call_to): + """ + Main function called by CapiSuite when an incoming call is received. - for u in userlist: - if config.has_option(u,'voice_numbers'): - numbers=config.get(u,'voice_numbers') - if (call_to in numbers.split(',') or numbers=="*"): - if (service==capisuite.SERVICE_VOICE): - curr_user=u - curr_service=capisuite.SERVICE_VOICE - break - if (service==capisuite.SERVICE_FAXG3): - curr_user=u - curr_service=capisuite.SERVICE_FAXG3 - break + It will decide if this call should be accepted, with which service + and for which user. The real call handling is done in faxIncoming + and voiceIncoming. - if config.has_option(u,'fax_numbers'): - numbers=config.get(u,'fax_numbers') - if (call_to in numbers.split(',') or numbers=="*"): - if (service in (capisuite.SERVICE_FAXG3,capisuite.SERVICE_VOICE)): - curr_user=u - curr_service=capisuite.SERVICE_FAXG3 - break + 'call' reference to the call. Needed by all capisuite functions + 'service' one of SERVICE_FAXG3, SERVICE_VOICE, SERVICE_OTHER + 'call_from' string containing the number of the calling party + 'call_to' string containing the number of the called party + """ + # convert into a python call handle + # TODO-gh: can't we get rid of this line? + call = core.Call(call, service, call_from, call_to) + # read config file and search for call.to_nr in the user sections + try: + config = capisuite.config.readGlobalConfig() + except IOError, e: + core.error("Error occured during config file reading: %s " + "Disconnecting..." % e) + call.reject(0x34A9) + return - except IOError,e: - capisuite.error("Error occured during config file reading: %s Disconnecting..." % e) - capisuite.reject(call,0x34A9) - return - # answer the call with the right service - if (curr_user==""): - capisuite.log("call from %s to %s ignoring" % (call_from,call_to),1,call) - capisuite.reject(call,1) - return - try: - if (curr_service==capisuite.SERVICE_VOICE): - delay=cs_helpers.getOption(config,curr_user,"voice_delay") - if (delay==None): - capisuite.error("voice_delay not found for user %s! -> rejecting call" % curr_user) - capisuite.reject(call,0x34A9) - return - capisuite.log("call from %s to %s for %s connecting with voice" % (call_from,call_to,curr_user),1,call) - capisuite.connect_voice(call,int(delay)) - voiceIncoming(call,call_from,call_to,curr_user,config) - elif (curr_service==capisuite.SERVICE_FAXG3): - faxIncoming(call,call_from,call_to,curr_user,config,0) - except capisuite.CallGoneError: # catch exceptions from connect_* - (cause,causeB3)=capisuite.disconnect(call) - capisuite.log("connection lost with cause 0x%x,0x%x" % (cause,causeB3),1,call) + for user in config.listUsers(): + # accept a voice call on 'voice_numbers' + if config.has_option(user, 'voice_numbers'): + numbers = config.getList(user, 'voice_numbers') + if numbers == ["*"] or call.to_nr in numbers: + if service in (core.SERVICE_VOICE, ): + break + # accept a voice or fax call on 'fax_numbers' + if config.has_option(user, 'fax_numbers'): + numbers = config.getList(user, 'fax_numbers') + if numbers == ["*"] or call.to_nr in numbers: + if service in (core.SERVICE_FAXG3, core.SERVICE_VOICE): + # set service type to 'fax' + service = core.SERVICE_FAXG3 + break + else: + # no matching entry found (no users as this number) + call.log("call from %s to %s ignoring" % (call.from_nr, call.to_nr), 1) + call.reject(1) + return -# @brief called by callIncoming when an incoming fax call is received -# -# @param call reference to the call. Needed by all capisuite functions -# @param call_from string containing the number of the calling party -# @param call_to string containing the number of the called party -# @param curr_user name of the user who is responsible for this -# @param config ConfigParser instance holding the config data -# @param already_connected 1 if we're already connected (that means we must switch to fax mode) -def faxIncoming(call,call_from,call_to,curr_user,config,already_connected): - try: - udir=cs_helpers.getOption(config,"","fax_user_dir") - if (udir==None): - capisuite.error("global option fax_user_dir not found! -> rejecting call") - capisuite.reject(call,0x34A9) - return - udir=os.path.join(udir,curr_user) - if (not os.path.exists(udir)): - userdata=pwd.getpwnam(curr_user) - os.mkdir(udir,0700) - os.chown(udir,userdata[2],userdata[3]) - if (not os.path.exists(os.path.join(udir,"received"))): - userdata=pwd.getpwnam(curr_user) - os.mkdir(os.path.join(udir,"received"),0700) - os.chown(os.path.join(udir,"received"),userdata[2],userdata[3]) - except KeyError: - capisuite.error("user %s is not a valid system user. Disconnecting" % curr_user,call) - capisuite.reject(call,0x34A9) - return - filename="" # assure the variable is defined... - faxInfo=None - try: - stationID=cs_helpers.getOption(config,curr_user,"fax_stationID") - if (stationID==None): - capisuite.error("Warning: fax_stationID not found for user %s -> using empty string" % curr_user) - stationID="" - headline=cs_helpers.getOption(config,curr_user,"fax_headline","") # empty string is no problem here - capisuite.log("call from %s to %s for %s connecting with fax" % (call_from,call_to,curr_user),1,call) - if (already_connected): - faxInfo=capisuite.switch_to_faxG3(call,stationID,headline) - else: - faxInfo=capisuite.connect_faxG3(call,stationID,headline,0) - if (faxInfo!=None and faxInfo[3]==1): - faxFormat="cff" # color fax - else: - faxFormat="sff" # normal b&w fax - filename=cs_helpers.uniqueName(os.path.join(udir,"received"),"fax",faxFormat) - faxInfo=capisuite.fax_receive(call,filename) - (cause,causeB3)=capisuite.disconnect(call) - capisuite.log("connection finished with cause 0x%x,0x%x" % (cause,causeB3),1,call) + # answer the call with the right service + try: + if service == core.SERVICE_VOICE: + voiceIncoming(config, user, call) + elif service == core.SERVICE_FAXG3: + faxIncoming(config, user, call, 0) + else: + raise RuntimeError + except NoOptionError, err: + core.error("global option %s not found -> rejecting call" % + err.option) + call.reject(0x34A9) + except fileutils.UnknownUserError: + core.error("user %s is not a valid system user. Disconnecting." % user, + call) + call.reject(0x34A9) + except core.CallGoneError: + causes = call.disconnect() + call.log("connection lost with cause 0x%x, 0x%x" % causes, 1) - except capisuite.CallGoneError: # catch this here to get the cause info in the mail - (cause,causeB3)=capisuite.disconnect(call) - capisuite.log("connection lost with cause 0x%x,0x%x" % (cause,causeB3),1,call) - if (os.access(filename,os.R_OK)): - cs_helpers.writeDescription(filename, - "call_from=\"%s\"\ncall_to=\"%s\"\ntime=\"%s\"\n" \ - "cause=\"0x%x/0x%x\"\n" % (call_from,call_to,time.ctime(),cause,causeB3)) - userdata=pwd.getpwnam(curr_user) - os.chmod(filename,0600) - os.chown(filename,userdata[2],userdata[3]) - os.chmod("%stxt" % filename[:-3],0600) - os.chown("%stxt" % filename[:-3],userdata[2],userdata[3]) +def faxIncoming(config, user, call, already_connected): + """ + Called by callIncoming when an incoming fax call is received - fromaddress=cs_helpers.getOption(config,curr_user,"fax_email_from","") - if (fromaddress==""): - fromaddress=curr_user - mailaddress=cs_helpers.getOption(config,curr_user,"fax_email","") - if (mailaddress==""): - mailaddress=curr_user - action=cs_helpers.getOption(config,curr_user,"fax_action","").lower() - if (action not in ("mailandsave","saveonly")): - capisuite.error("Warning: No valid fax_action definition found for user %s -> assuming SaveOnly" % curr_user) - action="saveonly" - if (action=="mailandsave"): - mailText="You got a fax from %s to %s\nDate: %s" % (call_from,call_to,time.ctime()) - if (faxInfo!=None and len(faxInfo)>=5): - mailText="%sStation ID: %s\nTransmission Details: bit rate %i " \ - "%s %s\nPages: %i\n\nSee attached file.\n" \ - "The original file was saved to file://%s " \ - "on host \"%s\"." % (mailText,faxInfo[0], \ - faxInfo[1],(faxInfo[2] and "hiRes" or "loRes"), \ - (faxInfo[3] and "color" or ""),faxInfo[4], \ - filename,os.uname()[1]) - cs_helpers.sendMIMEMail(fromaddress, mailaddress, "Fax received from %s to %s" % (call_from,call_to), - faxFormat, mailText, filename) + 'call' a python Call handle referencing to the call + 'user' name of the user who is responsible for this + 'config' ConfigParser instance holding the config data + 'already_connected' ture if we're already connected (that means we must + switch to fax mode) + """ + # todo: use config.getQueue + _mkdir here + receivedQ = fileutils._mkuserdir(user, + config.get('GLOBAL', "fax_user_dir"), + user, "received") -# @brief called by callIncoming when an incoming voice call is received -# -# @param call reference to the call. Needed by all capisuite functions -# @param call_from string containing the number of the calling party -# @param call_to string containing the number of the called party -# @param curr_user name of the user who is responsible for this -# @param config ConfigParser instance holding the config data -def voiceIncoming(call,call_from,call_to,curr_user,config): - try: - udir=cs_helpers.getOption(config,"","voice_user_dir") - if (udir==None): - capisuite.error("global option voice_user_dir not found! -> rejecting call") - capisuite.reject(call,0x34A9) - return - udir=os.path.join(udir,curr_user) - if (not os.path.exists(udir)): - userdata=pwd.getpwnam(curr_user) - os.mkdir(udir,0700) - os.chown(udir,userdata[2],userdata[3]) - if (not os.path.exists(os.path.join(udir,"received"))): - userdata=pwd.getpwnam(curr_user) - os.mkdir(os.path.join(udir,"received"),0700) - os.chown(os.path.join(udir,"received"),userdata[2],userdata[3]) - except KeyError: - capisuite.error("user %s is not a valid system user. Disconnecting" % curr_user,call) - capisuite.reject(call,0x34A9) - return - filename=cs_helpers.uniqueName(os.path.join(udir,"received"),"voice","la") - action=cs_helpers.getOption(config,curr_user,"voice_action","").lower() - if (action not in ("mailandsave","saveonly","none")): - capisuite.error("Warning: No valid voice_action definition found for user %s -> assuming SaveOnly" % curr_user) - action="saveonly" - try: - capisuite.enable_DTMF(call) - userannouncement=os.path.join(udir,cs_helpers.getOption(config,curr_user,"announcement","announcement.la")) - pin=cs_helpers.getOption(config,curr_user,"pin","") - if (os.access(userannouncement,os.R_OK)): - capisuite.audio_send(call,userannouncement,1) - else: - if (call_to!="-"): - capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"anrufbeantworter-von.la"),1) - cs_helpers.sayNumber(call,call_to,curr_user,config) - capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"bitte-nachricht.la"),1) + # assure these variables are defined + filename = faxInfo = None - if (action!="none"): - capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"beep.la"),1) - length=cs_helpers.getOption(config,curr_user,"record_length","60") - silence_timeout=cs_helpers.getOption(config,curr_user,"record_silence_timeout","5") - msg_length=capisuite.audio_receive(call,filename,int(length), int(silence_timeout),1) + stationID = config.getUser(user, "fax_stationID", default="") + if not stationID: + core.error("Warning: fax_stationID not found for user %s " + "-> using empty string" % user) + # empty string is no problem for headline + headline = config.getUser(user, "fax_headline", default="") - dtmf_list=capisuite.read_DTMF(call,0) - if (dtmf_list=="X"): - if (os.access(filename,os.R_OK)): - os.unlink(filename) - faxIncoming(call,call_from,call_to,curr_user,config,1) - elif (dtmf_list!="" and pin!=""): - dtmf_list="%s%s" % (dtmf_list,capisuite.read_DTMF(call,3)) # wait 5 seconds for input - count=1 - while (count<3 and pin!=dtmf_list): # try again if input was wrong - capisuite.log("wrong PIN entered...",1,call) - capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"beep.la")) - dtmf_list=capisuite.read_DTMF(call,3) - count+=1 - if (pin==dtmf_list): - if (os.access(filename,os.R_OK)): - os.unlink(filename) - capisuite.log("Starting remote inquiry...",1,call) - remoteInquiry(call,udir,curr_user,config) + call.log("call from %s to %s for %s connecting with fax" % \ + (call.from_nr, call.to_nr, user), 1) + try: + if already_connected: + faxInfo = call.switch_to_faxG3(stationID, headline) + else: + faxInfo = call.connect_faxG3(stationID, headline) + filename = fileutils.uniqueName(receivedQ, "fax", faxInfo.format)[1] + call.fax_receive(filename) + causes = call.disconnect() + call.log("connection finished with cause 0x%x, 0x%x" % causes, 1) + except core.CallGoneError: + # catch this here to get the cause info into the mail + causes = call.disconnect() + call.log("connection lost with cause 0x%x, 0x%x" % causes, 1) + # todo: send error mail here? Don't think it makes sense to send + # a mail on each try, which would mean sending 10 mails for one fax... + # If the user wants to know the current status he should use "capisuitefax -l" + else: + assert filename + if os.access(filename, os.R_OK): + faxInfo = faxInfo.as_dict() + faxInfo.update({ + 'filename' : filename, + 'call_from': call.from_nr, + 'call_to' : call.to_nr, + 'causes' : causes, + 'hostname' : os.uname()[1] + }) + capisuite.fax.createReceivedJob(user, **faxInfo) + action = _getAction(config, user, "fax_action", + ("saveonly", "mailandsave")) + if action == "mailandsave": + fromaddress = config.getUser(user, "fax_email_from", user) + mailaddress = config.getUser(user, "fax_email", user) - (cause,causeB3)=capisuite.disconnect(call) - capisuite.log("connection finished with cause 0x%x,0x%x" % (cause,causeB3),1,call) + cs_helpers.sendMIMEMail( + fromaddress, mailaddress, + config.get('MailFaxReceived', 'subject') % faxInfo, + faxInfo['format'], + config.get('MailFaxReceived', 'text') % faxInfo, + filename) - except capisuite.CallGoneError: # catch this here to get the cause info in the mail - (cause,causeB3)=capisuite.disconnect(call) - capisuite.log("connection lost with cause 0x%x,0x%x" % (cause,causeB3),1,call) - if (os.access(filename,os.R_OK)): - cs_helpers.writeDescription(filename, - "call_from=\"%s\"\ncall_to=\"%s\"\ntime=\"%s\"\n" \ - "cause=\"0x%x/0x%x\"\n" % (call_from,call_to,time.ctime(),cause,causeB3)) - userdata=pwd.getpwnam(curr_user) - os.chmod(filename,0600) - os.chown(filename,userdata[2],userdata[3]) - os.chmod("%stxt" % filename[:-2],0600) - os.chown("%stxt" % filename[:-2],userdata[2],userdata[3]) +def _getAction(config, user, action_name, allowed_actions): + action = config.getUser(user, action_name, "").lower() + if action not in allowed_actions: + action = allowed_actions[0] + core.error("Warning: No valid %s definition found for user %s -> " + "assuming %s" % action_name, user, action) + return action - fromaddress=cs_helpers.getOption(config,curr_user,"voice_email_from","") - if (fromaddress==""): - fromaddress=curr_user - mailaddress=cs_helpers.getOption(config,curr_user,"voice_email","") - if (mailaddress==""): - mailaddress=curr_user - if (action=="mailandsave"): - mailText="You got a voice call from %s to %s\n" \ - "Date: %s\nLength: %i s\n\nSee attached file.\n" \ - "The original file was saved to file://%s on" \ - "host \"%s\".\n\n" % (call_from,call_to,time.ctime(), \ - msg_length,filename,os.uname()[1]) - subject="Voice call received from %s to %s" % (call_from,call_to) - cs_helpers.sendMIMEMail(fromaddress,mailaddress,subject,"la",mailText,filename) -# @brief remote inquiry function (uses german wave snippets!) -# -# commands for remote inquiry -# delete message - 1 -# next message - 4 -# last message - 5 -# repeat current message - 6 -# -# @param call reference to the call. Needed by all capisuite functions -# @param userdir spool_dir of the current_user -# @param curr_user name of the user who is responsible for this -# @param config ConfigParser instance holding the config data -def remoteInquiry(call,userdir,curr_user,config): - import time,fcntl,errno,os - # acquire lock - lockfile=open(os.path.join(userdir,"received/inquiry_lock"),"w") - try: - fcntl.lockf(lockfile,fcntl.LOCK_EX | fcntl.LOCK_NB) # only one inquiry at a time! +def voiceIncoming(config, user, call): + """ + Called by callIncoming when an incoming voice call is received - except IOError,err: # can't get the lock - if (err.errno in (errno.EACCES,errno.EAGAIN)): - capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"fernabfrage-aktiv.la")) - lockfile.close() - return + 'call' a python Call handle referencing to the call + 'user' name of the user who is responsible for this + 'config' ConfigParser instance holding the config data + """ + try: + if not config.has_option(user, 'voice_delay'): + core.error("voice_delay not found for user %s! -> " + "rejecting call" % user) + call.reject(0x34A9) + return + delay = config.getint(user, "voice_delay") + call.log("call from %s to %s for %s connecting with voice" % \ + (call.from_nr, call.to_nr, user), 1) + call.connect_voice(delay) - try: - # read directory contents - messages=os.listdir(os.path.join(userdir,"received")) - messages=filter (lambda s: re.match("voice-.*\.la",s),messages) # only use voice-* files - messages=map(lambda s: int(re.match("voice-([0-9]+)\.la",s).group(1)),messages) # filter out numbers - messages.sort() + userdir = config.get('GLOBAL', "voice_user_dir") + action = _getAction(config, user, "voice_action", + ("saveonly", "mailandsave", "none")) + receivedQ = fileutils._mkuserdir(user, userdir, user, "received") + userannouncement = os.path.join(userdir, user, + config.getUser(user, "announcement", "announcement.la")) + pin = config.getUser(user, "pin", "") + filename = None # assure it's defined - # read the number of the message heard last at the last inquiry - lastinquiry=-1 - if (os.access(os.path.join(userdir,"received/last_inquiry"),os.W_OK)): - lastfile=open(os.path.join(userdir,"received/last_inquiry"),"r") - lastinquiry=int(lastfile.readline()) - lastfile.close() + call.enable_DTMF() + if os.access(userannouncement, os.R_OK): + call.audio_send(userannouncement, 1) + else: + if call.to_nr != "-": + say(config, user, call, "anrufbeantworter-von.la") + capisuite.voice.sayNumber(config, user, call, call.to_nr) + say(config, user, call, "bitte-nachricht.la") - # sort out old messages - oldmessages=[] - i=0 - while (ilastinquiry): - lastinquiry=curr_msgs[i] - lastfile=open(os.path.join(userdir,"received/last_inquiry"),"w") - lastfile.write("%i\n" % curr_msgs[i]) - lastfile.close() - i+=1 - elif (cmd=="5"): - i-=1 - capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"keine-weiteren-nachrichten.la")) + Implemented commands for remote inquiry are: + delete message - 1 + next message - 4 + last message - 5 + repeat current message - 6 - finally: - # unlock - fcntl.lockf(lockfile,fcntl.LOCK_UN) - lockfile.close() - os.unlink(os.path.join(userdir,"received/inquiry_lock")) + 'user' name of the user who is responsible for this + 'config' ConfigParser instance holding the config data + 'call' reference to the call. Needed by all capisuite functions + 'receivedQ' the received queue dir of the user + """ + try: + lock = fileutils._getLock(lockname=os.path.join(receivedQ, + 'inquiry_lock'), + blocking=0) + except fileutils.LockTakenError: + say(config, user, call, "fernabfrage-aktiv.la") + return + try: + # read directory contents + messages = capisuite.voice.getQueue(config, user) -# @brief remote inquiry: record new announcement (uses german wave snippets!) -# -# @param call reference to the call. Needed by all capisuite functions -# @param userdir spool_dir of the current_user -# @param curr_user name of the user who is responsible for this -# @param config ConfigParser instance holding the config data -def newAnnouncement(call,userdir,curr_user,config): - capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"bitte-neue-ansage-komplett.la")) - capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"beep.la")) - cmd="" - while (cmd!="1"): - capisuite.audio_receive(call,os.path.join(userdir,"announcement-tmp.la"),60,3) - capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"neue-ansage-lautet.la")) - capisuite.audio_send(call,os.path.join(userdir,"announcement-tmp.la")) - capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"wenn-einverstanden-1.la")) - cmd=capisuite.read_DTMF(call,0,1) - if (cmd!="1"): - capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"bitte-neue-ansage-kurz.la")) - capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"beep.la")) - userannouncement=os.path.join(userdir,cs_helpers.getOption(config,curr_user,"announcement","announcement.la")) - os.rename(os.path.join(userdir,"announcement-tmp.la"),userannouncement) - userdata=pwd.getpwnam(curr_user) - os.chown(userannouncement,userdata[2],userdata[3]) + # read the number of the message heard last at the last inquiry + lastinquiry = capisuite.voice.getInquiryCounter(config, user) + # filter out old messages + oldmessages = [m for m in messages if m[0] <= lastinquiry] + messages = [m for m in messages if m[0] > lastinquiry] + oldmessages.sort() + messages.sort() - capisuite.audio_send(call,cs_helpers.getAudio(config,curr_user,"ansage-gespeichert.la")) + announceNumMessages(config, user, call, len(messages), new=1) + + # menu for record new announcement + cmd = "" + while cmd not in ("1", "9"): + if len(messages) or len(oldmessages): + say(config, user, call, "zum-abhoeren-1.la") + say(config, user, call, "fuer-neue-ansage-9.la") + cmd = call.read_DTMF(0, 1) + if cmd == "9": + cmd_recordNewAnnouncement(config, user, call, receivedQ) + return + + # start inquiry + cmd_inquiry(config, user, call, oldmessages, messages, lastinquiry) + + finally: + fileutils._releaseLock(lock) + + +def announceNumMessages(config, user, call, numMessages, new): + if numMessages == 1: + msg = "nachricht.la" + else: + msg = "nachrichten.la" + if new: + msg = 'neue-' + msg + capisuite.voice.sayNumber(config, user, call, numMessages, gender="f") + say(config, user, call, msg) + + +def cmd_inquiry(config, user, call, oldmessages, messages, lastinquiry): + """ + Remote inquiry function (uses german wave snippets!) + + Implemented commands for remote inquiry are: + delete message - 1 + next message - 4 + last message - 5 + repeat current message - 6 + + 'user' name of the user who is responsible for this + 'config' ConfigParser instance holding the config data + 'call' reference to the call. Needed by all capisuite functions + 'userdir' spool_dir of the current_user + """ + for curr_msgs in (messages, oldmessages): + capisuite.voice.sayNumber(config, user, call, len(curr_msgs), "f") + announceNumMessages(config, user, call, len(messages), + new = (curr_msgs == messages)) + i = 0 + while i < len(curr_msgs): + msgnum, controlfile = curr_msgs[i] + descr = config.JobDescription(controlfile) + # play the announcement + for f in _getSoundsForAnnounceMessages(i, descr): + say(config, user, call, "%s.la" % f) + # play the recorded file + call.audio_send(descr.get('filename'), 1) + cmd = "" + while cmd not in ("1", "4", "5", "6"): + say(config, user, call, "erklaerung.la") + cmd = call.read_DTMF(0, 1) + if cmd == "1": + cmd_deleteMessage(config, user, call, controlfile) + del curr_msgs[i] + elif cmd == "4": + if msgnum > lastinquiry: + lastinquiry = msgnum + capisuite.voice.setInquiryCounter(config, user, msgnum) + i += 1 + elif cmd == "5": + i -= 1 + say(config, user, call, "keine-weiteren-nachrichten.la") + # todo: say 'hauptmenu' + +def _getSoundsForAnnounceMessages(msgnum, descr): + """ + generated a list of sound to be played before replaying a recored message + """ + getNumberFiles = capisuite.voice.getNumberFiles + yield 'nachricht' + for f in getNumberFiles(msgnum+1): yield f + yield 'von' + for f in getNumberFiles(descr.get('call_from')): yield f + yield 'fuer' + for f in getNumberFiles(descr.get('call_to')): yield f + yield 'am' + calltime = time.strptime(descr.get('time')) + for f in getNumberFiles(calltime[2]): yield f + yield '.' + for f in getNumberFiles(calltime[1]): yield f + yield '.' + yield 'um' + for f in getNumberFiles(calltime[3], 'n'): yield f + yield 'uhr' + for f in getNumberFiles(calltime[4]): yield f + +def cmd_deleteMessage(config, user, call, controlfile): + capisuite.fax.abortJob(controlfile) + say(config, user, call, "nachricht-geloescht.la") + + +def cmd_recordNewAnnouncement(config, user, call, userdir): + """ + remote inquiry command: record new announcement (uses german wave snippets!) + 'config' ConfigParser instance holding the config data + 'user' name of the user who is responsible for this + 'call' reference to the call. Needed by all capisuite functions + 'userdir' spool_dir of the current_user + """ + say(config, user, call, "bitte-neue-ansage-komplett.la", "beep.la") + + tmpfile = os.path.join(userdir, "announcement-tmp.la") + while 1: + call.audio_receive(tmpfile, 60, 3) + say(config, user, call, "neue-ansage-lautet.la") + call.audio_send(tmpfile) + say(config, user, call, "wenn-einverstanden-1.la") + cmd = call.read_DTMF(0, 1) + # todo: allow eg. '9' for cancel and go back to menu + if cmd == "1": + break + else: + say(config, user, call, "bitte-neue-ansage-kurz.la", "beep.la") + + userannouncement = os.path.join(userdir, + config.getUser(user, "announcement", "announcement.la")) + os.rename(tmpfile, userannouncement) + fileutils._setProtection(user, userannouncement, mode=0666) + say(config, user, call, "ansage-gespeichert.la") -# -# History: -# -# Old Log (for new changes see ChangeLog): -# Revision 1.13 2003/12/01 20:53:05 gernot -# - confused "hiRes" and "loRes". Thx to Ingo Goeppert -# for the report! -# -# Revision 1.12 2003/10/03 13:42:09 gernot -# - added new options "fax_email_from" and "voice_email_from" -# -# Revision 1.11 2003/08/24 12:47:50 gernot -# - faxIncoming tried to reconnect when it was called after a switch from -# voice to fax mode, which lead to a call abort. Thx to Harald Jansen & -# Andreas Scholz for reporting! -# -# Revision 1.10 2003/07/20 10:30:37 gernot -# - started implementing faxInfo output in sent mails, not working currently -# -# Revision 1.9 2003/06/27 07:51:09 gernot -# - replaced german umlaut in filename "nachricht-gelscht.la", can cause -# problems on Redhat, thx to Herbert Hübner for reporting -# -# Revision 1.8 2003/06/16 10:21:05 gernot -# - define filename in any case (thx to Axel Schneck for reporting and -# analyzing...) -# -# Revision 1.7 2003/05/25 13:38:30 gernot -# - support reception of color fax documents -# -# 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/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.4 2003/03/13 11:08:06 gernot -# - fix remote inquiry locking (should fix bug #534, but doesn't - anyway, -# this fix is definitely necessary) -# - stricter permissions of saved files and created dirs, fixes #544 -# - add "file://" prefix to the path shown in the mails to the user -# -# Revision 1.3 2003/02/21 13:13:34 gernot -# - removed some debug output (oops...) -# -# Revision 1.2 2003/02/21 11:02:17 gernot -# - removed os.setuid() from incoming script -# -> fixes Bug #527 -# -# Revision 1.1.1.1 2003/02/19 08:19:54 gernot -# initial checkin of 0.4 -# -# Revision 1.11 2003/02/17 11:13:43 ghillie -# - remoteinquiry supports new and old messages now -# -# Revision 1.10 2003/02/03 14:50:08 ghillie -# - fixed small typo -# -# Revision 1.9 2003/01/31 16:32:41 ghillie -# - support "*" for "all numbers" -# - automatic switch voice->fax when SI says fax -# -# Revision 1.8 2003/01/31 11:24:41 ghillie -# - wrong user handling for more than one users fixed -# - creates user_dir/user and user_dir/user/received now separately as -# idle.py can also create user_dir/user now -# -# Revision 1.7 2003/01/27 21:57:54 ghillie -# - fax_numbers and voice_numbers may not exist (no fatal error any more) -# - accept missing email option -# - fixed typo -# -# Revision 1.6 2003/01/27 19:24:29 ghillie -# - updated to use new configuration files for fax & answering machine -# -# Revision 1.5 2003/01/19 12:03:27 ghillie -# - use capisuite log functions instead of stdout/stderr -# -# Revision 1.4 2003/01/17 15:09:49 ghillie -# - cs_helpers.sendMail was renamed to sendMIMEMail -# -# Revision 1.3 2003/01/16 12:58:34 ghillie -# - changed DTMF timeout for pin to 3 seconds -# - delete recorded wave if fax or remote inquiry is recognized -# - updates in remoteInquiry: added menu for recording own announcement -# - fixed some typos -# - remoteInquiry: delete description file together with call if requested -# - new function: newAnnouncement -# -# Revision 1.2 2003/01/15 15:55:12 ghillie -# - added exception handler in callIncoming -# - faxIncoming: small typo corrected -# - voiceIncoming & remoteInquiry: updated to new config file system -# -# Revision 1.1 2003/01/13 16:12:58 ghillie -# - renamed from incoming.pyin to incoming.py as all previously processed -# variables are moved to config and cs_helpers.pyin -# -# Revision 1.4 2002/12/18 14:34:56 ghillie -# - added some informational prints -# - accept voice calls to fax nr -# -# Revision 1.3 2002/12/16 15:04:51 ghillie -# - added missing path prefix to delete routing in remote inquiry -# -# Revision 1.2 2002/12/16 13:09:25 ghillie -# - added some comments about the conf_* vars -# - added conf_wavedir -# - added support for B3 cause now returned by disconnect() -# - corrected some dir entries to work in installed system -# -# Revision 1.1 2002/12/14 13:53:18 ghillie -# - idle.py and incoming.py are now auto-created from *.pyin -# -# Revision 1.4 2002/12/11 12:58:05 ghillie -# - read return value from disconnect() -# - added disconnect() to exception handler -# -# Revision 1.3 2002/12/09 15:18:35 ghillie -# - added disconnect() in exception handler -# -# Revision 1.2 2002/12/02 21:30:42 ghillie -# fixed some minor typos -# -# Revision 1.1 2002/12/02 21:15:55 ghillie -# - moved scripts to own directory -# - added remote-connect script to repository -# -# Revision 1.20 2002/12/02 20:59:44 ghillie -# another typo :-| -# -# Revision 1.19 2002/12/02 20:54:07 ghillie -# fixed small typo -# -# Revision 1.18 2002/12/02 16:51:32 ghillie -# nearly complete new script, supports answering machine, fax receiving and remote inquiry now -# -# Revision 1.17 2002/11/29 16:28:43 ghillie -# - updated syntax (connect_telephony -> connect_voice) -# -# Revision 1.16 2002/11/29 11:09:04 ghillie -# renamed CapiCom to CapiSuite (name conflict with MS crypto API :-( ) -# -# Revision 1.15 2002/11/25 11:43:43 ghillie -# updated to new syntax -# -# Revision 1.14 2002/11/23 16:16:17 ghillie -# moved switch2fax after audio_receive() -# -# Revision 1.13 2002/11/22 15:48:58 ghillie -# renamed pcallcontrol module to capicom -# -# Revision 1.12 2002/11/22 15:02:39 ghillie -# - added automatic switch between speech and fax -# - some comments added -# -# Revision 1.11 2002/11/19 15:57:18 ghillie -# - Added missing throw() declarations -# - phew. Added error handling. All exceptions are caught now. -# -# Revision 1.10 2002/11/18 12:32:36 ghillie -# - callIncoming lives now in __main__, not necessarily in pcallcontrol any more -# - added some comments and header -# diff --git a/scripts/remote-connect.py b/scripts/remote-connect.py index 7c81327..fb00ca5 100644 --- a/scripts/remote-connect.py +++ b/scripts/remote-connect.py @@ -1,59 +1,81 @@ -import string,os,time -import pcallcontrol -cc=pcallcontrol +import pcallcontrol as cc +import os, time +import commands -def callWaiting(CIP,callingParty,calledParty): - if (calledParty=="23"): - print "nehme Anruf von",callingParty,"an",calledParty,"an." - cc.connect(16) # 16 = telephony - else: - print "nehme Anruf von",callingParty,"an",calledParty,"nicht an." - cc.reject(2) # 2 = normal call clearing +def acceptCall(CIP, callingParty, calledParty): + print "nehme Anruf von",callingParty,"an",calledParty,"an." + cc.connect(16) # 16 = telephony + +def rejectCall(CIP, callingParty, calledParty): + print "nehme Anruf von",callingParty,"an",calledParty,"nicht an." + cc.reject(2) # 2 = normal call clearing + + +def establishInternetConnection(): + # establish connection + timeout = time.time()+15 # 15 seconds timeout + os.spawnl(os.P_NOWAIT, "ping", "ping", "-w", "1", "www.hillier.de") + c_status = "" + # wait 30 secs for connect + while c_status != "CONNECTED" and time.time() < timeout: + lines = commands.getoutput("/usr/sbin/cinternet --status") + for l in lines.splitlines(): + if l.startswith("status"): + c_status = l[9:].strip() + print 'status:', c_status + time.sleep(1) + # get ip address + save_lang = os.environ["LANG"] + os.environ["LANG"] = "" + lines = commands.getoutput("/sbin/ifconfig ppp0") + os.environ["LANG"] = save_lang + for l in lines: # no need to split into lines + index = l.find("inet addr:") + if index >= 0: + index += 10 + rindex = l.find(" ", index) + ip_address = l[index:rindex] + # play ip address + for i in ip_address: + cc.audio_send(i+".la") + + +callWaitingMap = { + '23': acceptCall, + 'default': denyCall, + } + +def callWaiting(CIP, callingParty, calledParty): + action = callWaitingMap.get(calledParty, None) + if action: + action(CIP, callingParty, calledParty) + else: + action = callWaitingMap.get('default', None) + if action: + action(CIP, callingParty, calledParty) + +callConnectedMap = { + '3008': establishInternetConnection, + } def callConnected(): - try: - cc.enableDTMF() - cc.audio_send("send.la") - code=cc.getDTMF() - print "Bekommen habe ich",code - if code=="3008": - # establish connection - start_t=time.time() - os.spawnl(os.P_NOWAIT,"ping","ping","-w","1","www.hillier.de") - c_status="" - # wait 30 secs for connect - while ((c_status!="CONNECTED") and (time.time()/dev/null 2>&1; then \ + if ($(ETAGS) --etags-include --version) >/dev/null 2>&1; then \ include_option=--etags-include; \ + empty_fix=.; \ else \ include_option=--include; \ + empty_fix=; \ fi; \ list='$(SUBDIRS)'; for subdir in $$list; do \ if test "$$subdir" = .; then :; else \ - test -f $$subdir/TAGS && \ + test ! -f $$subdir/TAGS || \ tags="$$tags $$include_option=$$here/$$subdir/TAGS"; \ fi; \ done; \ @@ -374,9 +381,11 @@ TAGS: tags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ done | \ $(AWK) ' { files[$$0] = 1; } \ END { for (i in files) print i; }'`; \ - test -z "$(ETAGS_ARGS)$$tags$$unique" \ - || $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ - $$tags $$unique + if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique; \ + fi ctags: CTAGS CTAGS: ctags-recursive $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ $(TAGS_FILES) $(LISP) @@ -427,15 +436,17 @@ distdir: $(DISTFILES) || exit 1; \ fi; \ done - list='$(SUBDIRS)'; for subdir in $$list; do \ + list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ if test "$$subdir" = .; then :; else \ test -d "$(distdir)/$$subdir" \ - || mkdir "$(distdir)/$$subdir" \ + || $(mkdir_p) "$(distdir)/$$subdir" \ || exit 1; \ + distdir=`$(am__cd) $(distdir) && pwd`; \ + top_distdir=`$(am__cd) $(top_distdir) && pwd`; \ (cd $$subdir && \ $(MAKE) $(AM_MAKEFLAGS) \ - top_distdir="../$(top_distdir)" \ - distdir="../$(distdir)/$$subdir" \ + top_distdir="$$top_distdir" \ + distdir="$$distdir/$$subdir" \ distdir) \ || exit 1; \ fi; \ @@ -467,7 +478,7 @@ mostlyclean-generic: clean-generic: distclean-generic: - -rm -f $(CONFIG_CLEAN_FILES) + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" diff --git a/src/SConscript b/src/SConscript new file mode 100644 index 0000000..58f8d46 --- /dev/null +++ b/src/SConscript @@ -0,0 +1,23 @@ +# -*- python -*- + +Import('env') +env.Append(capisuite_sources = Dir('.')) + +SConscript('capisuite-py/SConscript') +libmodule = SConscript('modules/SConscript') +libappl = SConscript('application/SConscript') +libback = SConscript('backend/SConscript') + +capisuite = env.Program('capisuite', + ['main.cpp', libappl, libmodule, libback, ]) +env.AddPostAction(capisuite, 'strip $TARGET') + +capisuite_conf = env.FileSubst('capisuite.conf', 'capisuite.conf.in') + +install_exec = env.Install('$sbindir', capisuite) +Alias('install-exec', install_exec) + +Alias('install', + env.Install('$pkgsysconfdir', capisuite_conf), + install_exec, + ) diff --git a/src/application/Makefile.in b/src/application/Makefile.in index 5403d9c..d2b851f 100644 --- a/src/application/Makefile.in +++ b/src/application/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.8.3 from Makefile.am. +# Makefile.in generated by automake 1.9.1 from Makefile.am. # @configure_input@ # Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, @@ -46,9 +46,9 @@ am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = +LIBRARIES = $(noinst_LIBRARIES) AR = ar ARFLAGS = cru -LIBRARIES = $(noinst_LIBRARIES) libccapplication_a_AR = $(AR) $(ARFLAGS) libccapplication_a_LIBADD = am_libccapplication_a_OBJECTS = capisuite.$(OBJEXT) \ @@ -58,11 +58,6 @@ libccapplication_a_OBJECTS = $(am_libccapplication_a_OBJECTS) DEFAULT_INCLUDES = -I. -I$(srcdir) -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__depfiles_maybe = depfiles -@AMDEP_TRUE@DEP_FILES = ./$(DEPDIR)/capisuite.Po \ -@AMDEP_TRUE@ ./$(DEPDIR)/capisuitemodule.Po \ -@AMDEP_TRUE@ ./$(DEPDIR)/idlescript.Po \ -@AMDEP_TRUE@ ./$(DEPDIR)/incomingscript.Po \ -@AMDEP_TRUE@ ./$(DEPDIR)/pythonscript.Po CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) CXXLD = $(CXX) @@ -139,6 +134,8 @@ am__fastdepCXX_TRUE = @am__fastdepCXX_TRUE@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ bindir = @bindir@ build_alias = @build_alias@ datadir = @datadir@ @@ -235,16 +232,14 @@ distclean-compile: @am__fastdepCXX_TRUE@ if $(CXXCOMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ depfile='$(DEPDIR)/$*.Po' tmpdepfile='$(DEPDIR)/$*.TPo' @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ $< .cpp.obj: @am__fastdepCXX_TRUE@ if $(CXXCOMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ `$(CYGPATH_W) '$<'`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ depfile='$(DEPDIR)/$*.Po' tmpdepfile='$(DEPDIR)/$*.TPo' @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` uninstall-info-am: @@ -268,9 +263,11 @@ TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ done | \ $(AWK) ' { files[$$0] = 1; } \ END { for (i in files) print i; }'`; \ - test -z "$(ETAGS_ARGS)$$tags$$unique" \ - || $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ - $$tags $$unique + if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique; \ + fi ctags: CTAGS CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ $(TAGS_FILES) $(LISP) @@ -344,7 +341,7 @@ mostlyclean-generic: clean-generic: distclean-generic: - -rm -f $(CONFIG_CLEAN_FILES) + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" diff --git a/src/application/SConscript b/src/application/SConscript new file mode 100644 index 0000000..d2d2946 --- /dev/null +++ b/src/application/SConscript @@ -0,0 +1,9 @@ +# -*- python -*- + +Import('env') +libappl = env.StaticLibrary('ccapplication', source = Split(""" + capisuite.cpp capisuitemodule.cpp pythonscript.cpp + idlescript.cpp incomingscript.cpp + """)) + +Return('libappl') diff --git a/src/application/capisuitemodule.cpp b/src/application/capisuitemodule.cpp index 092d77f..d6a44d7 100644 --- a/src/application/capisuitemodule.cpp +++ b/src/application/capisuitemodule.cpp @@ -89,7 +89,7 @@ bool convertConnRef(PyObject *conn_ref, Connection** conn) { if (!PyCObject_Check(conn_ref)) { - PyErr_SetString(PyExc_TypeError,"First parameter must be the call reference."); + PyErr_SetString(PyExc_TypeError,"Invalid call reference given."); return 0; } @@ -112,7 +112,7 @@ bool convertCapiRef(PyObject *capi_ref, Capi** capi) { if (!PyCObject_Check(capi_ref)) { - PyErr_SetString(PyExc_TypeError,"First parameter must be the Capi reference."); + PyErr_SetString(PyExc_TypeError,"Invalid Capi reference given."); return 0; } @@ -966,7 +966,7 @@ capisuitemodule_init () throw (ApplicationError) { PyObject *mod,*d; try { - if ( ! ( mod=Py_InitModule3("capisuite", PCallControlMethods, "Python module for controlling CapiSuite") ) ) // m=borrowed ref + if ( ! ( mod=Py_InitModule3("_capisuite", PCallControlMethods, "Python module for controlling CapiSuite") ) ) // m=borrowed ref throw ApplicationError("unable to init python module capisuite (InitModule failed)","capisuite_init()"); if ( ! ( d=PyModule_GetDict(mod) ) ) // d=borrowed ref diff --git a/src/backend/Makefile.in b/src/backend/Makefile.in index 252fa28..da3282e 100644 --- a/src/backend/Makefile.in +++ b/src/backend/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.8.3 from Makefile.am. +# Makefile.in generated by automake 1.9.1 from Makefile.am. # @configure_input@ # Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, @@ -46,9 +46,9 @@ am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = +LIBRARIES = $(noinst_LIBRARIES) AR = ar ARFLAGS = cru -LIBRARIES = $(noinst_LIBRARIES) libccbackend_a_AR = $(AR) $(ARFLAGS) libccbackend_a_LIBADD = am_libccbackend_a_OBJECTS = capi.$(OBJEXT) connection.$(OBJEXT) @@ -56,7 +56,6 @@ libccbackend_a_OBJECTS = $(am_libccbackend_a_OBJECTS) DEFAULT_INCLUDES = -I. -I$(srcdir) -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__depfiles_maybe = depfiles -@AMDEP_TRUE@DEP_FILES = ./$(DEPDIR)/capi.Po ./$(DEPDIR)/connection.Po CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) CXXLD = $(CXX) @@ -133,6 +132,8 @@ am__fastdepCXX_TRUE = @am__fastdepCXX_TRUE@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ bindir = @bindir@ build_alias = @build_alias@ datadir = @datadir@ @@ -225,16 +226,14 @@ distclean-compile: @am__fastdepCXX_TRUE@ if $(CXXCOMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ depfile='$(DEPDIR)/$*.Po' tmpdepfile='$(DEPDIR)/$*.TPo' @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ $< .cpp.obj: @am__fastdepCXX_TRUE@ if $(CXXCOMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ `$(CYGPATH_W) '$<'`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ depfile='$(DEPDIR)/$*.Po' tmpdepfile='$(DEPDIR)/$*.TPo' @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` uninstall-info-am: @@ -258,9 +257,11 @@ TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ done | \ $(AWK) ' { files[$$0] = 1; } \ END { for (i in files) print i; }'`; \ - test -z "$(ETAGS_ARGS)$$tags$$unique" \ - || $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ - $$tags $$unique + if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique; \ + fi ctags: CTAGS CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ $(TAGS_FILES) $(LISP) @@ -334,7 +335,7 @@ mostlyclean-generic: clean-generic: distclean-generic: - -rm -f $(CONFIG_CLEAN_FILES) + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" diff --git a/src/backend/SConscript b/src/backend/SConscript new file mode 100644 index 0000000..2353dad --- /dev/null +++ b/src/backend/SConscript @@ -0,0 +1,8 @@ +# -*- python -*- + +Import('env') +libback = env.StaticLibrary('ccbackend', source = Split(""" + capi.cpp connection.cpp + """)) + +Return('libback') diff --git a/src/capisuite-py/Makefile.am b/src/capisuite-py/Makefile.am new file mode 100644 index 0000000..420ae87 --- /dev/null +++ b/src/capisuite-py/Makefile.am @@ -0,0 +1,12 @@ +pkgsysconfdir = @sysconfdir@/capisuite + +pkgpython_PYTHON = __init__.py config.py consts.py core.py exceptions.py \ + fax.py fileutils.py voice.py + +EXTRA_DIST = config.py.in + +config.py: config.py.in + rm -f $@ + sed -e 's,@\pkgsysconfdir@,$(pkgsysconfdir),g' $< >$@ + +all-local: config.py diff --git a/src/capisuite-py/Makefile.in b/src/capisuite-py/Makefile.in new file mode 100644 index 0000000..b155740 --- /dev/null +++ b/src/capisuite-py/Makefile.in @@ -0,0 +1,347 @@ +# Makefile.in generated by automake 1.9.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004 Free Software Foundation, Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +top_builddir = ../.. +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +INSTALL = @INSTALL@ +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +subdir = src/capisuite-py +DIST_COMMON = $(pkgpython_PYTHON) $(srcdir)/Makefile.am \ + $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/acinclude.m4 \ + $(top_srcdir)/configure.in +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +SOURCES = +DIST_SOURCES = +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = `echo $$p | sed -e 's|^.*/||'`; +am__installdirs = "$(DESTDIR)$(pkgpythondir)" +pkgpythonPYTHON_INSTALL = $(INSTALL_DATA) +py_compile = $(top_srcdir)/py-compile +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMDEP_FALSE = @AMDEP_FALSE@ +AMDEP_TRUE = @AMDEP_TRUE@ +AMTAR = @AMTAR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PYTHON = @PYTHON@ +PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@ +PYTHON_PLATFORM = @PYTHON_PLATFORM@ +PYTHON_PREFIX = @PYTHON_PREFIX@ +PYTHON_VERSION = @PYTHON_VERSION@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_RANLIB = @ac_ct_RANLIB@ +ac_ct_STRIP = @ac_ct_STRIP@ +am__fastdepCC_FALSE = @am__fastdepCC_FALSE@ +am__fastdepCC_TRUE = @am__fastdepCC_TRUE@ +am__fastdepCXX_FALSE = @am__fastdepCXX_FALSE@ +am__fastdepCXX_TRUE = @am__fastdepCXX_TRUE@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +datadir = @datadir@ +docdir = @docdir@ +doxygen = @doxygen@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pkgpyexecdir = @pkgpyexecdir@ +pkgpythondir = @pkgpythondir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +pyexecdir = @pyexecdir@ +python_configdir = @python_configdir@ +python_execprefix = @python_execprefix@ +python_includespec = @python_includespec@ +python_linkforshared = @python_linkforshared@ +python_moduledir = @python_moduledir@ +python_moduleexecdir = @python_moduleexecdir@ +python_prefix = @python_prefix@ +python_version = @python_version@ +pythondir = @pythondir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +pkgsysconfdir = @sysconfdir@/capisuite +pkgpython_PYTHON = __init__.py config.py consts.py core.py exceptions.py \ + fax.py fileutils.py voice.py + +EXTRA_DIST = config.py.in +all: all-am + +.SUFFIXES: +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/capisuite-py/Makefile'; \ + cd $(top_srcdir) && \ + $(AUTOMAKE) --gnu src/capisuite-py/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +uninstall-info-am: +install-pkgpythonPYTHON: $(pkgpython_PYTHON) + @$(NORMAL_INSTALL) + test -z "$(pkgpythondir)" || $(mkdir_p) "$(DESTDIR)$(pkgpythondir)" + @list='$(pkgpython_PYTHON)'; dlist=''; for p in $$list; do\ + if test -f "$$p"; then b=; else b="$(srcdir)/"; fi; \ + if test -f $$b$$p; then \ + f=$(am__strip_dir) \ + dlist="$$dlist $$f"; \ + echo " $(pkgpythonPYTHON_INSTALL) '$$b$$p' '$(DESTDIR)$(pkgpythondir)/$$f'"; \ + $(pkgpythonPYTHON_INSTALL) "$$b$$p" "$(DESTDIR)$(pkgpythondir)/$$f"; \ + else :; fi; \ + done; \ + test -z "$$dlist" || \ + PYTHON=$(PYTHON) $(py_compile) --basedir "$(DESTDIR)$(pkgpythondir)" $$dlist + +uninstall-pkgpythonPYTHON: + @$(NORMAL_UNINSTALL) + @list='$(pkgpython_PYTHON)'; dlist=''; for p in $$list; do\ + f=$(am__strip_dir) \ + rm -f "$(DESTDIR)$(pkgpythondir)/$$f"; \ + rm -f "$(DESTDIR)$(pkgpythondir)/$${f}c"; \ + rm -f "$(DESTDIR)$(pkgpythondir)/$${f}o"; \ + done +tags: TAGS +TAGS: + +ctags: CTAGS +CTAGS: + + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \ + list='$(DISTFILES)'; for file in $$list; do \ + case $$file in \ + $(srcdir)/*) file=`echo "$$file" | sed "s|^$$srcdirstrip/||"`;; \ + $(top_srcdir)/*) file=`echo "$$file" | sed "s|^$$topsrcdirstrip/|$(top_builddir)/|"`;; \ + esac; \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test "$$dir" != "$$file" && test "$$dir" != "."; then \ + dir="/$$dir"; \ + $(mkdir_p) "$(distdir)$$dir"; \ + else \ + dir=''; \ + fi; \ + if test -d $$d/$$file; then \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \ + fi; \ + cp -pR $$d/$$file $(distdir)$$dir || exit 1; \ + else \ + test -f $(distdir)/$$file \ + || cp -p $$d/$$file $(distdir)/$$file \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile all-local +installdirs: + for dir in "$(DESTDIR)$(pkgpythondir)"; do \ + test -z "$$dir" || $(mkdir_p) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic mostlyclean-am + +distclean: distclean-am + -rm -f Makefile +distclean-am: clean-am distclean-generic + +dvi: dvi-am + +dvi-am: + +html: html-am + +info: info-am + +info-am: + +install-data-am: install-pkgpythonPYTHON + +install-exec-am: + +install-info: install-info-am + +install-man: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-info-am uninstall-pkgpythonPYTHON + +.PHONY: all all-am all-local check check-am clean clean-generic \ + distclean distclean-generic distdir dvi dvi-am html html-am \ + info info-am install install-am install-data install-data-am \ + install-exec install-exec-am install-info install-info-am \ + install-man install-pkgpythonPYTHON install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-generic pdf \ + pdf-am ps ps-am uninstall uninstall-am uninstall-info-am \ + uninstall-pkgpythonPYTHON + + +config.py: config.py.in + rm -f $@ + sed -e 's,@\pkgsysconfdir@,$(pkgsysconfdir),g' $< >$@ + +all-local: config.py +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/src/capisuite-py/SConscript b/src/capisuite-py/SConscript new file mode 100644 index 0000000..7d5f14a --- /dev/null +++ b/src/capisuite-py/SConscript @@ -0,0 +1,27 @@ +# -*- python -*- + +Import('env') + +def py_compile(target, source, env): + """compile python modules for .../python2.x/site-packages/capisuite""" + # Note: this differs from #/scripts/SConscript.py_compile in 'dfile' + import py_compile, os.path + py_compile.compile(source[0].abspath, + #cfile=target[0].abspath, + dfile = os.path.join(env.subst('$pkgpython_moduledir'), + env.subst('$SOURCE.file')), + ) + +# substitute "pgksysconfdir" +env.FileSubst('config.py', 'config.py.in') + +modules = [] +for mod in Split('__init__ config consts fax fileutils voice exceptions ' + 'core'): + modules.append(mod+'.py') + modules.append(env.Command(mod + '.pyc', mod+'.py', py_compile)) + +install_pylib = env.Install('$pkgpython_moduledir', modules) + +Alias('install-pylib',install_pylib) +Alias('install', install_pylib) diff --git a/src/capisuite-py/__init__.py b/src/capisuite-py/__init__.py new file mode 100644 index 0000000..e68bdeb --- /dev/null +++ b/src/capisuite-py/__init__.py @@ -0,0 +1,24 @@ +"""capisuite + +The main package of CapiSuite. This package provides the interface between +the user programmable Python scripts for handling incoming and outgoing calls +and the CapiSuite core which makes the ISDN hardware available. + +This package consists of several files containing classes for certain aspects: + +- core: provides the basic access to all functions of the CapiSuite core +- fax: contains all routines necessary for fax connection handling +- voice: contains all routines necessary for voice connection handling +- config: allows to read and write configuration and job description files +- fileutils: contains general file handling routines typically needed + by CapiSuite scripts +- consts: some constant definitions +- exceptions: collection of exception classes used by the classes and functions + in the other files + +""" + +__author__ = "Hartmut Goebel " +__copyright__ = "Copyright (c) 2004 by Hartmut Goebel" +__version__ = "$Revision: 0.0 $" +__credits__ = "This is part of www.capisuite.de; thanks to Gernot Hiller" diff --git a/src/capisuite-py/config.py.in b/src/capisuite-py/config.py.in new file mode 100644 index 0000000..43f1f0b --- /dev/null +++ b/src/capisuite-py/config.py.in @@ -0,0 +1,216 @@ +# -*- mode: python -*- +"""capisuite.config + +Handling of configuration and job description files. + +This file contains CSConfigParser, a configuration parser derived from the +default Python parsers, but adapted to the needs of CapiSuite. + +Additionally, the class JobDescription is defined, which allows to read and +write special text files used to store meta data about incoming and outgoing +voice and fax jobs/calls. +""" + +__author__ = "Hartmut Goebel " +__copyright__ = "Copyright (c) 2004 by Hartmut Goebel" +__version__ = "$Revision: 0.0 $" +__credits__ = "This is part of www.capisuite.de; thanks to Gernot Hiller" + +import os.path, types, string + +# the name of the config file read by the scripts; see there for +# options and descriptions +pkgsysconfdir = "@pkgsysconfdir@" + +configfile_fax = os.path.join(pkgsysconfdir, "fax.conf") +configfile_voice = os.path.join(pkgsysconfdir, "answering_machine.conf") + +__all__ = ['configfile_fax', 'configfile_voice', + 'CSConfigParser', 'JobDescription', + 'readGlobalConfig', 'readDescription', 'createDescriptionFor', + 'NoOptionError', 'NoGlobalSectionError'] + +# try to use RawConfigParser if available +try: + from ConfigParser import RawConfigParser as ConfigParser +except: + from ConfigParser import ConfigParser as ConfigParser + +# capisuite stuff +import consts +#from exceptions import NoGlobalSectionError + +#--- spezialied ConfigParser --# + +class CSConfigParser(ConfigParser): + """ + Specialized version of Python ConfigParser: + - if no filenames are given, read the default config files + - values are automatically quoted on setting (if required) und + unquoted on reading + """ + + def read(self, filenames): + """Read configuration files given as filenames. If no names are given, + the default fax and voice files are read. + """ + if not filenames: + ConfigParser.read(self, configfile_fax) + ConfigParser.read(self, configfile_voice) + else: + ConfigParser.read(self, filenames) + + def get(self, section, option): + """Get the value of the option from the given section (w/o quot. marks).""" + value = ConfigParser.get(self, section, option) + if len(value) > 1 and value[0] == '"': + value = value[1:-1] + self.set(section, option, value) + return value + + def getList(self, section, option): + """Return the value of the option from the given section as a list. + Commas are used as separators and leading and trailing whitespaces are + removed from all items.""" + return map(string.strip, self.get(section, option).split(',')) + + def getUser(self, user, option, default=None, fail=0): + """Get an option from the user or global section. + + The option is searched in the user's section and if not found + in the global section. + + 'config' the ConfigParser object containing the values + 'user' user section to use, if empty only global section is read + 'option' the name of the option to search for + + Returns the value for this option or None if it's not found + """ + if self.has_option(user, option): + return self.get(user, option) + elif self.has_option('GLOBAL', option): + return self.get('GLOBAL', option) + elif fail: + raise NoOptionError(user, option) + else: + return default + + def listUsers(self): + ul = [ u + for u in self.sections() + if u not in consts.__known_sections__ ] + return ul + + def items(self, section): + """ + Return a dictionary (name, value) for each option in the section. + + NB: This differs from ConfigParser.items() which returns a list of + tuples! + """ + items = {} + for key, value in ConfigParser.items(self, section): + if len(value) > 1 and value[0] == '"': + value = value[1:-1] + items[key] = value + return items + + def set(self, section, option, value): + if isinstance(value, types.StringTypes): + if not value or value[0].isspace() or value[-1].isspace(): + value = '"%s"' % value + ConfigParser.set(self, section, option, value) + + +class JobDescription: + def __init__(self, filename=None, defaults=None): + self._config = config = CSConfigParser(defaults) + config.add_section('GLOBAL') + if filename: + config.read(filename) + + def add_section(self, section): + if 1: # PyChecker should _not_ take this a an abstract method + raise NotImplementedError + remove_section = add_section + + def options(self): return self._config.options('GLOBAL') + def items(self): return self._config.items('GLOBAL') + def get(self, option): return self._config.get('GLOBAL', option) + def getint(self, option): return self._config.getint('GLOBAL', option) + def getfloat(self, option): return self._config.getfloat('GLOBAL', option) + def getboolean(self, option): return self._config.getboolean('GLOBAL', option) + def has_option(self, option): return self._config.has_option('GLOBAL', option) + def set(self, option, value): return self._config.set('GLOBAL', option, value) + def remove_option(self, option): return self._config.remove_option('GLOBAL',option) + def read(self, filenames): return self._config.read(filenames) + def readfp(self, fp, filename=None): return self._config.read(fp, filename) + + def write(self, fp): + print >> fp, "# Description file for", self.get('filename') + print >> fp, "# This if for internal use of CapiSuite." + print >> fp, "# Only change if you know what you do!" + self._config.write(fp) + + + def x__getattr__(self, attr): + return getattr(self._config, attr) + def x__setattr__(self, attr, value): + setattr(self._config, attr, value) + +###--- utility functions ---### + + +def readGlobalConfig(file=None): + """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. + + Returns the constructed CSConfigParser object + """ + config = CSConfigParser() + config.read(file) + if not config.has_section('GLOBAL'): + raise NoGlobalSectionError() + return config + +def readDescription(jobfilename): + """read (job) description file for received fax or voice + + This function reads an INI-style description file which has prior + been written by writeDesc() . + + jobfilename the job' filename (with extension .txt) + + Returns the filename the data filename (with extension) and + content the description as a dictionary + """ + control = JobDescription() + control.read(jobfilename) + filename = control.get('filename') + description = control.items() + return filename, description + + +def createDescriptionFor(filename, **description): + """write (job) 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 the CSConfigParser + instance. The data file name is used, the extension stripped and + replaced by .txt + + filename the data filename (with extension), + content the description as a dictionary + """ + assert isinstance(description, dict) + descrname = os.path.splitext(filename)[0] + '.txt' + control = JobDescription() + control.set('filename', filename) + for key, value in description.items(): + control.set(key, value) + descr = open(descrname, 'w') + control.write(descr) + descr.close() + return descrname diff --git a/src/capisuite-py/consts.py b/src/capisuite-py/consts.py new file mode 100644 index 0000000..0dfcf50 --- /dev/null +++ b/src/capisuite-py/consts.py @@ -0,0 +1,38 @@ +"""capisuite.consts + +Constant defintions for the capisuite Python package. + +""" + +__author__ = "Hartmut Goebel " +__copyright__ = "Copyright (c) 2004 by Hartmut Goebel" +__version__ = "$Revision: 0.0 $" +__credits__ = "This part of www.capisuite.de; thanks to Gernot Hiller" + +SEND_Q = 'sendq' +RECEIVED_Q = 'received' + +__known_sections__ = ('GLOBAL', + 'Mail Fax Sent', + 'Mail Fax Failed', + 'Mail Fax Received', + 'Mail Voice Received') + +# capi return codes: +# 34D8 = Incompatible destination + +# returncodes from call_voice and call_faxG3 +CONNECTION_ESTABLISHED = 0 +CONNECTION_TIMEOUT_EXCEEDED = 1 +CONNECTION_FAILED_UNKNONW_REASON = 2 + +""" +reject causes: + 1 = ignore call + 2 = normal call clearing + 3 = user busy + 7 = incompatible destination + 8 = destination out of order + 0x34A9 = temporary failure +""" + diff --git a/src/capisuite-py/core.py b/src/capisuite-py/core.py new file mode 100644 index 0000000..0415a26 --- /dev/null +++ b/src/capisuite-py/core.py @@ -0,0 +1,433 @@ +"""capisuite.core + +This module exposes the built-in core of capisuite. +""" + +__author__ = "Hartmut Goebel " +__copyright__ = "Copyright (c) 2004 by Hartmut Goebel" +__version__ = "$Revision: 0.0 $" +__credits__ = "This part of www.capisuite.de; thanks to Gernot Hiller" + + +# _capisuite may only be imported when running within capisuite +try: + # import all capisuite symbols in the namespace "_capisuite.symbol" + import _capisuite + # now add symbols directly used by the scripts to our namespace + from _capisuite import log,error,SERVICE_VOICE,SERVICE_FAXG3,CallGoneError +except ImportError: + pass + + +######### +### +### ATTENTION: This interface is not yet stable. You may expect +### changes until capisuite 0.5 is released! +### +######### + +class Capi: + def __init__(self, handle): + """ + handle: a capi handle as received from _capisuite (given to the idle + function as parameter) + """ + self._handle = handle + + + def call_voice(self, controller, call_from, call_to, + timeout, clir=0): + """ + Initiate an outgoing call with service voice and wait for + successful connection. + + This will initiate an outgoing call and choose voice as + service, so you can use the audio commands (like audio_receive() + and audio_send()) with this connection. After this command has + finished, the call is connected successfully or the given + timeout has exceeded. The timeout is measured beginning at the + moment when the call is signalled (it's "ringing") to the + called party. + + Parameters: + controller: ISDN controller ID to use + call_from: own number to use (string) + call_to: the number to call (string) + timeout: timeout in seconds to wait for connection establishment + clir: disable sending of own number (default=0, send number) + + On success returns a call object; on failure returns an + error_code. + """ + call, result = _capisuite.call_voice(self.handle, controller, + call_from, call_to, + timeout, clir) + if result: + return result + return Call(call, SERVICE_VOICE, call_from, call_to) + + + def call_faxG3(self, controller, call_from, call_to, + timeout, stationID, headline, clir=0): + """ + Initiate an outgoing call with service faxG3 and wait for + successful connection. + + This will initiate an outgoing call and choose fax group 3 as + service, so you can use the fax commands (like fax_send() and + fax_receive()) with this connection. After this command has + finished, the call is connected successfully or the given + timeout has exceeded. The timeout is measured beginning at the + moment when the call is signalled (it's "ringing") to the + called party. + + Parameters: + controller: ISDN controller ID to use + call_from: own number to use (string) + call_to: the number to call (string) + timeout: timeout in seconds to wait for connection establishment + faxStationID: fax station ID (string) + faxHeadline: fax headline to print on every page (string) + clir: disable sending of own number (default=0, send number) + + On success returns a call object; on failure returns an + error_code. + """ + call, result = _capisuite.call_faxG3(self.handle, controller, + call_from, call_to, + timeout, stationID, headline) + if result: + return result + return Call(call, SERVICE_FAXG3, call_from, call_to) + + +class Call: + def __init__(self, handle, service, call_from, call_to): + """ + handle: a call handle as received from _capisuite + + NB: A Call instance is never True to ease testing results from + Capi.call_...() + """ + self._handle = handle + self.service = service + self.from_nr = call_from + self.to_nr = call_to + + ###--- python stuff --### + + def __nonzero__(self): + # 'if Call()' must never be true to allow easier results from + # Capi.call_...() + return 0 + + def __str__(self): + return str(self._handle) + + def __repr__(self): + # todo: add service, call_from, call_to + return repr(self._handle) + + + ###--- general --### + + def disconnect(self): + """ + Disconnect connection. + + This will cause an immediate disconnection. It should be + always the last command in every flow of a script. + + Returns a tuple of two result values. The first is the + disconnect cause of the physical connection, the second the + disconnect cause of the logical connection. See CAPI spec for + the logical causes and ETS 300 102-01 for the physical causes. + """ + result = _capisuite.disconnect(self._handle) + return result + + + def reject(self, rejectCause): + """ + Reject an incoming call. + + If you don't want to accept an incoming call for any reason + (e.g. if it has a service or comes from a number you don't + want to accept), use this command. There are several reasons + you can give when rejecting a call. Some important ones are: + + rejectCause: cause to signal when rejecting call. This may be one of + 1 = ignore call + 2 = normal call clearing + 3 = user busy + 7 = incompatible destination + 8 = destination out of order + 0x34A9 = temporary failure + """ + _capisuite.reject(self._handle, rejectCause) + + def log(self, message, level): + """ + Log a connection dependent message. + + This function writes a message to the CapiSuite log. As all messages + written with it are prefixed with the current call reference, you + should use it for connection-dependant messages (e.g. information about + handling *this* call). + + If you want to log messages of general nature not associated with a + certain call (e.g. problem in reading configuration files), please use + core.log instead. + + message: the log message to be written + level: parameter for CapiSuite log_level used (0=vital .. 3=debug info) + """ + _capisuite.log(message, level, self._handle) + + ###--- DTMF support --### + + def enable_DTMF(self): + """ + Enable recognition of DTMF tones. + """ + _capisuite.enable_DTMF(self._handle) + + def disable_DTMF(self): + """ + Disable recognition of DTMF tones. + """ + _capisuite.disable_DTMF(self._handle) + + + def read_DTMF(self, timeout, min_digits=0, max_digits=0): + """ + Read the received DTMF tones or wait for a certain amount of + them. + + This function allows to just read in the DTMF tones which were + already received. But it also supports to wait for a certain + amount of DTMF tones if you want the user to always input some + digits at a certain step in your script. + + You can specify how much DTMF tones you want in several ways - + see the parameter description. To just see if something was + entered before, use capisuite.read_DTMF(0). If you want to get + at least 1 and mostly 4 digits and want to wait 5 seconds for + additional digits, you'll use capisuite.read_DTMF(5,1,4). + + Valid DTMF characters are '0'...'9','A'...'D' and two special + fax tones: 'X' (CNG), 'Y' (CED) + + timeout: timeout in seconds after which reading is terminated; + only applied after min_digits have been read! (-1 = + infinite) + min_digits: minimum number of digits which must be read in ANY + case, i.e. timout doesn't count here (default: 0) + max_digits: maximum number of digits to read; aborts + immediately enough digits are read) (default: + 0=infinite, i.e. wait until timeout is reached) + + Returns a string containing the characters read. + """ + # todo: descibe what A...D means and where '#' and '*' go + return _capisuite.read_DTMF(self._handle, timeout, + min_digits, max_digits) + + + ###--- voice calls ---### + + def connect_voice (self, delay=0): + """ + Accept an incoming call and connect with voice service. + + This will accept an incoming call and choose voice as service, + so you can use the audio commands (like audio_receive() and + audio_send()) with this connection. After this command has + finished, the call is connected successfully. + + It's also possible to accept a call with some delay. This is + useful for an answering machine if you want to fetch a call + with your phone before your computer answers it. + + delay: delay in seconds _before_ connection will be established + (default: 0=immediate connect) + """ + _capisuite.connect_voice(self._handle, delay) + + + def audio_receive(self, filename, timeout, silence_timeout=0, + exit_DTMF=0): + """ + Receive an audio file in a speech mode connection. + + This functions receives an audio file. It can recognize + silence in the signal and timeout after a given period of + silence, after a general timeout or after the reception of a + DTMF signal. + + If the recording was finished because of silence_timeout, the + silence will be truncated away. + + If DTMF abort is enabled, the command will also abort + immediately if DTMF was received before it is called. This + allows to abort subsequent audio receive and send commands + with one DTMF signal w/o the need to check for received DTMF + after each command. + + The connction must be in audio mode (by connect_voice()), + otherwise an exception will be caused. + + The created file will be saved in bit-reversed A-Law format, 8 + kHz mono. Use sox to convert it to a normal wav file. + + filename: where to save the received message. + timeout: receive length in seconds (-1 = infinite). + silence_timeout: abort after x seconds of silence (default: no timeout) + exit_DTMF: abort sending when a DTMF signal is received (default: 0) + + Returns duration of receiving in seconds. + """ + return _capisuite.audio_receive(self._handle, filename, timeout, + silence_timeout, exit_DTMF) + + + def audio_send(self, filename, exit_DTMF=0): + """ + Send an audio file in a speech mode connection. + + This function sends an audio file, which must be in + bit-inversed A-Law format. Thus files can be created with eg. + sox using the suffix ".la". It supports abortion if DTMF + signal is received. + + If DTMF abort is enabled, the command will also abort + immediately if DTMF was received before it is called. That + allows you to abort subsequent audio receive and send commands + with one DTMF signal w/o the need to check for received DTMF + after each command. + + The connction must be in audio mode (use connect_voice()), + otherwise an exception will be caused. + + filename: file to send + exit_DTMF: abort sending when a DTMF signal is received (default: 0) + + Returns duration of send in seconds. + """ + return _capisuite.audio_send(self._handle, filename, exit_DTMF) + + + def switch_to_faxG3(self, faxStationID, faxHeadline): + """ + Switch a connection from voice mode to fax mode. + + This will switch from voice mode to fax group 3 after you have + connected, so you can use the fax commands afterwards. + + Attention: Not all ISDN cards or CAPI driver support this + command. + + faxStationID: the station ID to use (string) + faxHeadline: the fax headline to use (string) + + Returns a FaxInfo instance. + """ + faxInfo = _capisuite.switch_to_faxG3(self._handle, + faxStationID, faxHeadline) + self.service = SERVICE_FAXG3 + if not faxInfo: + return FaxInfo() + return FaxInfo(*faxInfo) + + + ###--- fax calls --### + + def connect_faxG3(self, faxStationID, faxHeadline, delay=0): + """ + Accept an incoming call and connect with fax (analog, group 3) service. + + This will accept an incoming call and choose fax group 3 as + service, so you can use the fax commands (like fax_receive()) + with this connection. After this command has finished, the + call is connected successfully. + + It's also possible to accept a call with some delay. This is + useful if eg. you want to have to fetch a call with your phone + before your computer answers it. + + faxStationID: the station ID to use (string) + faxHeadline: the fax headline to use (string) + delay: delay in seconds _before_ connection will be established + (default: 0=immediate connect) + + Returns a FaxInfo instance. + """ + faxInfo = _capisuite.connect_faxG3(self._handle, faxStationID, + faxHeadline, delay) + if not faxInfo: + return FaxInfo() + return FaxInfo(*faxInfo) + + + def fax_send(self, faxfilename): + """ + Send a fax in a fax mode connection. + + This command sends an analog fax (fax group 3). It starts the + send and waits for the end of the connection. So it should be + the last command before disconnect(). + + The connction must be in fax mode (use capi.call_faxG3() or + call.switch_to_faxG3()), otherwise an exception will be caused. + + The file to be sent must be in the Structured Fax File (SFF) + format. + + faxfilename: file to send + """ + capisuite.fax_send(self._handle, faxfilename) + + + def fax_receive(self, filename): + """ + Receive a fax in a fax mode connection. + + This command receives an analog fax (fax group 3). It starts + the reception and waits for the end of the connection. So it + should be the last command before disconnect(). + + The connction must be in fax mode (use capi.call_faxG3() or + call.switch_to_faxG3()), otherwise an exception will be caused. + + The created file will be saved in the Structured Fax File + (SFF) format. + + filename: where to save the received fax. + """ + _capisuite.fax_receive(self._handle, filename) + + +class FaxInfo: + def __init__(self, stationID='', rate=0, hiRes=0, format=0, numPages=0): + self.stationID = stationID + self.bitRate = rate + self.hiRes = hiRes + self.resolution = hiRes and "hiRes" or "loRes" + # cff: color fax; sff: normal black-and-white fax + self.format = format and 'cff' or 'sff' + self.color = format and 'color' or 'b&w' + self.numPages = numPages + + def as_dict(self): + d = {} + for a in ('stationID', 'bitRate', 'resolution', + 'hiRes', 'format', 'color', 'numPages'): + d[a] = getattr(self, a) + return d + +# implemented in _capisuite: +# +#def error(...): +# pass +#def log(...): +# pass diff --git a/src/capisuite-py/exceptions.py b/src/capisuite-py/exceptions.py new file mode 100644 index 0000000..b15e963 --- /dev/null +++ b/src/capisuite-py/exceptions.py @@ -0,0 +1,34 @@ +""" +Exceptions hierarchy for capisuite + +Exception ++ MissingConfigEntry ++ FaxError + + JobError + + InvalidJob + + JobLockedError +""" + +class Error(Exception): pass +class FaxError(Exception): pass +class VoiceError(Exception): pass + +class JobError(Error): + # todo: distinguish fax/voice + def __init__(self, jobnum, jobfile): + Error.__init__(self) + self.jobnum = jobnum + self.jobfile = jobfile + +class JobLockedError(JobError): pass +class InvalidJob(JobError): pass + +from ConfigParser import NoOptionError, NoSectionError, Error + +class NoGlobalSectionError(NoSectionError): + """Raised when the GLOBAL section is missing + in a configuration file.""" + + def __init__(self): + Error.__init__(self, "Invalid config file: section GLOBAL missing") + self.section = 'GLOBAL' diff --git a/src/capisuite-py/fax.py b/src/capisuite-py/fax.py new file mode 100644 index 0000000..688495c --- /dev/null +++ b/src/capisuite-py/fax.py @@ -0,0 +1,252 @@ +"""capisuite.fax + +Module for fax interfacing of capisuite. + +Most functions deal about creating and manipulationg job control +files. To actually send the fax out, call sendfax(). This is normaly +done by the idle-script. +""" + +__author__ = "Hartmut Goebel " +__copyright__ = "Copyright (c) 2004 by Hartmut Goebel" +__version__ = "$Revision: 0.0 $" +__credits__ = "This part of www.capisuite.de; thanks to Gernot Hiller" + +import os, os.path, time, re, errno +from types import ListType, TupleType + +# capisuite stuff +import fileutils +from capisuite.config import JobDescription, createDescriptionFor +from capisuite.exceptions import InvalidJob, JobLockedError +from capisuite.consts import * + +_job_pattern = re.compile("fax-([0-9]+)\.txt") + +###---- Utility functions ---### + +def _userQ(config, user, Q): + userdir= config.get('GLOBAL', "fax_user_dir") + # todo: enable this, iff using config.getUser() + #if not userdir: + # raise NoOptionError('', 'fax_user_dir') + return os.path.abspath(os.path.join(userdir, user, Q)) + + +###---- Job handling ---### + +def _createSendJob(user, filename, **controlinfo): + """ + Create a control file for a fax to be send. The control file is + written to the same directory as the filename. + + NB: This function does not care about the control information to + be written to the controlfile. This is to keep the definition of + what is required for sending the fax out of here. It's up to the + application layer to ensure correct parameters. + """ + controlfile = createDescriptionFor(filename, **controlinfo) + fileutils._setProtection(user, 0600, filename, controlfile) + + +def createReceivedJob(user, filename, call_from, call_to, causes, **kwargs): + """ + Create a file description for a received fax. The description file + is written to the same directory as the filename. + """ + controlinfo = { + 'call_from': call_from, + 'call_to': call_to, + 'time': time.ctime(), + 'cause': "0x%x/0x%x" % causes + } + controlinfo.update(kwargs) + controlfile = createDescriptionFor(filename, **controlinfo) + fileutils._setProtection(user, 0600, filename, controlfile) + + +def enqueueJob(config, user, infiles, converter, **controlinfo): + # todo: enable color-suffixes + """ + Enqueue a file into the fax send queue. + + This sets the spool-filename and calls 'converter(infile, + spoolfile)' to convert the infile into a faxfile. The later has to + be written tp 'spoolfile'. If the conversion succeeds, the + appropriate job controlfile is written . + + NB: This function does not care about the control information to + be written to the controlfile. This is to keep the definition of + what is required for sending the fax out of here. It's up to the + application layer to ensure correct parameters. + + @param user + @param infile + @param converter a function for converting the infile into a fax file + (will be called covnerter(infile, faxfile) + @controlinfo the fax job description + """ + assert isinstance(infiles, (ListType, TupleType)) + # ensure some required entries (which may have defaults) exist + if not controlinfo.has_key('tries'): + controlinfo['tries'] = 0 + if not controlinfo.has_key('starttime'): + controlinfo['starttime'] = time.ctime(time.time()) + + sendQ = _userQ(config, user, SEND_Q) + jobnum, faxname = fileutils.uniqueName(sendQ, "fax", "sff") + converter(infiles, faxname) + _createSendJob(user, faxname, **controlinfo) + return jobnum + + +def moveJob(controlfile, newdir, user=None): + # todo: important: update 'filenmae' in job description! + control = JobDescription(controlfile) + filename = control.get('filename') + cname = os.path.split(controlfile)[1] + fname = os.path.split(filename)[1] + if not user: + cname = os.path.join(newdir, cname) + fname = os.path.join(newdir, fname) + else: + cname = os.path.join(newdir, "%s-%s" % (user, cname)) + fname = os.path.join(newdir, "%s-%s" % (user, fname)) + + # move controlfile to keep owner and protection! + os.rename(controlfile, cname) + os.rename(filename, fname) + # update controlfile + control.set('filename', fname) + control.write(open(cname, 'w')) + + return control + + +def abortJob(controlfile): + """ + Abort a fax job defined by it's controlfile. + + This will remove the job from respective queue and delete both the + controlfile and the file defined in the controlfile. + """ + # todo: security: May this be missused for deleting other users files? + # todo: security: should ensure controlfile and filename are in the same + # directory + lockname = fileutils.lockname(controlfile) + if not os.access(controlfile, os.W_OK): + raise InvalidJob(None, controlfile) + try: + lock = fileutils._getLock(lockname, blocking=0) + except IOError, err: + if err.errno in (errno.EACCES, errno.EAGAIN): + raise JobLockedError(None, controlfile) + else: + raise + else: + try: + control = JobDescription(controlfile) + filename = control.get('filename') + os.unlink(filename) + os.unlink(controlfile) + finally: + fileutils._releaseLock(lock) + + +def abortUserJob(config, user, jobnum): + """ + Abort a fax send job defined by username and job number. + + This will remove the job from respective queue and delete both the + controlfile and the file defined in the controlfile. + """ + sendQ = _userQ(config, user, SEND_Q) + controlfile = fileutils.controlname(os.path.join(sendQ, + "fax-%03i" % jobnum)) + abortJob(controlfile) + + + +###---- Queue handling ---### + +def getQueueFiles(config, user): + """ + Generate a list of all fax jobs in the send queue. + + Result is a list of (job-number, controlfile) tuples, where + 'controlfile' is an absolut paht. + """ + sendQ = _userQ(config, user, SEND_Q) + for filename in os.listdir(sendQ): + m = _job_pattern.match(filename) + if m: + yield (int(m.group(1)), # job number + os.path.join(sendQ, filename)) + +def getQueue(config, user): + """ + Generate a list of all fax entries in the send queue. + + Result is a list of (job-number, description) tuples, where + 'description' is the content of the job's controlfile as a + dictionary. + """ + for num, controlfile in getQueueFiles(config, user): + jobDesc = JobDescription(controlfile).items() + yield (num, jobDesc) + + +def getQueueDetails(config, user): + """ + Return a list of interesting details about the fax jobs in the + send queue. + + Result is a list of tuples of (job-num, addresse/dialstring, + tries, starttime, subject), where starttime it the time when the + next try for sending is undertaken and addresse/dialstring is the + adresses (if not empty) or the dialstring otherwise). + """ + jobs = [] + for num, jobDesc in getQueue(config, user): + jobs.append( ( + num, + jobDesc['addressee'] or jobDesc['dialstring'], + jobDesc['tries'], + jobDesc['starttime'], + jobDesc['subject'], + )) + return jobs + + + +###--- Send/Receive Fax ---### + +def sendfax(config, user, capi, faxfile, + outgoing_num, dialstring, stationID=None, headline=None): + """ + Send a fax out via the capi. + """ + import capisuite.core as core + + controller = config.getint('GLOBAL', "send_controller") + timeout = int(config.getUser(user, "outgoing_timeout")) + + # get defaults for stationID and headline from config + if not stationID: + stationID = config.getUser(user, "fax_stationID") + if not stationID: + core.error("Warning: fax_stationID for user %s not set" %user) + if not headline: + headline = config.getUser(user, "fax_headline") + + try: + call = core.call_faxG3( + capi, controller, outgoing_num, + dialstring, timeout, stationID, headline) + if call: + # an errror occured + return call + call.fax_send(faxfile) + return call.disconnect() + except core.CallGoneError: + return call.disconnect() diff --git a/src/capisuite-py/fileutils.py b/src/capisuite-py/fileutils.py new file mode 100644 index 0000000..89993fd --- /dev/null +++ b/src/capisuite-py/fileutils.py @@ -0,0 +1,158 @@ +"""capisuite.fileutils + +File handling utility function. + +""" + +__author__ = "Hartmut Goebel " +__copyright__ = "Copyright (c) 2004 by Hartmut Goebel" +__version__ = "$Revision: 0.0 $" +__credits__ = "This part of www.capisuite.de; some ideas taken from Gernot Hiller" + +import fcntl, os, re, errno + +from types import IntType + +class UnknownUserError(KeyError): pass +class LockTakenError(Exception): pass + + +def lockname(path): + return "%s.lock" % os.path.splitext(path)[0] +def controlname(path): + return "%s.txt" % os.path.splitext(path)[0] + + +def _getLock(lockname_=None, forfile=None, blocking=0): + if forfile: + lockname_ = lockname(forfile) + elif not lockname_: + raise ValueError, lockname_ + lockfile = open(lockname_, "w") + try: + if blocking: + fcntl.lockf(lockfile, fcntl.LOCK_EX) + else: + fcntl.lockf(lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError, err: # can't get the lock + if err.errno in (errno.EACCES, errno.EAGAIN): + lockfile.close() + raise LockTakenError + else: + raise + return (lockname_, lockfile) + + +def _releaseLock((lockname, lockfile)): + fcntl.lockf(lockfile, fcntl.LOCK_UN) + lockfile.close() + try: + os.unlink(lockname) + except OSError, err: + # as we don't hold the lock any more, the other thread can be quicker + # in deleting than we; this doesn't harm, so ignore it + if (err.errno!=2): + raise + + +def _setProtection(user, mode=0600, *files): + import pwd + try: + userdata = pwd.getpwnam(user) + except KeyError: + raise UnknownUserError(user) + print files + if os.getuid() == 0: + # running as root, change ownership + for f in files: + os.chmod(f, mode) + os.chown(f, userdata.pw_uid, userdata.pw_gid) + else: + for f in files: + os.chmod(f, mode) + os.chown(f, os.getuid(), userdata.pw_gid) + + +def _mkuserdir(user, parrentdir, *dirs): + import pwd + try: + userdata = pwd.getpwnam(user) + except KeyError: + raise UnknownUserError(user) + path = parrentdir + for d in dirs: + path = os.path.join(path, d) + # todo: use os.path.exists? + if not os.access(path, os.F_OK): + os.mkdir(path, 0700) + os.chown(path, userdata.pw_uid, userdata.pw_gid) + return path + +###--- counter files ---### + +def readCounter(default=0, *fileparts): + assert isinstance(default, IntType) + filename = os.path.join(*fileparts) + if os.path.exists(filename): + lastfile = open(filename, "r") + count = int(lastfile.readline()) + lastfile.close() + return count + else: + return default + +def writeCounter(count, *fileparts): + assert isinstance(count, IntType) + filename = os.path.join(*fileparts) + lastfile = open(filename, "w") + print >> lastfile, count + lastfile.close() + + +###--- ... ---### + +def __makeCountedFilePattern(basename, suffix): + # todo: group should be digits only + return re.compile("%s-([0-9]+)\.%s" % (re.escape(basename), + re.escape(suffix))) + +# @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 job number, new file name +def uniqueName(directory, basename, suffix): + nextfile = "%s-nextnr" % os.path.join(directory, basename) + lock = _getLock(os.path.join(directory,"cs_lock"), blocking=1) + try: + nextnum = readCounter(-10, nextfile) + if nextnum < 0: + # search for next free sequence number + pattern = __makeCountedFilePattern(basename, suffix) + numbers = [0] + for f in os.listdir(directory): + m = pattern.match(f) + if m: + numbers.append(int(m.group(1))) + # take number of last file and increase it by one + nextnum = max(numbers)+1 + writeCounter(nextnum+1, nextfile) + except: + _releaseLock(lock) + raise + else: + _releaseLock(lock) + newname = "%s-%03i.%s" % (os.path.join(directory,basename), + nextnum, suffix) + return nextnum, newname diff --git a/src/capisuite-py/pychecker.rc b/src/capisuite-py/pychecker.rc new file mode 100644 index 0000000..732e9ca --- /dev/null +++ b/src/capisuite-py/pychecker.rc @@ -0,0 +1,216 @@ +# -*- python -*- +# .pycheckrc file created by PyChecker v0.8.13 @ Wed Feb 25 14:03:29 2004 +# +# It should be placed in your home directory (value of $HOME). +# If $HOME is not set, it will look in the current directory. +# + +# unused imports +importUsed = 1 + +# unused imports from __init__.py +packageImportUsed = 1 + +# module imports itself +reimportSelf = 1 + +# reimporting a module +moduleImportErrors = 1 + +# module does import and from ... import +mixImport = 1 + +# unused local variables, except tuples +localVariablesUsed = 1 + +# all unused local variables, including tuples +unusedLocalTuple = 0 + +# all unused class data members +membersUsed = 0 + +# all unused module variables +allVariablesUsed = 0 + +# unused private module variables +privateVariableUsed = 1 + +# report each occurrence of global warnings +reportAllGlobals = 0 + +# functions called with named arguments (like keywords) +namedArgs = 0 + +# Attributes (members) must be defined in __init__() +onlyCheckInitForMembers = 0 + +# Subclass.__init__() not defined +initDefinedInSubclass = 0 + +# Baseclass.__init__() not called +baseClassInitted = 1 + +# Subclass needs to override methods that only throw exceptions +abstractClasses = 1 + +# Return None from __init__() +returnNoneFromInit = 1 + +# unreachable code +unreachableCode = 0 + +# a constant is used in a conditional statement +constantConditions = 1 + +# 1 is used in a conditional statement (if 1: or while 1:) +constant1 = 0 + +# check if iterating over a string +stringIteration = 1 + +# Calling data members as functions +callingAttribute = 0 + +# class attribute does not exist +classAttrExists = 1 + +# First argument to methods +methodArgName = 'self' + +# First argument to classmethods +classmethodArgNames = ['cls', 'klass'] + +# unused method/function arguments +argumentsUsed = 1 + +# unused method/function variable arguments +varArgumentsUsed = 1 + +# ignore if self is unused in methods +ignoreSelfUnused = 0 + +# check if overridden methods have the same signature +checkOverridenMethods = 1 + +# check if __special__ methods exist and have the correct signature +checkSpecialMethods = 1 + +# check if function/class/method names are reused +redefiningFunction = 1 + +# check if using unary positive (+) which is usually meaningless +unaryPositive = 1 + +# check if modify (call method) on a parameter that has a default value +modifyDefaultValue = 1 + +# check if variables are set to different types +inconsistentTypes = 0 + +# check if unpacking a non-sequence +unpackNonSequence = 1 + +# check if unpacking sequence with the wrong length +unpackLength = 1 + +# check if raising or catching bad exceptions +badExceptions = 1 + +# check if statement appears to have no effect +noEffect = 1 + +# check if using (expr % 1), it has no effect on integers and strings +modulo1 = 1 + +# check if using (expr is const-literal), doesn't always work on integers and strings +isLiteral = 1 + +# check consistent return values +checkReturnValues = 1 + +# check if using implict and explicit return values +checkImplicitReturns = 1 + +# check that attributes of objects exist +checkObjectAttrs = 1 + +# various warnings about incorrect usage of __slots__ +slots = 1 + +# using properties with classic classes +classicProperties = 1 + +# check if __slots__ is empty +emptySlots = 1 + +# check if using integer division +intDivide = 1 + +# check if local variable shadows a global +shadows = 1 + +# check if a variable shadows a builtin +shadowBuiltins = 1 + +# check if input() is used +usesInput = 1 + +# check if the exec statement is used +usesExec = 0 + +# ignore warnings from files under standard library +ignoreStandardLibrary = 0 + +# ignore warnings from the list of modules +blacklist = ['Tkinter', 'wxPython', 'gtk', 'GTK', 'GDK'] + +# ignore global variables not used if name is one of these values +variablesToIgnore = ['__version__', '__warningregistry__', '__all__', + '__credits__', '__test__', '__author__', + '__email__', '__revision__', '__copyright__'] + +# ignore unused locals/arguments if name is one of these values +unusedNames = ['_', 'empty', 'unused', 'dummy'] + +# ignore use of deprecated modules/functions +deprecated = 1 + +# maximum lines in a function +maxLines = 200 + +# maximum branches in a function +maxBranches = 50 + +# maximum returns in a function +maxReturns = 10 + +# maximum # of arguments to a function +maxArgs = 10 + +# maximum # of locals in a function +maxLocals = 40 + +# maximum # of identifier references (Law of Demeter) +maxReferences = 5 + +# no module doc strings +noDocModule = 0 + +# no class doc strings +noDocClass = 0 + +# no function/method doc strings +noDocFunc = 0 + +# print internal checker parse structures +printParse = 0 + +# turn on debugging for checker +debug = 0 + +# { 'module1': 'no-namedargs maxlines=0', +# 'module2.my_func': 'argsused', +# 'module3.my_class': 'no-initreturn', } +suppressions = { + 'config.NoGlobalSectionError.__init__': 'no-callinit', +} diff --git a/src/capisuite-py/voice.py b/src/capisuite-py/voice.py new file mode 100644 index 0000000..ae12b15 --- /dev/null +++ b/src/capisuite-py/voice.py @@ -0,0 +1,176 @@ +"""capisuite.voice + +Module for voice interfacing and interactions of capisuite. + +""" + +__author__ = "Hartmut Goebel " +__copyright__ = "Copyright (c) 2004 by Hartmut Goebel" +__version__ = "$Revision: 0.0 $" +__credits__ = "This part of www.capisuite.de; thanks to Gernot Hiller" + +import os, re, errno + +# capisuite stuff +import fileutils +from capisuite.config import JobDescription, createDescriptionFor +from capisuite.exceptions import InvalidJob, JobLockedError +#from capisuite.consts import * + +_job_pattern = re.compile("voice-([0-9]+)\.txt") +_la_pattern = re.compile("voice-([0-9]+)\.la") + + +def _userQ(config, user, Q): + userdir= config.get('GLOBAL', "voice_user_dir") + # todo: enable this, iff using config.getUser() + #if not userdir: + # raise NoOptionError('', 'fax_user_dir') + return os.path.abspath(os.path.join(userdir, user, Q)) + + +def getAudio(config, user, filename): + """ + Search for an audio file first in user_dir, than in audio_dir + + 'config' is the ConfigParser object containing the configuration, + 'user' the name of the user, 'filename' the filename of the wave + file. + + Returns the found file with full path + """ + + userdir = config.get('GLOBAL', "voice_user_dir") + userdir = os.path.join(userdir, user) + if config.has_option('GLOBAL', "user_audio_files") and \ + config.getboolean('GLOBAL', "user_audio_files") and \ + os.access(os.path.join(userdir, filename),os.R_OK): + return os.path.join(userdir, filename) + else: + systemdir = config.get('GLOBAL', "audio_dir") + return os.path.join(systemdir, filename) + + +def getNumberFiles(number, gender="-"): + number = str(number) + if number == "-" or number == "??": + # "??" is needed for backward compatibility to versions <= 0.4.1a + yield 'unbekannt' + elif gender != "-" and number in ("1", "01"): + if gender in ("n", "m"): + yield 'ein' + else: + yield 'eine' + elif len(number) == 2 and number[0] != "0": + digit10, digit01 = number + if digit10 == "1" or digit01 == "0": + # for 10, 11...19, 20, 30, ... we have seperate voice files + yield number + else: + if digit01 == "1": + yield 'ein' + else: + yield digit01 + yield 'und' + yield '%s0' % digit10 + else: + for digit in list(number): + yield digit + + +# @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 +# @param gender if the number is used in connection with a singular noun ("f" --> "eine Nachricht") +def sayNumber(config, user, call, number, gender="-"): + for f in getNumberFiles(number, gender=gender): + say(config, user, call, "%s.la" % f) + +def say(config, user, call, *files): + for f in files: + call.audio_send(getAudio(config, user, f),1) + + +###---- Queue handling ---### + +def getInquiryCounter(config, user): + return fileutils.readCounter(-1, + _userQ(config, user, "received"), + "last_inquiry") + +def setInquiryCounter(config, user, count): + return fileutils.writeCounter(count, + _userQ(config, user, "received"), + "last_inquiry") + + +def getQueueFiles(config, user): + """ + Generated a list of all Queue files, where each entry consists of + a tuple (job-number, filename). + + filename is an absolute path + + """ + receivedQ = _userQ(config, user, "received") + for filename in os.listdir(receivedQ): + m = _job_pattern.match(filename) + if m: + yield (m.group(1), # job number + filename) + + +def abortJob(controlfile): + """ + Abort a fax job defined by it's controlfile. + + This will remove the job from respective queue and delete both the + controlfile and the file defined in the controlfile. + """ + # todo: security: May this be missused for deleting other users files? + # todo: security: should ensure controlfile and filename are in the same + # directory + lockname = fileutils.lockname(controlfile) + if not os.access(controlfile, os.W_OK): + raise InvalidJob(None, controlfile) + try: + lock = fileutils._getLock(lockname, blocking=0) + except IOError, err: + if err.errno in (errno.EACCES, errno.EAGAIN): + raise JobLockedError(None, controlfile) + else: + raise + else: + try: + control = JobDescription(controlfile) + filename = control.get('filename') + os.unlink(filename) + os.unlink(controlfile) + finally: + fileutils._releaseLock(lock) + + +def createReceivedJob(user, filename, call_from, call_to, causes): + """ + Create a file description for a received fax. The description file + is written to the same directory as the filename. + """ + import time + control = { + 'call_from': call_from, + 'call_to': call_to, + 'date': time.ctime(), + 'cause': "0x%x/0x%x" % causes, + # we return this dict, thus set the filename here, too + 'filename': filename, + } + controlfile = createDescriptionFor(**control) + fileutils._setProtection(user, 0600, filename, controlfile) + return control diff --git a/src/modules/Makefile.in b/src/modules/Makefile.in index 1effe1c..51a5f5e 100644 --- a/src/modules/Makefile.in +++ b/src/modules/Makefile.in @@ -1,4 +1,4 @@ -# Makefile.in generated by automake 1.8.3 from Makefile.am. +# Makefile.in generated by automake 1.9.1 from Makefile.am. # @configure_input@ # Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, @@ -46,9 +46,9 @@ am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = +LIBRARIES = $(noinst_LIBRARIES) AR = ar ARFLAGS = cru -LIBRARIES = $(noinst_LIBRARIES) libccmodules_a_AR = $(AR) $(ARFLAGS) libccmodules_a_LIBADD = am_libccmodules_a_OBJECTS = audiosend.$(OBJEXT) callmodule.$(OBJEXT) \ @@ -60,14 +60,6 @@ libccmodules_a_OBJECTS = $(am_libccmodules_a_OBJECTS) DEFAULT_INCLUDES = -I. -I$(srcdir) -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__depfiles_maybe = depfiles -@AMDEP_TRUE@DEP_FILES = ./$(DEPDIR)/audioreceive.Po \ -@AMDEP_TRUE@ ./$(DEPDIR)/audiosend.Po ./$(DEPDIR)/callmodule.Po \ -@AMDEP_TRUE@ ./$(DEPDIR)/calloutgoing.Po \ -@AMDEP_TRUE@ ./$(DEPDIR)/connectmodule.Po \ -@AMDEP_TRUE@ ./$(DEPDIR)/disconnectmodule.Po \ -@AMDEP_TRUE@ ./$(DEPDIR)/faxreceive.Po ./$(DEPDIR)/faxsend.Po \ -@AMDEP_TRUE@ ./$(DEPDIR)/readDTMF.Po \ -@AMDEP_TRUE@ ./$(DEPDIR)/switch2faxG3.Po CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) CXXLD = $(CXX) @@ -144,6 +136,8 @@ am__fastdepCXX_TRUE = @am__fastdepCXX_TRUE@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ bindir = @bindir@ build_alias = @build_alias@ datadir = @datadir@ @@ -247,16 +241,14 @@ distclean-compile: @am__fastdepCXX_TRUE@ if $(CXXCOMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ depfile='$(DEPDIR)/$*.Po' tmpdepfile='$(DEPDIR)/$*.TPo' @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ $< .cpp.obj: @am__fastdepCXX_TRUE@ if $(CXXCOMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ `$(CYGPATH_W) '$<'`; \ @am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ depfile='$(DEPDIR)/$*.Po' tmpdepfile='$(DEPDIR)/$*.TPo' @AMDEPBACKSLASH@ -@AMDEP_TRUE@@am__fastdepCXX_FALSE@ $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` uninstall-info-am: @@ -280,9 +272,11 @@ TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ done | \ $(AWK) ' { files[$$0] = 1; } \ END { for (i in files) print i; }'`; \ - test -z "$(ETAGS_ARGS)$$tags$$unique" \ - || $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ - $$tags $$unique + if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique; \ + fi ctags: CTAGS CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ $(TAGS_FILES) $(LISP) @@ -356,7 +350,7 @@ mostlyclean-generic: clean-generic: distclean-generic: - -rm -f $(CONFIG_CLEAN_FILES) + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" diff --git a/src/modules/SConscript b/src/modules/SConscript new file mode 100644 index 0000000..ba62437 --- /dev/null +++ b/src/modules/SConscript @@ -0,0 +1,11 @@ +# -*- python -*- + +Import('env') + +libmodules = env.StaticLibrary('ccmodules', source = Split(""" + audiosend.cpp callmodule.cpp audioreceive.cpp faxreceive.cpp + connectmodule.cpp switch2faxG3.cpp readDTMF.cpp calloutgoing.cpp + disconnectmodule.cpp faxsend.cpp + """)) + +Return('libmodules')