ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/Net-FCP/FCP.pm
Revision: 1.22
Committed: Wed Sep 17 05:05:33 2003 UTC (20 years, 9 months ago) by root
Branch: MAIN
Changes since 1.21: +3 -2 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 =head1 NAME
2    
3     Net::FCP - http://freenet.sf.net client protocol
4    
5     =head1 SYNOPSIS
6    
7     use Net::FCP;
8    
9     my $fcp = new Net::FCP;
10    
11     my $ni = $fcp->txn_node_info->result;
12     my $ni = $fcp->node_info;
13    
14     =head1 DESCRIPTION
15    
16     See L<http://freenet.sourceforge.net/index.php?page=fcp> for a description
17     of what the messages do. I am too lazy to document all this here.
18    
19     =head1 WARNING
20    
21     This module is alpha. While it probably won't destroy (much :) of your
22 root 1.9 data, it currently falls short of what it should provide (intelligent uri
23     following, splitfile downloads, healing...)
24    
25     =head2 IMPORT TAGS
26    
27     Nothing much can be "imported" from this module right now. There are,
28     however, certain "import tags" that can be used to select the event model
29     to be used.
30    
31     Event models are implemented as modules under the C<Net::FCP::Event::xyz>
32     class, where C<xyz> is the event model to use. The default is C<Event> (or
33     later C<Auto>).
34    
35     The import tag to use is named C<event=xyz>, e.g. C<event=Event>,
36     C<event=Glib> etc.
37    
38     You should specify the event module to use only in the main program.
39 root 1.1
40 root 1.20 If no event model has been specified, FCP tries to autodetect it on first
41     use (e.g. first transaction), in this order: Coro, Event, Glib, Tk.
42    
43 root 1.17 =head2 FREENET BASICS
44    
45     Ok, this section will not explain any freenet basics to you, just some
46     problems I found that you might want to avoid:
47    
48     =over 4
49    
50     =item freenet URIs are _NOT_ URIs
51    
52     Whenever a "uri" is required by the protocol, freenet expects a kind of
53     URI prefixed with the "freenet:" scheme, e.g. "freenet:CHK...". However,
54     these are not URIs, as freeent fails to parse them correctly, that is, you
55     must unescape an escaped characters ("%2c" => ",") yourself. Maybe in the
56     future this library will do it for you, so watch out for this incompatible
57     change.
58    
59     =item Numbers are in HEX
60    
61     Virtually every number in the FCP protocol is in hex. Be sure to use
62     C<hex()> on all such numbers, as the module (currently) does nothing to
63     convert these for you.
64    
65     =back
66    
67 root 1.1 =head2 THE Net::FCP CLASS
68    
69     =over 4
70    
71     =cut
72    
73     package Net::FCP;
74    
75     use Carp;
76    
77 root 1.22 $VERSION = 0.5;
78 root 1.10
79     no warnings;
80 root 1.1
81 root 1.9 our $EVENT = Net::FCP::Event::Auto::;
82 root 1.1
83 root 1.9 sub import {
84     shift;
85 root 1.1
86 root 1.9 for (@_) {
87     if (/^event=(\w+)$/) {
88     $EVENT = "Net::FCP::Event::$1";
89 root 1.20 eval "require $EVENT";
90 root 1.9 }
91     }
92 root 1.12 die $@ if $@;
93 root 1.1 }
94    
95 root 1.2 sub touc($) {
96     local $_ = shift;
97     1 while s/((?:^|_)(?:svk|chk|uri)(?:_|$))/\U$1/;
98     s/(?:^|_)(.)/\U$1/g;
99     $_;
100     }
101    
102     sub tolc($) {
103     local $_ = shift;
104     s/(?<=[a-z])(?=[A-Z])/_/g;
105     lc $_;
106     }
107    
108 root 1.7 =item $meta = Net::FCP::parse_metadata $string
109    
110     Parse a metadata string and return it.
111    
112 root 1.21 The metadata will be a hashref with key C<version> (containing the
113     mandatory version header entries) and key C<raw> containing the original
114     metadata string.
115 root 1.7
116     All other headers are represented by arrayrefs (they can be repeated).
117    
118 root 1.21 Since this description is confusing, here is a rather verbose example of a
119     parsed manifest:
120 root 1.7
121     (
122 root 1.21 raw => "Version...",
123 root 1.7 version => { revision => 1 },
124     document => [
125     {
126 root 1.17 info => { format" => "image/jpeg" },
127 root 1.7 name => "background.jpg",
128 root 1.17 redirect => { target => "freenet:CHK\@ZcagI,ra726bSw" },
129 root 1.7 },
130     {
131 root 1.17 info => { format" => "text/html" },
132 root 1.7 name => ".next",
133 root 1.17 redirect => { target => "freenet:SSK\@ilUPAgM/TFEE/3" },
134 root 1.7 },
135     {
136 root 1.17 info => { format" => "text/html" },
137     redirect => { target => "freenet:CHK\@8M8Po8ucwI,8xA" },
138 root 1.7 }
139     ]
140     )
141    
142     =cut
143    
144     sub parse_metadata {
145 root 1.21 my $data = shift;
146     my $meta = { raw => $data };
147 root 1.7
148     if ($data =~ /^Version\015?\012/gc) {
149     my $hdr = $meta->{version} = {};
150    
151     for (;;) {
152     while ($data =~ /\G([^=\015\012]+)=([^\015\012]*)\015?\012/gc) {
153     my ($k, $v) = ($1, $2);
154 root 1.12 my @p = split /\./, tolc $k, 3;
155    
156     $hdr->{$p[0]} = $v if @p == 1; # lamest code I ever wrote
157     $hdr->{$p[0]}{$p[1]} = $v if @p == 2;
158 root 1.15 $hdr->{$p[0]}{$p[1]}{$p[2]} = $v if @p == 3;
159 root 1.12 die "FATAL: 4+ dot metadata" if @p >= 4;
160 root 1.7 }
161    
162     if ($data =~ /\GEndPart\015?\012/gc) {
163 root 1.12 # nop
164 root 1.17 } elsif ($data =~ /\GEnd(\015?\012|$)/gc) {
165 root 1.7 last;
166     } elsif ($data =~ /\G([A-Za-z0-9.\-]+)\015?\012/gcs) {
167     push @{$meta->{tolc $1}}, $hdr = {};
168     } elsif ($data =~ /\G(.*)/gcs) {
169 root 1.18 print STDERR "metadata format error ($1), please report this string: <<$data>>";
170     die "metadata format error";
171 root 1.7 }
172     }
173     }
174    
175     #$meta->{tail} = substr $data, pos $data;
176    
177     $meta;
178     }
179    
180 root 1.1 =item $fcp = new Net::FCP [host => $host][, port => $port]
181    
182     Create a new virtual FCP connection to the given host and port (default
183 root 1.5 127.0.0.1:8481, or the environment variables C<FREDHOST> and C<FREDPORT>).
184 root 1.1
185     Connections are virtual because no persistent physical connection is
186 root 1.17 established.
187    
188     =begin comment
189    
190     However, the existance of the node is checked by executing a
191 root 1.1 C<ClientHello> transaction.
192    
193 root 1.17 =end
194    
195 root 1.1 =cut
196    
197     sub new {
198     my $class = shift;
199     my $self = bless { @_ }, $class;
200    
201 root 1.5 $self->{host} ||= $ENV{FREDHOST} || "127.0.0.1";
202 root 1.12 $self->{port} ||= $ENV{FREDPORT} || 8481;
203 root 1.1
204 root 1.12 #$self->{nodehello} = $self->client_hello
205     # or croak "unable to get nodehello from node\n";
206 root 1.1
207     $self;
208     }
209    
210 root 1.9 sub progress {
211     my ($self, $txn, $type, $attr) = @_;
212 root 1.18 #warn "progress<$txn,$type," . (join ":", %$attr) . ">\n";
213 root 1.9 }
214    
215 root 1.1 =item $txn = $fcp->txn(type => attr => val,...)
216    
217     The low-level interface to transactions. Don't use it.
218    
219 root 1.12 Here are some examples of using transactions:
220    
221     The blocking case, no (visible) transactions involved:
222    
223     my $nodehello = $fcp->client_hello;
224    
225     A transaction used in a blocking fashion:
226    
227     my $txn = $fcp->txn_client_hello;
228     ...
229     my $nodehello = $txn->result;
230    
231     Or shorter:
232    
233     my $nodehello = $fcp->txn_client_hello->result;
234    
235     Setting callbacks:
236    
237     $fcp->txn_client_hello->cb(
238     sub { my $nodehello => $_[0]->result }
239     );
240    
241 root 1.1 =cut
242    
243     sub txn {
244     my ($self, $type, %attr) = @_;
245    
246 root 1.2 $type = touc $type;
247    
248     my $txn = "Net::FCP::Txn::$type"->new(fcp => $self, type => tolc $type, attr => \%attr);
249 root 1.1
250     $txn;
251     }
252    
253 root 1.17 { # transactions
254    
255     my $txn = sub {
256 root 1.1 my ($name, $sub) = @_;
257 root 1.17 *{"txn_$name"} = $sub;
258 root 1.1 *{$name} = sub { $sub->(@_)->result };
259 root 1.17 };
260 root 1.1
261     =item $txn = $fcp->txn_client_hello
262    
263     =item $nodehello = $fcp->client_hello
264    
265     Executes a ClientHello request and returns it's results.
266    
267     {
268 root 1.2 max_file_size => "5f5e100",
269 root 1.4 node => "Fred,0.6,1.46,7050"
270 root 1.2 protocol => "1.2",
271 root 1.1 }
272    
273     =cut
274    
275 root 1.17 $txn->(client_hello => sub {
276 root 1.1 my ($self) = @_;
277    
278 root 1.2 $self->txn ("client_hello");
279 root 1.17 });
280 root 1.1
281     =item $txn = $fcp->txn_client_info
282    
283     =item $nodeinfo = $fcp->client_info
284    
285     Executes a ClientInfo request and returns it's results.
286    
287     {
288 root 1.2 active_jobs => "1f",
289     allocated_memory => "bde0000",
290     architecture => "i386",
291     available_threads => 17,
292 root 1.4 datastore_free => "5ce03400",
293     datastore_max => "2540be400",
294 root 1.2 datastore_used => "1f72bb000",
295 root 1.4 estimated_load => 52,
296     free_memory => "5cc0148",
297 root 1.2 is_transient => "false",
298 root 1.4 java_name => "Java HotSpot(_T_M) Server VM",
299 root 1.2 java_vendor => "http://www.blackdown.org/",
300 root 1.4 java_version => "Blackdown-1.4.1-01",
301     least_recent_timestamp => "f41538b878",
302     max_file_size => "5f5e100",
303 root 1.2 most_recent_timestamp => "f77e2cc520"
304 root 1.4 node_address => "1.2.3.4",
305     node_port => 369,
306     operating_system => "Linux",
307     operating_system_version => "2.4.20",
308     routing_time => "a5",
309 root 1.1 }
310    
311     =cut
312    
313 root 1.17 $txn->(client_info => sub {
314 root 1.1 my ($self) = @_;
315    
316 root 1.2 $self->txn ("client_info");
317 root 1.17 });
318 root 1.1
319 root 1.21 =item $txn = $fcp->txn_generate_chk ($metadata, $data[, $cipher])
320 root 1.1
321 root 1.21 =item $uri = $fcp->generate_chk ($metadata, $data[, $cipher])
322 root 1.1
323 root 1.21 Calculcates a CHK, given the metadata and data. C<$cipher> is either
324     C<Rijndael> or C<Twofish>, with the latter being the default.
325 root 1.1
326     =cut
327    
328 root 1.17 $txn->(generate_chk => sub {
329 root 1.21 my ($self, $metadata, $data, $cipher) = @_;
330 root 1.1
331 root 1.21 $self->txn (generate_chk =>
332     data => "$metadata$data",
333     metadata_length => length $metadata,
334     cipher => $cipher || "Twofish");
335 root 1.17 });
336 root 1.1
337     =item $txn = $fcp->txn_generate_svk_pair
338    
339     =item ($public, $private) = @{ $fcp->generate_svk_pair }
340    
341     Creates a new SVK pair. Returns an arrayref.
342    
343     [
344     "hKs0-WDQA4pVZyMPKNFsK1zapWY",
345     "ZnmvMITaTXBMFGl4~jrjuyWxOWg"
346     ]
347    
348     =cut
349    
350 root 1.17 $txn->(generate_svk_pair => sub {
351 root 1.1 my ($self) = @_;
352    
353 root 1.2 $self->txn ("generate_svk_pair");
354 root 1.17 });
355 root 1.1
356     =item $txn = $fcp->txn_insert_private_key ($private)
357    
358 root 1.17 =item $public = $fcp->insert_private_key ($private)
359 root 1.1
360     Inserts a private key. $private can be either an insert URI (must start
361 root 1.17 with C<freenet:SSK@>) or a raw private key (i.e. the private value you get
362     back from C<generate_svk_pair>).
363 root 1.1
364     Returns the public key.
365    
366     UNTESTED.
367    
368     =cut
369    
370 root 1.17 $txn->(insert_private_key => sub {
371 root 1.1 my ($self, $privkey) = @_;
372    
373 root 1.2 $self->txn (invert_private_key => private => $privkey);
374 root 1.17 });
375 root 1.1
376     =item $txn = $fcp->txn_get_size ($uri)
377    
378     =item $length = $fcp->get_size ($uri)
379    
380     Finds and returns the size (rounded up to the nearest power of two) of the
381     given document.
382    
383     UNTESTED.
384    
385     =cut
386    
387 root 1.17 $txn->(get_size => sub {
388 root 1.1 my ($self, $uri) = @_;
389    
390 root 1.2 $self->txn (get_size => URI => $uri);
391 root 1.17 });
392 root 1.1
393 root 1.5 =item $txn = $fcp->txn_client_get ($uri [, $htl = 15 [, $removelocal = 0]])
394    
395 root 1.7 =item ($metadata, $data) = @{ $fcp->client_get ($uri, $htl, $removelocal)
396 root 1.5
397 root 1.7 Fetches a (small, as it should fit into memory) file from
398     freenet. C<$meta> is the metadata (as returned by C<parse_metadata> or
399     C<undef>).
400 root 1.5
401 root 1.7 Due to the overhead, a better method to download big files should be used.
402 root 1.5
403 root 1.7 my ($meta, $data) = @{
404 root 1.5 $fcp->client_get (
405     "freenet:CHK@hdXaxkwZ9rA8-SidT0AN-bniQlgPAwI,XdCDmBuGsd-ulqbLnZ8v~w"
406     )
407     };
408    
409     =cut
410    
411 root 1.17 $txn->(client_get => sub {
412 root 1.5 my ($self, $uri, $htl, $removelocal) = @_;
413    
414 root 1.17 $self->txn (client_get => URI => $uri, hops_to_live => (defined $htl ? $htl :15),
415     remove_local_key => $removelocal ? "true" : "false");
416     });
417    
418     =item $txn = $fcp->txn_client_put ($uri, $metadata, $data, $htl, $removelocal)
419    
420     =item my $uri = $fcp->client_put ($uri, $metadata, $data, $htl, $removelocal);
421    
422     Insert a new key. If the client is inserting a CHK, the URI may be
423     abbreviated as just CHK@. In this case, the node will calculate the
424     CHK.
425    
426     C<$meta> can be a reference or a string (ONLY THE STRING CASE IS IMPLEMENTED!).
427    
428     THIS INTERFACE IS UNTESTED AND SUBJECT TO CHANGE.
429    
430     =cut
431    
432     $txn->(client_put => sub {
433     my ($self, $uri, $meta, $data, $htl, $removelocal) = @_;
434    
435     $self->txn (client_put => URI => $uri, hops_to_live => (defined $htl ? $htl :15),
436     remove_local_key => $removelocal ? "true" : "false",
437     data => "$meta$data", metadata_length => length $meta);
438     });
439    
440     } # transactions
441 root 1.5
442 root 1.17 =item MISSING: (ClientPut), InsretKey
443 root 1.1
444     =back
445    
446     =head2 THE Net::FCP::Txn CLASS
447    
448     All requests (or transactions) are executed in a asynchroneous way (LIE:
449     uploads are blocking). For each request, a C<Net::FCP::Txn> object is
450     created (worse: a tcp connection is created, too).
451    
452     For each request there is actually a different subclass (and it's possible
453     to subclass these, although of course not documented).
454    
455     The most interesting method is C<result>.
456    
457     =over 4
458    
459     =cut
460    
461     package Net::FCP::Txn;
462    
463 root 1.12 use Fcntl;
464     use Socket;
465    
466 root 1.1 =item new arg => val,...
467    
468     Creates a new C<Net::FCP::Txn> object. Not normally used.
469    
470     =cut
471    
472     sub new {
473     my $class = shift;
474     my $self = bless { @_ }, $class;
475    
476 root 1.12 $self->{signal} = $EVENT->new_signal;
477    
478     $self->{fcp}{txn}{$self} = $self;
479    
480 root 1.1 my $attr = "";
481     my $data = delete $self->{attr}{data};
482    
483     while (my ($k, $v) = each %{$self->{attr}}) {
484 root 1.2 $attr .= (Net::FCP::touc $k) . "=$v\012"
485 root 1.1 }
486    
487     if (defined $data) {
488 root 1.21 $attr .= sprintf "DataLength=%x\012", length $data;
489 root 1.1 $data = "Data\012$data";
490     } else {
491     $data = "EndMessage\012";
492     }
493    
494 root 1.12 socket my $fh, PF_INET, SOCK_STREAM, 0
495     or Carp::croak "unable to create new tcp socket: $!";
496 root 1.1 binmode $fh, ":raw";
497 root 1.12 fcntl $fh, F_SETFL, O_NONBLOCK;
498     connect $fh, (sockaddr_in $self->{fcp}{port}, inet_aton $self->{fcp}{host})
499     and !$!{EWOULDBLOCK}
500     and !$!{EINPROGRESS}
501     and Carp::croak "FCP::txn: unable to connect to $self->{fcp}{host}:$self->{fcp}{port}: $!\n";
502    
503     $self->{sbuf} =
504     "\x00\x00\x00\x02"
505 root 1.21 . (Net::FCP::touc $self->{type})
506 root 1.12 . "\012$attr$data";
507 root 1.1
508 root 1.21 #shutdown $fh, 1; # freenet buggy?, well, it's java...
509 root 1.1
510     $self->{fh} = $fh;
511    
512 root 1.12 $self->{w} = $EVENT->new_from_fh ($fh)->cb(sub { $self->fh_ready_w })->poll(0, 1, 1);
513 root 1.1
514     $self;
515     }
516    
517 root 1.12 =item $txn = $txn->cb ($coderef)
518    
519     Sets a callback to be called when the request is finished. The coderef
520     will be called with the txn as it's sole argument, so it has to call
521     C<result> itself.
522    
523     Returns the txn object, useful for chaining.
524 root 1.9
525 root 1.12 Example:
526    
527     $fcp->txn_client_get ("freenet:CHK....")
528     ->userdata ("ehrm")
529     ->cb(sub {
530     my $data = shift->result;
531     });
532 root 1.9
533     =cut
534    
535 root 1.12 sub cb($$) {
536     my ($self, $cb) = @_;
537     $self->{cb} = $cb;
538     $self;
539     }
540    
541     =item $txn = $txn->userdata ([$userdata])
542    
543     Set user-specific data. This is useful in progress callbacks. The data can be accessed
544     using C<< $txn->{userdata} >>.
545    
546     Returns the txn object, useful for chaining.
547    
548     =cut
549    
550     sub userdata($$) {
551 root 1.9 my ($self, $data) = @_;
552 root 1.12 $self->{userdata} = $data;
553     $self;
554     }
555    
556 root 1.17 =item $txn->cancel (%attr)
557    
558     Cancels the operation with a C<cancel> exception anf the given attributes
559     (consider at least giving the attribute C<reason>).
560    
561     UNTESTED.
562    
563     =cut
564    
565     sub cancel {
566     my ($self, %attr) = @_;
567     $self->throw (Net::FCP::Exception->new (cancel => { %attr }));
568     $self->set_result;
569     $self->eof;
570     }
571    
572 root 1.12 sub fh_ready_w {
573     my ($self) = @_;
574    
575     my $len = syswrite $self->{fh}, $self->{sbuf};
576    
577     if ($len > 0) {
578     substr $self->{sbuf}, 0, $len, "";
579     unless (length $self->{sbuf}) {
580     fcntl $self->{fh}, F_SETFL, 0;
581     $self->{w}->cb(sub { $self->fh_ready_r })->poll (1, 0, 1);
582     }
583     } elsif (defined $len) {
584     $self->throw (Net::FCP::Exception->new (network_error => { reason => "unexpected end of file while writing" }));
585     } else {
586     $self->throw (Net::FCP::Exception->new (network_error => { reason => "$!" }));
587     }
588 root 1.9 }
589    
590 root 1.12 sub fh_ready_r {
591 root 1.1 my ($self) = @_;
592    
593     if (sysread $self->{fh}, $self->{buf}, 65536, length $self->{buf}) {
594     for (;;) {
595     if ($self->{datalen}) {
596 root 1.13 #warn "expecting new datachunk $self->{datalen}, got ".(length $self->{buf})."\n";#d#
597 root 1.1 if (length $self->{buf} >= $self->{datalen}) {
598 root 1.11 $self->rcv_data (substr $self->{buf}, 0, delete $self->{datalen}, "");
599 root 1.1 } else {
600     last;
601     }
602 root 1.5 } elsif ($self->{buf} =~ s/^DataChunk\015?\012Length=([0-9a-fA-F]+)\015?\012Data\015?\012//) {
603     $self->{datalen} = hex $1;
604 root 1.13 #warn "expecting new datachunk $self->{datalen}\n";#d#
605 root 1.7 } elsif ($self->{buf} =~ s/^([a-zA-Z]+)\015?\012(?:(.+?)\015?\012)?EndMessage\015?\012//s) {
606 root 1.2 $self->rcv ($1, {
607     map { my ($a, $b) = split /=/, $_, 2; ((Net::FCP::tolc $a), $b) }
608     split /\015?\012/, $2
609     });
610 root 1.1 } else {
611     last;
612     }
613     }
614     } else {
615     $self->eof;
616     }
617     }
618    
619     sub rcv {
620     my ($self, $type, $attr) = @_;
621    
622 root 1.2 $type = Net::FCP::tolc $type;
623    
624 root 1.5 #use PApp::Util; warn PApp::Util::dumpval [$type, $attr];
625    
626 root 1.2 if (my $method = $self->can("rcv_$type")) {
627 root 1.1 $method->($self, $attr, $type);
628     } else {
629     warn "received unexpected reply type '$type' for '$self->{type}', ignoring\n";
630     }
631     }
632    
633 root 1.12 # used as a default exception thrower
634     sub rcv_throw_exception {
635     my ($self, $attr, $type) = @_;
636 root 1.15 $self->throw (Net::FCP::Exception->new ($type, $attr));
637 root 1.12 }
638    
639     *rcv_failed = \&Net::FCP::Txn::rcv_throw_exception;
640     *rcv_format_error = \&Net::FCP::Txn::rcv_throw_exception;
641    
642 root 1.9 sub throw {
643     my ($self, $exc) = @_;
644    
645     $self->{exception} = $exc;
646 root 1.17 $self->set_result;
647 root 1.12 $self->eof; # must be last to avoid loops
648 root 1.9 }
649    
650 root 1.5 sub set_result {
651 root 1.1 my ($self, $result) = @_;
652    
653 root 1.12 unless (exists $self->{result}) {
654     $self->{result} = $result;
655     $self->{cb}->($self) if exists $self->{cb};
656     $self->{signal}->send;
657     }
658 root 1.1 }
659    
660 root 1.5 sub eof {
661     my ($self) = @_;
662 root 1.12
663     delete $self->{w};
664     delete $self->{fh};
665    
666     delete $self->{fcp}{txn}{$self};
667    
668 root 1.17 unless (exists $self->{result}) {
669     $self->throw (Net::FCP::Exception->new (short_data => {
670     reason => "unexpected eof or internal node error",
671     }));
672     }
673 root 1.5 }
674    
675 root 1.9 sub progress {
676     my ($self, $type, $attr) = @_;
677     $self->{fcp}->progress ($self, $type, $attr);
678     }
679    
680 root 1.1 =item $result = $txn->result
681    
682     Waits until a result is available and then returns it.
683    
684 root 1.5 This waiting is (depending on your event model) not very efficient, as it
685 root 1.1 is done outside the "mainloop".
686    
687     =cut
688    
689     sub result {
690     my ($self) = @_;
691    
692 root 1.12 $self->{signal}->wait while !exists $self->{result};
693 root 1.9
694     die $self->{exception} if $self->{exception};
695 root 1.1
696     return $self->{result};
697     }
698    
699     package Net::FCP::Txn::ClientHello;
700    
701     use base Net::FCP::Txn;
702    
703 root 1.2 sub rcv_node_hello {
704 root 1.1 my ($self, $attr) = @_;
705    
706 root 1.5 $self->set_result ($attr);
707 root 1.1 }
708    
709     package Net::FCP::Txn::ClientInfo;
710    
711     use base Net::FCP::Txn;
712    
713 root 1.2 sub rcv_node_info {
714 root 1.1 my ($self, $attr) = @_;
715    
716 root 1.5 $self->set_result ($attr);
717 root 1.1 }
718    
719     package Net::FCP::Txn::GenerateCHK;
720    
721     use base Net::FCP::Txn;
722    
723     sub rcv_success {
724     my ($self, $attr) = @_;
725    
726 root 1.21 $self->set_result ($attr->{uri});
727 root 1.1 }
728    
729     package Net::FCP::Txn::GenerateSVKPair;
730    
731     use base Net::FCP::Txn;
732    
733     sub rcv_success {
734     my ($self, $attr) = @_;
735 root 1.5 $self->set_result ([$attr->{PublicKey}, $attr->{PrivateKey}]);
736 root 1.1 }
737    
738 root 1.17 package Net::FCP::Txn::InsertPrivateKey;
739 root 1.1
740     use base Net::FCP::Txn;
741    
742     sub rcv_success {
743     my ($self, $attr) = @_;
744 root 1.5 $self->set_result ($attr->{PublicKey});
745 root 1.1 }
746    
747     package Net::FCP::Txn::GetSize;
748    
749     use base Net::FCP::Txn;
750    
751     sub rcv_success {
752     my ($self, $attr) = @_;
753 root 1.5 $self->set_result ($attr->{Length});
754     }
755    
756 root 1.12 package Net::FCP::Txn::GetPut;
757    
758     # base class for get and put
759    
760     use base Net::FCP::Txn;
761    
762     *rcv_uri_error = \&Net::FCP::Txn::rcv_throw_exception;
763     *rcv_route_not_found = \&Net::FCP::Txn::rcv_throw_exception;
764    
765     sub rcv_restarted {
766     my ($self, $attr, $type) = @_;
767    
768     delete $self->{datalength};
769     delete $self->{metalength};
770     delete $self->{data};
771    
772     $self->progress ($type, $attr);
773     }
774    
775 root 1.5 package Net::FCP::Txn::ClientGet;
776    
777 root 1.12 use base Net::FCP::Txn::GetPut;
778    
779     *rcv_data_not_found = \&Net::FCP::Txn::rcv_throw_exception;
780 root 1.5
781 root 1.17 sub rcv_data {
782     my ($self, $chunk) = @_;
783 root 1.9
784 root 1.17 $self->{data} .= $chunk;
785 root 1.5
786 root 1.19 $self->progress ("data", { chunk => length $chunk, received => length $self->{data}, total => $self->{datalength} });
787 root 1.9
788 root 1.12 if ($self->{datalength} == length $self->{data}) {
789     my $data = delete $self->{data};
790     my $meta = Net::FCP::parse_metadata substr $data, 0, $self->{metalength}, "";
791    
792     $self->set_result ([$meta, $data]);
793 root 1.22 $self->eof;
794 root 1.12 }
795 root 1.9 }
796    
797 root 1.17 sub rcv_data_found {
798     my ($self, $attr, $type) = @_;
799    
800     $self->progress ($type, $attr);
801    
802     $self->{datalength} = hex $attr->{data_length};
803     $self->{metalength} = hex $attr->{metadata_length};
804     }
805    
806 root 1.12 package Net::FCP::Txn::ClientPut;
807 root 1.9
808 root 1.12 use base Net::FCP::Txn::GetPut;
809 root 1.9
810 root 1.12 *rcv_size_error = \&Net::FCP::Txn::rcv_throw_exception;
811     *rcv_key_collision = \&Net::FCP::Txn::rcv_throw_exception;
812 root 1.9
813 root 1.12 sub rcv_pending {
814 root 1.9 my ($self, $attr, $type) = @_;
815     $self->progress ($type, $attr);
816 root 1.5 }
817    
818 root 1.12 sub rcv_success {
819     my ($self, $attr, $type) = @_;
820     $self->set_result ($attr);
821 root 1.9 }
822    
823 root 1.17 =back
824    
825     =head2 The Net::FCP::Exception CLASS
826    
827     Any unexpected (non-standard) responses that make it impossible to return
828     the advertised result will result in an exception being thrown when the
829     C<result> method is called.
830    
831     These exceptions are represented by objects of this class.
832    
833     =over 4
834    
835     =cut
836    
837 root 1.9 package Net::FCP::Exception;
838    
839     use overload
840     '""' => sub {
841 root 1.22 "Net::FCP::Exception<<$_[0][0]," . (join ":", %{$_[0][1]}) . ">>";
842 root 1.9 };
843    
844 root 1.17 =item $exc = new Net::FCP::Exception $type, \%attr
845    
846     Create a new exception object of the given type (a string like
847     C<route_not_found>), and a hashref containing additional attributes
848     (usually the attributes of the message causing the exception).
849    
850     =cut
851    
852 root 1.9 sub new {
853     my ($class, $type, $attr) = @_;
854    
855 root 1.12 bless [Net::FCP::tolc $type, { %$attr }], $class;
856 root 1.17 }
857    
858     =item $exc->type([$type])
859    
860     With no arguments, returns the exception type. Otherwise a boolean
861     indicating wether the exception is of the given type is returned.
862    
863     =cut
864    
865     sub type {
866     my ($self, $type) = @_;
867    
868     @_ >= 2
869     ? $self->[0] eq $type
870     : $self->[0];
871     }
872    
873     =item $exc->attr([$attr])
874    
875     With no arguments, returns the attributes. Otherwise the named attribute
876     value is returned.
877    
878     =cut
879    
880     sub attr {
881     my ($self, $attr) = @_;
882    
883     @_ >= 2
884     ? $self->[1]{$attr}
885     : $self->[1];
886 root 1.1 }
887    
888     =back
889    
890     =head1 SEE ALSO
891    
892     L<http://freenet.sf.net>.
893    
894     =head1 BUGS
895    
896     =head1 AUTHOR
897    
898     Marc Lehmann <pcg@goof.com>
899     http://www.goof.com/pcg/marc/
900    
901     =cut
902 root 1.20
903     package Net::FCP::Event::Auto;
904    
905     my @models = (
906     [Coro => Coro::Event:: ],
907     [Event => Event::],
908     [Glib => Glib:: ],
909     [Tk => Tk::],
910     );
911    
912     sub AUTOLOAD {
913     $AUTOLOAD =~ s/.*://;
914    
915     for (@models) {
916     my ($model, $package) = @$_;
917     if (defined ${"$package\::VERSION"}) {
918     $EVENT = "Net::FCP::Event::$model";
919     eval "require $EVENT"; die if $@;
920     goto &{"$EVENT\::$AUTOLOAD"};
921     }
922     }
923    
924     for (@models) {
925     my ($model, $package) = @$_;
926     $EVENT = "Net::FCP::Event::$model";
927     if (eval "require $EVENT") {
928     goto &{"$EVENT\::$AUTOLOAD"};
929     }
930     }
931    
932     die "No event module selected for Net::FCP and autodetect failed. Install any of these: Coro, Event, Glib or Tk.";
933     }
934 root 1.1
935     1;
936