/usr/local/CPAN/Net-IChat/Net/IChat/Client.pm


package Net::IChat::Client;

use strict;
use warnings;

use EO;
use IO::Socket;
use Sys::Hostname;
use Net::Rendezvous::Publish;
use Class::Accessor::Chained;
use Net::IChat::Conversation;
use base qw( EO Class::Accessor::Chained );

our $VERSION = '0.01';

$SIG{CHLD} = "IGNORE";

exception Net::IChat::Client::CouldNotCreateSocket;

Net::IChat::Client->mk_accessors(
				 qw(
				    vc
				    name
				    port
				    version
				    first
				    last
				    email
				    txtvers
				    status
				    msg
				    pid
				    queuesize
				    server_socket
				    address
				   )
				);

sub init {
  my $self = shift;
  if ( $self->SUPER::init( @_ ) ) {
    $self->vc( '!' );
    $self->port( 5300 );
    $self->status( 'avail' );
    $self->version( '1' );
    $self->txtvers( '1' );
    $self->queuesize( 5 );
    $self->first('Net');
    $self->last('IChat client');
    $self->address( hostname() );
    $self->setup_name;

    return 1;
  }
  return 0;
}

sub setup_name {
  my $self = shift;
  my $hostname = hostname();
  $self->name( 'netichat' . $self->name_at_string );
}

sub name_at_string {
  my $self = shift;
  my $host = hostname;
  return '@' . substr( $host, 0, index( $host, '.' ) );
}

sub converse {
  my $self = shift;
  $self->setup_socket;
}

sub mdns_entry {
  my $self = shift;
  my $entry = shift;
  $self->vc( $entry->attribute('vc') );
  $self->msg( $entry->attribute('msg') );
  $self->first( $entry->attribute( '1st' ) );
  $self->last( $entry->attribute( 'last' ) );
  $self->port( $entry->attribute('port.p2pj') );
  $self->email( $entry->attribute('email') );
  $self->name( $entry->name );
  $self->address( $entry->address );
  $self;
}

sub publish_keys {
  my $self = shift;
  return qw( vc txtvers status port.p2pj version last email 1st phsh msg );
}

sub publish_alternate_vals {
  my $self = shift;
  return {
	  '1st' => 'first',
	  phsh  => 'oid',
	  'port.p2pj' => 'port'
	 };
}

sub publish_keyvals {
  my $self = shift;
  map {
    my $meth = $self->publish_alternate_vals->{ $_ } || $_;
    my $result = sprintf("%s=%s", $_, $self->$meth || '');
    $result;
  } $self->publish_keys;
}

sub publish_attrs {
  my $self = shift;
  join("\001", $self->publish_keyvals);
}

sub mdns_service_type {
  my $self = shift;
  return '_presence._tcp';
}

sub announce {
  my $self = shift;
  my $rp   = Net::Rendezvous::Publish->new;
  my $srv  = $rp->publish(
			  name => $self->name,
			  type => $self->mdns_service_type,
			  port => $self->port,
			  txt  => $self->publish_attrs
			 );
  if (my $pid = fork()) {
    $self->setup_server;
    $self->pid( $pid );
    return 1;
  } else {
    while(1) {
      $rp->step( 0.01 );
    }
  }
}

sub setup_socket {
  my $self = shift;
  my $sock = IO::Socket::INET->new(
				   PeerPort => $self->port,
				   PeerAddr => $self->address,
				   Reuse    => 1
				  );
  if (!$sock) {
    my $host = $self->address;
    my $port = $self->port;
    throw Net::IChat::Client::CouldNotCreateSocket
      text => "could not connect to $host on port $port ($!)";
  }
  Net::IChat::Conversation->new()->socket( $sock, $self->address );
}

sub setup_server {
  my $self = shift;
  my $io   = IO::Socket::INET->new(
				   LocalPort => $self->port,
				   Listen    => $self->queuesize,
				   Reuse     => 1,
				  );
  if (!$io) {
    my $port = $self->port;
    throw Net::IChat::Client::CouldNotCreateSocket
      text => "could not create socket on port $port: $!";
  } else {
    $self->server_socket( $io );
    return 1;
  }
}

sub conversation {
  my $self   = shift;
  local $| = 10;
  my $client = $self->server_socket->accept;
  return Net::IChat::Conversation->new->socket( $client );
}

sub DESTROY {
  my $self = shift;
  if ($self->pid()) {
    kill $self->pid;
  }
  if ($self->server_socket) {
    $self->server_socket->close();
  }
}

1;