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