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