#!/usr/bin/python

#     printconf - silly script to set up every printer attached to the
#                 system with zero fuss.
#     Copyright (C) 2004-10 Chris Lawrence <lawrencc@debian.org>

#     This program is free software; you can redistribute it and/or modify
#     it under the terms of the GNU General Public License as published by
#     the Free Software Foundation; either version 2 of the License, or
#     (at your option) any later version.
#
#     This program is distributed in the hope that it will be useful,
#     but WITHOUT ANY WARRANTY; without even the implied warranty of
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#     GNU General Public License for more details.
#
#     You should have received a copy of the GNU General Public License
#     along with this program; if not, write to the Free Software
#     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# $Id: printconf,v 1.29 2010/05/23 00:32:34 lordsutch Exp $

import foomatic
import foomatic.foomatic
import foomatic.detect
import pprint
import re
import os
import textwrap
import commands
import sys
import tempfile
import glob
import cups
import locale
locale.setlocale(locale.LC_ALL, '')
charset = locale.nl_langinfo(locale.CODESET)
if charset.lower() == 'ansi_x3.4-1968':
    charset = 'us-ascii'

from optparse import OptionParser

try:
    r, c = commands.getoutput('stty size').split()
    rows, columns = int(r) or 24, (int(c) or 80)-1
except:
    rows, columns = 24, 79

parser = OptionParser(version='%prog '+foomatic.__version__)
parser.add_option('-n', '--no-action', '--dry-run', dest='dryrun',
                  action='store_true', help="don't configure any printers")
parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
                  help="include more detail about what's happening")
parser.add_option('-m', '--parallel-module', dest='parallel_module',
                  action='store_true',
                  help="reload the parallel kernel modules to detect newly connected printers")
parser.add_option('-c', '--class', dest='printers_class', action='store',
                  help="add all printers to specified class")
parser.add_option('-p', '--papersize', dest='papersize', action='store',
                  help="use specified papersize")
parser.add_option('-s', '--spooler', dest='spooler', action='store',
		  help="specify the spooler in case it cannot be autodetected")
options, args = parser.parse_args()

dryrun = options.dryrun
verbose = options.verbose
parallel_module = options.parallel_module
printers_class = options.printers_class
papersize = options.papersize
spooler = options.spooler

if os.geteuid() and not dryrun:
    print >> sys.stderr, "Please run printconf as root or specify the --dry-run option."
    sys.exit(1)

# regex for invalid characters in queue names to be filtered
_invalidre = re.compile(r'([^A-Za-z0-9_])')

def print_fill(text, *args):
    global charset
    
    if args:
        text = text % args
    print unicode(textwrap.fill(text, columns)).encode(charset, 'replace')

def cleanup_name(model):
    model = model.lower().replace(' ', '_')
    newmod = _invalidre.sub('', model)
    return newmod

from xml.sax.saxutils import escape

def format_detectdata(data, device):
    dtype = 'general'
    if device.startswith('parallel'):
        dtype = 'parallel'
    elif device.startswith('usb'):
        dtype = 'usb'

    print '<autodetect>'
    print '  <%s>' % dtype
    for param in ('commandset', 'description', 'manufacturer', 'model'):
        d = data.get(param)
        if d:
            print '    <%s>%s</%s>' % (param, escape(d), param)
    print '  </%s>' % dtype
    print '</autodetect>'
    return

# CUPS needs a non-empty printers.conf before lpadmin will work properly
if not os.path.exists('/etc/cups/printers.conf'):
    f = file('/etc/cups/printers.conf', 'a')
    f.close()

if verbose:
    print 'Getting printer information...'

printdb = foomatic.foomatic.get_printer_db()
if not printdb:
    print_fill('Unable to read printer database.  Please ensure the "foomatic-db" package is installed properly.')
    sys.exit(1)

conns = foomatic.detect.printer_connections()

if verbose:
    print 'Autoconfiguring printers...'
# Ensure we don't overwrite any existing queues
queues = {}
existing = foomatic.foomatic.get_printer_queues()
if existing:
    for q in existing.queues:
        queues[q['name']] = True

# Make sure we reload the kernel module so that the files under /proc/sys/dev/parport get updated
if parallel_module and not dryrun:
    print 'Reload the parallel kernel modules to detect newly connected printers'
    os.system('/sbin/rmmod ppdev lp parport_pc parport 2>/dev/null')
    os.system('/sbin/modprobe -a parport parport_pc ppdev lp 2>/dev/null')

# Initiate cups connection so it is available during printer queue setup
c = cups.Connection()

