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

# Content
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 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
40 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 =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 =head2 THE Net::FCP CLASS
68
69 =over 4
70
71 =cut
72
73 package Net::FCP;
74
75 use Carp;
76
77 $VERSION = 0.6;
78
79 no warnings;
80
81 our $EVENT = Net::FCP::Event::Auto::;
82
83 sub import {
84 shift;
85
86 for (@_) {
87 if (/^event=(\w+)$/) {
88 $EVENT = "Net::FCP::Event::$1";
89 eval "require $EVENT";
90 }
91 }
92 die $@ if $@;
93 }
94
95 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 1 while s/(SVK|CHK|URI)([^_])/$1\_$2/i;
105 1 while s/([^_])(SVK|CHK|URI)/$1\_$2/i;
106 s/(?<=[a-z])(?=[A-Z])/_/g;
107 lc $_;
108 }
109
110 # the opposite of hex
111 sub xeh($) {
112 sprintf "%x", $_[0];
113 }
114
115 =item $meta = Net::FCP::parse_metadata $string
116
117 Parse a metadata string and return it.
118
119 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
123 All other headers are represented by arrayrefs (they can be repeated).
124
125 Since this description is confusing, here is a rather verbose example of a
126 parsed manifest:
127
128 (
129 raw => "Version...",
130 version => { revision => 1 },
131 document => [
132 {
133 info => { format" => "image/jpeg" },
134 name => "background.jpg",
135 redirect => { target => "freenet:CHK\@ZcagI,ra726bSw" },
136 },
137 {
138 info => { format" => "text/html" },
139 name => ".next",
140 redirect => { target => "freenet:SSK\@ilUPAgM/TFEE/3" },
141 },
142 {
143 info => { format" => "text/html" },
144 redirect => { target => "freenet:CHK\@8M8Po8ucwI,8xA" },
145 }
146 ]
147 )
148
149 =cut
150
151 sub parse_metadata {
152 my $data = shift;
153 my $meta = { raw => $data };
154
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 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 $hdr->{$p[0]}{$p[1]}{$p[2]} = $v if @p == 3;
166 die "FATAL: 4+ dot metadata" if @p >= 4;
167 }
168
169 if ($data =~ /\GEndPart\015?\012/gc) {
170 # nop
171 } elsif ($data =~ /\GEnd(\015?\012|$)/gc) {
172 last;
173 } elsif ($data =~ /\G([A-Za-z0-9.\-]+)\015?\012/gcs) {
174 push @{$meta->{tolc $1}}, $hdr = {};
175 } elsif ($data =~ /\G(.*)/gcs) {
176 print STDERR "metadata format error ($1), please report this string: <<$data>>";
177 die "metadata format error";
178 }
179 }
180 }
181
182 #$meta->{tail} = substr $data, pos $data;
183
184 $meta;
185 }
186
187 =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 =item $fcp = new Net::FCP [host => $host][, port => $port][, progress => \&cb]
235
236 Create a new virtual FCP connection to the given host and port (default
237 127.0.0.1:8481, or the environment variables C<FREDHOST> and C<FREDPORT>).
238
239 Connections are virtual because no persistent physical connection is
240 established.
241
242 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 =begin comment
253
254 However, the existance of the node is checked by executing a
255 C<ClientHello> transaction.
256
257 =end
258
259 =cut
260
261 sub new {
262 my $class = shift;
263 my $self = bless { @_ }, $class;
264
265 $self->{host} ||= $ENV{FREDHOST} || "127.0.0.1";
266 $self->{port} ||= $ENV{FREDPORT} || 8481;
267
268 #$self->{nodehello} = $self->client_hello
269 # or croak "unable to get nodehello from node\n";
270
271 $self;
272 }
273
274 sub progress {
275 my ($self, $txn, $type, $attr) = @_;
276
277 $self->{progress}->($self, $txn, $type, $attr)
278 if $self->{progress};
279 }
280
281 =item $txn = $fcp->txn(type => attr => val,...)
282
283 The low-level interface to transactions. Don't use it.
284
285 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 =cut
308
309 sub txn {
310 my ($self, $type, %attr) = @_;
311
312 $type = touc $type;
313
314 my $txn = "Net::FCP::Txn::$type"->new (fcp => $self, type => tolc $type, attr => \%attr);
315
316 $txn;
317 }
318
319 { # transactions
320
321 my $txn = sub {
322 my ($name, $sub) = @_;
323 *{"txn_$name"} = $sub;
324 *{$name} = sub { $sub->(@_)->result };
325 };
326
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 max_file_size => "5f5e100",
335 node => "Fred,0.6,1.46,7050"
336 protocol => "1.2",
337 }
338
339 =cut
340
341 $txn->(client_hello => sub {
342 my ($self) = @_;
343
344 $self->txn ("client_hello");
345 });
346
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 active_jobs => "1f",
355 allocated_memory => "bde0000",
356 architecture => "i386",
357 available_threads => 17,
358 datastore_free => "5ce03400",
359 datastore_max => "2540be400",
360 datastore_used => "1f72bb000",
361 estimated_load => 52,
362 free_memory => "5cc0148",
363 is_transient => "false",
364 java_name => "Java HotSpot(_T_M) Server VM",
365 java_vendor => "http://www.blackdown.org/",
366 java_version => "Blackdown-1.4.1-01",
367 least_recent_timestamp => "f41538b878",
368 max_file_size => "5f5e100",
369 most_recent_timestamp => "f77e2cc520"
370 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 }
376
377 =cut
378
379 $txn->(client_info => sub {
380 my ($self) = @_;
381
382 $self->txn ("client_info");
383 });
384
385 =item $txn = $fcp->txn_generate_chk ($metadata, $data[, $cipher])
386
387 =item $uri = $fcp->generate_chk ($metadata, $data[, $cipher])
388
389 Calculates a CHK, given the metadata and data. C<$cipher> is either
390 C<Rijndael> or C<Twofish>, with the latter being the default.
391
392 =cut
393
394 $txn->(generate_chk => sub {
395 my ($self, $metadata, $data, $cipher) = @_;
396
397 $self->txn (generate_chk =>
398 data => "$metadata$data",
399 metadata_length => xeh length $metadata,
400 cipher => $cipher || "Twofish");
401 });
402
403 =item $txn = $fcp->txn_generate_svk_pair
404
405 =item ($public, $private) = @{ $fcp->generate_svk_pair }
406
407 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
410 [
411 "acLx4dux9fvvABH15Gk6~d3I-yw",
412 "cPoDkDMXDGSMM32plaPZDhJDxSs",
413 "BH7LXCov0w51-y9i~BoB3g",
414 ]
415
416 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 =cut
427
428 $txn->(generate_svk_pair => sub {
429 my ($self) = @_;
430
431 $self->txn ("generate_svk_pair");
432 });
433
434 =item $txn = $fcp->txn_invert_private_key ($private)
435
436 =item $public = $fcp->invert_private_key ($private)
437
438 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
442 Returns the public key.
443
444 =cut
445
446 $txn->(invert_private_key => sub {
447 my ($self, $privkey) = @_;
448
449 $self->txn (invert_private_key => private => $privkey);
450 });
451
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 $txn->(get_size => sub {
462 my ($self, $uri) = @_;
463
464 $self->txn (get_size => URI => $uri);
465 });
466
467 =item $txn = $fcp->txn_client_get ($uri [, $htl = 15 [, $removelocal = 0]])
468
469 =item ($metadata, $data) = @{ $fcp->client_get ($uri, $htl, $removelocal)
470
471 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
475 The C<$uri> should begin with C<freenet:>, but the scheme is currently
476 added, if missing.
477
478 Due to the overhead, a better method to download big files should be used.
479
480 my ($meta, $data) = @{
481 $fcp->client_get (
482 "freenet:CHK@hdXaxkwZ9rA8-SidT0AN-bniQlgPAwI,XdCDmBuGsd-ulqbLnZ8v~w"
483 )
484 };
485
486 =cut
487
488 $txn->(client_get => sub {
489 my ($self, $uri, $htl, $removelocal) = @_;
490
491 $uri =~ s/^freenet://;
492 $uri = "freenet:$uri";
493
494 $self->txn (client_get => URI => $uri, hops_to_live => xeh (defined $htl ? $htl : 15),
495 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 CHK. If the key is a private SSK key, the node will calculcate the public
505 key and the resulting public URI.
506
507 C<$meta> can be a hash reference (same format as returned by
508 C<Net::FCP::parse_metadata>) or a string.
509
510 The result is an arrayref with the keys C<uri>, C<public_key> and C<private_key>.
511
512 =cut
513
514 $txn->(client_put => sub {
515 my ($self, $uri, $meta, $data, $htl, $removelocal) = @_;
516
517 $meta = build_metadata $meta;
518
519 $self->txn (client_put => URI => $uri,
520 hops_to_live => xeh (defined $htl ? $htl : 15),
521 remove_local_key => $removelocal ? "true" : "false",
522 data => "$meta$data", metadata_length => xeh length $meta);
523 });
524
525 } # transactions
526
527 =back
528
529 =head2 THE Net::FCP::Txn CLASS
530
531 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
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 use Fcntl;
547 use Socket;
548
549 =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 $self->{signal} = $EVENT->new_signal;
560
561 $self->{fcp}{txn}{$self} = $self;
562
563 my $attr = "";
564 my $data = delete $self->{attr}{data};
565
566 while (my ($k, $v) = each %{$self->{attr}}) {
567 $attr .= (Net::FCP::touc $k) . "=$v\012"
568 }
569
570 if (defined $data) {
571 $attr .= sprintf "DataLength=%x\012", length $data;
572 $data = "Data\012$data";
573 } else {
574 $data = "EndMessage\012";
575 }
576
577 socket my $fh, PF_INET, SOCK_STREAM, 0
578 or Carp::croak "unable to create new tcp socket: $!";
579 binmode $fh, ":raw";
580 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 . (Net::FCP::touc $self->{type})
589 . "\012$attr$data";
590
591 #shutdown $fh, 1; # freenet buggy?, well, it's java...
592
593 $self->{fh} = $fh;
594
595 $self->{w} = $EVENT->new_from_fh ($fh)->cb(sub { $self->fh_ready_w })->poll(0, 1, 1);
596
597 $self;
598 }
599
600 =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
608 Example:
609
610 $fcp->txn_client_get ("freenet:CHK....")
611 ->userdata ("ehrm")
612 ->cb(sub {
613 my $data = shift->result;
614 });
615
616 =cut
617
618 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 my ($self, $data) = @_;
635 $self->{userdata} = $data;
636 $self;
637 }
638
639 =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 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 }
672
673 sub fh_ready_r {
674 my ($self) = @_;
675
676 if (sysread $self->{fh}, $self->{buf}, 65536, length $self->{buf}) {
677 for (;;) {
678 if ($self->{datalen}) {
679 #warn "expecting new datachunk $self->{datalen}, got ".(length $self->{buf})."\n";#d#
680 if (length $self->{buf} >= $self->{datalen}) {
681 $self->rcv_data (substr $self->{buf}, 0, delete $self->{datalen}, "");
682 } else {
683 last;
684 }
685 } elsif ($self->{buf} =~ s/^DataChunk\015?\012Length=([0-9a-fA-F]+)\015?\012Data\015?\012//) {
686 $self->{datalen} = hex $1;
687 #warn "expecting new datachunk $self->{datalen}\n";#d#
688 } elsif ($self->{buf} =~ s/^([a-zA-Z]+)\015?\012(?:(.+?)\015?\012)?EndMessage\015?\012//s) {
689 $self->rcv ($1, {
690 map { my ($a, $b) = split /=/, $_, 2; ((Net::FCP::tolc $a), $b) }
691 split /\015?\012/, $2
692 });
693 } else {
694 last;
695 }
696 }
697 } else {
698 $self->eof;
699 }
700 }
701
702 sub rcv {
703 my ($self, $type, $attr) = @_;
704
705 $type = Net::FCP::tolc $type;
706
707 #use PApp::Util; warn PApp::Util::dumpval [$type, $attr];
708
709 if (my $method = $self->can("rcv_$type")) {
710 $method->($self, $attr, $type);
711 } else {
712 warn "received unexpected reply type '$type' for '$self->{type}', ignoring\n";
713 }
714 }
715
716 # used as a default exception thrower
717 sub rcv_throw_exception {
718 my ($self, $attr, $type) = @_;
719 $self->throw (Net::FCP::Exception->new ($type, $attr));
720 }
721
722 *rcv_failed = \&Net::FCP::Txn::rcv_throw_exception;
723 *rcv_format_error = \&Net::FCP::Txn::rcv_throw_exception;
724
725 sub throw {
726 my ($self, $exc) = @_;
727
728 $self->{exception} = $exc;
729 $self->set_result;
730 $self->eof; # must be last to avoid loops
731 }
732
733 sub set_result {
734 my ($self, $result) = @_;
735
736 unless (exists $self->{result}) {
737 $self->{result} = $result;
738 $self->{cb}->($self) if exists $self->{cb};
739 $self->{signal}->send;
740 }
741 }
742
743 sub eof {
744 my ($self) = @_;
745
746 delete $self->{w};
747 delete $self->{fh};
748
749 delete $self->{fcp}{txn}{$self};
750
751 unless (exists $self->{result}) {
752 $self->throw (Net::FCP::Exception->new (short_data => {
753 reason => "unexpected eof or internal node error",
754 }));
755 }
756 }
757
758 sub progress {
759 my ($self, $type, $attr) = @_;
760
761 $self->{fcp}->progress ($self, $type, $attr);
762 }
763
764 =item $result = $txn->result
765
766 Waits until a result is available and then returns it.
767
768 This waiting is (depending on your event model) not very efficient, as it
769 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
774 =cut
775
776 sub result {
777 my ($self) = @_;
778
779 $self->{signal}->wait while !exists $self->{result};
780
781 die $self->{exception} if $self->{exception};
782
783 return $self->{result};
784 }
785
786 package Net::FCP::Txn::ClientHello;
787
788 use base Net::FCP::Txn;
789
790 sub rcv_node_hello {
791 my ($self, $attr) = @_;
792
793 $self->set_result ($attr);
794 }
795
796 package Net::FCP::Txn::ClientInfo;
797
798 use base Net::FCP::Txn;
799
800 sub rcv_node_info {
801 my ($self, $attr) = @_;
802
803 $self->set_result ($attr);
804 }
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 $self->set_result ($attr->{uri});
814 }
815
816 package Net::FCP::Txn::GenerateSVKPair;
817
818 use base Net::FCP::Txn;
819
820 sub rcv_success {
821 my ($self, $attr) = @_;
822 $self->set_result ([$attr->{public_key}, $attr->{private_key}, $attr->{crypto_key}]);
823 }
824
825 package Net::FCP::Txn::InvertPrivateKey;
826
827 use base Net::FCP::Txn;
828
829 sub rcv_success {
830 my ($self, $attr) = @_;
831 $self->set_result ($attr->{public_key});
832 }
833
834 package Net::FCP::Txn::GetSize;
835
836 use base Net::FCP::Txn;
837
838 sub rcv_success {
839 my ($self, $attr) = @_;
840 $self->set_result (hex $attr->{length});
841 }
842
843 package Net::FCP::Txn::GetPut;
844
845 # base class for get and put
846
847 use base Net::FCP::Txn;
848
849 *rcv_uri_error = \&Net::FCP::Txn::rcv_throw_exception;
850 *rcv_route_not_found = \&Net::FCP::Txn::rcv_throw_exception;
851
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 package Net::FCP::Txn::ClientGet;
863
864 use base Net::FCP::Txn::GetPut;
865
866 *rcv_data_not_found = \&Net::FCP::Txn::rcv_throw_exception;
867
868 sub rcv_data {
869 my ($self, $chunk) = @_;
870
871 $self->{data} .= $chunk;
872
873 $self->progress ("data", { chunk => length $chunk, received => length $self->{data}, total => $self->{datalength} });
874
875 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 $self->eof;
881 }
882 }
883
884 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 package Net::FCP::Txn::ClientPut;
894
895 use base Net::FCP::Txn::GetPut;
896
897 *rcv_size_error = \&Net::FCP::Txn::rcv_throw_exception;
898 *rcv_key_collision = \&Net::FCP::Txn::rcv_throw_exception;
899
900 sub rcv_pending {
901 my ($self, $attr, $type) = @_;
902 $self->progress ($type, $attr);
903 }
904
905 sub rcv_success {
906 my ($self, $attr, $type) = @_;
907 $self->set_result ($attr);
908 }
909
910 =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 package Net::FCP::Exception;
925
926 use overload
927 '""' => sub {
928 "Net::FCP::Exception<<$_[0][0]," . (join ":", %{$_[0][1]}) . ">>";
929 };
930
931 =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 sub new {
940 my ($class, $type, $attr) = @_;
941
942 bless [Net::FCP::tolc $type, { %$attr }], $class;
943 }
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 }
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
990 package Net::FCP::Event::Auto;
991
992 my @models = (
993 [Coro => Coro::Event::],
994 [Event => Event::],
995 [Glib => Glib::],
996 [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
1022 1;
1023