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