for (device, detectdata, devdesc, detectdesc) in conns:
    # Skip everything we don't have autodetection data for
    if not detectdata:
        if verbose:
            print 'Skipping %s; no autodetection data available.' % device
        continue

    # Get the IEEE 1284 printer description
    desc = detectdata.get('description')
    pdbinfo = None
    if desc:
        pdbinfo = printdb.autodetect_ids.get(desc)

    if not pdbinfo:
        mfg = detectdata.get('manufacturer', '')
        model = detectdata.get('model', '')
        pdbinfo = printdb.autodetect_ids.get((mfg, model))

    if not pdbinfo:
        # Try using the manufacturer and model information to query the
        # database (this is an ad hoc approach, but may often work)
        mfg = detectdata.get('manufacturer', '').upper()
        model = detectdata.get('model', '').upper()
        pdbinfo = printdb.cmakes.get(mfg, {}).get(model)
        if pdbinfo:
            print_fill('Printer on %s was detected by Debian using the ad-hoc method.  Please submit the following information to foomatic-db@packages.debian.org:', device)
            format_detectdata(detectdata, device)
            print
    
    # Unknown printer; probably should print a message here asking
    # people to contribute this information to foomatic-db
    if not pdbinfo:
        if detectdata:
            print_fill('Printer on %s was not automatically configurable by Debian.  Please submit the following information to foomatic-db@packages.debian.org:', device)
            format_detectdata(detectdata, device)
            print
        continue

    make = pdbinfo.get('make')
    model = pdbinfo.get('model')

    prefdriver = pdbinfo.get('driver')
    printer = pdbinfo.get('id')
    # Unsupported printer; barf something here
    if not prefdriver or not printer:
        print_fill('%s %s on %s is not currently supported by Debian.',
                   make, model, devdesc)
        print
        continue

    drivers = pdbinfo.get('drivers', [])

    if prefdriver == 'gimp-print' and 'gimp-print-ijs' in drivers:
        prefdriver = 'gimp-print-ijs'

    if verbose:
        print 'Printer database data:'
        pprint.pprint(pdbinfo)

    # In case more than one printer of the same type is on the computer
    qname = cleanname = cleanup_name(model)
    i=1
    while queues.get(qname):
        qname = '%s_%d' % (cleanname, i)
        i += 1

    data = {'connect' : device, 'description' : desc, 'name' : qname,
            'location' : devdesc }
    if spooler:
        data['spooler'] = spooler
    
    a = printer.replace('-', '_')
    prefppd = '%s%s%s' % ('/usr/local/share/ppd/', a, '.ppd')
    if glob.glob(prefppd):
	print 'Using prefered ppd file'
	foundppd = True
	system_ppd = False
	data['printer'] = printer
	data['ppdfile'] = prefppd
        print_fill(
            'Configuring %s %s on %s with %s driver as queue "%s".',
            make, model, device, prefdriver, qname)
    else:
	foundppd = False

    if not foundppd:
	if prefdriver == 'postscript' and pdbinfo.get('ppd'):
		ppd_content = foomatic.get_ppd_content(pdbinfo.get('ppd'))
        	if ppd_content:
        	    foundppd = True
		    system_ppd = True
	
	            (tfh, tfname) = tempfile.mkstemp(text=True, suffix=".ppd",
	                                             prefix="printconf-")
	            tf = os.fdopen(tfh, 'w+')
	            tf.write(ppd_content)
	            tf.close()
	            data['ppdfile'] = tfname
		    print_fill(
	                'Configuring %s %s on %s with PPD %s as queue "%s".',
	                make, model, device, fp.name, qname)

    if not foundppd:
        data['driver'] = prefdriver
        data['printer'] = printer
        print_fill(
            'Configuring %s %s on %s with %s driver as queue "%s".',
            make, model, device, prefdriver, qname)

    if dryrun:
        print '(dry run; skipping call to foomatic-configure)'
    else:
        foomatic.foomatic.setup_queue(data)

    if foundppd:
        os.unlink(tfname)
    
    # Queue X is now set up with name based on the IEEE model
    # some sort of message here would be nice
    queues[qname] = True
    if papersize:
	foomatic.foomatic.set_printer_options(queuename=qname, options={ "PageSize": papersize})
    print

# Add printers to specified class
if printers_class:
   c = cups.Connection()
   available_classes = c.getClasses()
   available_printers = c.getPrinters()
   # Only do sanity checks, if class doesn't exist yet
   if printers_class in available_classes:
       for iter_class, iter_class_printers in available_classes.iteritems():
           if printers_class in iter_class:
               for iter_printers in available_printers:
                   # Check that printer is neither in the group, nor a classname itself
                   if iter_printers not in iter_class_printers and iter_printers not in available_classes and iter_printers not in str(available_classes):
                       print_fill('Adding "%s" printer to class "%s"', iter_printers, printers_class)
                       c.addPrinterToClass(iter_printers, printers_class)
                       c.enablePrinter(printers_class)
                       c.acceptJobs(printers_class)
                       # Check that given class is not already a printer, otherwise we'd allow a potential DoS
                   elif iter_printers not in available_classes:
                       print_fill('Printer "%s" already in group "%s"', iter_printers, printers_class)
                   else:
                       print_fill('Class "%s" is already defined as a printer and thus cannot be used', printers_class)
   elif printers_class not in available_classes:
       print_fill('Creating new class "%s" and adding all printers to it', printers_class)
       for i in c.getPrinters():
               if i not in available_classes:
                       c.addPrinterToClass(i, printers_class)
                       c.enablePrinter(printers_class)
                       c.acceptJobs(printers_class)




# After loop, reinit CUPS
if not dryrun:
    # Need to restart cups completely, otherwise we are crashing it with the cups calls
    os.system('/usr/sbin/invoke-rc.d cups restart')

    print_fill('''If printconf was unable to install all of your printers,
please visit http://www.linuxprinting.org/ for printer information and support
from fellow users.''')
