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