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