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