ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Net-FCP/FCP.pm
Revision: 1.35
Committed: Thu Mar 3 17:31:26 2005 UTC (19 years, 2 months ago) by root
Branch: MAIN
Changes since 1.34: +2 -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.8;
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, $crypto) = @{ $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)
448 ->cb (sub { $self->fh_ready_w })
449 ->poll (0, 1, 1);
450
451 $self;
452 }
453
454 =item $txn = $txn->cb ($coderef)
455
456 Sets a callback to be called when the request is finished. The coderef
457 will be called with the txn as it's sole argument, so it has to call
458 C<result> itself.
459
460 Returns the txn object, useful for chaining.
461
462 Example:
463
464 $fcp->txn_client_get ("freenet:CHK....")
465 ->userdata ("ehrm")
466 ->cb(sub {
467 my $data = shift->result;
468 });
469
470 =cut
471
472 sub cb($$) {
473 my ($self, $cb) = @_;
474 $self->{cb} = $cb;
475 $self;
476 }
477
478 =item $txn = $txn->userdata ([$userdata])
479
480 Set user-specific data. This is useful in progress callbacks. The data can be accessed
481 using C<< $txn->{userdata} >>.
482
483 Returns the txn object, useful for chaining.
484
485 =cut
486
487 sub userdata($$) {
488 my ($self, $data) = @_;
489 $self->{userdata} = $data;
490 $self;
491 }
492
493 =item $txn->cancel (%attr)
494
495 Cancels the operation with a C<cancel> exception and the given attributes
496 (consider at least giving the attribute C<reason>).
497
498 UNTESTED.
499
500 =cut
501
502 sub cancel {
503 my ($self, %attr) = @_;
504 $self->throw (Net::FCP::Exception->new (cancel => { %attr }));
505 $self->set_result;
506 $self->eof;
507 }
508
509 sub fh_ready_w {
510 my ($self) = @_;
511
512 my $len = syswrite $self->{fh}, $self->{sbuf};
513
514 if ($len > 0) {
515 substr $self->{sbuf}, 0, $len, "";
516 unless (length $self->{sbuf}) {
517 fcntl $self->{fh}, F_SETFL, 0;
518 $self->{w}->cb(sub { $self->fh_ready_r })->poll (1, 0, 1);
519 }
520 } elsif (defined $len) {
521 $self->throw (Net::FCP::Exception->new (network_error => { reason => "unexpected end of file while writing" }));
522 } else {
523 $self->throw (Net::FCP::Exception->new (network_error => { reason => "$!" }));
524 }
525 }
526
527 sub fh_ready_r {
528 my ($self) = @_;
529
530 if (sysread $self->{fh}, $self->{buf}, 65536, length $self->{buf}) {
531 for (;;) {
532 if ($self->{datalen}) {
533 #warn "expecting new datachunk $self->{datalen}, got ".(length $self->{buf})."\n";#d#
534 if (length $self->{buf} >= $self->{datalen}) {
535 $self->rcv_data (substr $self->{buf}, 0, delete $self->{datalen}, "");
536 } else {
537 last;
538 }
539 } elsif ($self->{buf} =~ s/^DataChunk\015?\012Length=([0-9a-fA-F]+)\015?\012Data\015?\012//) {
540 $self->{datalen} = hex $1;
541 #warn "expecting new datachunk $self->{datalen}\n";#d#
542 } elsif ($self->{buf} =~ s/^([a-zA-Z]+)\015?\012(?:(.+?)\015?\012)?EndMessage\015?\012//s) {
543 $self->rcv ($1, {
544 map { my ($a, $b) = split /=/, $_, 2; ((Net::FCP::tolc $a), $b) }
545 split /\015?\012/, $2
546 });
547 } else {
548 last;
549 }
550 }
551 } else {
552 $self->eof;
553 }
554 }
555
556 sub rcv {
557 my ($self, $type, $attr) = @_;
558
559 $type = Net::FCP::tolc $type;
560
561 #use PApp::Util; warn PApp::Util::dumpval [$type, $attr];
562
563 if (my $method = $self->can("rcv_$type")) {
564 $method->($self, $attr, $type);
565 } else {
566 warn "received unexpected reply type '$type' for '$self->{type}', ignoring\n";
567 }
568 }
569
570 # used as a default exception thrower
571 sub rcv_throw_exception {
572 my ($self, $attr, $type) = @_;
573 $self->throw (Net::FCP::Exception->new ($type, $attr));
574 }
575
576 *rcv_failed = \&Net::FCP::Txn::rcv_throw_exception;
577 *rcv_format_error = \&Net::FCP::Txn::rcv_throw_exception;
578
579 sub throw {
580 my ($self, $exc) = @_;
581
582 $self->{exception} = $exc;
583 $self->set_result;
584 $self->eof; # must be last to avoid loops
585 }
586
587 sub set_result {
588 my ($self, $result) = @_;
589
590 unless (exists $self->{result}) {
591 $self->{result} = $result;
592 $self->{cb}->($self) if exists $self->{cb};
593 $self->{signal}->send;
594 }
595 }
596
597 sub eof {
598 my ($self) = @_;
599
600 delete $self->{w};
601 delete $self->{fh};
602
603 delete $self->{fcp}{txn}{$self};
604
605 unless (exists $self->{result}) {
606 $self->throw (Net::FCP::Exception->new (short_data => {
607 reason => "unexpected eof or internal node error",
608 }));
609 }
610 }
611
612 sub progress {
613 my ($self, $type, $attr) = @_;
614
615 $self->{fcp}->progress ($self, $type, $attr);
616 }
617
618 =item $result = $txn->result
619
620 Waits until a result is available and then returns it.
621
622 This waiting is (depending on your event model) not very efficient, as it
623 is done outside the "mainloop". The biggest problem, however, is that it's
624 blocking one thread of execution. Try to use the callback mechanism, if
625 possible, and call result from within the callback (or after is has been
626 run), as then no waiting is necessary.
627
628 =cut
629
630 sub result {
631 my ($self) = @_;
632
633 $self->{signal}->wait while !exists $self->{result};
634
635 die $self->{exception} if $self->{exception};
636
637 return $self->{result};
638 }
639
640 package Net::FCP::Txn::ClientHello;
641
642 use base Net::FCP::Txn;
643
644 sub rcv_node_hello {
645 my ($self, $attr) = @_;
646
647 $self->set_result ($attr);
648 }
649
650 package Net::FCP::Txn::ClientInfo;
651
652 use base Net::FCP::Txn;
653
654 sub rcv_node_info {
655 my ($self, $attr) = @_;
656
657 $self->set_result ($attr);
658 }
659
660 package Net::FCP::Txn::GenerateCHK;
661
662 use base Net::FCP::Txn;
663
664 sub rcv_success {
665 my ($self, $attr) = @_;
666
667 $self->set_result ($attr->{uri});
668 }
669
670 package Net::FCP::Txn::GenerateSVKPair;
671
672 use base Net::FCP::Txn;
673
674 sub rcv_success {
675 my ($self, $attr) = @_;
676 $self->set_result ([$attr->{public_key}, $attr->{private_key}, $attr->{crypto_key}]);
677 }
678
679 package Net::FCP::Txn::InvertPrivateKey;
680
681 use base Net::FCP::Txn;
682
683 sub rcv_success {
684 my ($self, $attr) = @_;
685 $self->set_result ($attr->{public_key});
686 }
687
688 package Net::FCP::Txn::GetSize;
689
690 use base Net::FCP::Txn;
691
692 sub rcv_success {
693 my ($self, $attr) = @_;
694 $self->set_result (hex $attr->{length});
695 }
696
697 package Net::FCP::Txn::GetPut;
698
699 # base class for get and put
700
701 use base Net::FCP::Txn;
702
703 *rcv_uri_error = \&Net::FCP::Txn::rcv_throw_exception;
704 *rcv_route_not_found = \&Net::FCP::Txn::rcv_throw_exception;
705
706 sub rcv_restarted {
707 my ($self, $attr, $type) = @_;
708
709 delete $self->{datalength};
710 delete $self->{metalength};
711 delete $self->{data};
712
713 $self->progress ($type, $attr);
714 }
715
716 package Net::FCP::Txn::ClientGet;
717
718 use base Net::FCP::Txn::GetPut;
719
720 *rcv_data_not_found = \&Net::FCP::Txn::rcv_throw_exception;
721
722 sub rcv_data {
723 my ($self, $chunk) = @_;
724
725 $self->{data} .= $chunk;
726
727 $self->progress ("data", { chunk => length $chunk, received => length $self->{data}, total => $self->{datalength} });
728
729 if ($self->{datalength} == length $self->{data}) {
730 my $data = delete $self->{data};
731 my $meta = new Net::FCP::Metadata (substr $data, 0, $self->{metalength}, "");
732
733 $self->set_result ([$meta, $data]);
734 $self->eof;
735 }
736 }
737
738 sub rcv_data_found {
739 my ($self, $attr, $type) = @_;
740
741 $self->progress ($type, $attr);
742
743 $self->{datalength} = hex $attr->{data_length};
744 $self->{metalength} = hex $attr->{metadata_length};
745 }
746
747 package Net::FCP::Txn::ClientPut;
748
749 use base Net::FCP::Txn::GetPut;
750
751 *rcv_size_error = \&Net::FCP::Txn::rcv_throw_exception;
752
753 sub rcv_pending {
754 my ($self, $attr, $type) = @_;
755 $self->progress ($type, $attr);
756 }
757
758 sub rcv_success {
759 my ($self, $attr, $type) = @_;
760 $self->set_result ($attr);
761 }
762
763 sub rcv_key_collision {
764 my ($self, $attr, $type) = @_;
765 $self->set_result ({ key_collision => 1, %$attr });
766 }
767
768 =back
769
770 =head2 The Net::FCP::Exception CLASS
771
772 Any unexpected (non-standard) responses that make it impossible to return
773 the advertised result will result in an exception being thrown when the
774 C<result> method is called.
775
776 These exceptions are represented by objects of this class.
777
778 =over 4
779
780 =cut
781
782 package Net::FCP::Exception;
783
784 use overload
785 '""' => sub {
786 "Net::FCP::Exception<<$_[0][0]," . (join ":", %{$_[0][1]}) . ">>";
787 };
788
789 =item $exc = new Net::FCP::Exception $type, \%attr
790
791 Create a new exception object of the given type (a string like
792 C<route_not_found>), and a hashref containing additional attributes
793 (usually the attributes of the message causing the exception).
794
795 =cut
796
797 sub new {
798 my ($class, $type, $attr) = @_;
799
800 bless [Net::FCP::tolc $type, { %$attr }], $class;
801 }
802
803 =item $exc->type([$type])
804
805 With no arguments, returns the exception type. Otherwise a boolean
806 indicating wether the exception is of the given type is returned.
807
808 =cut
809
810 sub type {
811 my ($self, $type) = @_;
812
813 @_ >= 2
814 ? $self->[0] eq $type
815 : $self->[0];
816 }
817
818 =item $exc->attr([$attr])
819
820 With no arguments, returns the attributes. Otherwise the named attribute
821 value is returned.
822
823 =cut
824
825 sub attr {
826 my ($self, $attr) = @_;
827
828 @_ >= 2
829 ? $self->[1]{$attr}
830 : $self->[1];
831 }
832
833 =back
834
835 =head1 SEE ALSO
836
837 L<http://freenet.sf.net>.
838
839 =head1 BUGS
840
841 =head1 AUTHOR
842
843 Marc Lehmann <schmorp@schmorp.de>
844 http://home.schmorp.de/
845
846 =cut
847
848 package Net::FCP::Event::Auto;
849
850 my @models = (
851 [Coro => Coro::Event::],
852 [Event => Event::],
853 [Glib => Glib::],
854 [Tk => Tk::],
855 );
856
857 sub AUTOLOAD {
858 $AUTOLOAD =~ s/.*://;
859
860 for (@models) {
861 my ($model, $package) = @$_;
862 if (defined ${"$package\::VERSION"}) {
863 $EVENT = "Net::FCP::Event::$model";
864 eval "require $EVENT"; die if $@;
865 goto &{"$EVENT\::$AUTOLOAD"};
866 }
867 }
868
869 for (@models) {
870 my ($model, $package) = @$_;
871 $EVENT = "Net::FCP::Event::$model";
872 if (eval "require $EVENT") {
873 goto &{"$EVENT\::$AUTOLOAD"};
874 }
875 }
876
877 die "No event module selected for Net::FCP and autodetect failed. Install any of these: Coro, Event, Glib or Tk.";
878 }
879
880 1;
881