ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/Net-FCP/FCP.pm
Revision: 1.25
Committed: Tue Dec 9 06:54:16 2003 UTC (20 years, 6 months ago) by root
Branch: MAIN
Changes since 1.24: +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     =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.24 $VERSION = 0.6;
78 root 1.10
79     no warnings;
80 root 1.1
81 root 1.9 our $EVENT = Net::FCP::Event::Auto::;
82 root 1.1
83 root 1.9 sub import {
84     shift;
85 root 1.1
86 root 1.9 for (@_) {
87     if (/^event=(\w+)$/) {
88     $EVENT = "Net::FCP::Event::$1";
89 root 1.20 eval "require $EVENT";
90 root 1.9 }
91     }
92 root 1.12 die $@ if $@;
93 root 1.1 }
94    
95 root 1.2 sub touc($) {
96     local $_ = shift;
97     1 while s/((?:^|_)(?:svk|chk|uri)(?:_|$))/\U$1/;
98     s/(?:^|_)(.)/\U$1/g;
99     $_;
100     }
101    
102     sub tolc($) {
103     local $_ = shift;
104     s/(?<=[a-z])(?=[A-Z])/_/g;
105     lc $_;
106     }
107    
108 root 1.23 # the opposite of hex
109     sub xeh($) {
110     sprintf "%x", $_[0];
111     }
112    
113 root 1.7 =item $meta = Net::FCP::parse_metadata $string
114    
115     Parse a metadata string and return it.
116    
117 root 1.21 The metadata will be a hashref with key C<version> (containing the
118     mandatory version header entries) and key C<raw> containing the original
119     metadata string.
120 root 1.7
121     All other headers are represented by arrayrefs (they can be repeated).
122    
123 root 1.21 Since this description is confusing, here is a rather verbose example of a
124     parsed manifest:
125 root 1.7
126     (
127 root 1.21 raw => "Version...",
128 root 1.7 version => { revision => 1 },
129     document => [
130     {
131 root 1.17 info => { format" => "image/jpeg" },
132 root 1.7 name => "background.jpg",
133 root 1.17 redirect => { target => "freenet:CHK\@ZcagI,ra726bSw" },
134 root 1.7 },
135     {
136 root 1.17 info => { format" => "text/html" },
137 root 1.7 name => ".next",
138 root 1.17 redirect => { target => "freenet:SSK\@ilUPAgM/TFEE/3" },
139 root 1.7 },
140     {
141 root 1.17 info => { format" => "text/html" },
142     redirect => { target => "freenet:CHK\@8M8Po8ucwI,8xA" },
143 root 1.7 }
144     ]
145     )
146    
147     =cut
148    
149     sub parse_metadata {
150 root 1.21 my $data = shift;
151     my $meta = { raw => $data };
152 root 1.7
153     if ($data =~ /^Version\015?\012/gc) {
154     my $hdr = $meta->{version} = {};
155    
156     for (;;) {
157     while ($data =~ /\G([^=\015\012]+)=([^\015\012]*)\015?\012/gc) {
158     my ($k, $v) = ($1, $2);
159 root 1.12 my @p = split /\./, tolc $k, 3;
160    
161     $hdr->{$p[0]} = $v if @p == 1; # lamest code I ever wrote
162     $hdr->{$p[0]}{$p[1]} = $v if @p == 2;
163 root 1.15 $hdr->{$p[0]}{$p[1]}{$p[2]} = $v if @p == 3;
164 root 1.12 die "FATAL: 4+ dot metadata" if @p >= 4;
165 root 1.7 }
166    
167     if ($data =~ /\GEndPart\015?\012/gc) {
168 root 1.12 # nop
169 root 1.17 } elsif ($data =~ /\GEnd(\015?\012|$)/gc) {
170 root 1.7 last;
171     } elsif ($data =~ /\G([A-Za-z0-9.\-]+)\015?\012/gcs) {
172     push @{$meta->{tolc $1}}, $hdr = {};
173     } elsif ($data =~ /\G(.*)/gcs) {
174 root 1.18 print STDERR "metadata format error ($1), please report this string: <<$data>>";
175     die "metadata format error";
176 root 1.7 }
177     }
178     }
179    
180     #$meta->{tail} = substr $data, pos $data;
181    
182     $meta;
183     }
184    
185 root 1.1 =item $fcp = new Net::FCP [host => $host][, port => $port]
186    
187     Create a new virtual FCP connection to the given host and port (default
188 root 1.5 127.0.0.1:8481, or the environment variables C<FREDHOST> and C<FREDPORT>).
189 root 1.1
190     Connections are virtual because no persistent physical connection is
191 root 1.17 established.
192    
193     =begin comment
194    
195     However, the existance of the node is checked by executing a
196 root 1.1 C<ClientHello> transaction.
197    
198 root 1.17 =end
199    
200 root 1.1 =cut
201    
202     sub new {
203     my $class = shift;
204     my $self = bless { @_ }, $class;
205    
206 root 1.5 $self->{host} ||= $ENV{FREDHOST} || "127.0.0.1";
207 root 1.12 $self->{port} ||= $ENV{FREDPORT} || 8481;
208 root 1.1
209 root 1.12 #$self->{nodehello} = $self->client_hello
210     # or croak "unable to get nodehello from node\n";
211 root 1.1
212     $self;
213     }
214    
215 root 1.9 sub progress {
216     my ($self, $txn, $type, $attr) = @_;
217 root 1.18 #warn "progress<$txn,$type," . (join ":", %$attr) . ">\n";
218 root 1.9 }
219    
220 root 1.1 =item $txn = $fcp->txn(type => attr => val,...)
221    
222     The low-level interface to transactions. Don't use it.
223    
224 root 1.12 Here are some examples of using transactions:
225    
226     The blocking case, no (visible) transactions involved:
227    
228     my $nodehello = $fcp->client_hello;
229    
230     A transaction used in a blocking fashion:
231    
232     my $txn = $fcp->txn_client_hello;
233     ...
234     my $nodehello = $txn->result;
235    
236     Or shorter:
237    
238     my $nodehello = $fcp->txn_client_hello->result;
239    
240     Setting callbacks:
241    
242     $fcp->txn_client_hello->cb(
243     sub { my $nodehello => $_[0]->result }
244     );
245    
246 root 1.1 =cut
247    
248     sub txn {
249     my ($self, $type, %attr) = @_;
250    
251 root 1.2 $type = touc $type;
252    
253     my $txn = "Net::FCP::Txn::$type"->new(fcp => $self, type => tolc $type, attr => \%attr);
254 root 1.1
255     $txn;
256     }
257    
258 root 1.17 { # transactions
259    
260     my $txn = sub {
261 root 1.1 my ($name, $sub) = @_;
262 root 1.17 *{"txn_$name"} = $sub;
263 root 1.1 *{$name} = sub { $sub->(@_)->result };
264 root 1.17 };
265 root 1.1
266     =item $txn = $fcp->txn_client_hello
267    
268     =item $nodehello = $fcp->client_hello
269    
270     Executes a ClientHello request and returns it's results.
271    
272     {
273 root 1.2 max_file_size => "5f5e100",
274 root 1.4 node => "Fred,0.6,1.46,7050"
275 root 1.2 protocol => "1.2",
276 root 1.1 }
277    
278     =cut
279    
280 root 1.17 $txn->(client_hello => sub {
281 root 1.1 my ($self) = @_;
282    
283 root 1.2 $self->txn ("client_hello");
284 root 1.17 });
285 root 1.1
286     =item $txn = $fcp->txn_client_info
287    
288     =item $nodeinfo = $fcp->client_info
289    
290     Executes a ClientInfo request and returns it's results.
291    
292     {
293 root 1.2 active_jobs => "1f",
294     allocated_memory => "bde0000",
295     architecture => "i386",
296     available_threads => 17,
297 root 1.4 datastore_free => "5ce03400",
298     datastore_max => "2540be400",
299 root 1.2 datastore_used => "1f72bb000",
300 root 1.4 estimated_load => 52,
301     free_memory => "5cc0148",
302 root 1.2 is_transient => "false",
303 root 1.4 java_name => "Java HotSpot(_T_M) Server VM",
304 root 1.2 java_vendor => "http://www.blackdown.org/",
305 root 1.4 java_version => "Blackdown-1.4.1-01",
306     least_recent_timestamp => "f41538b878",
307     max_file_size => "5f5e100",
308 root 1.2 most_recent_timestamp => "f77e2cc520"
309 root 1.4 node_address => "1.2.3.4",
310     node_port => 369,
311     operating_system => "Linux",
312     operating_system_version => "2.4.20",
313     routing_time => "a5",
314 root 1.1 }
315    
316     =cut
317    
318 root 1.17 $txn->(client_info => sub {
319 root 1.1 my ($self) = @_;
320    
321 root 1.2 $self->txn ("client_info");
322 root 1.17 });
323 root 1.1
324 root 1.21 =item $txn = $fcp->txn_generate_chk ($metadata, $data[, $cipher])
325 root 1.1
326 root 1.21 =item $uri = $fcp->generate_chk ($metadata, $data[, $cipher])
327 root 1.1
328 root 1.21 Calculcates a CHK, given the metadata and data. C<$cipher> is either
329     C<Rijndael> or C<Twofish>, with the latter being the default.
330 root 1.1
331     =cut
332    
333 root 1.17 $txn->(generate_chk => sub {
334 root 1.21 my ($self, $metadata, $data, $cipher) = @_;
335 root 1.1
336 root 1.21 $self->txn (generate_chk =>
337     data => "$metadata$data",
338 root 1.23 metadata_length => xeh length $metadata,
339 root 1.21 cipher => $cipher || "Twofish");
340 root 1.17 });
341 root 1.1
342     =item $txn = $fcp->txn_generate_svk_pair
343    
344     =item ($public, $private) = @{ $fcp->generate_svk_pair }
345    
346     Creates a new SVK pair. Returns an arrayref.
347    
348     [
349     "hKs0-WDQA4pVZyMPKNFsK1zapWY",
350     "ZnmvMITaTXBMFGl4~jrjuyWxOWg"
351     ]
352    
353     =cut
354    
355 root 1.17 $txn->(generate_svk_pair => sub {
356 root 1.1 my ($self) = @_;
357    
358 root 1.2 $self->txn ("generate_svk_pair");
359 root 1.17 });
360 root 1.1
361     =item $txn = $fcp->txn_insert_private_key ($private)
362    
363 root 1.17 =item $public = $fcp->insert_private_key ($private)
364 root 1.1
365     Inserts a private key. $private can be either an insert URI (must start
366 root 1.17 with C<freenet:SSK@>) or a raw private key (i.e. the private value you get
367     back from C<generate_svk_pair>).
368 root 1.1
369     Returns the public key.
370    
371     UNTESTED.
372    
373     =cut
374    
375 root 1.17 $txn->(insert_private_key => sub {
376 root 1.1 my ($self, $privkey) = @_;
377    
378 root 1.2 $self->txn (invert_private_key => private => $privkey);
379 root 1.17 });
380 root 1.1
381     =item $txn = $fcp->txn_get_size ($uri)
382    
383     =item $length = $fcp->get_size ($uri)
384    
385     Finds and returns the size (rounded up to the nearest power of two) of the
386     given document.
387    
388     UNTESTED.
389    
390     =cut
391    
392 root 1.17 $txn->(get_size => sub {
393 root 1.1 my ($self, $uri) = @_;
394    
395 root 1.2 $self->txn (get_size => URI => $uri);
396 root 1.17 });
397 root 1.1
398 root 1.5 =item $txn = $fcp->txn_client_get ($uri [, $htl = 15 [, $removelocal = 0]])
399    
400 root 1.7 =item ($metadata, $data) = @{ $fcp->client_get ($uri, $htl, $removelocal)
401 root 1.5
402 root 1.7 Fetches a (small, as it should fit into memory) file from
403     freenet. C<$meta> is the metadata (as returned by C<parse_metadata> or
404     C<undef>).
405 root 1.5
406 root 1.7 Due to the overhead, a better method to download big files should be used.
407 root 1.5
408 root 1.7 my ($meta, $data) = @{
409 root 1.5 $fcp->client_get (
410     "freenet:CHK@hdXaxkwZ9rA8-SidT0AN-bniQlgPAwI,XdCDmBuGsd-ulqbLnZ8v~w"
411     )
412     };
413    
414     =cut
415    
416 root 1.17 $txn->(client_get => sub {
417 root 1.5 my ($self, $uri, $htl, $removelocal) = @_;
418    
419 root 1.23 $self->txn (client_get => URI => $uri, hops_to_live => xeh (defined $htl ? $htl : 15),
420 root 1.17 remove_local_key => $removelocal ? "true" : "false");
421     });
422    
423     =item $txn = $fcp->txn_client_put ($uri, $metadata, $data, $htl, $removelocal)
424    
425     =item my $uri = $fcp->client_put ($uri, $metadata, $data, $htl, $removelocal);
426    
427     Insert a new key. If the client is inserting a CHK, the URI may be
428     abbreviated as just CHK@. In this case, the node will calculate the
429     CHK.
430    
431     C<$meta> can be a reference or a string (ONLY THE STRING CASE IS IMPLEMENTED!).
432    
433     THIS INTERFACE IS UNTESTED AND SUBJECT TO CHANGE.
434    
435     =cut
436    
437     $txn->(client_put => sub {
438     my ($self, $uri, $meta, $data, $htl, $removelocal) = @_;
439    
440 root 1.23 $self->txn (client_put => URI => $uri, xeh (defined $htl ? $htl : 15),
441 root 1.17 remove_local_key => $removelocal ? "true" : "false",
442 root 1.23 data => "$meta$data", metadata_length => xeh length $meta);
443 root 1.17 });
444    
445     } # transactions
446 root 1.5
447 root 1.23 =item MISSING: (ClientPut), InsertKey
448 root 1.1
449     =back
450    
451     =head2 THE Net::FCP::Txn CLASS
452    
453 root 1.23 All requests (or transactions) are executed in a asynchronous way. For
454     each request, a C<Net::FCP::Txn> object is created (worse: a tcp
455     connection is created, too).
456 root 1.1
457     For each request there is actually a different subclass (and it's possible
458     to subclass these, although of course not documented).
459    
460     The most interesting method is C<result>.
461    
462     =over 4
463    
464     =cut
465    
466     package Net::FCP::Txn;
467    
468 root 1.12 use Fcntl;
469     use Socket;
470    
471 root 1.1 =item new arg => val,...
472    
473     Creates a new C<Net::FCP::Txn> object. Not normally used.
474    
475     =cut
476    
477     sub new {
478     my $class = shift;
479     my $self = bless { @_ }, $class;
480    
481 root 1.12 $self->{signal} = $EVENT->new_signal;
482    
483     $self->{fcp}{txn}{$self} = $self;
484    
485 root 1.1 my $attr = "";
486     my $data = delete $self->{attr}{data};
487    
488     while (my ($k, $v) = each %{$self->{attr}}) {
489 root 1.2 $attr .= (Net::FCP::touc $k) . "=$v\012"
490 root 1.1 }
491    
492     if (defined $data) {
493 root 1.21 $attr .= sprintf "DataLength=%x\012", length $data;
494 root 1.1 $data = "Data\012$data";
495     } else {
496     $data = "EndMessage\012";
497     }
498    
499 root 1.12 socket my $fh, PF_INET, SOCK_STREAM, 0
500     or Carp::croak "unable to create new tcp socket: $!";
501 root 1.1 binmode $fh, ":raw";
502 root 1.12 fcntl $fh, F_SETFL, O_NONBLOCK;
503     connect $fh, (sockaddr_in $self->{fcp}{port}, inet_aton $self->{fcp}{host})
504     and !$!{EWOULDBLOCK}
505     and !$!{EINPROGRESS}
506     and Carp::croak "FCP::txn: unable to connect to $self->{fcp}{host}:$self->{fcp}{port}: $!\n";
507    
508     $self->{sbuf} =
509     "\x00\x00\x00\x02"
510 root 1.21 . (Net::FCP::touc $self->{type})
511 root 1.12 . "\012$attr$data";
512 root 1.1
513 root 1.21 #shutdown $fh, 1; # freenet buggy?, well, it's java...
514 root 1.1
515     $self->{fh} = $fh;
516    
517 root 1.12 $self->{w} = $EVENT->new_from_fh ($fh)->cb(sub { $self->fh_ready_w })->poll(0, 1, 1);
518 root 1.1
519     $self;
520     }
521    
522 root 1.12 =item $txn = $txn->cb ($coderef)
523    
524     Sets a callback to be called when the request is finished. The coderef
525     will be called with the txn as it's sole argument, so it has to call
526     C<result> itself.
527    
528     Returns the txn object, useful for chaining.
529 root 1.9
530 root 1.12 Example:
531    
532     $fcp->txn_client_get ("freenet:CHK....")
533     ->userdata ("ehrm")
534     ->cb(sub {
535     my $data = shift->result;
536     });
537 root 1.9
538     =cut
539    
540 root 1.12 sub cb($$) {
541     my ($self, $cb) = @_;
542     $self->{cb} = $cb;
543     $self;
544     }
545    
546     =item $txn = $txn->userdata ([$userdata])
547    
548     Set user-specific data. This is useful in progress callbacks. The data can be accessed
549     using C<< $txn->{userdata} >>.
550    
551     Returns the txn object, useful for chaining.
552    
553     =cut
554    
555     sub userdata($$) {
556 root 1.9 my ($self, $data) = @_;
557 root 1.12 $self->{userdata} = $data;
558     $self;
559     }
560    
561 root 1.17 =item $txn->cancel (%attr)
562    
563     Cancels the operation with a C<cancel> exception anf the given attributes
564     (consider at least giving the attribute C<reason>).
565    
566     UNTESTED.
567    
568     =cut
569    
570     sub cancel {
571     my ($self, %attr) = @_;
572     $self->throw (Net::FCP::Exception->new (cancel => { %attr }));
573     $self->set_result;
574     $self->eof;
575     }
576    
577 root 1.12 sub fh_ready_w {
578     my ($self) = @_;
579    
580     my $len = syswrite $self->{fh}, $self->{sbuf};
581    
582     if ($len > 0) {
583     substr $self->{sbuf}, 0, $len, "";
584     unless (length $self->{sbuf}) {
585     fcntl $self->{fh}, F_SETFL, 0;
586     $self->{w}->cb(sub { $self->fh_ready_r })->poll (1, 0, 1);
587     }
588     } elsif (defined $len) {
589     $self->throw (Net::FCP::Exception->new (network_error => { reason => "unexpected end of file while writing" }));
590     } else {
591     $self->throw (Net::FCP::Exception->new (network_error => { reason => "$!" }));
592     }
593 root 1.9 }
594    
595 root 1.12 sub fh_ready_r {
596 root 1.1 my ($self) = @_;
597    
598     if (sysread $self->{fh}, $self->{buf}, 65536, length $self->{buf}) {
599     for (;;) {
600     if ($self->{datalen}) {
601 root 1.13 #warn "expecting new datachunk $self->{datalen}, got ".(length $self->{buf})."\n";#d#
602 root 1.1 if (length $self->{buf} >= $self->{datalen}) {
603 root 1.11 $self->rcv_data (substr $self->{buf}, 0, delete $self->{datalen}, "");
604 root 1.1 } else {
605     last;
606     }
607 root 1.5 } elsif ($self->{buf} =~ s/^DataChunk\015?\012Length=([0-9a-fA-F]+)\015?\012Data\015?\012//) {
608     $self->{datalen} = hex $1;
609 root 1.13 #warn "expecting new datachunk $self->{datalen}\n";#d#
610 root 1.7 } elsif ($self->{buf} =~ s/^([a-zA-Z]+)\015?\012(?:(.+?)\015?\012)?EndMessage\015?\012//s) {
611 root 1.2 $self->rcv ($1, {
612     map { my ($a, $b) = split /=/, $_, 2; ((Net::FCP::tolc $a), $b) }
613     split /\015?\012/, $2
614     });
615 root 1.1 } else {
616     last;
617     }
618     }
619     } else {
620     $self->eof;
621     }
622     }
623    
624     sub rcv {
625     my ($self, $type, $attr) = @_;
626    
627 root 1.2 $type = Net::FCP::tolc $type;
628    
629 root 1.5 #use PApp::Util; warn PApp::Util::dumpval [$type, $attr];
630    
631 root 1.2 if (my $method = $self->can("rcv_$type")) {
632 root 1.1 $method->($self, $attr, $type);
633     } else {
634     warn "received unexpected reply type '$type' for '$self->{type}', ignoring\n";
635     }
636     }
637    
638 root 1.12 # used as a default exception thrower
639     sub rcv_throw_exception {
640     my ($self, $attr, $type) = @_;
641 root 1.15 $self->throw (Net::FCP::Exception->new ($type, $attr));
642 root 1.12 }
643    
644     *rcv_failed = \&Net::FCP::Txn::rcv_throw_exception;
645     *rcv_format_error = \&Net::FCP::Txn::rcv_throw_exception;
646    
647 root 1.9 sub throw {
648     my ($self, $exc) = @_;
649    
650     $self->{exception} = $exc;
651 root 1.17 $self->set_result;
652 root 1.12 $self->eof; # must be last to avoid loops
653 root 1.9 }
654    
655 root 1.5 sub set_result {
656 root 1.1 my ($self, $result) = @_;
657    
658 root 1.12 unless (exists $self->{result}) {
659     $self->{result} = $result;
660     $self->{cb}->($self) if exists $self->{cb};
661     $self->{signal}->send;
662     }
663 root 1.1 }
664    
665 root 1.5 sub eof {
666     my ($self) = @_;
667 root 1.12
668     delete $self->{w};
669     delete $self->{fh};
670    
671     delete $self->{fcp}{txn}{$self};
672    
673 root 1.17 unless (exists $self->{result}) {
674     $self->throw (Net::FCP::Exception->new (short_data => {
675     reason => "unexpected eof or internal node error",
676     }));
677     }
678 root 1.5 }
679    
680 root 1.9 sub progress {
681     my ($self, $type, $attr) = @_;
682     $self->{fcp}->progress ($self, $type, $attr);
683     }
684    
685 root 1.1 =item $result = $txn->result
686    
687     Waits until a result is available and then returns it.
688    
689 root 1.5 This waiting is (depending on your event model) not very efficient, as it
690 root 1.23 is done outside the "mainloop". The biggest problem, however, is that it's
691     blocking one thread of execution. Try to use the callback mechanism, if
692     possible, and call result from within the callback (or after is has been
693     run), as then no waiting is necessary.
694 root 1.1
695     =cut
696    
697     sub result {
698     my ($self) = @_;
699    
700 root 1.12 $self->{signal}->wait while !exists $self->{result};
701 root 1.9
702     die $self->{exception} if $self->{exception};
703 root 1.1
704     return $self->{result};
705     }
706    
707     package Net::FCP::Txn::ClientHello;
708    
709     use base Net::FCP::Txn;
710    
711 root 1.2 sub rcv_node_hello {
712 root 1.1 my ($self, $attr) = @_;
713    
714 root 1.5 $self->set_result ($attr);
715 root 1.1 }
716    
717     package Net::FCP::Txn::ClientInfo;
718    
719     use base Net::FCP::Txn;
720    
721 root 1.2 sub rcv_node_info {
722 root 1.1 my ($self, $attr) = @_;
723    
724 root 1.5 $self->set_result ($attr);
725 root 1.1 }
726    
727     package Net::FCP::Txn::GenerateCHK;
728    
729     use base Net::FCP::Txn;
730    
731     sub rcv_success {
732     my ($self, $attr) = @_;
733    
734 root 1.21 $self->set_result ($attr->{uri});
735 root 1.1 }
736    
737     package Net::FCP::Txn::GenerateSVKPair;
738    
739     use base Net::FCP::Txn;
740    
741     sub rcv_success {
742     my ($self, $attr) = @_;
743 root 1.5 $self->set_result ([$attr->{PublicKey}, $attr->{PrivateKey}]);
744 root 1.1 }
745    
746 root 1.17 package Net::FCP::Txn::InsertPrivateKey;
747 root 1.1
748     use base Net::FCP::Txn;
749    
750     sub rcv_success {
751     my ($self, $attr) = @_;
752 root 1.5 $self->set_result ($attr->{PublicKey});
753 root 1.1 }
754    
755     package Net::FCP::Txn::GetSize;
756    
757     use base Net::FCP::Txn;
758    
759     sub rcv_success {
760     my ($self, $attr) = @_;
761 root 1.23 $self->set_result (hex $attr->{Length});
762 root 1.5 }
763    
764 root 1.12 package Net::FCP::Txn::GetPut;
765    
766     # base class for get and put
767    
768     use base Net::FCP::Txn;
769    
770 root 1.25 *rcv_urierror = \&Net::FCP::Txn::rcv_throw_exception;
771 root 1.12 *rcv_route_not_found = \&Net::FCP::Txn::rcv_throw_exception;
772    
773     sub rcv_restarted {
774     my ($self, $attr, $type) = @_;
775    
776     delete $self->{datalength};
777     delete $self->{metalength};
778     delete $self->{data};
779    
780     $self->progress ($type, $attr);
781     }
782    
783 root 1.5 package Net::FCP::Txn::ClientGet;
784    
785 root 1.12 use base Net::FCP::Txn::GetPut;
786    
787     *rcv_data_not_found = \&Net::FCP::Txn::rcv_throw_exception;
788 root 1.5
789 root 1.17 sub rcv_data {
790     my ($self, $chunk) = @_;
791 root 1.9
792 root 1.17 $self->{data} .= $chunk;
793 root 1.5
794 root 1.19 $self->progress ("data", { chunk => length $chunk, received => length $self->{data}, total => $self->{datalength} });
795 root 1.9
796 root 1.12 if ($self->{datalength} == length $self->{data}) {
797     my $data = delete $self->{data};
798     my $meta = Net::FCP::parse_metadata substr $data, 0, $self->{metalength}, "";
799    
800     $self->set_result ([$meta, $data]);
801 root 1.22 $self->eof;
802 root 1.12 }
803 root 1.9 }
804    
805 root 1.17 sub rcv_data_found {
806     my ($self, $attr, $type) = @_;
807    
808     $self->progress ($type, $attr);
809    
810     $self->{datalength} = hex $attr->{data_length};
811     $self->{metalength} = hex $attr->{metadata_length};
812     }
813    
814 root 1.12 package Net::FCP::Txn::ClientPut;
815 root 1.9
816 root 1.12 use base Net::FCP::Txn::GetPut;
817 root 1.9
818 root 1.12 *rcv_size_error = \&Net::FCP::Txn::rcv_throw_exception;
819     *rcv_key_collision = \&Net::FCP::Txn::rcv_throw_exception;
820 root 1.9
821 root 1.12 sub rcv_pending {
822 root 1.9 my ($self, $attr, $type) = @_;
823     $self->progress ($type, $attr);
824 root 1.5 }
825    
826 root 1.12 sub rcv_success {
827     my ($self, $attr, $type) = @_;
828     $self->set_result ($attr);
829 root 1.9 }
830    
831 root 1.17 =back
832    
833     =head2 The Net::FCP::Exception CLASS
834    
835     Any unexpected (non-standard) responses that make it impossible to return
836     the advertised result will result in an exception being thrown when the
837     C<result> method is called.
838    
839     These exceptions are represented by objects of this class.
840    
841     =over 4
842    
843     =cut
844    
845 root 1.9 package Net::FCP::Exception;
846    
847     use overload
848     '""' => sub {
849 root 1.22 "Net::FCP::Exception<<$_[0][0]," . (join ":", %{$_[0][1]}) . ">>";
850 root 1.9 };
851    
852 root 1.17 =item $exc = new Net::FCP::Exception $type, \%attr
853    
854     Create a new exception object of the given type (a string like
855     C<route_not_found>), and a hashref containing additional attributes
856     (usually the attributes of the message causing the exception).
857    
858     =cut
859    
860 root 1.9 sub new {
861     my ($class, $type, $attr) = @_;
862    
863 root 1.12 bless [Net::FCP::tolc $type, { %$attr }], $class;
864 root 1.17 }
865    
866     =item $exc->type([$type])
867    
868     With no arguments, returns the exception type. Otherwise a boolean
869     indicating wether the exception is of the given type is returned.
870    
871     =cut
872    
873     sub type {
874     my ($self, $type) = @_;
875    
876     @_ >= 2
877     ? $self->[0] eq $type
878     : $self->[0];
879     }
880    
881     =item $exc->attr([$attr])
882    
883     With no arguments, returns the attributes. Otherwise the named attribute
884     value is returned.
885    
886     =cut
887    
888     sub attr {
889     my ($self, $attr) = @_;
890    
891     @_ >= 2
892     ? $self->[1]{$attr}
893     : $self->[1];
894 root 1.1 }
895    
896     =back
897    
898     =head1 SEE ALSO
899    
900     L<http://freenet.sf.net>.
901    
902     =head1 BUGS
903    
904     =head1 AUTHOR
905    
906     Marc Lehmann <pcg@goof.com>
907     http://www.goof.com/pcg/marc/
908    
909     =cut
910 root 1.20
911     package Net::FCP::Event::Auto;
912    
913     my @models = (
914     [Coro => Coro::Event:: ],
915     [Event => Event::],
916     [Glib => Glib:: ],
917     [Tk => Tk::],
918     );
919    
920     sub AUTOLOAD {
921     $AUTOLOAD =~ s/.*://;
922    
923     for (@models) {
924     my ($model, $package) = @$_;
925     if (defined ${"$package\::VERSION"}) {
926     $EVENT = "Net::FCP::Event::$model";
927     eval "require $EVENT"; die if $@;
928     goto &{"$EVENT\::$AUTOLOAD"};
929     }
930     }
931    
932     for (@models) {
933     my ($model, $package) = @$_;
934     $EVENT = "Net::FCP::Event::$model";
935     if (eval "require $EVENT") {
936     goto &{"$EVENT\::$AUTOLOAD"};
937     }
938     }
939    
940     die "No event module selected for Net::FCP and autodetect failed. Install any of these: Coro, Event, Glib or Tk.";
941     }
942 root 1.1
943     1;
944