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.1 by root, Sun Sep 7 22:57:40 2003 UTC vs.
Revision 1.29 by root, Thu May 13 21:43:16 2004 UTC

17of what the messages do. I am too lazy to document all this here. 17of what the messages do. I am too lazy to document all this here.
18 18
19=head1 WARNING 19=head1 WARNING
20 20
21This module is alpha. While it probably won't destroy (much :) of your 21This module is alpha. While it probably won't destroy (much :) of your
22data, it currently works only with the Event module (alkthough the event 22data, it currently falls short of what it should provide (intelligent uri
23mechanism is fully pluggable). 23following, splitfile downloads, healing...)
24
25=head2 IMPORT TAGS
26
27Nothing much can be "imported" from this module right now. There are,
28however, certain "import tags" that can be used to select the event model
29to be used.
30
31Event models are implemented as modules under the C<Net::FCP::Event::xyz>
32class, where C<xyz> is the event model to use. The default is C<Event> (or
33later C<Auto>).
34
35The import tag to use is named C<event=xyz>, e.g. C<event=Event>,
36C<event=Glib> etc.
37
38You should specify the event module to use only in the main program.
39
40If no event model has been specified, FCP tries to autodetect it on first
41use (e.g. first transaction), in this order: Coro, Event, Glib, Tk.
42
43=head2 FREENET BASICS
44
45Ok, this section will not explain any freenet basics to you, just some
46problems I found that you might want to avoid:
47
48=over 4
49
50=item freenet URIs are _NOT_ URIs
51
52Whenever a "uri" is required by the protocol, freenet expects a kind of
53URI prefixed with the "freenet:" scheme, e.g. "freenet:CHK...". However,
54these are not URIs, as freeent fails to parse them correctly, that is, you
55must unescape an escaped characters ("%2c" => ",") yourself. Maybe in the
56future this library will do it for you, so watch out for this incompatible
57change.
58
59=item Numbers are in HEX
60
61Virtually every number in the FCP protocol is in hex. Be sure to use
62C<hex()> on all such numbers, as the module (currently) does nothing to
63convert these for you.
64
65=back
24 66
25=head2 THE Net::FCP CLASS 67=head2 THE Net::FCP CLASS
26 68
27=over 4 69=over 4
28 70
29=cut 71=cut
30 72
31package Net::FCP; 73package Net::FCP;
32 74
33use Carp; 75use Carp;
34use IO::Socket::INET;
35 76
36$VERSION = 0.01; 77$VERSION = 0.6;
37 78
38sub event_reg_cb { 79no warnings;
39 my ($obj) = @_;
40 require Event;
41 80
42 $obj->{eventdata} = Event->io ( 81our $EVENT = Net::FCP::Event::Auto::;
43 fd => $obj->{fh}, 82
44 poll => 'r', 83sub import {
45 cb => sub { 84 shift;
46 $obj->fh_ready; 85
86 for (@_) {
87 if (/^event=(\w+)$/) {
88 $EVENT = "Net::FCP::Event::$1";
89 eval "require $EVENT";
47 }, 90 }
91 }
92 die $@ if $@;
93}
94
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 1 while s/(SVK|CHK|URI)([^_])/$1\_$2/i;
105 1 while s/([^_])(SVK|CHK|URI)/$1\_$2/i;
106 s/(?<=[a-z])(?=[A-Z])/_/g;
107 lc $_;
108}
109
110# the opposite of hex
111sub xeh($) {
112 sprintf "%x", $_[0];
113}
114
115=item $meta = Net::FCP::parse_metadata $string
116
117Parse a metadata string and return it.
118
119The metadata will be a hashref with key C<version> (containing the
120mandatory version header entries) and key C<raw> containing the original
121metadata string.
122
123All other headers are represented by arrayrefs (they can be repeated).
124
125Since this description is confusing, here is a rather verbose example of a
126parsed manifest:
127
128 (
129 raw => "Version...",
130 version => { revision => 1 },
131 document => [
132 {
133 info => { format" => "image/jpeg" },
134 name => "background.jpg",
135 redirect => { target => "freenet:CHK\@ZcagI,ra726bSw" },
136 },
137 {
138 info => { format" => "text/html" },
139 name => ".next",
140 redirect => { target => "freenet:SSK\@ilUPAgM/TFEE/3" },
141 },
142 {
143 info => { format" => "text/html" },
144 redirect => { target => "freenet:CHK\@8M8Po8ucwI,8xA" },
145 }
146 ]
48 ); 147 )
49}
50 148
51sub event_unreg_cb { 149=cut
52 $_[0]{eventdata}
53 and (delete $_[0]{eventdata})->cancel;
54}
55 150
56sub event_wait_cb { 151sub parse_metadata {
57 Event::one_event(); 152 my $data = shift;
58} 153 my $meta = { raw => $data };
59 154
60$regcb = \&event_reg_cb; 155 if ($data =~ /^Version\015?\012/gc) {
61$unregcb = \&event_unreg_cb; 156 my $hdr = $meta->{version} = {};
62$waitcb = \&event_wait_cb;
63 157
158 for (;;) {
159 while ($data =~ /\G([^=\015\012]+)=([^\015\012]*)\015?\012/gc) {
160 my ($k, $v) = ($1, $2);
161 my @p = split /\./, tolc $k, 3;
162
163 $hdr->{$p[0]} = $v if @p == 1; # lamest code I ever wrote
164 $hdr->{$p[0]}{$p[1]} = $v if @p == 2;
165 $hdr->{$p[0]}{$p[1]}{$p[2]} = $v if @p == 3;
166 die "FATAL: 4+ dot metadata" if @p >= 4;
167 }
168
169 if ($data =~ /\GEndPart\015?\012/gc) {
170 # nop
171 } elsif ($data =~ /\GEnd(\015?\012|$)/gc) {
172 last;
173 } elsif ($data =~ /\G([A-Za-z0-9.\-]+)\015?\012/gcs) {
174 push @{$meta->{tolc $1}}, $hdr = {};
175 } elsif ($data =~ /\G(.*)/gcs) {
176 print STDERR "metadata format error ($1), please report this string: <<$data>>";
177 die "metadata format error";
178 }
179 }
180 }
181
182 #$meta->{tail} = substr $data, pos $data;
183
184 $meta;
185}
186
187=item $string = Net::FCP::build_metadata $meta
188
189Takes a hash reference as returned by C<Net::FCP::parse_metadata> and
190returns the corresponding string form. If a string is given, it's returned
191as is.
192
193=cut
194
195sub build_metadata_subhash($$$) {
196 my ($prefix, $level, $hash) = @_;
197
198 join "",
199 map
200 ref $hash->{$_} ? build_metadata_subhash ($prefix . (Net::FCP::touc $_) . ".", $level + 1, $hash->{$_})
201 : $prefix . ($level > 1 ? $_ : Net::FCP::touc $_) . "=" . $hash->{$_} . "\n",
202 keys %$hash;
203}
204
205sub build_metadata_hash($$) {
206 my ($header, $hash) = @_;
207
208 if (ref $hash eq ARRAY::) {
209 join "", map build_metadata_hash ($header, $_), @$hash
210 } else {
211 (Net::FCP::touc $header) . "\n"
212 . (build_metadata_subhash "", 0, $hash)
213 . "EndPart\n";
214 }
215}
216
217sub build_metadata($) {
218 my ($meta) = @_;
219
220 return $meta unless ref $meta;
221
222 $meta = { %$meta };
223
224 delete $meta->{raw};
225
226 my $res =
227 (build_metadata_hash version => delete $meta->{version})
228 . (join "", map +(build_metadata_hash $_, $meta->{$_}), keys %$meta);
229
230 substr $res, 0, -5; # get rid of "Part". Broken Syntax....
231}
232
233
64=item $fcp = new Net::FCP [host => $host][, port => $port] 234=item $fcp = new Net::FCP [host => $host][, port => $port][, progress => \&cb]
65 235
66Create a new virtual FCP connection to the given host and port (default 236Create a new virtual FCP connection to the given host and port (default
67127.0.0.1:8481). 237127.0.0.1:8481, or the environment variables C<FREDHOST> and C<FREDPORT>).
68 238
69Connections are virtual because no persistent physical connection is 239Connections are virtual because no persistent physical connection is
240established.
241
242You can install a progress callback that is being called with the Net::FCP
243object, a txn object, the type of the transaction and the attributes. Use
244it like this:
245
246 sub progress_cb {
247 my ($self, $txn, $type, $attr) = @_;
248
249 warn "progress<$txn,$type," . (join ":", %$attr) . ">\n";
250 }
251
252=begin comment
253
70established. However, the existance of the node is checked by executing a 254However, the existance of the node is checked by executing a
71C<ClientHello> transaction. 255C<ClientHello> transaction.
256
257=end
72 258
73=cut 259=cut
74 260
75sub new { 261sub new {
76 my $class = shift; 262 my $class = shift;
77 my $self = bless { @_ }, $class; 263 my $self = bless { @_ }, $class;
78 264
79 $self->{host} ||= "127.0.0.1"; 265 $self->{host} ||= $ENV{FREDHOST} || "127.0.0.1";
80 $self->{port} ||= 8481; 266 $self->{port} ||= $ENV{FREDPORT} || 8481;
81 267
82 $self->{nodehello} = $self->txn("ClientHello")->result 268 #$self->{nodehello} = $self->client_hello
83 or croak "unable to get nodehello from node\n"; 269 # or croak "unable to get nodehello from node\n";
84 270
85 $self; 271 $self;
86} 272}
87 273
274sub progress {
275 my ($self, $txn, $type, $attr) = @_;
276
277 $self->{progress}->($self, $txn, $type, $attr)
278 if $self->{progress};
279}
280
88=item $txn = $fcp->txn(type => attr => val,...) 281=item $txn = $fcp->txn(type => attr => val,...)
89 282
90The low-level interface to transactions. Don't use it. 283The low-level interface to transactions. Don't use it.
284
285Here are some examples of using transactions:
286
287The blocking case, no (visible) transactions involved:
288
289 my $nodehello = $fcp->client_hello;
290
291A transaction used in a blocking fashion:
292
293 my $txn = $fcp->txn_client_hello;
294 ...
295 my $nodehello = $txn->result;
296
297Or shorter:
298
299 my $nodehello = $fcp->txn_client_hello->result;
300
301Setting callbacks:
302
303 $fcp->txn_client_hello->cb(
304 sub { my $nodehello => $_[0]->result }
305 );
91 306
92=cut 307=cut
93 308
94sub txn { 309sub txn {
95 my ($self, $type, %attr) = @_; 310 my ($self, $type, %attr) = @_;
96 311
312 $type = touc $type;
313
97 my $txn = "Net::FCP::Txn::$type"->new(fcp => $self, type => $type, attr => \%attr); 314 my $txn = "Net::FCP::Txn::$type"->new (fcp => $self, type => tolc $type, attr => \%attr);
98 315
99 $txn; 316 $txn;
100} 317}
101 318
102sub _txn($&) { 319{ # transactions
320
321my $txn = sub {
103 my ($name, $sub) = @_; 322 my ($name, $sub) = @_;
104 *{"$name\_txn"} = $sub; 323 *{"txn_$name"} = $sub;
105 *{$name} = sub { $sub->(@_)->result }; 324 *{$name} = sub { $sub->(@_)->result };
106} 325};
107 326
108=item $txn = $fcp->txn_client_hello 327=item $txn = $fcp->txn_client_hello
109 328
110=item $nodehello = $fcp->client_hello 329=item $nodehello = $fcp->client_hello
111 330
112Executes a ClientHello request and returns it's results. 331Executes a ClientHello request and returns it's results.
113 332
114 { 333 {
115 MaxFileSize => "5f5e100", 334 max_file_size => "5f5e100",
116 Protocol => "1.2",
117 Node => "Fred,0.6,1.46,7050" 335 node => "Fred,0.6,1.46,7050"
336 protocol => "1.2",
118 } 337 }
119 338
120=cut 339=cut
121 340
122_txn client_hello => sub { 341$txn->(client_hello => sub {
123 my ($self) = @_; 342 my ($self) = @_;
124 343
125 $self->txn ("ClientHello"); 344 $self->txn ("client_hello");
126}; 345});
127 346
128=item $txn = $fcp->txn_client_info 347=item $txn = $fcp->txn_client_info
129 348
130=item $nodeinfo = $fcp->client_info 349=item $nodeinfo = $fcp->client_info
131 350
132Executes a ClientInfo request and returns it's results. 351Executes a ClientInfo request and returns it's results.
133 352
134 { 353 {
135 MaxFileSize => "5f5e100",
136 DatastoreMax => "2540be400",
137 NodePort => 369,
138 JavaName => "Java HotSpot(TM) Server VM",
139 OperatingSystemVersion => "2.4.20",
140 EstimatedLoad => 52,
141 FreeMemory => "5cc0148",
142 DatastoreFree => "5ce03400",
143 NodeAddress => "1.2.3.4",
144 ActiveJobs => "1f", 354 active_jobs => "1f",
145 AllocatedMemory => "bde0000", 355 allocated_memory => "bde0000",
146 Architecture => "i386", 356 architecture => "i386",
147 RoutingTime => "a5",
148 LeastRecentTimestamp => "f41538b878",
149 AvailableThreads => 17, 357 available_threads => 17,
358 datastore_free => "5ce03400",
359 datastore_max => "2540be400",
150 DatastoreUsed => "1f72bb000", 360 datastore_used => "1f72bb000",
151 JavaVersion => "Blackdown-1.4.1-01", 361 estimated_load => 52,
362 free_memory => "5cc0148",
152 IsTransient => "false", 363 is_transient => "false",
153 OperatingSystem => "Linux", 364 java_name => "Java HotSpot(_T_M) Server VM",
154 JavaVendor => "http://www.blackdown.org/", 365 java_vendor => "http://www.blackdown.org/",
366 java_version => "Blackdown-1.4.1-01",
367 least_recent_timestamp => "f41538b878",
368 max_file_size => "5f5e100",
155 MostRecentTimestamp => "f77e2cc520" 369 most_recent_timestamp => "f77e2cc520"
370 node_address => "1.2.3.4",
371 node_port => 369,
372 operating_system => "Linux",
373 operating_system_version => "2.4.20",
374 routing_time => "a5",
156 } 375 }
157 376
158=cut 377=cut
159 378
160_txn client_info => sub { 379$txn->(client_info => sub {
161 my ($self) = @_; 380 my ($self) = @_;
162 381
163 $self->txn ("ClientInfo"); 382 $self->txn ("client_info");
164}; 383});
165 384
166=item $txn = $fcp->txn_generate_chk ($metadata, $data) 385=item $txn = $fcp->txn_generate_chk ($metadata, $data[, $cipher])
167 386
168=item $uri = $fcp->generate_chk ($metadata, $data) 387=item $uri = $fcp->generate_chk ($metadata, $data[, $cipher])
169 388
170Creates a new CHK, given the metadata and data. UNTESTED. 389Calculates a CHK, given the metadata and data. C<$cipher> is either
390C<Rijndael> or C<Twofish>, with the latter being the default.
171 391
172=cut 392=cut
173 393
174_txn generate_chk => sub { 394$txn->(generate_chk => sub {
175 my ($self, $metadata, $data) = @_; 395 my ($self, $metadata, $data, $cipher) = @_;
176 396
177 $self->txn (GenerateCHK => data => "$data$metadata", MetaDataLength => length $metadata); 397 $self->txn (generate_chk =>
398 data => "$metadata$data",
399 metadata_length => xeh length $metadata,
400 cipher => $cipher || "Twofish");
178}; 401});
179 402
180=item $txn = $fcp->txn_generate_svk_pair 403=item $txn = $fcp->txn_generate_svk_pair
181 404
182=item ($public, $private) = @{ $fcp->generate_svk_pair } 405=item ($public, $private) = @{ $fcp->generate_svk_pair }
183 406
184Creates a new SVK pair. Returns an arrayref. 407Creates a new SVK pair. Returns an arrayref with the public key, the
408private key and a crypto key, which is just additional entropy.
185 409
186 [ 410 [
187 "hKs0-WDQA4pVZyMPKNFsK1zapWY", 411 "acLx4dux9fvvABH15Gk6~d3I-yw",
188 "ZnmvMITaTXBMFGl4~jrjuyWxOWg" 412 "cPoDkDMXDGSMM32plaPZDhJDxSs",
413 "BH7LXCov0w51-y9i~BoB3g",
189 ] 414 ]
190 415
191=cut 416A private key (for inserting) can be constructed like this:
192 417
418 SSK@<private_key>,<crypto_key>/<name>
419
420It can be used to insert data. The corresponding public key looks like this:
421
422 SSK@<public_key>PAgM,<crypto_key>/<name>
423
424Watch out for the C<PAgM>-part!
425
426=cut
427
193_txn generate_svk_pair => sub { 428$txn->(generate_svk_pair => sub {
194 my ($self) = @_; 429 my ($self) = @_;
195 430
196 $self->txn ("GenerateSVKPair"); 431 $self->txn ("generate_svk_pair");
197}; 432});
198 433
199=item $txn = $fcp->txn_insert_private_key ($private) 434=item $txn = $fcp->txn_invert_private_key ($private)
200 435
201=item $uri = $fcp->insert_private_key ($private) 436=item $public = $fcp->invert_private_key ($private)
202 437
203Inserts a private key. $private can be either an insert URI (must start 438Inverts a private key (returns the public key). C<$private> can be either
204with freenet:SSK@) or a raw private key (i.e. the private value you get back 439an insert URI (must start with C<freenet:SSK@>) or a raw private key (i.e.
205from C<generate_svk_pair>). 440the private value you get back from C<generate_svk_pair>).
206 441
207Returns the public key. 442Returns the public key.
208 443
209UNTESTED.
210
211=cut 444=cut
212 445
213_txn insert_private_key => sub { 446$txn->(invert_private_key => sub {
214 my ($self, $privkey) = @_; 447 my ($self, $privkey) = @_;
215 448
216 $self->txn (InvertPrivateKey => Private => $privkey); 449 $self->txn (invert_private_key => private => $privkey);
217}; 450});
218 451
219=item $txn = $fcp->txn_get_size ($uri) 452=item $txn = $fcp->txn_get_size ($uri)
220 453
221=item $length = $fcp->get_size ($uri) 454=item $length = $fcp->get_size ($uri)
222 455
223Finds and returns the size (rounded up to the nearest power of two) of the 456Finds and returns the size (rounded up to the nearest power of two) of the
224given document. 457given document.
225 458
226UNTESTED.
227
228=cut 459=cut
229 460
230_txn get_size => sub { 461$txn->(get_size => sub {
231 my ($self, $uri) = @_; 462 my ($self, $uri) = @_;
232 463
233 $self->txn (GetSize => URI => $uri); 464 $self->txn (get_size => URI => $uri);
234}; 465});
235 466
236=item MISSING: ClientGet, ClientPut 467=item $txn = $fcp->txn_client_get ($uri [, $htl = 15 [, $removelocal = 0]])
468
469=item ($metadata, $data) = @{ $fcp->client_get ($uri, $htl, $removelocal)
470
471Fetches a (small, as it should fit into memory) file from
472freenet. C<$meta> is the metadata (as returned by C<parse_metadata> or
473C<undef>).
474
475The C<$uri> should begin with C<freenet:>, but the scheme is currently
476added, if missing.
477
478Due to the overhead, a better method to download big files should be used.
479
480 my ($meta, $data) = @{
481 $fcp->client_get (
482 "freenet:CHK@hdXaxkwZ9rA8-SidT0AN-bniQlgPAwI,XdCDmBuGsd-ulqbLnZ8v~w"
483 )
484 };
485
486=cut
487
488$txn->(client_get => sub {
489 my ($self, $uri, $htl, $removelocal) = @_;
490
491 $uri =~ s/^freenet://;
492 $uri = "freenet:$uri";
493
494 $self->txn (client_get => URI => $uri, hops_to_live => xeh (defined $htl ? $htl : 15),
495 remove_local_key => $removelocal ? "true" : "false");
496});
497
498=item $txn = $fcp->txn_client_put ($uri, $metadata, $data, $htl, $removelocal)
499
500=item my $uri = $fcp->client_put ($uri, $metadata, $data, $htl, $removelocal);
501
502Insert a new key. If the client is inserting a CHK, the URI may be
503abbreviated as just CHK@. In this case, the node will calculate the
504CHK. If the key is a private SSK key, the node will calculcate the public
505key and the resulting public URI.
506
507C<$meta> can be a hash reference (same format as returned by
508C<Net::FCP::parse_metadata>) or a string.
509
510The result is an arrayref with the keys C<uri>, C<public_key> and C<private_key>.
511
512=cut
513
514$txn->(client_put => sub {
515 my ($self, $uri, $meta, $data, $htl, $removelocal) = @_;
516
517 $meta = build_metadata $meta;
518
519 $self->txn (client_put => URI => $uri,
520 hops_to_live => xeh (defined $htl ? $htl : 15),
521 remove_local_key => $removelocal ? "true" : "false",
522 data => "$meta$data", metadata_length => xeh length $meta);
523});
524
525} # transactions
237 526
238=back 527=back
239 528
240=head2 THE Net::FCP::Txn CLASS 529=head2 THE Net::FCP::Txn CLASS
241 530
242All requests (or transactions) are executed in a asynchroneous way (LIE: 531All requests (or transactions) are executed in a asynchronous way. For
243uploads are blocking). For each request, a C<Net::FCP::Txn> object is 532each request, a C<Net::FCP::Txn> object is created (worse: a tcp
244created (worse: a tcp connection is created, too). 533connection is created, too).
245 534
246For each request there is actually a different subclass (and it's possible 535For each request there is actually a different subclass (and it's possible
247to subclass these, although of course not documented). 536to subclass these, although of course not documented).
248 537
249The most interesting method is C<result>. 538The most interesting method is C<result>.
251=over 4 540=over 4
252 541
253=cut 542=cut
254 543
255package Net::FCP::Txn; 544package Net::FCP::Txn;
545
546use Fcntl;
547use Socket;
256 548
257=item new arg => val,... 549=item new arg => val,...
258 550
259Creates a new C<Net::FCP::Txn> object. Not normally used. 551Creates a new C<Net::FCP::Txn> object. Not normally used.
260 552
262 554
263sub new { 555sub new {
264 my $class = shift; 556 my $class = shift;
265 my $self = bless { @_ }, $class; 557 my $self = bless { @_ }, $class;
266 558
559 $self->{signal} = $EVENT->new_signal;
560
561 $self->{fcp}{txn}{$self} = $self;
562
267 my $attr = ""; 563 my $attr = "";
268 my $data = delete $self->{attr}{data}; 564 my $data = delete $self->{attr}{data};
269 565
270 while (my ($k, $v) = each %{$self->{attr}}) { 566 while (my ($k, $v) = each %{$self->{attr}}) {
271 $attr .= "$k=$v\012" 567 $attr .= (Net::FCP::touc $k) . "=$v\012"
272 } 568 }
273 569
274 if (defined $data) { 570 if (defined $data) {
275 $attr .= "DataLength=" . (length $data) . "\012"; 571 $attr .= sprintf "DataLength=%x\012", length $data;
276 $data = "Data\012$data"; 572 $data = "Data\012$data";
277 } else { 573 } else {
278 $data = "EndMessage\012"; 574 $data = "EndMessage\012";
279 } 575 }
280 576
281 my $fh = new IO::Socket::INET 577 socket my $fh, PF_INET, SOCK_STREAM, 0
282 PeerHost => $self->{fcp}{host}, 578 or Carp::croak "unable to create new tcp socket: $!";
283 PeerPort => $self->{fcp}{port}
284 or Carp::croak "FCP::txn: unable to connect to $self->{fcp}{host}:$self->{fcp}{port}: $!\n";
285
286 binmode $fh, ":raw"; 579 binmode $fh, ":raw";
580 fcntl $fh, F_SETFL, O_NONBLOCK;
581 connect $fh, (sockaddr_in $self->{fcp}{port}, inet_aton $self->{fcp}{host})
582 and !$!{EWOULDBLOCK}
583 and !$!{EINPROGRESS}
584 and Carp::croak "FCP::txn: unable to connect to $self->{fcp}{host}:$self->{fcp}{port}: $!\n";
287 585
288 print 586 $self->{sbuf} =
289 $self->{type}, "\012", 587 "\x00\x00\x00\x02"
290 $attr, 588 . (Net::FCP::touc $self->{type})
291 $data, "\012"; 589 . "\012$attr$data";
292 590
293 print $fh
294 "\x00\x00", "\x00\x02", # SESSID, PRESID
295 $self->{type}, "\012",
296 $attr,
297 $data;
298
299 #$fh->shutdown (1); # freenet buggy?, well, it's java... 591 #shutdown $fh, 1; # freenet buggy?, well, it's java...
300 592
301 $self->{fh} = $fh; 593 $self->{fh} = $fh;
302 594
303 $Net::FCP::regcb->($self); 595 $self->{w} = $EVENT->new_from_fh ($fh)->cb(sub { $self->fh_ready_w })->poll(0, 1, 1);
304 596
305 $self; 597 $self;
306} 598}
307 599
600=item $txn = $txn->cb ($coderef)
601
602Sets a callback to be called when the request is finished. The coderef
603will be called with the txn as it's sole argument, so it has to call
604C<result> itself.
605
606Returns the txn object, useful for chaining.
607
608Example:
609
610 $fcp->txn_client_get ("freenet:CHK....")
611 ->userdata ("ehrm")
612 ->cb(sub {
613 my $data = shift->result;
614 });
615
616=cut
617
618sub cb($$) {
619 my ($self, $cb) = @_;
620 $self->{cb} = $cb;
621 $self;
622}
623
624=item $txn = $txn->userdata ([$userdata])
625
626Set user-specific data. This is useful in progress callbacks. The data can be accessed
627using C<< $txn->{userdata} >>.
628
629Returns the txn object, useful for chaining.
630
631=cut
632
633sub userdata($$) {
634 my ($self, $data) = @_;
635 $self->{userdata} = $data;
636 $self;
637}
638
639=item $txn->cancel (%attr)
640
641Cancels the operation with a C<cancel> exception anf the given attributes
642(consider at least giving the attribute C<reason>).
643
644UNTESTED.
645
646=cut
647
648sub cancel {
649 my ($self, %attr) = @_;
650 $self->throw (Net::FCP::Exception->new (cancel => { %attr }));
651 $self->set_result;
652 $self->eof;
653}
654
308sub fh_ready { 655sub fh_ready_w {
656 my ($self) = @_;
657
658 my $len = syswrite $self->{fh}, $self->{sbuf};
659
660 if ($len > 0) {
661 substr $self->{sbuf}, 0, $len, "";
662 unless (length $self->{sbuf}) {
663 fcntl $self->{fh}, F_SETFL, 0;
664 $self->{w}->cb(sub { $self->fh_ready_r })->poll (1, 0, 1);
665 }
666 } elsif (defined $len) {
667 $self->throw (Net::FCP::Exception->new (network_error => { reason => "unexpected end of file while writing" }));
668 } else {
669 $self->throw (Net::FCP::Exception->new (network_error => { reason => "$!" }));
670 }
671}
672
673sub fh_ready_r {
309 my ($self) = @_; 674 my ($self) = @_;
310 675
311 if (sysread $self->{fh}, $self->{buf}, 65536, length $self->{buf}) { 676 if (sysread $self->{fh}, $self->{buf}, 65536, length $self->{buf}) {
312 for (;;) { 677 for (;;) {
313 if ($self->{datalen}) { 678 if ($self->{datalen}) {
679 #warn "expecting new datachunk $self->{datalen}, got ".(length $self->{buf})."\n";#d#
314 if (length $self->{buf} >= $self->{datalen}) { 680 if (length $self->{buf} >= $self->{datalen}) {
315 $self->recv_data (substr $self->{buf}, 0, $self->{datalen}, ""); 681 $self->rcv_data (substr $self->{buf}, 0, delete $self->{datalen}, "");
316 } else { 682 } else {
317 last; 683 last;
318 } 684 }
319 } elsif ($self->{buf} =~ s/^DataChunk\015?\012Length=(\d+)\015?\012Data\015?\012//) { 685 } elsif ($self->{buf} =~ s/^DataChunk\015?\012Length=([0-9a-fA-F]+)\015?\012Data\015?\012//) {
320 $self->{datalen} = $1; 686 $self->{datalen} = hex $1;
687 #warn "expecting new datachunk $self->{datalen}\n";#d#
321 } elsif ($self->{buf} =~ s/^([a-zA-Z]+)\015?\012(.*?)\015?\012EndMessage\015?\012//s) { 688 } elsif ($self->{buf} =~ s/^([a-zA-Z]+)\015?\012(?:(.+?)\015?\012)?EndMessage\015?\012//s) {
322 $self->rcv ($1, {map { split /=/, $_, 2 } split /\015?\012/, $2}); 689 $self->rcv ($1, {
690 map { my ($a, $b) = split /=/, $_, 2; ((Net::FCP::tolc $a), $b) }
691 split /\015?\012/, $2
692 });
323 } else { 693 } else {
324 last; 694 last;
325 } 695 }
326 } 696 }
327 } else { 697 } else {
328 $Net::FCP::unregcb->($self);
329 delete $self->{fh};
330 $self->eof; 698 $self->eof;
331 } 699 }
332}
333
334sub rcv_data {
335 my ($self, $chunk) = @_;
336} 700}
337 701
338sub rcv { 702sub rcv {
339 my ($self, $type, $attr) = @_; 703 my ($self, $type, $attr) = @_;
704
705 $type = Net::FCP::tolc $type;
706
340 #use PApp::Util;warn "$type => ".PApp::Util::dumpval($attr); 707 #use PApp::Util; warn PApp::Util::dumpval [$type, $attr];
341 708
342 if (my $method = $self->can("rcv_\L$type")) { 709 if (my $method = $self->can("rcv_$type")) {
343 $method->($self, $attr, $type); 710 $method->($self, $attr, $type);
344 } else { 711 } else {
345 warn "received unexpected reply type '$type' for '$self->{type}', ignoring\n"; 712 warn "received unexpected reply type '$type' for '$self->{type}', ignoring\n";
346 $self->eof; 713 }
714}
715
716# used as a default exception thrower
717sub rcv_throw_exception {
718 my ($self, $attr, $type) = @_;
719 $self->throw (Net::FCP::Exception->new ($type, $attr));
720}
721
722*rcv_failed = \&Net::FCP::Txn::rcv_throw_exception;
723*rcv_format_error = \&Net::FCP::Txn::rcv_throw_exception;
724
725sub throw {
726 my ($self, $exc) = @_;
727
728 $self->{exception} = $exc;
729 $self->set_result;
730 $self->eof; # must be last to avoid loops
731}
732
733sub set_result {
734 my ($self, $result) = @_;
735
736 unless (exists $self->{result}) {
737 $self->{result} = $result;
738 $self->{cb}->($self) if exists $self->{cb};
739 $self->{signal}->send;
347 } 740 }
348} 741}
349 742
350sub eof { 743sub eof {
351 my ($self, $result) = @_; 744 my ($self) = @_;
352 745
353 $self->{result} = $result unless exists $self->{result}; 746 delete $self->{w};
747 delete $self->{fh};
748
749 delete $self->{fcp}{txn}{$self};
750
751 unless (exists $self->{result}) {
752 $self->throw (Net::FCP::Exception->new (short_data => {
753 reason => "unexpected eof or internal node error",
754 }));
755 }
756}
757
758sub progress {
759 my ($self, $type, $attr) = @_;
760
761 $self->{fcp}->progress ($self, $type, $attr);
354} 762}
355 763
356=item $result = $txn->result 764=item $result = $txn->result
357 765
358Waits until a result is available and then returns it. 766Waits until a result is available and then returns it.
359 767
360This waiting is (depending on your event modul) not very efficient, as it 768This waiting is (depending on your event model) not very efficient, as it
361is done outside the "mainloop". 769is done outside the "mainloop". The biggest problem, however, is that it's
770blocking one thread of execution. Try to use the callback mechanism, if
771possible, and call result from within the callback (or after is has been
772run), as then no waiting is necessary.
362 773
363=cut 774=cut
364 775
365sub result { 776sub result {
366 my ($self) = @_; 777 my ($self) = @_;
367 778
368 $Net::FCP::waitcb->() while !exists $self->{result}; 779 $self->{signal}->wait while !exists $self->{result};
780
781 die $self->{exception} if $self->{exception};
369 782
370 return $self->{result}; 783 return $self->{result};
371} 784}
372 785
373sub DESTROY {
374 $Net::FCP::unregcb->($_[0]);
375}
376
377package Net::FCP::Txn::ClientHello; 786package Net::FCP::Txn::ClientHello;
378 787
379use base Net::FCP::Txn; 788use base Net::FCP::Txn;
380 789
381sub rcv_nodehello { 790sub rcv_node_hello {
382 my ($self, $attr) = @_; 791 my ($self, $attr) = @_;
383 792
384 $self->eof ($attr); 793 $self->set_result ($attr);
385} 794}
386 795
387package Net::FCP::Txn::ClientInfo; 796package Net::FCP::Txn::ClientInfo;
388 797
389use base Net::FCP::Txn; 798use base Net::FCP::Txn;
390 799
391sub rcv_nodeinfo { 800sub rcv_node_info {
392 my ($self, $attr) = @_; 801 my ($self, $attr) = @_;
393 802
394 $self->eof ($attr); 803 $self->set_result ($attr);
395} 804}
396 805
397package Net::FCP::Txn::GenerateCHK; 806package Net::FCP::Txn::GenerateCHK;
398 807
399use base Net::FCP::Txn; 808use base Net::FCP::Txn;
400 809
401sub rcv_success { 810sub rcv_success {
402 my ($self, $attr) = @_; 811 my ($self, $attr) = @_;
403 812
404 $self->eof ($attr); 813 $self->set_result ($attr->{uri});
405} 814}
406 815
407package Net::FCP::Txn::GenerateSVKPair; 816package Net::FCP::Txn::GenerateSVKPair;
408 817
409use base Net::FCP::Txn; 818use base Net::FCP::Txn;
410 819
411sub rcv_success { 820sub rcv_success {
412 my ($self, $attr) = @_; 821 my ($self, $attr) = @_;
413 822 $self->set_result ([$attr->{public_key}, $attr->{private_key}, $attr->{crypto_key}]);
414 $self->eof ([$attr->{PublicKey}, $attr->{PrivateKey}]);
415} 823}
416 824
417package Net::FCP::Txn::InvertPrivateKey; 825package Net::FCP::Txn::InvertPrivateKey;
418 826
419use base Net::FCP::Txn; 827use base Net::FCP::Txn;
420 828
421sub rcv_success { 829sub rcv_success {
422 my ($self, $attr) = @_; 830 my ($self, $attr) = @_;
423
424 $self->eof ($attr->{PublicKey}); 831 $self->set_result ($attr->{public_key});
425} 832}
426 833
427package Net::FCP::Txn::GetSize; 834package Net::FCP::Txn::GetSize;
428 835
429use base Net::FCP::Txn; 836use base Net::FCP::Txn;
430 837
431sub rcv_success { 838sub rcv_success {
432 my ($self, $attr) = @_; 839 my ($self, $attr) = @_;
433
434 $self->eof ($attr->{Length}); 840 $self->set_result (hex $attr->{length});
841}
842
843package Net::FCP::Txn::GetPut;
844
845# base class for get and put
846
847use base Net::FCP::Txn;
848
849*rcv_uri_error = \&Net::FCP::Txn::rcv_throw_exception;
850*rcv_route_not_found = \&Net::FCP::Txn::rcv_throw_exception;
851
852sub rcv_restarted {
853 my ($self, $attr, $type) = @_;
854
855 delete $self->{datalength};
856 delete $self->{metalength};
857 delete $self->{data};
858
859 $self->progress ($type, $attr);
860}
861
862package Net::FCP::Txn::ClientGet;
863
864use base Net::FCP::Txn::GetPut;
865
866*rcv_data_not_found = \&Net::FCP::Txn::rcv_throw_exception;
867
868sub rcv_data {
869 my ($self, $chunk) = @_;
870
871 $self->{data} .= $chunk;
872
873 $self->progress ("data", { chunk => length $chunk, received => length $self->{data}, total => $self->{datalength} });
874
875 if ($self->{datalength} == length $self->{data}) {
876 my $data = delete $self->{data};
877 my $meta = Net::FCP::parse_metadata substr $data, 0, $self->{metalength}, "";
878
879 $self->set_result ([$meta, $data]);
880 $self->eof;
881 }
882}
883
884sub rcv_data_found {
885 my ($self, $attr, $type) = @_;
886
887 $self->progress ($type, $attr);
888
889 $self->{datalength} = hex $attr->{data_length};
890 $self->{metalength} = hex $attr->{metadata_length};
891}
892
893package Net::FCP::Txn::ClientPut;
894
895use base Net::FCP::Txn::GetPut;
896
897*rcv_size_error = \&Net::FCP::Txn::rcv_throw_exception;
898*rcv_key_collision = \&Net::FCP::Txn::rcv_throw_exception;
899
900sub rcv_pending {
901 my ($self, $attr, $type) = @_;
902 $self->progress ($type, $attr);
903}
904
905sub rcv_success {
906 my ($self, $attr, $type) = @_;
907 $self->set_result ($attr);
908}
909
910=back
911
912=head2 The Net::FCP::Exception CLASS
913
914Any unexpected (non-standard) responses that make it impossible to return
915the advertised result will result in an exception being thrown when the
916C<result> method is called.
917
918These exceptions are represented by objects of this class.
919
920=over 4
921
922=cut
923
924package Net::FCP::Exception;
925
926use overload
927 '""' => sub {
928 "Net::FCP::Exception<<$_[0][0]," . (join ":", %{$_[0][1]}) . ">>";
929 };
930
931=item $exc = new Net::FCP::Exception $type, \%attr
932
933Create a new exception object of the given type (a string like
934C<route_not_found>), and a hashref containing additional attributes
935(usually the attributes of the message causing the exception).
936
937=cut
938
939sub new {
940 my ($class, $type, $attr) = @_;
941
942 bless [Net::FCP::tolc $type, { %$attr }], $class;
943}
944
945=item $exc->type([$type])
946
947With no arguments, returns the exception type. Otherwise a boolean
948indicating wether the exception is of the given type is returned.
949
950=cut
951
952sub type {
953 my ($self, $type) = @_;
954
955 @_ >= 2
956 ? $self->[0] eq $type
957 : $self->[0];
958}
959
960=item $exc->attr([$attr])
961
962With no arguments, returns the attributes. Otherwise the named attribute
963value is returned.
964
965=cut
966
967sub attr {
968 my ($self, $attr) = @_;
969
970 @_ >= 2
971 ? $self->[1]{$attr}
972 : $self->[1];
435} 973}
436 974
437=back 975=back
438 976
439=head1 SEE ALSO 977=head1 SEE ALSO
447 Marc Lehmann <pcg@goof.com> 985 Marc Lehmann <pcg@goof.com>
448 http://www.goof.com/pcg/marc/ 986 http://www.goof.com/pcg/marc/
449 987
450=cut 988=cut
451 989
990package Net::FCP::Event::Auto;
991
992my @models = (
993 [Coro => Coro::Event::],
994 [Event => Event::],
995 [Glib => Glib::],
996 [Tk => Tk::],
997);
998
999sub AUTOLOAD {
1000 $AUTOLOAD =~ s/.*://;
1001
1002 for (@models) {
1003 my ($model, $package) = @$_;
1004 if (defined ${"$package\::VERSION"}) {
1005 $EVENT = "Net::FCP::Event::$model";
1006 eval "require $EVENT"; die if $@;
1007 goto &{"$EVENT\::$AUTOLOAD"};
1008 }
1009 }
1010
1011 for (@models) {
1012 my ($model, $package) = @$_;
1013 $EVENT = "Net::FCP::Event::$model";
1014 if (eval "require $EVENT") {
1015 goto &{"$EVENT\::$AUTOLOAD"};
1016 }
1017 }
1018
1019 die "No event module selected for Net::FCP and autodetect failed. Install any of these: Coro, Event, Glib or Tk.";
1020}
1021
4521; 10221;
453 1023

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines