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