#!/usr/bin/perl
#
# printd
# 
# print spooling daemon
#
use strict;
use Sys::Syslog;
use Fcntl ':flock';
use lib '/home/mjd/lib';
use MJD::Print;
my %CONF = load_conf($CONFIG);
my $NEXTOP = 0;

open STDERR, ">>", '/tmp/printd.err' if $CONF{debug_printd};
print STDERR "printd $$ running\n" if $CONF{debug_printd};
close STDOUT;
chdir $CONF{spooldir} 
  or die "Couldn't chdir to spool directory $CONF{spooldir}: $!; aborting";
open D, "." or die "Couldn't open .: $!; aborting";
flock D, LOCK_EX | LOCK_NB 
  or exit 0;
print STDERR "Got lock.\n" if $CONF{debug_printd};
mainloop();

sub mainloop {
 MAINLOOP:
  while (1) {
    sleep ($NEXTOP - time()) while $NEXTOP > time();
    %CONF = load_conf($CONFIG);
    my @QUEUE = read_spoold($CONF{spooldir});

    if (@QUEUE == 0) {          # empty queue
#      $NEXTOP = time() + 2;     # try again in two seconds
#      next;
      # It seems there is a race condition here.
      # printd #1 reads the queue and finds that it is empty.
      # The someone prints a document, which writes a new queue item
      # and starts printd #2.  printd #2 exits because printd #1 is 
      # still running.  Then printd #1 exits because the queue was empty.
      # Now the document is never printed.
      print STDERR "queue empty; exiting\n" if $CONF{debug_printd};
      exit 0;
    }

    my $head = shift @QUEUE;
    unless (unlink $CONF{curfile}) {
      if ($! !~ /no such file/i) {
        die "Couldn't unlink $CONF{curfile}: $!; aborting";
      }
    }
    symlink $head, $CONF{curfile} or
      temp_error("Couldn't symlink $head to $CONF{curfile}: $!", 30);
    print STDERR "current job is now $head\n" if $CONF{debug_printd};

    unless (open FILE, $CONF{curfile}) {
#      system ("ls -l");
      die "Couldn't open spool file $head ($CONF{curfile}): $!; aborting";
    }
    my $pid;
    if ($pid = fork()) {               # parent
      wait;
      handle_printlp_result($? >> 8) if $? != 0;
    } elsif (defined $pid) {    # child
      open STDIN, "< &=FILE" or exit 8;
      close FILE;
      close STDOUT;
      print STDERR "exec-ing $CONF{printlp}, $CONF{printer}\n" 
          if $CONF{debug_printd};
      exec $CONF{printlp}, $CONF{printer};
      exit 9;
    } else {                    # fork error
      log_error("Couldn't fork for $CONF{printlp}: $!");
    }
    close FILE
      or log_error("Couldn't close spool file $head: $!");
    until (unlink $head) {
      log_error("Couldn't unlink spool file $head: $!!!");
      sleep 10;
    }
    unlink $CONF{curfile};
  }
}

sub read_spoold {
  my $dir = shift;
  local *SPOOLD;
  unless (opendir SPOOLD, $dir) {
    temp_error("Couldn't read spool directory $dir: $!", 10);
  }
  my @f = grep $_ ne $CONF{curfile}, grep !/^\./, readdir SPOOLD;
  print STDERR "queue contains ", scalar(@f), " items\n" 
    if $CONF{debug_printd};
  map $_->[0], sort { $b->[1] <=> $a->[1] } map [$_, -M], @f;
}

# temp_error 
# Something has gone wrong that prevents printing. Log a message with
# syslog and then suspend operations temporarily; try again later
#
# args: $message: message to log
#       $delay: time to wait before trying again
sub temp_error {
  my ($message, $delay) = @_;
  ###XXX TODO log the $message
  warn "printd: $message\n";

  $NEXTOP = time() + $delay;
  next MAINLOOP;
}

sub log_error {
  print STDERR "ERROR: @_\n";
  exit 1;
}

sub handle_printlp_result {
  my @errors = ('No error',
                'printer argument missing',
                'printer argument malformed',
                'printer file is not a character device',
                'group is not lp',
                'couldn\'t open printer device',
                'read error',
                'write error',
                'RESERVED',
                'exec failure',
                );
  my $err = shift;
  temp_error("printlp failed: ($err: $errors[$err])", 10);
}
