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.32 by root, Fri May 14 17:25:17 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)->cb(sub { $self->fh_ready_w })->poll(0, 1, 1);
507 448
666 } 607 }
667} 608}
668 609
669sub progress { 610sub progress {
670 my ($self, $type, $attr) = @_; 611 my ($self, $type, $attr) = @_;
612
671 $self->{fcp}->progress ($self, $type, $attr); 613 $self->{fcp}->progress ($self, $type, $attr);
672} 614}
673 615
674=item $result = $txn->result 616=item $result = $txn->result
675 617
676Waits until a result is available and then returns it. 618Waits until a result is available and then returns it.
677 619
678This waiting is (depending on your event model) not very efficient, as it 620This waiting is (depending on your event model) not very efficient, as it
679is done outside the "mainloop". 621is done outside the "mainloop". The biggest problem, however, is that it's
622blocking one thread of execution. Try to use the callback mechanism, if
623possible, and call result from within the callback (or after is has been
624run), as then no waiting is necessary.
680 625
681=cut 626=cut
682 627
683sub result { 628sub result {
684 my ($self) = @_; 629 my ($self) = @_;
715use base Net::FCP::Txn; 660use base Net::FCP::Txn;
716 661
717sub rcv_success { 662sub rcv_success {
718 my ($self, $attr) = @_; 663 my ($self, $attr) = @_;
719 664
720 $self->set_result ($attr); 665 $self->set_result ($attr->{uri});
721} 666}
722 667
723package Net::FCP::Txn::GenerateSVKPair; 668package Net::FCP::Txn::GenerateSVKPair;
724 669
725use base Net::FCP::Txn; 670use base Net::FCP::Txn;
726 671
727sub rcv_success { 672sub rcv_success {
728 my ($self, $attr) = @_; 673 my ($self, $attr) = @_;
729 $self->set_result ([$attr->{PublicKey}, $attr->{PrivateKey}]); 674 $self->set_result ([$attr->{public_key}, $attr->{private_key}, $attr->{crypto_key}]);
730} 675}
731 676
732package Net::FCP::Txn::InsertPrivateKey; 677package Net::FCP::Txn::InvertPrivateKey;
733 678
734use base Net::FCP::Txn; 679use base Net::FCP::Txn;
735 680
736sub rcv_success { 681sub rcv_success {
737 my ($self, $attr) = @_; 682 my ($self, $attr) = @_;
738 $self->set_result ($attr->{PublicKey}); 683 $self->set_result ($attr->{public_key});
739} 684}
740 685
741package Net::FCP::Txn::GetSize; 686package Net::FCP::Txn::GetSize;
742 687
743use base Net::FCP::Txn; 688use base Net::FCP::Txn;
744 689
745sub rcv_success { 690sub rcv_success {
746 my ($self, $attr) = @_; 691 my ($self, $attr) = @_;
747 $self->set_result ($attr->{Length}); 692 $self->set_result (hex $attr->{length});
748} 693}
749 694
750package Net::FCP::Txn::GetPut; 695package Net::FCP::Txn::GetPut;
751 696
752# base class for get and put 697# base class for get and put
753 698
754use base Net::FCP::Txn; 699use base Net::FCP::Txn;
755 700
756*rcv_uri_error = \&Net::FCP::Txn::rcv_throw_exception; 701*rcv_uri_error = \&Net::FCP::Txn::rcv_throw_exception;
757*rcv_route_not_found = \&Net::FCP::Txn::rcv_throw_exception; 702*rcv_route_not_found = \&Net::FCP::Txn::rcv_throw_exception;
758 703
759sub rcv_restarted { 704sub rcv_restarted {
760 my ($self, $attr, $type) = @_; 705 my ($self, $attr, $type) = @_;
761 706
762 delete $self->{datalength}; 707 delete $self->{datalength};
779 724
780 $self->progress ("data", { chunk => length $chunk, received => length $self->{data}, total => $self->{datalength} }); 725 $self->progress ("data", { chunk => length $chunk, received => length $self->{data}, total => $self->{datalength} });
781 726
782 if ($self->{datalength} == length $self->{data}) { 727 if ($self->{datalength} == length $self->{data}) {
783 my $data = delete $self->{data}; 728 my $data = delete $self->{data};
784 my $meta = Net::FCP::parse_metadata substr $data, 0, $self->{metalength}, ""; 729 my $meta = new Net::FCP::Metadata (substr $data, 0, $self->{metalength}, "");
785 730
786 $self->set_result ([$meta, $data]); 731 $self->set_result ([$meta, $data]);
732 $self->eof;
787 } 733 }
788} 734}
789 735
790sub rcv_data_found { 736sub rcv_data_found {
791 my ($self, $attr, $type) = @_; 737 my ($self, $attr, $type) = @_;
799package Net::FCP::Txn::ClientPut; 745package Net::FCP::Txn::ClientPut;
800 746
801use base Net::FCP::Txn::GetPut; 747use base Net::FCP::Txn::GetPut;
802 748
803*rcv_size_error = \&Net::FCP::Txn::rcv_throw_exception; 749*rcv_size_error = \&Net::FCP::Txn::rcv_throw_exception;
804*rcv_key_collision = \&Net::FCP::Txn::rcv_throw_exception;
805 750
806sub rcv_pending { 751sub rcv_pending {
807 my ($self, $attr, $type) = @_; 752 my ($self, $attr, $type) = @_;
808 $self->progress ($type, $attr); 753 $self->progress ($type, $attr);
809} 754}
811sub rcv_success { 756sub rcv_success {
812 my ($self, $attr, $type) = @_; 757 my ($self, $attr, $type) = @_;
813 $self->set_result ($attr); 758 $self->set_result ($attr);
814} 759}
815 760
761sub rcv_key_collision {
762 my ($self, $attr, $type) = @_;
763 $self->set_result ({ key_collision => 1, %$attr });
764}
765
816=back 766=back
817 767
818=head2 The Net::FCP::Exception CLASS 768=head2 The Net::FCP::Exception CLASS
819 769
820Any unexpected (non-standard) responses that make it impossible to return 770Any unexpected (non-standard) responses that make it impossible to return
829 779
830package Net::FCP::Exception; 780package Net::FCP::Exception;
831 781
832use overload 782use overload
833 '""' => sub { 783 '""' => sub {
834 "Net::FCP::Exception<<$_[0][0]," . (join ":", %{$_[0][1]}) . ">>\n"; 784 "Net::FCP::Exception<<$_[0][0]," . (join ":", %{$_[0][1]}) . ">>";
835 }; 785 };
836 786
837=item $exc = new Net::FCP::Exception $type, \%attr 787=item $exc = new Net::FCP::Exception $type, \%attr
838 788
839Create a new exception object of the given type (a string like 789Create a new exception object of the given type (a string like
894=cut 844=cut
895 845
896package Net::FCP::Event::Auto; 846package Net::FCP::Event::Auto;
897 847
898my @models = ( 848my @models = (
899 [Coro => Coro::Event:: ], 849 [Coro => Coro::Event::],
900 [Event => Event::], 850 [Event => Event::],
901 [Glib => Glib:: ], 851 [Glib => Glib::],
902 [Tk => Tk::], 852 [Tk => Tk::],
903); 853);
904 854
905sub AUTOLOAD { 855sub AUTOLOAD {
906 $AUTOLOAD =~ s/.*://; 856 $AUTOLOAD =~ s/.*://;

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines