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