ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Net-FCP/FCP.pm
Revision: 1.38
Committed: Mon Nov 27 13:16:25 2006 UTC (19 years, 9 months ago) by root
Branch: MAIN
Changes since 1.37: +1 -1 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 $self->{w} = AnyEvent->io (fh => $self->{fh}, poll => 'r', cb => sub { $self->fh_ready_r });
493 root 1.12 }
494     } elsif (defined $len) {
495     $self->throw (Net::FCP::Exception->new (network_error => { reason => "unexpected end of file while writing" }));
496     } else {
497     $self->throw (Net::FCP::Exception->new (network_error => { reason => "$!" }));
498     }
499 root 1.9 }
500    
501 root 1.12 sub fh_ready_r {
502 root 1.1 my ($self) = @_;
503    
504 root 1.38 if (sysread $self->{fh}, $self->{buf}, 16384 + 1024, length $self->{buf}) {
505 root 1.1 for (;;) {
506     if ($self->{datalen}) {
507 root 1.13 #warn "expecting new datachunk $self->{datalen}, got ".(length $self->{buf})."\n";#d#
508 root 1.1 if (length $self->{buf} >= $self->{datalen}) {
509 root 1.11 $self->rcv_data (substr $self->{buf}, 0, delete $self->{datalen}, "");
510 root 1.1 } else {
511     last;
512     }
513 root 1.5 } elsif ($self->{buf} =~ s/^DataChunk\015?\012Length=([0-9a-fA-F]+)\015?\012Data\015?\012//) {
514     $self->{datalen} = hex $1;
515 root 1.13 #warn "expecting new datachunk $self->{datalen}\n";#d#
516 root 1.7 } elsif ($self->{buf} =~ s/^([a-zA-Z]+)\015?\012(?:(.+?)\015?\012)?EndMessage\015?\012//s) {
517 root 1.2 $self->rcv ($1, {
518     map { my ($a, $b) = split /=/, $_, 2; ((Net::FCP::tolc $a), $b) }
519     split /\015?\012/, $2
520     });
521 root 1.1 } else {
522     last;
523     }
524     }
525     } else {
526     $self->eof;
527     }
528     }
529    
530     sub rcv {
531     my ($self, $type, $attr) = @_;
532    
533 root 1.2 $type = Net::FCP::tolc $type;
534    
535 root 1.5 #use PApp::Util; warn PApp::Util::dumpval [$type, $attr];
536    
537 root 1.2 if (my $method = $self->can("rcv_$type")) {
538 root 1.1 $method->($self, $attr, $type);
539     } else {
540     warn "received unexpected reply type '$type' for '$self->{type}', ignoring\n";
541     }
542     }
543    
544 root 1.12 # used as a default exception thrower
545     sub rcv_throw_exception {
546     my ($self, $attr, $type) = @_;
547 root 1.15 $self->throw (Net::FCP::Exception->new ($type, $attr));
548 root 1.12 }
549    
550     *rcv_failed = \&Net::FCP::Txn::rcv_throw_exception;
551     *rcv_format_error = \&Net::FCP::Txn::rcv_throw_exception;
552    
553 root 1.9 sub throw {
554     my ($self, $exc) = @_;
555    
556     $self->{exception} = $exc;
557 root 1.17 $self->set_result;
558 root 1.12 $self->eof; # must be last to avoid loops
559 root 1.9 }
560    
561 root 1.5 sub set_result {
562 root 1.1 my ($self, $result) = @_;
563    
564 root 1.12 unless (exists $self->{result}) {
565     $self->{result} = $result;
566     $self->{cb}->($self) if exists $self->{cb};
567 root 1.36 $self->{signal}->broadcast;
568 root 1.12 }
569 root 1.1 }
570    
571 root 1.5 sub eof {
572     my ($self) = @_;
573 root 1.12
574     delete $self->{w};
575     delete $self->{fh};
576    
577     delete $self->{fcp}{txn}{$self};
578    
579 root 1.17 unless (exists $self->{result}) {
580     $self->throw (Net::FCP::Exception->new (short_data => {
581     reason => "unexpected eof or internal node error",
582     }));
583     }
584 root 1.5 }
585    
586 root 1.9 sub progress {
587     my ($self, $type, $attr) = @_;
588 root 1.27
589 root 1.9 $self->{fcp}->progress ($self, $type, $attr);
590     }
591    
592 root 1.1 =item $result = $txn->result
593    
594     Waits until a result is available and then returns it.
595    
596 root 1.5 This waiting is (depending on your event model) not very efficient, as it
597 root 1.23 is done outside the "mainloop". The biggest problem, however, is that it's
598     blocking one thread of execution. Try to use the callback mechanism, if
599     possible, and call result from within the callback (or after is has been
600     run), as then no waiting is necessary.
601 root 1.1
602     =cut
603    
604     sub result {
605     my ($self) = @_;
606    
607 root 1.12 $self->{signal}->wait while !exists $self->{result};
608 root 1.9
609     die $self->{exception} if $self->{exception};
610 root 1.1
611     return $self->{result};
612     }
613    
614     package Net::FCP::Txn::ClientHello;
615    
616     use base Net::FCP::Txn;
617    
618 root 1.2 sub rcv_node_hello {
619 root 1.1 my ($self, $attr) = @_;
620    
621 root 1.5 $self->set_result ($attr);
622 root 1.1 }
623    
624     package Net::FCP::Txn::ClientInfo;
625    
626     use base Net::FCP::Txn;
627    
628 root 1.2 sub rcv_node_info {
629 root 1.1 my ($self, $attr) = @_;
630    
631 root 1.5 $self->set_result ($attr);
632 root 1.1 }
633    
634     package Net::FCP::Txn::GenerateCHK;
635    
636     use base Net::FCP::Txn;
637    
638     sub rcv_success {
639     my ($self, $attr) = @_;
640    
641 root 1.21 $self->set_result ($attr->{uri});
642 root 1.1 }
643    
644     package Net::FCP::Txn::GenerateSVKPair;
645    
646     use base Net::FCP::Txn;
647    
648     sub rcv_success {
649     my ($self, $attr) = @_;
650 root 1.29 $self->set_result ([$attr->{public_key}, $attr->{private_key}, $attr->{crypto_key}]);
651 root 1.1 }
652    
653 root 1.29 package Net::FCP::Txn::InvertPrivateKey;
654 root 1.1
655     use base Net::FCP::Txn;
656    
657     sub rcv_success {
658     my ($self, $attr) = @_;
659 root 1.29 $self->set_result ($attr->{public_key});
660 root 1.1 }
661    
662     package Net::FCP::Txn::GetSize;
663    
664     use base Net::FCP::Txn;
665    
666     sub rcv_success {
667     my ($self, $attr) = @_;
668 root 1.29 $self->set_result (hex $attr->{length});
669 root 1.5 }
670    
671 root 1.12 package Net::FCP::Txn::GetPut;
672    
673     # base class for get and put
674    
675     use base Net::FCP::Txn;
676    
677 root 1.27 *rcv_uri_error = \&Net::FCP::Txn::rcv_throw_exception;
678     *rcv_route_not_found = \&Net::FCP::Txn::rcv_throw_exception;
679 root 1.12
680     sub rcv_restarted {
681     my ($self, $attr, $type) = @_;
682    
683     delete $self->{datalength};
684     delete $self->{metalength};
685     delete $self->{data};
686    
687     $self->progress ($type, $attr);
688     }
689    
690 root 1.5 package Net::FCP::Txn::ClientGet;
691    
692 root 1.12 use base Net::FCP::Txn::GetPut;
693    
694     *rcv_data_not_found = \&Net::FCP::Txn::rcv_throw_exception;
695 root 1.5
696 root 1.17 sub rcv_data {
697     my ($self, $chunk) = @_;
698 root 1.9
699 root 1.17 $self->{data} .= $chunk;
700 root 1.5
701 root 1.19 $self->progress ("data", { chunk => length $chunk, received => length $self->{data}, total => $self->{datalength} });
702 root 1.9
703 root 1.12 if ($self->{datalength} == length $self->{data}) {
704     my $data = delete $self->{data};
705 root 1.30 my $meta = new Net::FCP::Metadata (substr $data, 0, $self->{metalength}, "");
706 root 1.12
707     $self->set_result ([$meta, $data]);
708 root 1.22 $self->eof;
709 root 1.12 }
710 root 1.9 }
711    
712 root 1.17 sub rcv_data_found {
713     my ($self, $attr, $type) = @_;
714    
715     $self->progress ($type, $attr);
716    
717     $self->{datalength} = hex $attr->{data_length};
718     $self->{metalength} = hex $attr->{metadata_length};
719     }
720    
721 root 1.12 package Net::FCP::Txn::ClientPut;
722 root 1.9
723 root 1.12 use base Net::FCP::Txn::GetPut;
724 root 1.9
725 root 1.12 *rcv_size_error = \&Net::FCP::Txn::rcv_throw_exception;
726 root 1.9
727 root 1.12 sub rcv_pending {
728 root 1.9 my ($self, $attr, $type) = @_;
729     $self->progress ($type, $attr);
730 root 1.5 }
731    
732 root 1.12 sub rcv_success {
733     my ($self, $attr, $type) = @_;
734     $self->set_result ($attr);
735 root 1.30 }
736    
737     sub rcv_key_collision {
738     my ($self, $attr, $type) = @_;
739     $self->set_result ({ key_collision => 1, %$attr });
740 root 1.9 }
741    
742 root 1.17 =back
743    
744     =head2 The Net::FCP::Exception CLASS
745    
746     Any unexpected (non-standard) responses that make it impossible to return
747     the advertised result will result in an exception being thrown when the
748     C<result> method is called.
749    
750     These exceptions are represented by objects of this class.
751    
752     =over 4
753    
754     =cut
755    
756 root 1.9 package Net::FCP::Exception;
757    
758     use overload
759     '""' => sub {
760 root 1.22 "Net::FCP::Exception<<$_[0][0]," . (join ":", %{$_[0][1]}) . ">>";
761 root 1.9 };
762    
763 root 1.17 =item $exc = new Net::FCP::Exception $type, \%attr
764    
765     Create a new exception object of the given type (a string like
766     C<route_not_found>), and a hashref containing additional attributes
767     (usually the attributes of the message causing the exception).
768    
769     =cut
770    
771 root 1.9 sub new {
772     my ($class, $type, $attr) = @_;
773    
774 root 1.12 bless [Net::FCP::tolc $type, { %$attr }], $class;
775 root 1.17 }
776    
777     =item $exc->type([$type])
778    
779     With no arguments, returns the exception type. Otherwise a boolean
780     indicating wether the exception is of the given type is returned.
781    
782     =cut
783    
784     sub type {
785     my ($self, $type) = @_;
786    
787     @_ >= 2
788     ? $self->[0] eq $type
789     : $self->[0];
790     }
791    
792     =item $exc->attr([$attr])
793    
794     With no arguments, returns the attributes. Otherwise the named attribute
795     value is returned.
796    
797     =cut
798    
799     sub attr {
800     my ($self, $attr) = @_;
801    
802     @_ >= 2
803     ? $self->[1]{$attr}
804     : $self->[1];
805 root 1.1 }
806    
807     =back
808    
809     =head1 SEE ALSO
810    
811     L<http://freenet.sf.net>.
812    
813     =head1 BUGS
814    
815     =head1 AUTHOR
816    
817 root 1.35 Marc Lehmann <schmorp@schmorp.de>
818 root 1.34 http://home.schmorp.de/
819 root 1.1
820     =cut
821 root 1.20
822 root 1.36 1
823 root 1.1