Tizen 2.1 base
[platform/upstream/hplip.git] / prnt / cups.py
1 # -*- coding: utf-8 -*-
2 #
3 # (c) Copyright 2003-2009 Hewlett-Packard Development Company, L.P.
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
18 #
19 # Author: Don Welch
20 #
21
22 # Std Lib
23 import os
24 import os.path
25 import gzip
26 import re
27 import time
28 import urllib
29 import tempfile
30 import glob
31
32 # Local
33 from base.g import *
34 from base import utils, models
35
36 INVALID_PRINTER_NAME_CHARS = """~`!@#$%^&*()=+[]{}()\\/,.<>?'\";:| """
37
38 # Handle case where cups.py (via device.py) is loaded
39 # and cupsext doesn't exist yet. This happens in the
40 # installer and in a fresh sandbox if the Python extensions
41 # aren't installed yet.
42 try:
43     current_language = os.getenv("LANG")
44     newlang = "C"
45
46     # this is a workaround due CUPS rejecting all encoding except ASCII
47     # and utf-8
48     # if the locale contains the encoding, switch to the same locale,
49     # but with utf-8 encoding. Otherwise use C locale.
50     if current_language is not None and current_language.count('.'):
51         newlang, encoding = current_language.split('.')
52         newlang += ".UTF-8"
53
54     os.environ['LANG'] = newlang
55
56     # the same works for LC_CTYPE, in case it's not set
57     current_ctype = os.getenv("LC_CTYPE")
58     newctype = "C"
59
60     if current_ctype is not None and current_ctype.count('.'):
61         newctype, encoding = current_ctype.split('.')
62         newctype += ".UTF-8"
63
64     os.environ['LC_CTYPE'] = newctype
65
66     import cupsext
67
68     # restore the old env values
69     if current_ctype is not None:
70         os.environ['LC_CTYPE'] = current_ctype
71
72     if current_language is not None:
73         os.environ['LANG'] = current_language
74
75 except ImportError:
76     if not os.getenv("HPLIP_BUILD"):
77         log.warn("CUPSEXT could not be loaded. Please check HPLIP installation.")
78         sys.exit(1)
79
80
81 IPP_PRINTER_STATE_IDLE = 3
82 IPP_PRINTER_STATE_PROCESSING = 4
83 IPP_PRINTER_STATE_STOPPED = 5
84
85 # Std CUPS option types
86 PPD_UI_BOOLEAN = 0   # True or False option
87 PPD_UI_PICKONE = 1   # Pick one from a list
88 PPD_UI_PICKMANY = 2  # Pick zero or more from a list
89
90 # Non-std: General
91 UI_SPINNER = 100           # Simple spinner with opt. suffix (ie, %)
92 UI_UNITS_SPINNER = 101     # Spinner control w/pts, cm, in, etc. units (not impl.)
93 UI_BANNER_JOB_SHEETS = 102 # dual combos for banner job-sheets
94 UI_PAGE_RANGE = 103        # Radio + page range entry field
95
96 # Non-std: Job storage
97 UI_JOB_STORAGE_MODE = 104      # Combo w/linkage
98 UI_JOB_STORAGE_PIN = 105       # Radios w/PIN entry
99 UI_JOB_STORAGE_USERNAME = 106  # Radios w/text entry
100 UI_JOB_STORAGE_ID = 107        # Radios w/text entry
101 UI_JOB_STORAGE_ID_EXISTS = 108 # Combo
102
103 UI_INFO = 109        # Information field, required Information name and Value
104
105 # ipp_op_t
106 IPP_PAUSE_PRINTER = 0x0010
107 IPP_RESUME_PRINTER = 0x011
108 IPP_PURGE_JOBS = 0x012
109 CUPS_GET_DEFAULT = 0x4001
110 CUPS_GET_PRINTERS = 0x4002
111 CUPS_ADD_MODIFY_PRINTER = 0x4003
112 CUPS_DELETE_PRINTER = 0x4004
113 CUPS_GET_CLASSES = 0x4005
114 CUPS_ADD_MODIFY_CLASS = 0x4006
115 CUPS_DELETE_CLASS = 0x4007
116 CUPS_ACCEPT_JOBS = 0x4008
117 CUPS_REJECT_JOBS = 0x4009
118 CUPS_SET_DEFAULT = 0x400a
119 CUPS_GET_DEVICES = 0x400b
120 CUPS_GET_PPDS = 0x400c
121 CUPS_MOVE_JOB = 0x400d
122 CUPS_AUTHENTICATE_JOB = 0x400e
123
124 # ipp_jstate_t
125 IPP_JOB_PENDING = 3    # Job is waiting to be printed
126 IPP_JOB_HELD = 4       # Job is held for printing
127 IPP_JOB_PROCESSING = 5 # Job is currently printing
128 IPP_JOB_STOPPED = 6    # Job has been stopped
129 IPP_JOB_CANCELLED = 7  # Job has been cancelled
130 IPP_JOB_ABORTED = 8    # Job has aborted due to error
131 IPP_JOB_COMPLETED = 8  # Job has completed successfully
132
133 # ipp_status_e
134 IPP_OK = 0x0000 # successful-ok
135 IPP_OK_SUBST = 0x001 # successful-ok-ignored-or-substituted-attributes
136 IPP_OK_CONFLICT = 0x002 # successful-ok-conflicting-attributes
137 IPP_OK_IGNORED_SUBSCRIPTIONS = 0x003 # successful-ok-ignored-subscriptions
138 IPP_OK_IGNORED_NOTIFICATIONS = 0x004 # successful-ok-ignored-notifications
139 IPP_OK_TOO_MANY_EVENTS = 0x005 # successful-ok-too-many-events
140 IPP_OK_BUT_CANCEL_SUBSCRIPTION = 0x006 # successful-ok-but-cancel-subscription
141 IPP_OK_EVENTS_COMPLETE = 0x007 # successful-ok-events-complete
142 IPP_REDIRECTION_OTHER_SITE = 0x300
143 IPP_BAD_REQUEST = 0x0400 # client-error-bad-request
144 IPP_FORBIDDEN = 0x0401 # client-error-forbidden
145 IPP_NOT_AUTHENTICATED = 0x0402 # client-error-not-authenticated
146 IPP_NOT_AUTHORIZED = 0x0403 # client-error-not-authorized
147 IPP_NOT_POSSIBLE = 0x0404 # client-error-not-possible
148 IPP_TIMEOUT = 0x0405 # client-error-timeout
149 IPP_NOT_FOUND = 0x0406 # client-error-not-found
150 IPP_GONE = 0x0407 # client-error-gone
151 IPP_REQUEST_ENTITY = 0x0408 # client-error-request-entity-too-large
152 IPP_REQUEST_VALUE = 0x0409 # client-error-request-value-too-long
153 IPP_DOCUMENT_FORMAT = 0x040a # client-error-document-format-not-supported
154 IPP_ATTRIBUTES = 0x040b # client-error-attributes-or-values-not-supported
155 IPP_URI_SCHEME = 0x040c # client-error-uri-scheme-not-supported
156 IPP_CHARSET = 0x040d # client-error-charset-not-supported
157 IPP_CONFLICT = 0x040e # client-error-conflicting-attributes
158 IPP_COMPRESSION_NOT_SUPPORTED = 0x040f # client-error-compression-not-supported
159 IPP_COMPRESSION_ERROR = 0x0410 # client-error-compression-error
160 IPP_DOCUMENT_FORMAT_ERROR = 0x0411 # client-error-document-format-error
161 IPP_DOCUMENT_ACCESS_ERROR = 0x0412 # client-error-document-access-error
162 IPP_ATTRIBUTES_NOT_SETTABLE = 0x0413 # client-error-attributes-not-settable
163 IPP_IGNORED_ALL_SUBSCRIPTIONS = 0x0414 # client-error-ignored-all-subscriptions
164 IPP_TOO_MANY_SUBSCRIPTIONS = 0x0415 # client-error-too-many-subscriptions
165 IPP_IGNORED_ALL_NOTIFICATIONS = 0x0416 # client-error-ignored-all-notifications
166 IPP_PRINT_SUPPORT_FILE_NOT_FOUND = 0x0417 # client-error-print-support-file-not-found
167 IPP_INTERNAL_ERROR = 0x0500 # server-error-internal-error
168 IPP_OPERATION_NOT_SUPPORTED = 0x0501 # server-error-operation-not-supported
169 IPP_SERVICE_UNAVAILABLE = 0x0502 # server-error-service-unavailable
170 IPP_VERSION_NOT_SUPPORTED = 0x0503 # server-error-version-not-supported
171 IPP_DEVICE_ERROR = 0x0504 # server-error-device-error
172 IPP_TEMPORARY_ERROR = 0x0505 # server-error-temporary-error
173 IPP_NOT_ACCEPTING = 0x0506 # server-error-not-accepting-jobs
174 IPP_PRINTER_BUSY = 0x0507 # server-error-busy
175 IPP_ERROR_JOB_CANCELLED = 0x0508 # server-error-job-canceled
176 IPP_MULTIPLE_JOBS_NOT_SUPPORTED = 0x0509 # server-error-multiple-document-jobs-not-supported
177 IPP_PRINTER_IS_DEACTIVATED = 0x050a # server-error-printer-is-deactivated
178
179 CUPS_ERROR_BAD_NAME = 0x0f00
180 CUPS_ERROR_BAD_PARAMETERS = 0x0f01
181
182 nickname_pat = re.compile(r'''\*NickName:\s*\"(.*)"''', re.MULTILINE)
183 pat_cups_error_log = re.compile("""^loglevel\s?(debug|debug2|warn|info|error|none)""", re.I)
184 ppd_pat = re.compile(r'''.*hp-(.*?)(-.*)*\.ppd.*''', re.I)
185
186
187
188 def getPPDPath(addtional_paths=None):
189     """
190         Returns the CUPS ppd path (not the foomatic one under /usr/share/ppd).
191         Usually this is /usr/share/cups/model.
192     """
193     if addtional_paths is None:
194         addtional_paths = []
195
196     search_paths = prop.ppd_search_path.split(';') + addtional_paths
197
198     for path in search_paths:
199         ppd_path = os.path.join(path, 'cups/model')
200         if os.path.exists(ppd_path):
201             return ppd_path
202
203
204 def getAllowableMIMETypes():
205     """
206         Scan all /etc/cups/*.convs and /usr/share/cups/mime 
207         files for allowable file formats.
208     """
209     paths = []
210     allowable_mime_types = []
211     files = []
212     if os.path.exists("/etc/cups"):
213         paths.append("/etc/cups/*.convs")
214     if os.path.exists("/usr/share/cups/mime"):
215         paths.append("/usr/share/cups/mime/*.convs")
216     for path in paths:
217         files.extend(glob.glob(path))
218     for f in files:
219         #log.debug( "Capturing allowable MIME types from: %s" % f )
220         conv_file = file(f, 'r')
221
222         for line in conv_file:
223             if not line.startswith("#") and len(line) > 1:
224                 try:
225                     source, dest, cost, prog =  line.split()
226                 except ValueError:
227                     continue
228
229                 if source not in ('application/octet-stream', 'application/vnd.cups-postscript'):
230                     allowable_mime_types.append(source)
231
232     # Add some well-known MIME types that may not appear in the .convs files
233     allowable_mime_types.append("image/x-bmp")
234     allowable_mime_types.append("text/cpp")
235     allowable_mime_types.append("application/x-python")
236     allowable_mime_types.append("application/hplip-fax")
237
238     return allowable_mime_types
239
240
241 def getPPDDescription(f):
242     if f.endswith('.gz'):
243         nickname = gzip.GzipFile(f, 'r').read(4096)
244     else:
245         nickname = file(f, 'r').read(4096)
246
247     try:
248         desc = nickname_pat.search(nickname).group(1)
249     except AttributeError:
250         desc = ''
251
252     return desc
253
254
255 def getSystemPPDs():
256     major, minor, patch = getVersionTuple()
257     ppds = {} # {'ppd name' : 'desc', ...}
258
259     if major == 1 and minor < 2:
260         ppd_dir = sys_conf.get('dirs', 'ppd')
261         log.debug("(CUPS 1.1.x) Searching for PPDs in: %s" % ppd_dir)
262
263         for f in utils.walkFiles(ppd_dir, pattern="HP*ppd*;hp*ppd*", abs_paths=True):
264             desc = getPPDDescription(f)
265
266             if not ('foo2' in desc or
267                     'gutenprint' in desc.lower() or
268                     'gutenprint' in f):
269
270                 ppds[f] = desc
271                 log.debug("%s: %s" % (f, desc))
272
273     else: # 1.2.x
274         log.debug("(CUPS 1.2.x) Getting list of PPDs using CUPS_GET_PPDS...")
275         ppd_dict = cupsext.getPPDList()
276         cups_ppd_path = getPPDPath() # usually /usr/share/cups/model
277         foomatic_ppd_path = sys_conf.get('dirs', 'ppdbase', '/usr/share/ppd')
278
279         if not foomatic_ppd_path or not os.path.exists(foomatic_ppd_path):
280             foomatic_ppd_path = '/usr/share/ppd'
281
282         log.debug("CUPS PPD base path = %s" % cups_ppd_path)
283         log.debug("Foomatic PPD base path = %s" % foomatic_ppd_path)
284
285         for ppd in ppd_dict:
286             if not ppd:
287                 continue
288
289             if 'hp-' in ppd.lower() or 'hp_' in ppd.lower() and \
290                 ppd_dict[ppd]['ppd-make'] == 'HP':
291
292                 desc = ppd_dict[ppd]['ppd-make-and-model']
293
294                 if not ('foo2' in desc.lower() or
295                         'gutenprint' in desc.lower() or
296                         'gutenprint' in ppd):
297
298                     # PPD files returned by CUPS_GET_PPDS (and by lpinfo -m)
299                     # can be relative to /usr/share/ppd/ or to
300                     # /usr/share/cups/model/. Not sure why this is.
301                     # Here we will try both and see which one it is...
302
303                     if os.path.exists(ppd):
304                         path = ppd
305                     else:
306                         try:
307                             path = os.path.join(foomatic_ppd_path, ppd)
308                         except AttributeError: # happens on some boxes with provider: style ppds (foomatic: etc)
309                             path = ppd
310                         else:
311                             if not os.path.exists(path):
312                                 try:
313                                     path = os.path.join(cups_ppd_path, ppd)
314                                 except AttributeError:
315                                     path = ppd
316                                 else:
317                                     if not os.path.exists(path):
318                                         path = ppd # foomatic: or some other driver
319
320                     ppds[path] = desc
321                     #log.debug("%s: %s" % (path, desc))
322
323     return ppds
324
325
326 ## TODO: Move this to CUPSEXT for better performance
327 def levenshtein_distance(a,b):
328     """
329     Calculates the Levenshtein distance between a and b.
330     Written by Magnus Lie Hetland.
331     """
332     n, m = len(a), len(b)
333     if n > m:
334         a,b = b,a
335         n,m = m,n
336
337     current = range(n+1)
338     for i in range(1,m+1):
339         previous, current = current, [i]+[0]*m
340
341         for j in range(1,n+1):
342             add, delete = previous[j]+1, current[j-1]+1
343             change = previous[j-1]
344
345             if a[j-1] != b[i-1]:
346                 change = change + 1
347
348             current[j] = min(add, delete, change)
349
350     return current[n]
351
352
353 number_pat = re.compile(r""".*?(\d+)""", re.IGNORECASE)
354
355 STRIP_STRINGS2 = ['foomatic:', 'hp-', 'hp_', 'hp ', '.gz', '.ppd',
356                   'drv:', '-pcl', '-pcl3', '-jetready',
357                  '-zxs', '-zjs', '-ps', '-postscript',
358                  '-jr', '-lidl', '-lidil', '-ldl', '-hpijs']
359
360
361 for p in models.TECH_CLASS_PDLS.values():
362     pp = '-%s' % p
363     if pp not in STRIP_STRINGS2:
364         STRIP_STRINGS2.append(pp)
365
366
367 STRIP_STRINGS = STRIP_STRINGS2[:]
368 STRIP_STRINGS.extend(['-series', ' series', '_series'])
369
370
371 def stripModel2(model): # For new 2.8.10+ PPD find algorithm
372     model = model.lower()
373
374     for x in STRIP_STRINGS2:
375         model = model.replace(x, '')
376
377     return model
378
379
380 def stripModel(model): # for old PPD find algorithm (removes "series" as well)
381     model = model.lower()
382
383     for x in STRIP_STRINGS:
384         model = model.replace(x, '')
385
386     return model
387
388
389 def getPPDFile(stripped_model, ppds): # Old PPD find
390     """
391         Match up a model name to a PPD from a list of system PPD files.
392     """
393     log.debug("1st stage edit distance match")
394     mins = {}
395     eds = {}
396     min_edit_distance = sys.maxint
397
398     log.debug("Determining edit distance from %s (only showing edit distances < 4)..." % stripped_model)
399     for f in ppds:
400         t = stripModel(os.path.basename(f))
401         eds[f] = levenshtein_distance(stripped_model, t)
402         if eds[f] < 4:
403             log.debug("dist('%s') = %d" % (t, eds[f]))
404         min_edit_distance = min(min_edit_distance, eds[f])
405
406     log.debug("Min. dist = %d" % min_edit_distance)
407
408     for f in ppds:
409         if eds[f] == min_edit_distance:
410             for m in mins:
411                 if os.path.basename(m) == os.path.basename(f):
412                     break # File already in list possibly with different path (Ubuntu, etc)
413             else:
414                 mins[f] = ppds[f]
415
416     log.debug(mins)
417
418     if len(mins) > 1: # try pattern matching the model number
419         log.debug("2nd stage matching with model number")
420
421         try:
422             model_number = number_pat.match(stripped_model).group(1)
423             model_number = int(model_number)
424         except AttributeError:
425             pass
426         except ValueError:
427             pass
428         else:
429             log.debug("model_number=%d" % model_number)
430             matches = {} #[]
431             for x in range(3): # 1, 10, 100
432                 factor = 10**x
433                 log.debug("Factor = %d" % factor)
434                 adj_model_number = int(model_number/factor)*factor
435                 number_matching, match = 0, ''
436
437                 for m in mins:
438                     try:
439                         mins_model_number = number_pat.match(os.path.basename(m)).group(1)
440                         mins_model_number = int(mins_model_number)
441                         log.debug("mins_model_number= %d" % mins_model_number)
442                     except AttributeError:
443                         continue
444                     except ValueError:
445                         continue
446
447                     mins_adj_model_number = int(mins_model_number/factor)*factor
448                     log.debug("mins_adj_model_number=%d" % mins_adj_model_number)
449                     log.debug("adj_model_number=%d" % adj_model_number)
450
451                     if mins_adj_model_number == adj_model_number:
452                         log.debug("match")
453                         number_matching += 1
454                         matches[m] = ppds[m]
455                         log.debug(matches)
456
457                     log.debug("***")
458
459                 if len(matches):
460                     mins = matches
461                     break
462
463     return mins
464
465
466 def getPPDFile2(stripped_model, ppds): # New PPD find
467     # This routine is for the new PPD naming scheme begun in 2.8.10
468     # and beginning with implementation in 2.8.12 (Qt4 hp-setup)
469     # hp-<model name from models.dat w/o beginning hp_>[-<pdl>][-<pdl>][...].ppd[.gz]
470     # 3.9.6: Added handling for hpijs vs. hpcups PPDs/DRVs
471     log.debug("Matching PPD list to model %s..." % stripped_model)
472     matches = []
473     for f in ppds:
474         match = ppd_pat.match(f)
475         if match is not None:
476             if match.group(1) == stripped_model:
477                 log.debug("Found match: %s" % f)
478                 try:
479                     pdls = match.group(2).split('-')
480                 except AttributeError:
481                     pdls = []
482
483                 if (prop.hpcups_build and 'hpijs' not in f) or \
484                     ((prop.hpijs_build and 'hpijs' in pdls) or (prop.hpcups_build and 'hpijs' not in pdls)) or \
485                     ('ps' in pdls):
486                     matches.append((f, [p for p in pdls if p and p != 'hpijs']))
487
488     log.debug(matches)
489     num_matches = len(matches)
490
491     if num_matches == 0:
492         log.warn("No PPD found for model %s using new algorithm. Trying old algorithm..." % stripped_model)
493         matches2 = getPPDFile(stripModel(stripped_model), ppds).items()
494         log.debug(matches2)
495         num_matches2 = len(matches2)
496         if num_matches2:
497             for f, d in matches2:
498                 match = ppd_pat.match(f)
499                 if match is not None:
500                     log.debug("Found match: %s" % f)
501                     try:
502                         pdls = match.group(2).split('-')
503                     except AttributeError:
504                         pdls = []
505
506                     if (prop.hpcups_build and 'hpijs' not in f) or \
507                        ((prop.hpijs_build and 'hpijs' in pdls) or (prop.hpcups_build and 'hpijs' not in pdls)) or \
508                        ('ps' in pdls):
509                         matches.append((f, [p for p in pdls if p and p != 'hpijs']))
510
511         log.debug(matches)
512         num_matches = len(matches)
513
514     if num_matches == 0:
515         log.error("No PPD found for model %s using old algorithm." % stripModel(stripped_model))
516         return None
517
518     elif num_matches == 1:
519         log.debug("One match found.")
520         return (matches[0][0], '')
521
522     # > 1
523     log.debug("%d matches found. Selecting based on PDL: Host > PS > PCL/Other" % num_matches)
524     for p in [models.PDL_TYPE_HOST, models.PDL_TYPE_PS, models.PDL_TYPE_PCL]:
525         for f, pdl_list in matches:
526             for x in pdl_list:
527                 # default to HOST-based PDLs, as newly supported PDLs will most likely be of this type
528                 if models.PDL_TYPES.get(x, models.PDL_TYPE_HOST) == p:
529                     log.debug("Selecting '-%s' PPD: %s" % (x, f))
530                     return (f, '')
531
532     # No specific PDL found, so just return 1st found PPD file
533     log.debug("No specific PDL located. Defaulting to first found PPD file.")
534     return (matches[0][0], '')
535
536
537
538 def getErrorLogLevel():
539     cups_conf = '/etc/cups/cupsd.conf'
540     try:
541         f = file(cups_conf, 'r')
542     except OSError:
543         log.error("%s not found." % cups_conf)
544     except IOError:
545         log.error("%s: I/O error." % cups_conf)
546     else:
547         for l in f:
548             m = pat_cups_error_log.match(l)
549             if m is not None:
550                 level = m.group(1).lower()
551                 log.debug("CUPS error_log LogLevel: %s" % level)
552                 return level
553
554     log.debug("CUPS error_log LogLevel: unknown")
555     return 'unknown'
556
557
558 def getPrintJobErrorLog(job_id, max_lines=1000, cont_interval=5):
559     ret = []
560     s = '[Job %d]' % job_id
561     #level = getErrorLogLevel()
562     cups_conf = '/var/log/cups/error_log'
563
564     #if level in ('debug', 'debug2'):
565     if 1:
566         try:
567             f = file(cups_conf, 'r')
568         except (IOError, OSError):
569             log.error("Could not open the CUPS error_log file: %s" % cups_conf)
570             return ''
571
572         else:
573             if s in file(cups_conf, 'r').read():
574                 queue = utils.Queue()
575                 job_found = False
576
577                 while True:
578                     line = f.readline()
579
580                     if s in line:
581                         job_found = True
582
583                         while len(queue):
584                             ret.append(queue.get())
585
586                         ret.append(line.strip())
587
588                         if len(ret) > max_lines:
589                             break
590
591                     else:
592                         if job_found:
593                             queue.put(line.strip())
594
595                             if len(queue) > cont_interval:
596                                 break
597
598             return '\n'.join(ret)
599
600
601 #
602 # cupsext wrappers
603 #
604
605 def getDefaultPrinter():
606     r = cupsext.getDefaultPrinter()
607     if r is None:
608         log.debug("The CUPS default printer is not set.")
609     return r
610
611 def setDefaultPrinter(printer_name):
612     setPasswordPrompt("You do not have permission to set the default printer.")
613     return cupsext.setDefaultPrinter(printer_name)
614
615 def accept(printer_name):
616     setPasswordPrompt("You do not have permission to accept jobs on a printer queue.")
617     return controlPrinter(printer_name, CUPS_ACCEPT_JOBS)
618
619 def reject(printer_name):
620     setPasswordPrompt("You do not have permission to reject jobs on a printer queue.")
621     return controlPrinter(printer_name, CUPS_REJECT_JOBS)
622
623 def start(printer_name):
624     setPasswordPrompt("You do not have permission to start a printer queue.")
625     return controlPrinter(printer_name, IPP_RESUME_PRINTER)
626
627 def stop(printer_name):
628     setPasswordPrompt("You do not have permission to stop a printer queue.")
629     return controlPrinter(printer_name, IPP_PAUSE_PRINTER)
630
631 def purge(printer_name):
632     setPasswordPrompt("You do not have permission to purge jobs.")
633     return controlPrinter(printer_name, IPP_PURGE_JOBS)
634
635 def controlPrinter(printer_name, cups_op):
636     if cups_op in (CUPS_ACCEPT_JOBS, CUPS_REJECT_JOBS, IPP_PAUSE_PRINTER, IPP_RESUME_PRINTER, IPP_PURGE_JOBS):
637         return cupsext.controlPrinter(printer_name, cups_op)
638
639     return 0;
640
641 def openPPD(printer):
642     if not printer:
643         return
644
645     return cupsext.openPPD(printer)
646
647 def closePPD():
648     return cupsext.closePPD()
649
650 def getPPD(printer):
651     if not printer:
652         return
653
654     return cupsext.getPPD(printer)
655
656 def getPPDOption(option):
657     return cupsext.getPPDOption(option)
658
659 def getPPDPageSize():
660     return cupsext.getPPDPageSize()
661
662 def getPrinters():
663 ##    p2 = []
664 ##    p = cupsext.getPrinters()
665 ##    for pp in p:
666 ##        print pp
667 ##        try:
668 ##            pn = pp.name.decode('utf-8')
669 ##        except UnicodeError:
670 ##            pass
671 ##
672 ##        p2.append(pp)
673 ##
674 ##    return p2
675     return cupsext.getPrinters()
676
677 def getJobs(my_job=0, completed=0):
678     return cupsext.getJobs(my_job, completed)
679
680 def getAllJobs(my_job=0):
681     return cupsext.getJobs(my_job, 0) + cupsext.getJobs(my_job, 1)
682
683 def getVersion():
684     return cupsext.getVersion()
685
686 def getVersionTuple():
687     return cupsext.getVersionTuple()
688
689 def getServer():
690     return cupsext.getServer()
691
692 def cancelJob(jobid, dest=None):
693     setPasswordPrompt("You do not have permission to cancel a job.")
694     if dest is not None:
695         return cupsext.cancelJob(dest, jobid)
696     else:
697         jobs = cupsext.getJobs(0, 0)
698         for j in jobs:
699             if j.id == jobid:
700                 return cupsext.cancelJob(j.dest, jobid)
701
702     return False
703
704 def resetOptions():
705     return cupsext.resetOptions()
706
707 def addOption(option):
708     return cupsext.addOption(option)
709
710 def getOptions():
711     return cupsext.getOptions()
712
713 def printFile(printer, filename, title):
714     if os.path.exists(filename):
715         printer = printer.encode('utf-8')
716         filename = filename.encode('utf-8')
717         title = title.encode('utf-8')
718         return cupsext.printFileWithOptions(printer, filename, title)
719         
720     else:
721         return -1
722
723 def addPrinter(printer_name, device_uri, location, ppd_file, model, info):
724     log.debug("addPrinter('%s', '%s', '%s', '%s', '%s', '%s')" %
725         ( printer_name, device_uri, location, ppd_file, model, info))
726
727     if ppd_file and not os.path.exists(ppd_file):
728         log.error("PPD file '%s' not found." % ppd_file)
729         return (-1, "PPD file not found")
730
731     return cupsext.addPrinter(printer_name, device_uri, location, ppd_file, model, info)
732
733 def delPrinter(printer_name):
734     setPasswordPrompt("You do not have permission to delete a printer.")
735     return cupsext.delPrinter(printer_name)
736
737 def enablePrinter(printer_name):
738     setPasswordPrompt("You do not have permission to enable a printer.")
739     cmd= "cupsenable %s"%printer_name
740     return os.system(cmd)
741
742 def getGroupList():
743     return cupsext.getGroupList()
744
745 def getGroup(group):
746     return cupsext.getGroup(group)
747
748 def getOptionList(group):
749     return cupsext.getOptionList(group)
750
751 def getOption(group, option):
752     return cupsext.getOption(group, option)
753
754 def getChoiceList(group, option):
755     return cupsext.getChoiceList(group, option)
756
757 def getChoice(group, option, choice):
758     return cupsext.getChoice(group, option, choice)
759
760 def setOptions():
761     return cupsext.setOptions()
762
763 def removeOption(option):
764     return cupsext.removeOption(option)
765
766 def setPasswordCallback(func):
767     return cupsext.setPasswordCallback(func)
768
769 def setPasswordPrompt(prompt):
770     return cupsext.setPasswordPrompt(prompt)
771
772 def findPPDAttribute(name, spec):
773     return cupsext.findPPDAttribute(name, spec)