ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Net-FCP/FCP.pm
Revision: 1.35
Committed: Thu Mar 3 17:31:26 2005 UTC (21 years, 6 months ago) by root
Branch: MAIN
Changes since 1.34: +2 -2 lines
Log Message:
*** empty log message ***

File Contents

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