#!/usr/bin/perl
#
# add airprint records to match any existing printers
#
use strict;
use warnings;

# use the version of Net::Bonjour that comes with this code, as the
# current CPAN distro dies when fetching TXT records.
BEGIN {
    print STDERR scalar(localtime) . " Starting airprint republication server\n";
    use Cwd;
    use File::Basename;
    use File::Spec;

    my $binary_dir = dirname( $0 );
    if ( $binary_dir !~ m@/@ ) {
        $binary_dir = File::Spec->join( getcwd(), $binary_dir );
    }
    # not really important, just pickiness
    $binary_dir =~ s@^(.+)/.$@$1@;

    unshift @INC, $binary_dir;
}

use Net::Bonjour 0.97;

# there doesn't appear to be a way to republish the printers without a
# prefix. dns-sd says it's done so, but the records don't appear in the local
# browse domains.
my $prefix = 'AirPrint ';

# First, find some printers.
my %printers;
my $res = new Net::Bonjour( 'ipp', 'tcp', 'local' );
$res->discover();

# stash what we've found, but skip anything starting with our prefix.
for my $entry ( $res->entries ) {
    $printers{$entry->name} = $entry unless $entry->name =~ /$prefix/;
}

my @printers = sort keys %printers;
my @children;
if (@printers) {
    print STDERR "Found printers: " .join(", ", @printers) . "\n";
} else {
    print STDERR "No printers found\n";
}

# Now republish all the printers we found
for my $printer ( @printers ) {
    # XXX check if we've already exported this as an airprint printer
    #my $airprinted = get_dnssd_record( "AirPrint ". $printer, '_universal._ipp._tcp' && next;

    # get the current DNS-SD attributes
    my $record = { $printers{$printer}->all_attrs };

    # add the URF entries to the record
    $record->{pdl} .= ',image/urf';
    $record->{URF} = 'none';

    # publish the new record
    push @children, publish_dnssd_record( $printer, $record );
}

if ( @children ) {
    print STDERR "Waiting for child processes to exit.\n";
    wait();
}

print STDERR scalar(localtime) . " Exiting\n";

sub publish_dnssd_record {
    my $printer = shift;

    # need to clean out the \\032 crap. Not sure if there's other
    # stuff I should be cleaning out.
    $printer =~ s/\\032/ /g;
    my $record = shift;
    my @args;
    for my $key ( sort keys %{$record} ) {
        my $string = "$key=";
        my $value = $record->{$key};

        $value =~ s@\\@\\\\@g;
        $value =~ s@(\s)@\\$1@g;
        $string .= $value;
        push @args, $string;
    }

    # Ideally I'd do this with Net::MDNS, but right now that doesn't
    # build on MacOS (at least not the CPAN version). This forks off a
    # process per printer, and they remain running after the script
    # has exited. You can get rid of them with 'killall -TERM dns-sd'
    my @cmd = ( '/usr/bin/dns-sd', '-R', $prefix . $printer, '_ipp._tcp,_universal', '.', '631', @args );

    my $pid = fork();
    if ( !defined( $pid )) {
        print STDERR "Fork failed: $!";
    }
    if ( $pid == 0  ) { # child
        exec( @cmd ) or
            print STDERR "dns-sd -R failed\n";
	exit(1);
    }
    return $pid;
}

# Better approach:
#  set up a dns-sd listener (e.g. run dns-sd in a fork
#  any time a printer shows up, spin up a dns-sd child to republish it as an airprint printer
#  if a printer is removed, remove the corresponding dns-sd child
