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