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