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

Comparing Net-FCP/FCP.pm (file contents):
Revision 1.8 by root, Mon Sep 8 01:47:31 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.04; 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 }
48 ); 91 }
92 die $@ if $@;
49} 93}
50
51sub event_unreg_cb {
52 $_[0]{eventdata}
53 and (delete $_[0]{eventdata})->cancel;
54}
55
56sub event_wait_cb {
57 Event::one_event();
58}
59
60$regcb = \&event_reg_cb;
61$unregcb = \&event_unreg_cb;
62$waitcb = \&event_wait_cb;
63 94
64sub touc($) { 95sub touc($) {
65 local $_ = shift; 96 local $_ = shift;
66 1 while s/((?:^|_)(?:svk|chk|uri)(?:_|$))/\U$1/; 97 1 while s/((?:^|_)(?:svk|chk|uri)(?:_|$))/\U$1/;
67 s/(?:^|_)(.)/\U$1/g; 98 s/(?:^|_)(.)/\U$1/g;
68 $_; 99 $_;
69} 100}
70 101
71sub tolc($) { 102sub tolc($) {
72 local $_ = shift; 103 local $_ = shift;
104 1 while s/(SVK|CHK|URI)([^_])/$1\_$2/i;
105 1 while s/([^_])(SVK|CHK|URI)/$1\_$2/i;
73 s/(?<=[a-z])(?=[A-Z])/_/g; 106 s/(?<=[a-z])(?=[A-Z])/_/g;
74 lc $_; 107 lc $_;
75} 108}
76 109
110# the opposite of hex
111sub xeh($) {
112 sprintf "%x", $_[0];
113}
114
77=item $meta = Net::FCP::parse_metadata $string 115=item $meta = Net::FCP::parse_metadata $string
78 116
79Parse a metadata string and return it. 117Parse a metadata string and return it.
80 118
81The metadata will be a hashref with key C<version> (containing 119The metadata will be a hashref with key C<version> (containing the
82the mandatory version header entries). 120mandatory version header entries) and key C<raw> containing the original
121metadata string.
83 122
84All other headers are represented by arrayrefs (they can be repeated). 123All other headers are represented by arrayrefs (they can be repeated).
85 124
86Since this is confusing, here is a rather verbose example of a parsed 125Since this description is confusing, here is a rather verbose example of a
87manifest: 126parsed manifest:
88 127
89 ( 128 (
129 raw => "Version...",
90 version => { revision => 1 }, 130 version => { revision => 1 },
91 document => [ 131 document => [
92 { 132 {
93 "info.format" => "image/jpeg", 133 info => { format" => "image/jpeg" },
94 name => "background.jpg", 134 name => "background.jpg",
95 "redirect.target" => "freenet:CHK\@ZcagI,ra726bSw" 135 redirect => { target => "freenet:CHK\@ZcagI,ra726bSw" },
96 }, 136 },
97 { 137 {
98 "info.format" => "text/html", 138 info => { format" => "text/html" },
99 name => ".next", 139 name => ".next",
100 "redirect.target" => "freenet:SSK\@ilUPAgM/TFEE/3" 140 redirect => { target => "freenet:SSK\@ilUPAgM/TFEE/3" },
101 }, 141 },
102 { 142 {
103 "info.format" => "text/html", 143 info => { format" => "text/html" },
104 "redirect.target" => "freenet:CHK\@8M8Po8ucwI,8xA" 144 redirect => { target => "freenet:CHK\@8M8Po8ucwI,8xA" },
105 } 145 }
106 ] 146 ]
107 ) 147 )
108 148
109=cut 149=cut
110 150
111sub parse_metadata { 151sub parse_metadata {
112 my $meta;
113
114 my $data = shift; 152 my $data = shift;
153 my $meta = { raw => $data };
154
115 if ($data =~ /^Version\015?\012/gc) { 155 if ($data =~ /^Version\015?\012/gc) {
116 my $hdr = $meta->{version} = {}; 156 my $hdr = $meta->{version} = {};
117 157
118 for (;;) { 158 for (;;) {
119 while ($data =~ /\G([^=\015\012]+)=([^\015\012]*)\015?\012/gc) { 159 while ($data =~ /\G([^=\015\012]+)=([^\015\012]*)\015?\012/gc) {
120 my ($k, $v) = ($1, $2); 160 my ($k, $v) = ($1, $2);
121 $hdr->{tolc $k} = $v; 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;
122 } 167 }
123 168
124 if ($data =~ /\GEndPart\015?\012/gc) { 169 if ($data =~ /\GEndPart\015?\012/gc) {
170 # nop
125 } elsif ($data =~ /\GEnd\015?\012/gc) { 171 } elsif ($data =~ /\GEnd(\015?\012|$)/gc) {
126 last; 172 last;
127 } elsif ($data =~ /\G([A-Za-z0-9.\-]+)\015?\012/gcs) { 173 } elsif ($data =~ /\G([A-Za-z0-9.\-]+)\015?\012/gcs) {
128 push @{$meta->{tolc $1}}, $hdr = {}; 174 push @{$meta->{tolc $1}}, $hdr = {};
129 } elsif ($data =~ /\G(.*)/gcs) { 175 } elsif ($data =~ /\G(.*)/gcs) {
176 print STDERR "metadata format error ($1), please report this string: <<$data>>";
130 die "metadata format error ($1)"; 177 die "metadata format error";
131 } 178 }
132 } 179 }
133 } 180 }
134 181
135 #$meta->{tail} = substr $data, pos $data; 182 #$meta->{tail} = substr $data, pos $data;
136 183
137 $meta; 184 $meta;
138} 185}
139 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
140=item $fcp = new Net::FCP [host => $host][, port => $port] 234=item $fcp = new Net::FCP [host => $host][, port => $port][, progress => \&cb]
141 235
142Create 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
143127.0.0.1:8481, or the environment variables C<FREDHOST> and C<FREDPORT>). 237127.0.0.1:8481, or the environment variables C<FREDHOST> and C<FREDPORT>).
144 238
145Connections 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
146established. However, the existance of the node is checked by executing a 254However, the existance of the node is checked by executing a
147C<ClientHello> transaction. 255C<ClientHello> transaction.
256
257=end
148 258
149=cut 259=cut
150 260
151sub new { 261sub new {
152 my $class = shift; 262 my $class = shift;
153 my $self = bless { @_ }, $class; 263 my $self = bless { @_ }, $class;
154 264
155 $self->{host} ||= $ENV{FREDHOST} || "127.0.0.1"; 265 $self->{host} ||= $ENV{FREDHOST} || "127.0.0.1";
156 $self->{port} ||= $ENV{FREDPORt} || 8481; 266 $self->{port} ||= $ENV{FREDPORT} || 8481;
157 267
158 $self->{nodehello} = $self->client_hello 268 #$self->{nodehello} = $self->client_hello
159 or croak "unable to get nodehello from node\n"; 269 # or croak "unable to get nodehello from node\n";
160 270
161 $self; 271 $self;
162} 272}
163 273
274sub progress {
275 my ($self, $txn, $type, $attr) = @_;
276
277 $self->{progress}->($self, $txn, $type, $attr)
278 if $self->{progress};
279}
280
164=item $txn = $fcp->txn(type => attr => val,...) 281=item $txn = $fcp->txn(type => attr => val,...)
165 282
166The 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 );
167 306
168=cut 307=cut
169 308
170sub txn { 309sub txn {
171 my ($self, $type, %attr) = @_; 310 my ($self, $type, %attr) = @_;
172 311
173 $type = touc $type; 312 $type = touc $type;
174 313
175 my $txn = "Net::FCP::Txn::$type"->new(fcp => $self, type => tolc $type, attr => \%attr); 314 my $txn = "Net::FCP::Txn::$type"->new (fcp => $self, type => tolc $type, attr => \%attr);
176 315
177 $txn; 316 $txn;
178} 317}
179 318
180sub _txn($&) { 319{ # transactions
320
321my $txn = sub {
181 my ($name, $sub) = @_; 322 my ($name, $sub) = @_;
182 *{"$name\_txn"} = $sub; 323 *{"txn_$name"} = $sub;
183 *{$name} = sub { $sub->(@_)->result }; 324 *{$name} = sub { $sub->(@_)->result };
184} 325};
185 326
186=item $txn = $fcp->txn_client_hello 327=item $txn = $fcp->txn_client_hello
187 328
188=item $nodehello = $fcp->client_hello 329=item $nodehello = $fcp->client_hello
189 330
195 protocol => "1.2", 336 protocol => "1.2",
196 } 337 }
197 338
198=cut 339=cut
199 340
200_txn client_hello => sub { 341$txn->(client_hello => sub {
201 my ($self) = @_; 342 my ($self) = @_;
202 343
203 $self->txn ("client_hello"); 344 $self->txn ("client_hello");
204}; 345});
205 346
206=item $txn = $fcp->txn_client_info 347=item $txn = $fcp->txn_client_info
207 348
208=item $nodeinfo = $fcp->client_info 349=item $nodeinfo = $fcp->client_info
209 350
233 routing_time => "a5", 374 routing_time => "a5",
234 } 375 }
235 376
236=cut 377=cut
237 378
238_txn client_info => sub { 379$txn->(client_info => sub {
239 my ($self) = @_; 380 my ($self) = @_;
240 381
241 $self->txn ("client_info"); 382 $self->txn ("client_info");
242}; 383});
243 384
244=item $txn = $fcp->txn_generate_chk ($metadata, $data) 385=item $txn = $fcp->txn_generate_chk ($metadata, $data[, $cipher])
245 386
246=item $uri = $fcp->generate_chk ($metadata, $data) 387=item $uri = $fcp->generate_chk ($metadata, $data[, $cipher])
247 388
248Creates 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.
249 391
250=cut 392=cut
251 393
252_txn generate_chk => sub { 394$txn->(generate_chk => sub {
253 my ($self, $metadata, $data) = @_; 395 my ($self, $metadata, $data, $cipher) = @_;
254 396
255 $self->txn (generate_chk => data => "$data$metadata", metadata_length => length $metadata); 397 $self->txn (generate_chk =>
398 data => "$metadata$data",
399 metadata_length => xeh length $metadata,
400 cipher => $cipher || "Twofish");
256}; 401});
257 402
258=item $txn = $fcp->txn_generate_svk_pair 403=item $txn = $fcp->txn_generate_svk_pair
259 404
260=item ($public, $private) = @{ $fcp->generate_svk_pair } 405=item ($public, $private) = @{ $fcp->generate_svk_pair }
261 406
262Creates 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.
263 409
264 [ 410 [
265 "hKs0-WDQA4pVZyMPKNFsK1zapWY", 411 "acLx4dux9fvvABH15Gk6~d3I-yw",
266 "ZnmvMITaTXBMFGl4~jrjuyWxOWg" 412 "cPoDkDMXDGSMM32plaPZDhJDxSs",
413 "BH7LXCov0w51-y9i~BoB3g",
267 ] 414 ]
268 415
269=cut 416A private key (for inserting) can be constructed like this:
270 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
271_txn generate_svk_pair => sub { 428$txn->(generate_svk_pair => sub {
272 my ($self) = @_; 429 my ($self) = @_;
273 430
274 $self->txn ("generate_svk_pair"); 431 $self->txn ("generate_svk_pair");
275}; 432});
276 433
277=item $txn = $fcp->txn_insert_private_key ($private) 434=item $txn = $fcp->txn_invert_private_key ($private)
278 435
279=item $uri = $fcp->insert_private_key ($private) 436=item $public = $fcp->invert_private_key ($private)
280 437
281Inserts 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
282with 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.
283from C<generate_svk_pair>). 440the private value you get back from C<generate_svk_pair>).
284 441
285Returns the public key. 442Returns the public key.
286 443
287UNTESTED.
288
289=cut 444=cut
290 445
291_txn insert_private_key => sub { 446$txn->(invert_private_key => sub {
292 my ($self, $privkey) = @_; 447 my ($self, $privkey) = @_;
293 448
294 $self->txn (invert_private_key => private => $privkey); 449 $self->txn (invert_private_key => private => $privkey);
295}; 450});
296 451
297=item $txn = $fcp->txn_get_size ($uri) 452=item $txn = $fcp->txn_get_size ($uri)
298 453
299=item $length = $fcp->get_size ($uri) 454=item $length = $fcp->get_size ($uri)
300 455
301Finds 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
302given document. 457given document.
303 458
304UNTESTED.
305
306=cut 459=cut
307 460
308_txn get_size => sub { 461$txn->(get_size => sub {
309 my ($self, $uri) = @_; 462 my ($self, $uri) = @_;
310 463
311 $self->txn (get_size => URI => $uri); 464 $self->txn (get_size => URI => $uri);
312}; 465});
313 466
314=item $txn = $fcp->txn_client_get ($uri [, $htl = 15 [, $removelocal = 0]]) 467=item $txn = $fcp->txn_client_get ($uri [, $htl = 15 [, $removelocal = 0]])
315 468
316=item ($metadata, $data) = @{ $fcp->client_get ($uri, $htl, $removelocal) 469=item ($metadata, $data) = @{ $fcp->client_get ($uri, $htl, $removelocal)
317 470
318Fetches a (small, as it should fit into memory) file from 471Fetches a (small, as it should fit into memory) file from
319freenet. C<$meta> is the metadata (as returned by C<parse_metadata> or 472freenet. C<$meta> is the metadata (as returned by C<parse_metadata> or
320C<undef>). 473C<undef>).
474
475The C<$uri> should begin with C<freenet:>, but the scheme is currently
476added, if missing.
321 477
322Due to the overhead, a better method to download big files should be used. 478Due to the overhead, a better method to download big files should be used.
323 479
324 my ($meta, $data) = @{ 480 my ($meta, $data) = @{
325 $fcp->client_get ( 481 $fcp->client_get (
327 ) 483 )
328 }; 484 };
329 485
330=cut 486=cut
331 487
332_txn client_get => sub { 488$txn->(client_get => sub {
333 my ($self, $uri, $htl, $removelocal) = @_; 489 my ($self, $uri, $htl, $removelocal) = @_;
334 490
335 $self->txn (client_get => URI => $uri, hops_to_live => ($htl || 15), remove_local => $removelocal*1); 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");
336}; 496});
337 497
338=item MISSING: ClientPut 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
339 526
340=back 527=back
341 528
342=head2 THE Net::FCP::Txn CLASS 529=head2 THE Net::FCP::Txn CLASS
343 530
344All requests (or transactions) are executed in a asynchroneous way (LIE: 531All requests (or transactions) are executed in a asynchronous way. For
345uploads 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
346created (worse: a tcp connection is created, too). 533connection is created, too).
347 534
348For 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
349to subclass these, although of course not documented). 536to subclass these, although of course not documented).
350 537
351The most interesting method is C<result>. 538The most interesting method is C<result>.
353=over 4 540=over 4
354 541
355=cut 542=cut
356 543
357package Net::FCP::Txn; 544package Net::FCP::Txn;
545
546use Fcntl;
547use Socket;
358 548
359=item new arg => val,... 549=item new arg => val,...
360 550
361Creates a new C<Net::FCP::Txn> object. Not normally used. 551Creates a new C<Net::FCP::Txn> object. Not normally used.
362 552
364 554
365sub new { 555sub new {
366 my $class = shift; 556 my $class = shift;
367 my $self = bless { @_ }, $class; 557 my $self = bless { @_ }, $class;
368 558
559 $self->{signal} = $EVENT->new_signal;
560
561 $self->{fcp}{txn}{$self} = $self;
562
369 my $attr = ""; 563 my $attr = "";
370 my $data = delete $self->{attr}{data}; 564 my $data = delete $self->{attr}{data};
371 565
372 while (my ($k, $v) = each %{$self->{attr}}) { 566 while (my ($k, $v) = each %{$self->{attr}}) {
373 $attr .= (Net::FCP::touc $k) . "=$v\012" 567 $attr .= (Net::FCP::touc $k) . "=$v\012"
374 } 568 }
375 569
376 if (defined $data) { 570 if (defined $data) {
377 $attr .= "DataLength=" . (length $data) . "\012"; 571 $attr .= sprintf "DataLength=%x\012", length $data;
378 $data = "Data\012$data"; 572 $data = "Data\012$data";
379 } else { 573 } else {
380 $data = "EndMessage\012"; 574 $data = "EndMessage\012";
381 } 575 }
382 576
383 my $fh = new IO::Socket::INET 577 socket my $fh, PF_INET, SOCK_STREAM, 0
384 PeerHost => $self->{fcp}{host}, 578 or Carp::croak "unable to create new tcp socket: $!";
385 PeerPort => $self->{fcp}{port}
386 or Carp::croak "FCP::txn: unable to connect to $self->{fcp}{host}:$self->{fcp}{port}: $!\n";
387
388 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";
389 585
390 if (0) { 586 $self->{sbuf} =
391 print 587 "\x00\x00\x00\x02"
392 Net::FCP::touc $self->{type}, "\012",
393 $attr,
394 $data, "\012";
395 }
396
397 print $fh
398 "\x00\x00", "\x00\x02", # SESSID, PRESID
399 Net::FCP::touc $self->{type}, "\012", 588 . (Net::FCP::touc $self->{type})
400 $attr, 589 . "\012$attr$data";
401 $data;
402 590
403 #$fh->shutdown (1); # freenet buggy?, well, it's java... 591 #shutdown $fh, 1; # freenet buggy?, well, it's java...
404 592
405 $self->{fh} = $fh; 593 $self->{fh} = $fh;
406 594
407 $Net::FCP::regcb->($self); 595 $self->{w} = $EVENT->new_from_fh ($fh)->cb(sub { $self->fh_ready_w })->poll(0, 1, 1);
408 596
409 $self; 597 $self;
410} 598}
411 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
412sub 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 {
413 my ($self) = @_; 674 my ($self) = @_;
414 675
415 if (sysread $self->{fh}, $self->{buf}, 65536, length $self->{buf}) { 676 if (sysread $self->{fh}, $self->{buf}, 65536, length $self->{buf}) {
416 for (;;) { 677 for (;;) {
417 if ($self->{datalen}) { 678 if ($self->{datalen}) {
679 #warn "expecting new datachunk $self->{datalen}, got ".(length $self->{buf})."\n";#d#
418 if (length $self->{buf} >= $self->{datalen}) { 680 if (length $self->{buf} >= $self->{datalen}) {
419 $self->rcv_data (substr $self->{buf}, 0, $self->{datalen}, ""); 681 $self->rcv_data (substr $self->{buf}, 0, delete $self->{datalen}, "");
420 } else { 682 } else {
421 last; 683 last;
422 } 684 }
423 } elsif ($self->{buf} =~ s/^DataChunk\015?\012Length=([0-9a-fA-F]+)\015?\012Data\015?\012//) { 685 } elsif ($self->{buf} =~ s/^DataChunk\015?\012Length=([0-9a-fA-F]+)\015?\012Data\015?\012//) {
424 $self->{datalen} = hex $1; 686 $self->{datalen} = hex $1;
687 #warn "expecting new datachunk $self->{datalen}\n";#d#
425 } elsif ($self->{buf} =~ s/^([a-zA-Z]+)\015?\012(?:(.+?)\015?\012)?EndMessage\015?\012//s) { 688 } elsif ($self->{buf} =~ s/^([a-zA-Z]+)\015?\012(?:(.+?)\015?\012)?EndMessage\015?\012//s) {
426 $self->rcv ($1, { 689 $self->rcv ($1, {
427 map { my ($a, $b) = split /=/, $_, 2; ((Net::FCP::tolc $a), $b) } 690 map { my ($a, $b) = split /=/, $_, 2; ((Net::FCP::tolc $a), $b) }
428 split /\015?\012/, $2 691 split /\015?\012/, $2
429 }); 692 });
430 } else { 693 } else {
431 last; 694 last;
432 } 695 }
433 } 696 }
434 } else { 697 } else {
435 $Net::FCP::unregcb->($self);
436 delete $self->{fh};
437 $self->eof; 698 $self->eof;
438 } 699 }
439}
440
441sub rcv_data {
442 my ($self, $chunk) = @_;
443
444 $self->{data} .= $chunk;
445} 700}
446 701
447sub rcv { 702sub rcv {
448 my ($self, $type, $attr) = @_; 703 my ($self, $type, $attr) = @_;
449 704
456 } else { 711 } else {
457 warn "received unexpected reply type '$type' for '$self->{type}', ignoring\n"; 712 warn "received unexpected reply type '$type' for '$self->{type}', ignoring\n";
458 } 713 }
459} 714}
460 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
461sub set_result { 733sub set_result {
462 my ($self, $result) = @_; 734 my ($self, $result) = @_;
463 735
464 $self->{result} = $result unless exists $self->{result}; 736 unless (exists $self->{result}) {
737 $self->{result} = $result;
738 $self->{cb}->($self) if exists $self->{cb};
739 $self->{signal}->send;
740 }
465} 741}
466 742
467sub eof { 743sub eof {
468 my ($self) = @_; 744 my ($self) = @_;
469 $self->set_result; 745
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);
470} 762}
471 763
472=item $result = $txn->result 764=item $result = $txn->result
473 765
474Waits until a result is available and then returns it. 766Waits until a result is available and then returns it.
475 767
476This waiting is (depending on your event model) not very efficient, as it 768This waiting is (depending on your event model) not very efficient, as it
477is 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.
478 773
479=cut 774=cut
480 775
481sub result { 776sub result {
482 my ($self) = @_; 777 my ($self) = @_;
483 778
484 $Net::FCP::waitcb->() while !exists $self->{result}; 779 $self->{signal}->wait while !exists $self->{result};
780
781 die $self->{exception} if $self->{exception};
485 782
486 return $self->{result}; 783 return $self->{result};
487}
488
489sub DESTROY {
490 $Net::FCP::unregcb->($_[0]);
491} 784}
492 785
493package Net::FCP::Txn::ClientHello; 786package Net::FCP::Txn::ClientHello;
494 787
495use base Net::FCP::Txn; 788use base Net::FCP::Txn;
515use base Net::FCP::Txn; 808use base Net::FCP::Txn;
516 809
517sub rcv_success { 810sub rcv_success {
518 my ($self, $attr) = @_; 811 my ($self, $attr) = @_;
519 812
520 $self->set_result ($attr); 813 $self->set_result ($attr->{uri});
521} 814}
522 815
523package Net::FCP::Txn::GenerateSVKPair; 816package Net::FCP::Txn::GenerateSVKPair;
524 817
525use base Net::FCP::Txn; 818use base Net::FCP::Txn;
526 819
527sub rcv_success { 820sub rcv_success {
528 my ($self, $attr) = @_; 821 my ($self, $attr) = @_;
529
530 $self->set_result ([$attr->{PublicKey}, $attr->{PrivateKey}]); 822 $self->set_result ([$attr->{public_key}, $attr->{private_key}, $attr->{crypto_key}]);
531} 823}
532 824
533package Net::FCP::Txn::InvertPrivateKey; 825package Net::FCP::Txn::InvertPrivateKey;
534 826
535use base Net::FCP::Txn; 827use base Net::FCP::Txn;
536 828
537sub rcv_success { 829sub rcv_success {
538 my ($self, $attr) = @_; 830 my ($self, $attr) = @_;
539
540 $self->set_result ($attr->{PublicKey}); 831 $self->set_result ($attr->{public_key});
541} 832}
542 833
543package Net::FCP::Txn::GetSize; 834package Net::FCP::Txn::GetSize;
544 835
545use base Net::FCP::Txn; 836use base Net::FCP::Txn;
546 837
547sub rcv_success { 838sub rcv_success {
548 my ($self, $attr) = @_; 839 my ($self, $attr) = @_;
549
550 $self->set_result ($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);
551} 860}
552 861
553package Net::FCP::Txn::ClientGet; 862package Net::FCP::Txn::ClientGet;
554 863
555use base Net::FCP::Txn; 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}
556 883
557sub rcv_data_found { 884sub rcv_data_found {
558 my ($self, $attr) = @_; 885 my ($self, $attr, $type) = @_;
886
887 $self->progress ($type, $attr);
559 888
560 $self->{datalength} = hex $attr->{data_length}; 889 $self->{datalength} = hex $attr->{data_length};
561 $self->{metalength} = hex $attr->{metadata_length}; 890 $self->{metalength} = hex $attr->{metadata_length};
562} 891}
563 892
564sub rcv_restarted { 893package Net::FCP::Txn::ClientPut;
565 # nop, maybe feedback
566}
567 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
568sub eof { 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 {
569 my ($self) = @_; 953 my ($self, $type) = @_;
570 954
571 my $data = delete $self->{data}; 955 @_ >= 2
572 my $meta = Net::FCP::parse_metadata substr $data, 0, $self->{metalength}, ""; 956 ? $self->[0] eq $type
957 : $self->[0];
958}
573 959
574 $self->set_result ([$meta, $data]); 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];
575} 973}
576 974
577=back 975=back
578 976
579=head1 SEE ALSO 977=head1 SEE ALSO
587 Marc Lehmann <pcg@goof.com> 985 Marc Lehmann <pcg@goof.com>
588 http://www.goof.com/pcg/marc/ 986 http://www.goof.com/pcg/marc/
589 987
590=cut 988=cut
591 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
5921; 10221;
593 1023

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines