ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/Net-FCP/FCP.pm
(Generate patch)

Comparing cvsroot/Net-FCP/FCP.pm (file contents):
Revision 1.20 by root, Mon Sep 15 00:05:32 2003 UTC vs.
Revision 1.33 by root, Sun May 16 00:19:38 2004 UTC

72 72
73package Net::FCP; 73package Net::FCP;
74 74
75use Carp; 75use Carp;
76 76
77$VERSION = 0.08; 77$VERSION = 0.7;
78 78
79no warnings; 79no warnings;
80
81use Net::FCP::Metadata;
82use Net::FCP::Util qw(tolc touc xeh);
80 83
81our $EVENT = Net::FCP::Event::Auto::; 84our $EVENT = Net::FCP::Event::Auto::;
82 85
83sub import { 86sub import {
84 shift; 87 shift;
90 } 93 }
91 } 94 }
92 die $@ if $@; 95 die $@ if $@;
93} 96}
94 97
95sub touc($) {
96 local $_ = shift;
97 1 while s/((?:^|_)(?:svk|chk|uri)(?:_|$))/\U$1/;
98 s/(?:^|_)(.)/\U$1/g;
99 $_;
100}
101
102sub tolc($) {
103 local $_ = shift;
104 s/(?<=[a-z])(?=[A-Z])/_/g;
105 lc $_;
106}
107
108=item $meta = Net::FCP::parse_metadata $string
109
110Parse a metadata string and return it.
111
112The metadata will be a hashref with key C<version> (containing
113the mandatory version header entries).
114
115All other headers are represented by arrayrefs (they can be repeated).
116
117Since this is confusing, here is a rather verbose example of a parsed
118manifest:
119
120 (
121 version => { revision => 1 },
122 document => [
123 {
124 info => { format" => "image/jpeg" },
125 name => "background.jpg",
126 redirect => { target => "freenet:CHK\@ZcagI,ra726bSw" },
127 },
128 {
129 info => { format" => "text/html" },
130 name => ".next",
131 redirect => { target => "freenet:SSK\@ilUPAgM/TFEE/3" },
132 },
133 {
134 info => { format" => "text/html" },
135 redirect => { target => "freenet:CHK\@8M8Po8ucwI,8xA" },
136 }
137 ]
138 )
139
140=cut
141
142sub parse_metadata {
143 my $meta;
144
145 my $data = shift;
146 if ($data =~ /^Version\015?\012/gc) {
147 my $hdr = $meta->{version} = {};
148
149 for (;;) {
150 while ($data =~ /\G([^=\015\012]+)=([^\015\012]*)\015?\012/gc) {
151 my ($k, $v) = ($1, $2);
152 my @p = split /\./, tolc $k, 3;
153
154 $hdr->{$p[0]} = $v if @p == 1; # lamest code I ever wrote
155 $hdr->{$p[0]}{$p[1]} = $v if @p == 2;
156 $hdr->{$p[0]}{$p[1]}{$p[2]} = $v if @p == 3;
157 die "FATAL: 4+ dot metadata" if @p >= 4;
158 }
159
160 if ($data =~ /\GEndPart\015?\012/gc) {
161 # nop
162 } elsif ($data =~ /\GEnd(\015?\012|$)/gc) {
163 last;
164 } elsif ($data =~ /\G([A-Za-z0-9.\-]+)\015?\012/gcs) {
165 push @{$meta->{tolc $1}}, $hdr = {};
166 } elsif ($data =~ /\G(.*)/gcs) {
167 print STDERR "metadata format error ($1), please report this string: <<$data>>";
168 die "metadata format error";
169 }
170 }
171 }
172
173 #$meta->{tail} = substr $data, pos $data;
174
175 $meta;
176}
177
178=item $fcp = new Net::FCP [host => $host][, port => $port] 98=item $fcp = new Net::FCP [host => $host][, port => $port][, progress => \&cb]
179 99
180Create a new virtual FCP connection to the given host and port (default 100Create a new virtual FCP connection to the given host and port (default
181127.0.0.1:8481, or the environment variables C<FREDHOST> and C<FREDPORT>). 101127.0.0.1:8481, or the environment variables C<FREDHOST> and C<FREDPORT>).
182 102
183Connections are virtual because no persistent physical connection is 103Connections are virtual because no persistent physical connection is
184established. 104established.
185 105
186=begin comment 106You can install a progress callback that is being called with the Net::FCP
107object, a txn object, the type of the transaction and the attributes. Use
108it like this:
187 109
188However, the existance of the node is checked by executing a 110 sub progress_cb {
189C<ClientHello> transaction. 111 my ($self, $txn, $type, $attr) = @_;
190 112
191=end 113 warn "progress<$txn,$type," . (join ":", %$attr) . ">\n";
114 }
192 115
193=cut 116=cut
194 117
195sub new { 118sub new {
196 my $class = shift; 119 my $class = shift;
197 my $self = bless { @_ }, $class; 120 my $self = bless { @_ }, $class;
198 121
199 $self->{host} ||= $ENV{FREDHOST} || "127.0.0.1"; 122 $self->{host} ||= $ENV{FREDHOST} || "127.0.0.1";
200 $self->{port} ||= $ENV{FREDPORT} || 8481; 123 $self->{port} ||= $ENV{FREDPORT} || 8481;
201 124
202 #$self->{nodehello} = $self->client_hello
203 # or croak "unable to get nodehello from node\n";
204
205 $self; 125 $self;
206} 126}
207 127
208sub progress { 128sub progress {
209 my ($self, $txn, $type, $attr) = @_; 129 my ($self, $txn, $type, $attr) = @_;
210 #warn "progress<$txn,$type," . (join ":", %$attr) . ">\n";
211}
212 130
131 $self->{progress}->($self, $txn, $type, $attr)
132 if $self->{progress};
133}
134
213=item $txn = $fcp->txn(type => attr => val,...) 135=item $txn = $fcp->txn (type => attr => val,...)
214 136
215The low-level interface to transactions. Don't use it. 137The low-level interface to transactions. Don't use it unless you have
216 138"special needs". Instead, use predefiend transactions like this:
217Here are some examples of using transactions:
218 139
219The blocking case, no (visible) transactions involved: 140The blocking case, no (visible) transactions involved:
220 141
221 my $nodehello = $fcp->client_hello; 142 my $nodehello = $fcp->client_hello;
222 143
241sub txn { 162sub txn {
242 my ($self, $type, %attr) = @_; 163 my ($self, $type, %attr) = @_;
243 164
244 $type = touc $type; 165 $type = touc $type;
245 166
246 my $txn = "Net::FCP::Txn::$type"->new(fcp => $self, type => tolc $type, attr => \%attr); 167 my $txn = "Net::FCP::Txn::$type"->new (fcp => $self, type => tolc $type, attr => \%attr);
247 168
248 $txn; 169 $txn;
249} 170}
250 171
251{ # transactions 172{ # transactions
312 my ($self) = @_; 233 my ($self) = @_;
313 234
314 $self->txn ("client_info"); 235 $self->txn ("client_info");
315}); 236});
316 237
317=item $txn = $fcp->txn_generate_chk ($metadata, $data) 238=item $txn = $fcp->txn_generate_chk ($metadata, $data[, $cipher])
318 239
319=item $uri = $fcp->generate_chk ($metadata, $data) 240=item $uri = $fcp->generate_chk ($metadata, $data[, $cipher])
320 241
321Creates a new CHK, given the metadata and data. UNTESTED. 242Calculates a CHK, given the metadata and data. C<$cipher> is either
243C<Rijndael> or C<Twofish>, with the latter being the default.
322 244
323=cut 245=cut
324 246
325$txn->(generate_chk => sub { 247$txn->(generate_chk => sub {
326 my ($self, $metadata, $data) = @_; 248 my ($self, $metadata, $data, $cipher) = @_;
327 249
328 $self->txn (generate_chk => data => "$metadata$data", metadata_length => length $metadata); 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");
329}); 256});
330 257
331=item $txn = $fcp->txn_generate_svk_pair 258=item $txn = $fcp->txn_generate_svk_pair
332 259
333=item ($public, $private) = @{ $fcp->generate_svk_pair } 260=item ($public, $private, $crypto) = @{ $fcp->generate_svk_pair }
334 261
335Creates a new SVK pair. Returns an arrayref. 262Creates a new SVK pair. Returns an arrayref with the public key, the
263private key and a crypto key, which is just additional entropy.
336 264
337 [ 265 [
338 "hKs0-WDQA4pVZyMPKNFsK1zapWY", 266 "acLx4dux9fvvABH15Gk6~d3I-yw",
339 "ZnmvMITaTXBMFGl4~jrjuyWxOWg" 267 "cPoDkDMXDGSMM32plaPZDhJDxSs",
268 "BH7LXCov0w51-y9i~BoB3g",
340 ] 269 ]
270
271A private key (for inserting) can be constructed like this:
272
273 SSK@<private_key>,<crypto_key>/<name>
274
275It can be used to insert data. The corresponding public key looks like this:
276
277 SSK@<public_key>PAgM,<crypto_key>/<name>
278
279Watch out for the C<PAgM>-part!
341 280
342=cut 281=cut
343 282
344$txn->(generate_svk_pair => sub { 283$txn->(generate_svk_pair => sub {
345 my ($self) = @_; 284 my ($self) = @_;
346 285
347 $self->txn ("generate_svk_pair"); 286 $self->txn ("generate_svk_pair");
348}); 287});
349 288
350=item $txn = $fcp->txn_insert_private_key ($private) 289=item $txn = $fcp->txn_invert_private_key ($private)
351 290
352=item $public = $fcp->insert_private_key ($private) 291=item $public = $fcp->invert_private_key ($private)
353 292
354Inserts a private key. $private can be either an insert URI (must start 293Inverts a private key (returns the public key). C<$private> can be either
355with C<freenet:SSK@>) or a raw private key (i.e. the private value you get 294an insert URI (must start with C<freenet:SSK@>) or a raw private key (i.e.
356back from C<generate_svk_pair>). 295the private value you get back from C<generate_svk_pair>).
357 296
358Returns the public key. 297Returns the public key.
359 298
360UNTESTED.
361
362=cut 299=cut
363 300
364$txn->(insert_private_key => sub { 301$txn->(invert_private_key => sub {
365 my ($self, $privkey) = @_; 302 my ($self, $privkey) = @_;
366 303
367 $self->txn (invert_private_key => private => $privkey); 304 $self->txn (invert_private_key => private => $privkey);
368}); 305});
369 306
372=item $length = $fcp->get_size ($uri) 309=item $length = $fcp->get_size ($uri)
373 310
374Finds and returns the size (rounded up to the nearest power of two) of the 311Finds and returns the size (rounded up to the nearest power of two) of the
375given document. 312given document.
376 313
377UNTESTED.
378
379=cut 314=cut
380 315
381$txn->(get_size => sub { 316$txn->(get_size => sub {
382 my ($self, $uri) = @_; 317 my ($self, $uri) = @_;
383 318
386 321
387=item $txn = $fcp->txn_client_get ($uri [, $htl = 15 [, $removelocal = 0]]) 322=item $txn = $fcp->txn_client_get ($uri [, $htl = 15 [, $removelocal = 0]])
388 323
389=item ($metadata, $data) = @{ $fcp->client_get ($uri, $htl, $removelocal) 324=item ($metadata, $data) = @{ $fcp->client_get ($uri, $htl, $removelocal)
390 325
391Fetches a (small, as it should fit into memory) file from 326Fetches a (small, as it should fit into memory) key content block from
392freenet. C<$meta> is the metadata (as returned by C<parse_metadata> or 327freenet. C<$meta> is a C<Net::FCP::Metadata> object or C<undef>).
393C<undef>).
394 328
395Due to the overhead, a better method to download big files should be used. 329The C<$uri> should begin with C<freenet:>, but the scheme is currently
330added, if missing.
396 331
397 my ($meta, $data) = @{ 332 my ($meta, $data) = @{
398 $fcp->client_get ( 333 $fcp->client_get (
399 "freenet:CHK@hdXaxkwZ9rA8-SidT0AN-bniQlgPAwI,XdCDmBuGsd-ulqbLnZ8v~w" 334 "freenet:CHK@hdXaxkwZ9rA8-SidT0AN-bniQlgPAwI,XdCDmBuGsd-ulqbLnZ8v~w"
400 ) 335 )
403=cut 338=cut
404 339
405$txn->(client_get => sub { 340$txn->(client_get => sub {
406 my ($self, $uri, $htl, $removelocal) = @_; 341 my ($self, $uri, $htl, $removelocal) = @_;
407 342
343 $uri =~ s/^freenet://; $uri = "freenet:$uri";
344
408 $self->txn (client_get => URI => $uri, hops_to_live => (defined $htl ? $htl :15), 345 $self->txn (client_get => URI => $uri, hops_to_live => xeh (defined $htl ? $htl : 15),
409 remove_local_key => $removelocal ? "true" : "false"); 346 remove_local_key => $removelocal ? "true" : "false");
410}); 347});
411 348
412=item $txn = $fcp->txn_client_put ($uri, $metadata, $data, $htl, $removelocal) 349=item $txn = $fcp->txn_client_put ($uri, $metadata, $data, $htl, $removelocal)
413 350
414=item my $uri = $fcp->client_put ($uri, $metadata, $data, $htl, $removelocal); 351=item my $uri = $fcp->client_put ($uri, $metadata, $data, $htl, $removelocal);
415 352
416Insert a new key. If the client is inserting a CHK, the URI may be 353Insert a new key. If the client is inserting a CHK, the URI may be
417abbreviated as just CHK@. In this case, the node will calculate the 354abbreviated as just CHK@. In this case, the node will calculate the
418CHK. 355CHK. If the key is a private SSK key, the node will calculcate the public
356key and the resulting public URI.
419 357
420C<$meta> can be a reference or a string (ONLY THE STRING CASE IS IMPLEMENTED!). 358C<$meta> can be a hash reference (same format as returned by
359C<Net::FCP::parse_metadata>) or a string.
421 360
422THIS INTERFACE IS UNTESTED AND SUBJECT TO CHANGE. 361The result is an arrayref with the keys C<uri>, C<public_key> and C<private_key>.
423 362
424=cut 363=cut
425 364
426$txn->(client_put => sub { 365$txn->(client_put => sub {
427 my ($self, $uri, $meta, $data, $htl, $removelocal) = @_; 366 my ($self, $uri, $metadata, $data, $htl, $removelocal) = @_;
428 367
429 $self->txn (client_put => URI => $uri, hops_to_live => (defined $htl ? $htl :15), 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),
430 remove_local_key => $removelocal ? "true" : "false", 373 remove_local_key => $removelocal ? "true" : "false",
431 data => "$meta$data", metadata_length => length $meta); 374 data => "$metadata$data", metadata_length => xeh length $metadata);
432}); 375});
433 376
434} # transactions 377} # transactions
435 378
436=item MISSING: (ClientPut), InsretKey
437
438=back 379=back
439 380
440=head2 THE Net::FCP::Txn CLASS 381=head2 THE Net::FCP::Txn CLASS
441 382
442All requests (or transactions) are executed in a asynchroneous way (LIE: 383All requests (or transactions) are executed in a asynchronous way. For
443uploads are blocking). For each request, a C<Net::FCP::Txn> object is 384each request, a C<Net::FCP::Txn> object is created (worse: a tcp
444created (worse: a tcp connection is created, too). 385connection is created, too).
445 386
446For each request there is actually a different subclass (and it's possible 387For each request there is actually a different subclass (and it's possible
447to subclass these, although of course not documented). 388to subclass these, although of course not documented).
448 389
449The most interesting method is C<result>. 390The most interesting method is C<result>.
477 while (my ($k, $v) = each %{$self->{attr}}) { 418 while (my ($k, $v) = each %{$self->{attr}}) {
478 $attr .= (Net::FCP::touc $k) . "=$v\012" 419 $attr .= (Net::FCP::touc $k) . "=$v\012"
479 } 420 }
480 421
481 if (defined $data) { 422 if (defined $data) {
482 $attr .= "DataLength=" . (length $data) . "\012"; 423 $attr .= sprintf "DataLength=%x\012", length $data;
483 $data = "Data\012$data"; 424 $data = "Data\012$data";
484 } else { 425 } else {
485 $data = "EndMessage\012"; 426 $data = "EndMessage\012";
486 } 427 }
487 428
494 and !$!{EINPROGRESS} 435 and !$!{EINPROGRESS}
495 and Carp::croak "FCP::txn: unable to connect to $self->{fcp}{host}:$self->{fcp}{port}: $!\n"; 436 and Carp::croak "FCP::txn: unable to connect to $self->{fcp}{host}:$self->{fcp}{port}: $!\n";
496 437
497 $self->{sbuf} = 438 $self->{sbuf} =
498 "\x00\x00\x00\x02" 439 "\x00\x00\x00\x02"
499 . Net::FCP::touc $self->{type} 440 . (Net::FCP::touc $self->{type})
500 . "\012$attr$data"; 441 . "\012$attr$data";
501 442
502 #$fh->shutdown (1); # freenet buggy?, well, it's java... 443 #shutdown $fh, 1; # freenet buggy?, well, it's java...
503 444
504 $self->{fh} = $fh; 445 $self->{fh} = $fh;
505 446
506 $self->{w} = $EVENT->new_from_fh ($fh)->cb(sub { $self->fh_ready_w })->poll(0, 1, 1); 447 $self->{w} = $EVENT->new_from_fh ($fh)
448 ->cb (sub { $self->fh_ready_w })
449 ->poll (0, 1, 1);
507 450
508 $self; 451 $self;
509} 452}
510 453
511=item $txn = $txn->cb ($coderef) 454=item $txn = $txn->cb ($coderef)
666 } 609 }
667} 610}
668 611
669sub progress { 612sub progress {
670 my ($self, $type, $attr) = @_; 613 my ($self, $type, $attr) = @_;
614
671 $self->{fcp}->progress ($self, $type, $attr); 615 $self->{fcp}->progress ($self, $type, $attr);
672} 616}
673 617
674=item $result = $txn->result 618=item $result = $txn->result
675 619
676Waits until a result is available and then returns it. 620Waits until a result is available and then returns it.
677 621
678This waiting is (depending on your event model) not very efficient, as it 622This waiting is (depending on your event model) not very efficient, as it
679is done outside the "mainloop". 623is done outside the "mainloop". The biggest problem, however, is that it's
624blocking one thread of execution. Try to use the callback mechanism, if
625possible, and call result from within the callback (or after is has been
626run), as then no waiting is necessary.
680 627
681=cut 628=cut
682 629
683sub result { 630sub result {
684 my ($self) = @_; 631 my ($self) = @_;
715use base Net::FCP::Txn; 662use base Net::FCP::Txn;
716 663
717sub rcv_success { 664sub rcv_success {
718 my ($self, $attr) = @_; 665 my ($self, $attr) = @_;
719 666
720 $self->set_result ($attr); 667 $self->set_result ($attr->{uri});
721} 668}
722 669
723package Net::FCP::Txn::GenerateSVKPair; 670package Net::FCP::Txn::GenerateSVKPair;
724 671
725use base Net::FCP::Txn; 672use base Net::FCP::Txn;
726 673
727sub rcv_success { 674sub rcv_success {
728 my ($self, $attr) = @_; 675 my ($self, $attr) = @_;
729 $self->set_result ([$attr->{PublicKey}, $attr->{PrivateKey}]); 676 $self->set_result ([$attr->{public_key}, $attr->{private_key}, $attr->{crypto_key}]);
730} 677}
731 678
732package Net::FCP::Txn::InsertPrivateKey; 679package Net::FCP::Txn::InvertPrivateKey;
733 680
734use base Net::FCP::Txn; 681use base Net::FCP::Txn;
735 682
736sub rcv_success { 683sub rcv_success {
737 my ($self, $attr) = @_; 684 my ($self, $attr) = @_;
738 $self->set_result ($attr->{PublicKey}); 685 $self->set_result ($attr->{public_key});
739} 686}
740 687
741package Net::FCP::Txn::GetSize; 688package Net::FCP::Txn::GetSize;
742 689
743use base Net::FCP::Txn; 690use base Net::FCP::Txn;
744 691
745sub rcv_success { 692sub rcv_success {
746 my ($self, $attr) = @_; 693 my ($self, $attr) = @_;
747 $self->set_result ($attr->{Length}); 694 $self->set_result (hex $attr->{length});
748} 695}
749 696
750package Net::FCP::Txn::GetPut; 697package Net::FCP::Txn::GetPut;
751 698
752# base class for get and put 699# base class for get and put
753 700
754use base Net::FCP::Txn; 701use base Net::FCP::Txn;
755 702
756*rcv_uri_error = \&Net::FCP::Txn::rcv_throw_exception; 703*rcv_uri_error = \&Net::FCP::Txn::rcv_throw_exception;
757*rcv_route_not_found = \&Net::FCP::Txn::rcv_throw_exception; 704*rcv_route_not_found = \&Net::FCP::Txn::rcv_throw_exception;
758 705
759sub rcv_restarted { 706sub rcv_restarted {
760 my ($self, $attr, $type) = @_; 707 my ($self, $attr, $type) = @_;
761 708
762 delete $self->{datalength}; 709 delete $self->{datalength};
779 726
780 $self->progress ("data", { chunk => length $chunk, received => length $self->{data}, total => $self->{datalength} }); 727 $self->progress ("data", { chunk => length $chunk, received => length $self->{data}, total => $self->{datalength} });
781 728
782 if ($self->{datalength} == length $self->{data}) { 729 if ($self->{datalength} == length $self->{data}) {
783 my $data = delete $self->{data}; 730 my $data = delete $self->{data};
784 my $meta = Net::FCP::parse_metadata substr $data, 0, $self->{metalength}, ""; 731 my $meta = new Net::FCP::Metadata (substr $data, 0, $self->{metalength}, "");
785 732
786 $self->set_result ([$meta, $data]); 733 $self->set_result ([$meta, $data]);
734 $self->eof;
787 } 735 }
788} 736}
789 737
790sub rcv_data_found { 738sub rcv_data_found {
791 my ($self, $attr, $type) = @_; 739 my ($self, $attr, $type) = @_;
799package Net::FCP::Txn::ClientPut; 747package Net::FCP::Txn::ClientPut;
800 748
801use base Net::FCP::Txn::GetPut; 749use base Net::FCP::Txn::GetPut;
802 750
803*rcv_size_error = \&Net::FCP::Txn::rcv_throw_exception; 751*rcv_size_error = \&Net::FCP::Txn::rcv_throw_exception;
804*rcv_key_collision = \&Net::FCP::Txn::rcv_throw_exception;
805 752
806sub rcv_pending { 753sub rcv_pending {
807 my ($self, $attr, $type) = @_; 754 my ($self, $attr, $type) = @_;
808 $self->progress ($type, $attr); 755 $self->progress ($type, $attr);
809} 756}
811sub rcv_success { 758sub rcv_success {
812 my ($self, $attr, $type) = @_; 759 my ($self, $attr, $type) = @_;
813 $self->set_result ($attr); 760 $self->set_result ($attr);
814} 761}
815 762
763sub rcv_key_collision {
764 my ($self, $attr, $type) = @_;
765 $self->set_result ({ key_collision => 1, %$attr });
766}
767
816=back 768=back
817 769
818=head2 The Net::FCP::Exception CLASS 770=head2 The Net::FCP::Exception CLASS
819 771
820Any unexpected (non-standard) responses that make it impossible to return 772Any unexpected (non-standard) responses that make it impossible to return
829 781
830package Net::FCP::Exception; 782package Net::FCP::Exception;
831 783
832use overload 784use overload
833 '""' => sub { 785 '""' => sub {
834 "Net::FCP::Exception<<$_[0][0]," . (join ":", %{$_[0][1]}) . ">>\n"; 786 "Net::FCP::Exception<<$_[0][0]," . (join ":", %{$_[0][1]}) . ">>";
835 }; 787 };
836 788
837=item $exc = new Net::FCP::Exception $type, \%attr 789=item $exc = new Net::FCP::Exception $type, \%attr
838 790
839Create a new exception object of the given type (a string like 791Create a new exception object of the given type (a string like
894=cut 846=cut
895 847
896package Net::FCP::Event::Auto; 848package Net::FCP::Event::Auto;
897 849
898my @models = ( 850my @models = (
899 [Coro => Coro::Event:: ], 851 [Coro => Coro::Event::],
900 [Event => Event::], 852 [Event => Event::],
901 [Glib => Glib:: ], 853 [Glib => Glib::],
902 [Tk => Tk::], 854 [Tk => Tk::],
903); 855);
904 856
905sub AUTOLOAD { 857sub AUTOLOAD {
906 $AUTOLOAD =~ s/.*://; 858 $AUTOLOAD =~ s/.*://;

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines