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