root / tea4cups / trunk / tea4cups @ 587

Revision 587, 27.3 kB (checked in by jerome, 19 years ago)

Added (buggy) code to handle the transmission to the real backend

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Rev
Line 
1#! /usr/bin/env python
2# -*- coding: ISO-8859-15 -*-
3
4# Tea4CUPS : Tee for CUPS
5#
6# (c) 2005 Jerome Alet <alet@librelogiciel.com>
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
20#
21# $Id$
22#
23#
24
25import sys
26import os
27import popen2
28import errno
29import md5
30import cStringIO
31import shlex
32import tempfile
33import ConfigParser
34import threading
35from struct import unpack
36
37version = "0.99"
38
39class TeeError(Exception):
40    """Base exception for Tea4CUPS related stuff."""
41    def __init__(self, message = ""):
42        self.message = message
43        Exception.__init__(self, message)
44    def __repr__(self):
45        return self.message
46    __str__ = __repr__
47   
48class ConfigError(TeeError) :   
49    """Configuration related exceptions."""
50    pass 
51   
52class IPPError(TeeError) :   
53    """IPP related exceptions."""
54    pass 
55   
56class Popen4ForCUPS(popen2.Popen4) :
57    """Our own class to execute real backends.
58   
59       Their first argument is different from their path so using
60       native popen2.Popen3 would not be feasible.
61    """
62    def __init__(self, cmd, bufsize=-1, arg0=None) :
63        self.arg0 = arg0
64        popen2.Popen4.__init__(self, cmd, bufsize)
65       
66    def _run_child(self, cmd):
67        try :
68            MAXFD = os.sysconf("SC_OPEN_MAX")
69        except (AttributeError, ValueError) :   
70            MAXFD = 256
71        for i in range(3, MAXFD) : 
72            try:
73                os.close(i)
74            except OSError:
75                pass
76        try:
77            os.execvpe(cmd[0], [self.arg0 or cmd[0]] + cmd[1:], os.environ)
78        finally:
79            os._exit(1)
80   
81# Some IPP constants   
82OPERATION_ATTRIBUTES_TAG = 0x01
83JOB_ATTRIBUTES_TAG = 0x02
84END_OF_ATTRIBUTES_TAG = 0x03
85PRINTER_ATTRIBUTES_TAG = 0x04
86UNSUPPORTED_ATTRIBUTES_TAG = 0x05
87
88class IPPMessage :
89    """A class for IPP message files."""
90    def __init__(self, data) :
91        """Initializes an IPP Message object."""
92        self.data = data
93        self._attributes = {}
94        self.curname = None
95        self.tags = [ None ] * 256      # by default all tags reserved
96       
97        # Delimiter tags
98        self.tags[0x01] = "operation-attributes-tag"
99        self.tags[0x02] = "job-attributes-tag"
100        self.tags[0x03] = "end-of-attributes-tag"
101        self.tags[0x04] = "printer-attributes-tag"
102        self.tags[0x05] = "unsupported-attributes-tag"
103       
104        # out of band values
105        self.tags[0x10] = "unsupported"
106        self.tags[0x11] = "reserved-for-future-default"
107        self.tags[0x12] = "unknown"
108        self.tags[0x13] = "no-value"
109       
110        # integer values
111        self.tags[0x20] = "generic-integer"
112        self.tags[0x21] = "integer"
113        self.tags[0x22] = "boolean"
114        self.tags[0x23] = "enum"
115       
116        # octetString
117        self.tags[0x30] = "octetString-with-an-unspecified-format"
118        self.tags[0x31] = "dateTime"
119        self.tags[0x32] = "resolution"
120        self.tags[0x33] = "rangeOfInteger"
121        self.tags[0x34] = "reserved-for-collection"
122        self.tags[0x35] = "textWithLanguage"
123        self.tags[0x36] = "nameWithLanguage"
124       
125        # character strings
126        self.tags[0x20] = "generic-character-string"
127        self.tags[0x41] = "textWithoutLanguage"
128        self.tags[0x42] = "nameWithoutLanguage"
129        # self.tags[0x43] = "reserved"
130        self.tags[0x44] = "keyword"
131        self.tags[0x45] = "uri"
132        self.tags[0x46] = "uriScheme"
133        self.tags[0x47] = "charset"
134        self.tags[0x48] = "naturalLanguage"
135        self.tags[0x49] = "mimeMediaType"
136       
137        # now parses the IPP message
138        self.parse()
139       
140    def __getattr__(self, attrname) :   
141        """Allows self.attributes to return the attributes names."""
142        if attrname == "attributes" :
143            keys = self._attributes.keys()
144            keys.sort()
145            return keys
146        raise AttributeError, attrname
147           
148    def __getitem__(self, ippattrname) :   
149        """Fakes a dictionnary d['key'] notation."""
150        value = self._attributes.get(ippattrname)
151        if value is not None :
152            if len(value) == 1 :
153                value = value[0]
154        return value       
155    get = __getitem__   
156       
157    def parseTag(self) :   
158        """Extracts information from an IPP tag."""
159        pos = self.position
160        valuetag = self.tags[ord(self.data[pos])]
161        # print valuetag.get("name")
162        pos += 1
163        posend = pos2 = pos + 2
164        namelength = unpack(">H", self.data[pos:pos2])[0]
165        if not namelength :
166            name = self.curname
167        else :   
168            posend += namelength
169            self.curname = name = self.data[pos2:posend]
170        pos2 = posend + 2
171        valuelength = unpack(">H", self.data[posend:pos2])[0]
172        posend = pos2 + valuelength
173        value = self.data[pos2:posend]
174        oldval = self._attributes.setdefault(name, [])
175        oldval.append(value)
176        return posend - self.position
177       
178    def operation_attributes_tag(self) : 
179        """Indicates that the parser enters into an operation-attributes-tag group."""
180        return self.parseTag()
181       
182    def job_attributes_tag(self) : 
183        """Indicates that the parser enters into an operation-attributes-tag group."""
184        return self.parseTag()
185       
186    def printer_attributes_tag(self) : 
187        """Indicates that the parser enters into an operation-attributes-tag group."""
188        return self.parseTag()
189       
190    def parse(self) :
191        """Parses an IPP Message.
192       
193           NB : Only a subset of RFC2910 is implemented.
194           We are only interested in textual informations for now anyway.
195        """
196        self.version = "%s.%s" % (ord(self.data[0]), ord(self.data[1]))
197        self.operation_id = "0x%04x" % unpack(">H", self.data[2:4])[0]
198        self.request_id = "0x%08x" % unpack(">I", self.data[4:8])[0]
199        self.position = 8
200        try :
201            tag = ord(self.data[self.position])
202            while tag != END_OF_ATTRIBUTES_TAG :
203                self.position += 1
204                name = self.tags[tag]
205                if name is not None :
206                    func = getattr(self, name.replace("-", "_"), None)
207                    if func is not None :
208                        self.position += func()
209                        if ord(self.data[self.position]) > UNSUPPORTED_ATTRIBUTES_TAG :
210                            self.position -= 1
211                            continue
212                tag = ord(self.data[self.position])
213        except IndexError :
214            raise IPPError, "Unexpected end of IPP message."
215           
216class FakeConfig :   
217    """Fakes a configuration file parser."""
218    def get(self, section, option, raw=0) :
219        """Fakes the retrieval of a global option."""
220        raise ConfigError, "Invalid configuration file : no option %s in section [%s]" % (option, section)
221       
222class CupsBackend :
223    """Base class for tools with no database access."""
224    def __init__(self) :
225        """Initializes the CUPS backend wrapper."""
226        self.MyName = "Tea4CUPS"
227        self.myname = "tea4cups"
228        self.pid = os.getpid()
229        confdir = os.environ.get("CUPS_SERVERROOT", ".") 
230        self.conffile = os.path.join(confdir, "%s.conf" % self.myname)
231        if os.path.isfile(self.conffile) :
232            self.config = ConfigParser.ConfigParser()
233            self.config.read([self.conffile])
234            self.debug = self.isTrue(self.getGlobalOption("debug", ignore=1))
235        else :   
236            self.config = FakeConfig()
237            self.debug = 1      # no config, so force debug mode !
238           
239    def logInfo(self, message, level="info") :       
240        """Logs a message to CUPS' error_log file."""
241        sys.stderr.write("%s: %s v%s (PID %i) : %s\n" % (level.upper(), self.MyName, version, os.getpid(), message))
242        sys.stderr.flush()
243       
244    def logDebug(self, message) :   
245        """Logs something to debug output if debug is enabled."""
246        if self.debug :
247            self.logInfo(message, level="debug")
248       
249    def isTrue(self, option) :       
250        """Returns 1 if option is set to true, else 0."""
251        if (option is not None) and (option.upper().strip() in ['Y', 'YES', '1', 'ON', 'T', 'TRUE']) :
252            return 1
253        else :   
254            return 0
255                       
256    def getGlobalOption(self, option, ignore=0) :   
257        """Returns an option from the global section, or raises a ConfigError if ignore is not set, else returns None."""
258        try :
259            return self.config.get("global", option, raw=1)
260        except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) :   
261            if not ignore :
262                raise ConfigError, "Option %s not found in section global of %s" % (option, self.conffile)
263               
264    def getPrintQueueOption(self, printqueuename, option, ignore=0) :   
265        """Returns an option from the printer section, or the global section, or raises a ConfigError."""
266        globaloption = self.getGlobalOption(option, ignore=1)
267        try :
268            return self.config.get(printqueuename, option, raw=1)
269        except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) :   
270            if globaloption is not None :
271                return globaloption
272            elif not ignore :
273                raise ConfigError, "Option %s not found in section [%s] of %s" % (option, printqueuename, self.conffile)
274               
275    def enumTeeBranches(self, printqueuename) :
276        """Returns the list of branches for a particular section's Tee."""
277        try :
278            globalbranches = [ (k, v) for (k, v) in self.config.items("global") if k.startswith("tee_") ]
279        except ConfigParser.NoSectionError, msg :   
280            raise ConfigError, "Invalid configuration file : %s" % msg
281        try :
282            sectionbranches = [ (k, v) for (k, v) in self.config.items(printqueuename) if k.startswith("tee_") ]
283        except ConfigParser.NoSectionError, msg :   
284            self.logInfo("No section for print queue %s : %s" % (printqueuename, msg))
285            sectionbranches = []
286        branches = {}
287        for (k, v) in globalbranches :
288            value = v.strip()
289            if value :
290                branches[k] = value
291        for (k, v) in sectionbranches :   
292            value = v.strip()
293            if value :
294                branches[k] = value # overwrite any global option or set a new value
295            else :   
296                del branches[k] # empty value disables a global option
297        return branches
298       
299    def discoverOtherBackends(self) :   
300        """Discovers the other CUPS backends.
301       
302           Executes each existing backend in turn in device enumeration mode.
303           Returns the list of available backends.
304        """
305        # Unfortunately this method can't output any debug information
306        # to stdout or stderr, else CUPS considers that the device is
307        # not available.
308        available = []
309        (directory, myname) = os.path.split(sys.argv[0])
310        tmpdir = tempfile.gettempdir()
311        lockfilename = os.path.join(tmpdir, "%s..LCK" % myname)
312        if os.path.exists(lockfilename) :
313            lockfile = open(lockfilename, "r")
314            pid = int(lockfile.read())
315            lockfile.close()
316            try :
317                # see if the pid contained in the lock file is still running
318                os.kill(pid, 0)
319            except OSError, e :   
320                if e.errno != errno.EPERM :
321                    # process doesn't exist anymore
322                    os.remove(lockfilename)
323           
324        if not os.path.exists(lockfilename) :
325            lockfile = open(lockfilename, "w")
326            lockfile.write("%i" % self.pid)
327            lockfile.close()
328            allbackends = [ os.path.join(directory, b) \
329                                for b in os.listdir(directory) 
330                                    if os.access(os.path.join(directory, b), os.X_OK) \
331                                        and (b != myname)] 
332            for backend in allbackends :                           
333                answer = os.popen(backend, "r")
334                try :
335                    devices = [line.strip() for line in answer.readlines()]
336                except :   
337                    devices = []
338                status = answer.close()
339                if status is None :
340                    for d in devices :
341                        # each line is of the form :
342                        # 'xxxx xxxx "xxxx xxx" "xxxx xxx"'
343                        # so we have to decompose it carefully
344                        fdevice = cStringIO.StringIO(d)
345                        tokenizer = shlex.shlex(fdevice)
346                        tokenizer.wordchars = tokenizer.wordchars + \
347                                                        r".:,?!~/\_$*-+={}[]()#"
348                        arguments = []
349                        while 1 :
350                            token = tokenizer.get_token()
351                            if token :
352                                arguments.append(token)
353                            else :
354                                break
355                        fdevice.close()
356                        try :
357                            (devicetype, device, name, fullname) = arguments
358                        except ValueError :   
359                            pass    # ignore this 'bizarre' device
360                        else :   
361                            if name.startswith('"') and name.endswith('"') :
362                                name = name[1:-1]
363                            if fullname.startswith('"') and fullname.endswith('"') :
364                                fullname = fullname[1:-1]
365                            available.append('%s %s:%s "%s+%s" "%s managed %s"' \
366                                                 % (devicetype, self.myname, device, self.MyName, name, self.MyName, fullname))
367            os.remove(lockfilename)
368        return available
369                       
370    def initBackend(self) :   
371        """Initializes the backend's attributes."""
372        # check that the DEVICE_URI environment variable's value is
373        # prefixed with self.myname otherwise don't touch it.
374        # If this is the case, we have to remove the prefix from
375        # the environment before launching the real backend
376        muststartwith = "%s:" % self.myname
377        device_uri = os.environ.get("DEVICE_URI", "")
378        if device_uri.startswith(muststartwith) :
379            fulldevice_uri = device_uri[:]
380            device_uri = fulldevice_uri[len(muststartwith):]
381            for i in range(2) :
382                if device_uri.startswith("/") : 
383                    device_uri = device_uri[1:]
384        try :
385            (backend, destination) = device_uri.split(":", 1) 
386        except ValueError :   
387            if not device_uri :
388                self.logInfo("Not attached to an existing print queue.")
389                backend = ""
390            else :   
391                raise TeeError, "Invalid DEVICE_URI : %s\n" % device_uri
392       
393        self.JobId = sys.argv[1].strip()
394        self.UserName = sys.argv[2].strip()
395        self.Title = sys.argv[3].strip()
396        self.Copies = int(sys.argv[4].strip())
397        self.Options = sys.argv[5].strip()
398        if len(sys.argv) == 7 :
399            self.InputFile = sys.argv[6] # read job's datas from file
400        else :   
401            self.InputFile = None        # read job's datas from stdin
402           
403        self.RealBackend = backend
404        self.DeviceURI = device_uri
405        self.PrinterName = os.environ.get("PRINTER", "")
406        self.Directory = self.getPrintQueueOption(self.PrinterName, "directory")
407        self.DataFile = os.path.join(self.Directory, "%s-%s-%s-%s" % (self.myname, self.PrinterName, self.UserName, self.JobId))
408        self.ClientHost = self.extractJobOriginatingHostName()
409           
410    def getCupsConfigDirectives(self, directives=[]) :
411        """Retrieves some CUPS directives from its configuration file.
412       
413           Returns a mapping with lowercased directives as keys and
414           their setting as values.
415        """
416        dirvalues = {} 
417        cupsroot = os.environ.get("CUPS_SERVERROOT", "/etc/cups")
418        cupsdconf = os.path.join(cupsroot, "cupsd.conf")
419        try :
420            conffile = open(cupsdconf, "r")
421        except IOError :   
422            raise TeeError, "Unable to open %s" % cupsdconf
423        else :   
424            for line in conffile.readlines() :
425                linecopy = line.strip().lower()
426                for di in [d.lower() for d in directives] :
427                    if linecopy.startswith("%s " % di) :
428                        try :
429                            val = line.split()[1]
430                        except :   
431                            pass # ignore errors, we take the last value in any case.
432                        else :   
433                            dirvalues[di] = val
434            conffile.close()           
435        return dirvalues       
436           
437    def extractJobOriginatingHostName(self) :       
438        """Extracts the client's hostname or IP address from the CUPS message file for current job."""
439        cupsdconf = self.getCupsConfigDirectives(["RequestRoot"])
440        requestroot = cupsdconf.get("requestroot", "/var/spool/cups")
441        if (len(self.JobId) < 5) and self.JobId.isdigit() :
442            ippmessagefile = "c%05i" % int(self.JobId)
443        else :   
444            ippmessagefile = "c%s" % self.JobId
445        ippmessagefile = os.path.join(requestroot, ippmessagefile)
446        ippmessage = {}
447        try :
448            ippdatafile = open(ippmessagefile)
449        except :   
450            self.logInfo("Unable to open IPP message file %s" % ippmessagefile, "warn")
451        else :   
452            self.logDebug("Parsing of IPP message file %s begins." % ippmessagefile)
453            try :
454                ippmessage = IPPMessage(ippdatafile.read())
455            except IPPError, msg :   
456                self.logInfo("Error while parsing %s : %s" % (ippmessagefile, msg), "warn")
457            else :   
458                self.logDebug("Parsing of IPP message file %s ends." % ippmessagefile)
459            ippdatafile.close()
460        return ippmessage.get("job-originating-host-name")   
461               
462    def exportAttributes(self) :   
463        """Exports our backend's attributes to the environment."""
464        os.environ["DEVICE_URI"] = self.DeviceURI       # WARNING !
465        os.environ["TEAPRINTERNAME"] = self.PrinterName
466        os.environ["TEADIRECTORY"] = self.Directory
467        os.environ["TEADATAFILE"] = self.DataFile
468        os.environ["TEAJOBSIZE"] = str(self.JobSize)
469        os.environ["TEAMD5SUM"] = self.JobMD5Sum
470        os.environ["TEACLIENTHOST"] = self.ClientHost or ""
471        os.environ["TEAJOBID"] = self.JobId
472        os.environ["TEAUSERNAME"] = self.UserName
473        os.environ["TEATITLE"] = self.Title
474        os.environ["TEACOPIES"] = str(self.Copies)
475        os.environ["TEAOPTIONS"] = self.Options
476        os.environ["TEAINPUTFILE"] = self.InputFile or ""
477       
478    def saveDatasAndCheckSum(self) :
479        """Saves the input datas into a static file."""
480        self.logDebug("Duplicating data stream into %s" % self.DataFile)
481        mustclose = 0
482        if self.InputFile is not None :
483            infile = open(self.InputFile, "rb")
484            mustclose = 1
485        else :   
486            infile = sys.stdin
487        CHUNK = 64*1024         # read 64 Kb at a time
488        dummy = 0
489        sizeread = 0
490        checksum = md5.new()
491        outfile = open(self.DataFile, "wb")   
492        while 1 :
493            data = infile.read(CHUNK) 
494            if not data :
495                break
496            sizeread += len(data)   
497            outfile.write(data)
498            checksum.update(data)   
499            if not (dummy % 32) : # Only display every 2 Mb
500                self.logDebug("%s bytes saved..." % sizeread)
501            dummy += 1   
502        outfile.close()
503        if mustclose :   
504            infile.close()
505        self.JobSize = sizeread   
506        self.JobMD5Sum = checksum.hexdigest()
507        self.logDebug("Job %s is %s bytes long." % (self.JobId, self.JobSize))
508        self.logDebug("Job %s MD5 sum is %s" % (self.JobId, self.JobMD5Sum))
509
510    def cleanUp(self) :
511        """Cleans up the place."""
512        if not self.isTrue(self.getPrintQueueOption(self.PrinterName, "keepfiles", ignore=1)) :
513            os.remove(self.DataFile)
514           
515    def runBranches(self) :         
516        """Launches each tee defined for the current print queue."""
517        exitcode = 0
518        branches = self.enumTeeBranches(self.PrinterName)
519        if self.isTrue(self.getPrintQueueOption(self.PrinterName, "serialize", ignore=1)) :
520            self.logDebug("Serialized Tees")
521            if self.RealBackend :
522                self.logDebug("Launching original backend %s for printer %s" % (self.RealBackend, self.PrinterName))
523                exitcode = self.runOriginalBackend()
524            for (branch, command) in branches.items() :
525                self.logDebug("Launching %s : %s" % (branch, command))
526                retcode = os.system(command)
527                self.logDebug("Exit code for tee %s on printer %s is %s" % (branch, self.PrinterName, retcode))
528                if os.WIFEXITED(retcode) :
529                    retcode = os.WEXITSTATUS(retcode)
530                if retcode :   
531                    self.logInfo("Tee %s on printer %s didn't exit successfully." % (branch, self.PrinterName), "error")
532                    exitcode = 1
533        else :       
534            self.logDebug("Forked Tees")
535            pids = {}
536            if self.RealBackend :
537                branches["Original backend"] = None     # Fakes a tee to launch one more child
538            for (branch, command) in branches.items() :
539                pid = os.fork()
540                if pid :
541                    pids[branch] = pid
542                else :   
543                    if branch == "Original backend" :
544                        self.logDebug("Launching original backend %s for printer %s" % (self.RealBackend, self.PrinterName))
545                        sys.exit(self.runOriginalBackend())
546                    else :
547                        self.logDebug("Launching %s : %s" % (branch, command))
548                        retcode = os.system(command)
549                        if os.WIFEXITED(retcode) :
550                            retcode = os.WEXITSTATUS(retcode)
551                        else :   
552                            retcode = -1
553                        sys.exit(retcode)
554            for (branch, pid) in pids.items() :
555                (childpid, retcode) = os.waitpid(pid, 0)
556                self.logDebug("Exit code for tee %s (PID %s) on printer %s is %s" % (branch, childpid, self.PrinterName, retcode))
557                if os.WIFEXITED(retcode) :
558                    retcode = os.WEXITSTATUS(retcode)
559                if retcode :   
560                    self.logInfo("Tee %s (PID %s) on printer %s didn't exit successfully." % (branch, childpid, self.PrinterName), "error")
561                    exitcode = 1
562        return exitcode
563       
564    def runOriginalBackend(self) :   
565        """Launches the original backend."""
566        originalbackend = os.path.join(os.path.split(sys.argv[0])[0], self.RealBackend)
567        arguments = sys.argv
568        self.logDebug("Starting original backend %s with args %s" % (originalbackend, " ".join(['"%s"' % a for a in ([os.environ["DEVICE_URI"]] + arguments[1:])])))
569        subprocess = Popen4ForCUPS([originalbackend] + arguments[1:], bufsize=0, arg0=os.environ["DEVICE_URI"])
570        rendezvous = threading.Event()
571        if self.InputFile is None :
572           self.logDebug("Launching data thread.")
573           infile = open(self.DataFile, "rb")
574           iothread = threading.Thread(target = self.handleOriginalBackendIO, kwargs = { "parent" : threading.currentThread(), "event" : rendezvous, "inf": infile, "outf" : subprocess.tochild})
575           iothread.start()
576           
577        # here we only want to read its output and send it on our stderr
578        while (self.InputFile is not None) or iothread.isAlive() : # until all data transmitted to original backend
579            data = subprocess.fromchild.readline()
580            if (not data) or rendezvous.isSet() :
581                break
582            sys.stderr.write(data)   
583            sys.stderr.flush()
584           
585        if not rendezvous.isSet() :   
586            rendezvous.set()   
587        if self.InputFile is None :
588            infile.close()
589            iothread.join()
590        subprocess.fromchild.close()
591        subprocess.tochild.close()
592        status = subprocess.wait()
593        if os.WIFEXITED(status) :
594            return os.WEXITSTATUS(status)
595        else :   
596            return 1
597       
598    def handleOriginalBackendIO(self, parent, event, inf, outf) :   
599        """Thread to handles the original backend's I/O."""
600        dummy = 0
601        totalsent = 0
602        while 1 :
603            if not parent.isAlive() :
604                self.logInfo("Parent died unexpectedly.", level = "warn")
605                break
606            if event.isSet() :   
607                self.logInfo("Parent said work is finished.")
608                break
609            try :   
610                data = inf.readline()
611                if not data :
612                    break
613                outf.write(data)   
614                totalsent += len(data)
615                dummy += 1
616                if not (dummy % 10) :
617                    self.logDebug("Sent %i bytes to original backend." % totalsent)
618                try :
619                    outf.flush()
620                except :   
621                    pass
622            except IOError, msg :
623                self.logInfo("I/O Error : %s" % msg, level = "warn")
624                break
625        event.set()       
626        sys.exit(0)
627       
628if __name__ == "__main__" :   
629    # This is a CUPS backend, we should act and die like a CUPS backend
630    wrapper = CupsBackend()
631    if len(sys.argv) == 1 :
632        print "\n".join(wrapper.discoverOtherBackends())
633        sys.exit(0)               
634    elif len(sys.argv) not in (6, 7) :   
635        sys.stderr.write("ERROR: %s job-id user title copies options [file]\n"\
636                              % sys.argv[0])
637        sys.exit(1)
638    else :   
639        try :
640            wrapper.initBackend()
641            wrapper.saveDatasAndCheckSum()
642            wrapper.exportAttributes()
643            retcode = wrapper.runBranches()
644            wrapper.cleanUp()
645        except SystemExit, e :   
646            retcode = e.code
647        except :   
648            import traceback
649            lines = []
650            for line in traceback.format_exception(*sys.exc_info()) :
651                lines.extend([l for l in line.split("\n") if l])
652            msg = "ERROR: ".join(["%s (PID %s) : %s\n" % (wrapper.MyName, wrapper.pid, l) for l in (["ERROR: Tea4CUPS v%s" % version] + lines)])
653            sys.stderr.write(msg)
654            sys.stderr.flush()
655            retcode = 1
656        sys.exit(retcode)
Note: See TracBrowser for help on using the browser.