/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;