File Coverage

blib/lib/Net/Gnutella.pm
Criterion Covered Total %
statement 30 208 14.4
branch 0 106 0.0
condition 0 18 0.0
subroutine 10 27 37.0
pod 0 10 0.0
total 40 369 10.8


line stmt bran cond sub pod time code
1             package Net::Gnutella;
2 1     1   1950 use Net::Gnutella::Client;
  1         8  
  1         32  
3 1     1   611 use Net::Gnutella::Server;
  1         4  
  1         30  
4 1     1   6 use Net::Gnutella::Event;
  1         2  
  1         22  
5 1     1   5 use IO::Socket;
  1         2  
  1         4  
6 1     1   24574 use IO::Select;
  1         11034  
  1         200  
7 1     1   16 use Carp;
  1         2  
  1         190  
8 1     1   11 use strict;
  1         3  
  1         53  
9 1     1   7 use vars qw/@ISA @EXPORT $VERSION $AUTOLOAD/;
  1         4  
  1         147  
10            
11             $VERSION = $VERSION = "0.1";
12            
13 1     1   7 use constant GNUTELLA_CONNECT => 1;
  1         2  
  1         80  
14 1     1   6 use constant GNUTELLA_REQUEST => 2;
  1         3  
  1         6085  
15            
16             require Exporter;
17             @ISA = qw(Exporter);
18             @EXPORT = qw(GNUTELLA_CONNECT GNUTELLA_REQUEST);
19            
20             # Use AUTOHANDLER to supply generic attribute methods
21             #
22             sub AUTOLOAD {
23 0     0     my $self = shift;
24 0           my $attr = $AUTOLOAD;
25 0           $attr =~ s/.*:://;
26 0 0         return unless $attr =~ /[^A-Z]/; # skip DESTROY and all-cap methods
27 0 0         croak sprintf "invalid attribute method: %s->%s()", ref($self), $attr unless exists $self->{_attr}->{lc $attr};
28 0 0         $self->{_attr}->{lc $attr} = shift if @_;
29 0           return $self->{_attr}->{lc $attr};
30             }
31            
32             sub add_handler {
33 0     0 0   my ($self, $event, $coderef, $replace, @args) = @_;
34            
35 0           return $self->_add_handler($event, $coderef, $replace, $self->{_handler}, @args);
36             }
37            
38             sub dequeue {
39 0     0 0   my ($self, $qid) = @_;
40            
41 0           return delete $self->{_queue}->{$qid};
42             }
43            
44             sub do_one_loop {
45 0     0 0   my $self = shift;
46            
47 0           my $timeout = $self->timeout;
48 0           my $time = time();
49            
50 0           foreach my $key ($self->queue) {
51 0           my $event = $self->queue($key);
52            
53 0 0         if ($event->[0] <= $time) {
54 0           $event->[1]->( @{$event}[2..$#{$event}] );
  0            
  0            
55            
56 0           $self->dequeue($key);
57             } else {
58 0           my $nexttimeout = $event->[0] - $time;
59            
60 0 0 0       $timeout = $nexttimeout if $nexttimeout < $timeout or not $timeout;
61             }
62             }
63            
64 0           my ($rr, $wr, $er) = IO::Select->select(@{$self}{'_read', '_write', '_error'}, $timeout);
  0            
65            
66 0           foreach my $sock (@$rr) {
67 0 0         my $conn = $self->{_connhash}->{read}->{$sock} or next;
68            
69 0 0         $conn->[0]->($conn->[1] ? ($conn->[1], $sock) : $sock, @{$conn}[2..$#{$conn}]);
  0            
  0            
70             }
71            
72 0           foreach my $sock (@$wr) {
73 0 0         my $conn = $self->{_connhash}->{write}->{$sock} or next;
74            
75 0 0         $conn->[0]->($conn->[1] ? ($conn->[1], $sock) : $sock, @{$conn}[2..$#{$conn}]);
  0            
  0            
76             }
77             }
78            
79             # Cache the latest 500 PONG hosts (host:port combinations)
80             #
81             sub _host_cache {
82 0     0     my $self = shift;
83            
84 0 0         if (@_) {
85 0           my $time = time();
86 0           my $count = 500;
87 0           my $cache = $self->{_host_cache};
88 0           my $new = {};
89 0           my $i = 0;
90            
91             # Add the specified entries
92             #
93 0           foreach (@_) {
94 0           $cache->{$_} = $time;
95             }
96            
97             # Build a new list containing the most recent n elements
98             #
99 0           foreach (grep { $i++ < $count } sort { $cache->{$b} <=> $cache->{$a} } keys %{$cache}) {
  0            
  0            
  0            
100 0           $new->{$_} = $cache->{$_};
101             }
102            
103 0           $self->{_host_cache} = $new;
104             }
105            
106 0           return keys %{ $self->{_host_cache} };
  0            
107             }
108            
109             sub connections {
110 0     0 0   my $self = shift;
111 0           my @ret;
112            
113 0           foreach my $key (keys %{ $self->{_connhash}->{all} }) {
  0            
114 0           my $conn = $self->{_connhash}->{all}->{$key};
115            
116 0 0         next unless ref $conn eq "Net::Gnutella::Connection";
117 0 0         next unless $conn->connected;
118            
119 0           push @ret, $conn;
120             }
121            
122 0           return @ret;
123             }
124            
125             sub new {
126 0     0 0   my $class = shift;
127 0           my %args = @_;
128            
129 0           my $self = {
130             _connhash => {
131             read => {},
132             write => {},
133             all => {},
134             },
135             _read => new IO::Select,
136             _write => new IO::Select,
137             _attr => {
138             timeout => 10,
139             debug => 0,
140 0           id => [ map { rand(65535**2) } 0..4 ],
141             },
142             _handler => {},
143             _host_cache => {},
144             _msgid_source => {},
145             _qid => 'a',
146             _queue => {},
147             };
148            
149 0           bless $self, $class;
150            
151 0           foreach my $key (keys %args) {
152 0           my $lkey = lc $key;
153            
154 0           $self->$lkey($args{$key});
155             }
156            
157 0           return $self;
158             }
159            
160             sub new_client {
161 0     0 0   my $self = shift;
162 0           my $conn = Net::Gnutella::Client->new($self, @_);
163            
164 0 0         return if $conn->error;
165 0           return $conn;
166             }
167            
168             sub new_server {
169 0     0 0   my $self = shift;
170 0           my $conn = Net::Gnutella::Server->new($self, @_);
171            
172 0 0         return if $conn->error;
173 0           return $conn;
174             }
175            
176             sub queue {
177 0     0 0   my $self = shift;
178            
179 0 0         if (@_) {
180 0           return $self->{_queue}->{$_[0]};
181             } else {
182 0           return keys %{ $self->{_queue} };
  0            
183             }
184             }
185            
186             sub schedule {
187 0     0 0   my ($self, $when, $coderef, @args) = @_;
188            
189 0 0         unless ($when =~ /^\d+[dhmst]$/i) {
190 0           croak "First argument must be a number";
191             }
192            
193 0 0 0       unless (defined $coderef && ref $coderef eq 'CODE') {
194 0           croak "Second argument must be a coderef!";
195             }
196            
197 0           my $time = time();
198            
199 0 0         $when *= 24*60*60 if $when =~ s/d$//i;
200 0 0         $when *= 60*60 if $when =~ s/h$//i;
201 0 0         $when *= 60 if $when =~ s/m$//i;
202 0           $when =~ s/s$//i;
203            
204 0 0         if ($when =~ s/t$//i) {
205 0           $time = $when;
206             } else {
207 0           $time += $when;
208             }
209            
210 0 0         $self->{_qid} = 'a' if $self->{_qid} eq 'zzzzzzzz';
211            
212 0           my $id = $self->{_qid}++;
213 0           $self->{_queue}->{$id} = [ $time, $coderef, @args ];
214 0           return $id;
215             }
216            
217             # Returns the connection a msgid originated from if it
218             # has been seen previously.
219             #
220             sub _msgid_source {
221 0     0     my ($self, $msgid, $conn) = @_;
222            
223 0 0 0       unless ($msgid && ref($msgid) eq 'ARRAY') {
224 0           carp "Invalid message ID: $msgid";
225             }
226            
227 0 0         if ($conn) {
228 0           my $i = 0;
229 0           my $count = 5000;
230 0           my $source = $self->{_msgid_source};
231            
232 0           $source->{join(":", @$msgid)} = [ $conn, time() ];
233            
234 0           foreach (grep { $i++ > $count } sort { $source->{$b}->[1] <=> $source->{$a}->[1] } keys %{$source}) {
  0            
  0            
  0            
235 0           delete $source->{$_};
236             }
237             }
238            
239 0 0         return unless $self->{_msgid_source}->{join(":", @$msgid)};
240 0           return $self->{_msgid_source}->{join(":", @$msgid)}->[0];
241             }
242            
243             sub start {
244 0     0 0   my $self = shift;
245            
246 0           $self->do_one_loop while 1;
247             }
248            
249             sub _add_fh {
250 0     0     my ($self, $fh, $coderef, $flags, $obj, @args) = @_;
251            
252 0 0         unless (ref $coderef eq "CODE") {
253 0           croak "Second argument to ->_add_fh not a coderef";
254             }
255            
256 0   0       $flags ||= 'r';
257            
258 0 0         if ($flags =~ /r/i) {
259 0           $self->{_read}->add($fh);
260 0           $self->{_connhash}->{read}->{$fh} = [ $coderef, $obj, @args ];
261             }
262            
263 0 0         if ($flags =~ /w/i) {
264 0           $self->{_write}->add($fh);
265 0           $self->{_connhash}->{write}->{$fh} = [ $coderef, $obj, @args ];
266             }
267            
268 0           $self->{_connhash}->{all}->{$fh} = $obj;
269             }
270            
271             sub _add_handler {
272 0     0     my ($self, $event, $coderef, $replace, $hashref, @args) = @_;
273            
274 0 0         unless (ref $coderef eq "CODE") {
275 0           croak "Second argument to ->_add_handler not a coderef";
276             }
277            
278 0           my %define = ( replace=>0, before=>1, after=>2 );
279            
280 0 0         if (not defined $replace) {
    0          
281 0           $replace = 2;
282             } elsif ($replace =~ /^\D/) {
283 0   0       $replace = $define{lc $replace} || 2;
284             }
285            
286 0 0         foreach my $ev (ref $event eq "ARRAY" ? @{$event} : $event) {
  0            
287 0 0         if ($ev =~ /^\d/) {
288 0           $ev = Net::Gnutella::Event->trans($ev);
289            
290 0 0         unless ($ev) {
291 0           carp "Unknown event type in ->add_handler";
292 0           return;
293             }
294             }
295            
296 0           $hashref->{lc $ev} = [ $coderef, $replace, @args ];
297             }
298             }
299            
300             sub _handler {
301 0     0     my ($self, $event) = @_;
302 0           my $handler;
303            
304 0 0         unless ($event) {
305 0           confess "I messed up";
306             }
307            
308 0           my $type = $event->type;
309 0           my $conn = $event->from;
310 0 0         my $default = $conn->can('_default') if $conn;
311            
312 0 0 0       if ($conn && exists $conn->{_handler}->{$type}) {
    0          
    0          
313 0 0         printf STDERR " - Connection wide handler exists\n" if $self->debug >= 2;
314 0           $handler = $conn->{_handler}->{$type};
315             } elsif (exists $self->{_handler}->{$type}) {
316 0 0         printf STDERR " - Global handler exists\n" if $self->debug >= 2;
317 0           $handler = $self->{_handler}->{$type};
318             } elsif ($default) {
319 0 0         printf STDERR " - Calling default handler on connection\n" if $self->debug >= 2;
320 0           return $conn->_default($event);
321             } else {
322 0 0         printf STDERR " - Calling default global handler\n" if $self->debug >= 2;
323 0           return $self->_default($event);
324             }
325            
326 0           my ($coderef, $replace, @args) = @$handler;
327            
328 0 0         if ($replace == 0) { # REPLACE
    0          
    0          
329 0           $coderef->($conn, $event, @args);
330             } elsif ($replace == 1) { # BEFORE
331 0 0         $coderef->($conn, $event, @args) or return;
332            
333 0 0         if ($default) {
334 0           $conn->_default($event, @args);
335             } else {
336 0           $self->_default($event, @args);
337             }
338             } elsif ($replace == 2) { # AFTER
339 0 0         if ($default) {
340 0 0         $conn->_default($event, @args) or return;
341             } else {
342 0 0         $self->_default($event, @args) or return;
343             }
344            
345 0           $coderef->($conn, $event, @args);
346             }
347             }
348            
349             sub _remove_fh {
350 0     0     my ($self, $fh, $flags) = @_;
351            
352 0   0       $flags ||= 'r';
353            
354 0 0         if ($flags =~ /r/i) {
355 0           $self->{_read}->remove($fh);
356 0           delete $self->{_connhash}->{read}->{$fh};
357             }
358            
359 0 0         if ($flags =~ /w/i) {
360 0           $self->{_write}->remove($fh);
361 0           delete $self->{_connhash}->{write}->{$fh};
362             }
363             }
364            
365             1;