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.28 by root, Thu May 13 16:13:42 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
140=item $fcp = new Net::FCP [host => $host][, port => $port] 187=item $fcp = new Net::FCP [host => $host][, port => $port][, progress => \&cb]
141 188
142Create a new virtual FCP connection to the given host and port (default 189Create 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>). 190127.0.0.1:8481, or the environment variables C<FREDHOST> and C<FREDPORT>).
144 191
145Connections are virtual because no persistent physical connection is 192Connections are virtual because no persistent physical connection is
193established.
194
195You can install a progress callback that is being called with the Net::FCP
196object, a txn object, the type of the transaction and the attributes. Use
197it like this:
198
199 sub progress_cb {
200 my ($self, $txn, $type, $attr) = @_;
201
202 warn "progress<$txn,$type," . (join ":", %$attr) . ">\n";
203 }
204
205=begin comment
206
146established. However, the existance of the node is checked by executing a 207However, the existance of the node is checked by executing a
147C<ClientHello> transaction. 208C<ClientHello> transaction.
209
210=end
148 211
149=cut 212=cut
150 213
151sub new { 214sub new {
152 my $class = shift; 215 my $class = shift;
153 my $self = bless { @_ }, $class; 216 my $self = bless { @_ }, $class;
154 217
155 $self->{host} ||= $ENV{FREDHOST} || "127.0.0.1"; 218 $self->{host} ||= $ENV{FREDHOST} || "127.0.0.1";
156 $self->{port} ||= $ENV{FREDPORt} || 8481; 219 $self->{port} ||= $ENV{FREDPORT} || 8481;
157 220
158 $self->{nodehello} = $self->client_hello 221 #$self->{nodehello} = $self->client_hello
159 or croak "unable to get nodehello from node\n"; 222 # or croak "unable to get nodehello from node\n";
160 223
161 $self; 224 $self;
162} 225}
163 226
227sub progress {
228 my ($self, $txn, $type, $attr) = @_;
229
230 $self->{progress}->($self, $txn, $type, $attr)
231 if $self->{progress};
232}
233
164=item $txn = $fcp->txn(type => attr => val,...) 234=item $txn = $fcp->txn(type => attr => val,...)
165 235
166The low-level interface to transactions. Don't use it. 236The low-level interface to transactions. Don't use it.
237
238Here are some examples of using transactions:
239
240The blocking case, no (visible) transactions involved:
241
242 my $nodehello = $fcp->client_hello;
243
244A transaction used in a blocking fashion:
245
246 my $txn = $fcp->txn_client_hello;
247 ...
248 my $nodehello = $txn->result;
249
250Or shorter:
251
252 my $nodehello = $fcp->txn_client_hello->result;
253
254Setting callbacks:
255
256 $fcp->txn_client_hello->cb(
257 sub { my $nodehello => $_[0]->result }
258 );
167 259
168=cut 260=cut
169 261
170sub txn { 262sub txn {
171 my ($self, $type, %attr) = @_; 263 my ($self, $type, %attr) = @_;
175 my $txn = "Net::FCP::Txn::$type"->new(fcp => $self, type => tolc $type, attr => \%attr); 267 my $txn = "Net::FCP::Txn::$type"->new(fcp => $self, type => tolc $type, attr => \%attr);
176 268
177 $txn; 269 $txn;
178} 270}
179 271
180sub _txn($&) { 272{ # transactions
273
274my $txn = sub {
181 my ($name, $sub) = @_; 275 my ($name, $sub) = @_;
182 *{"$name\_txn"} = $sub; 276 *{"txn_$name"} = $sub;
183 *{$name} = sub { $sub->(@_)->result }; 277 *{$name} = sub { $sub->(@_)->result };
184} 278};
185 279
186=item $txn = $fcp->txn_client_hello 280=item $txn = $fcp->txn_client_hello
187 281
188=item $nodehello = $fcp->client_hello 282=item $nodehello = $fcp->client_hello
189 283
195 protocol => "1.2", 289 protocol => "1.2",
196 } 290 }
197 291
198=cut 292=cut
199 293
200_txn client_hello => sub { 294$txn->(client_hello => sub {
201 my ($self) = @_; 295 my ($self) = @_;
202 296
203 $self->txn ("client_hello"); 297 $self->txn ("client_hello");
204}; 298});
205 299
206=item $txn = $fcp->txn_client_info 300=item $txn = $fcp->txn_client_info
207 301
208=item $nodeinfo = $fcp->client_info 302=item $nodeinfo = $fcp->client_info
209 303
233 routing_time => "a5", 327 routing_time => "a5",
234 } 328 }
235 329
236=cut 330=cut
237 331
238_txn client_info => sub { 332$txn->(client_info => sub {
239 my ($self) = @_; 333 my ($self) = @_;
240 334
241 $self->txn ("client_info"); 335 $self->txn ("client_info");
242}; 336});
243 337
244=item $txn = $fcp->txn_generate_chk ($metadata, $data) 338=item $txn = $fcp->txn_generate_chk ($metadata, $data[, $cipher])
245 339
246=item $uri = $fcp->generate_chk ($metadata, $data) 340=item $uri = $fcp->generate_chk ($metadata, $data[, $cipher])
247 341
248Creates a new CHK, given the metadata and data. UNTESTED. 342Calculates a CHK, given the metadata and data. C<$cipher> is either
343C<Rijndael> or C<Twofish>, with the latter being the default.
249 344
250=cut 345=cut
251 346
252_txn generate_chk => sub { 347$txn->(generate_chk => sub {
253 my ($self, $metadata, $data) = @_; 348 my ($self, $metadata, $data, $cipher) = @_;
254 349
255 $self->txn (generate_chk => data => "$data$metadata", metadata_length => length $metadata); 350 $self->txn (generate_chk =>
351 data => "$metadata$data",
352 metadata_length => xeh length $metadata,
353 cipher => $cipher || "Twofish");
256}; 354});
257 355
258=item $txn = $fcp->txn_generate_svk_pair 356=item $txn = $fcp->txn_generate_svk_pair
259 357
260=item ($public, $private) = @{ $fcp->generate_svk_pair } 358=item ($public, $private) = @{ $fcp->generate_svk_pair }
261 359
266 "ZnmvMITaTXBMFGl4~jrjuyWxOWg" 364 "ZnmvMITaTXBMFGl4~jrjuyWxOWg"
267 ] 365 ]
268 366
269=cut 367=cut
270 368
271_txn generate_svk_pair => sub { 369$txn->(generate_svk_pair => sub {
272 my ($self) = @_; 370 my ($self) = @_;
273 371
274 $self->txn ("generate_svk_pair"); 372 $self->txn ("generate_svk_pair");
275}; 373});
276 374
277=item $txn = $fcp->txn_insert_private_key ($private) 375=item $txn = $fcp->txn_insert_private_key ($private)
278 376
279=item $uri = $fcp->insert_private_key ($private) 377=item $public = $fcp->insert_private_key ($private)
280 378
281Inserts a private key. $private can be either an insert URI (must start 379Inserts a private key. $private can be either an insert URI (must start
282with freenet:SSK@) or a raw private key (i.e. the private value you get back 380with C<freenet:SSK@>) or a raw private key (i.e. the private value you get
283from C<generate_svk_pair>). 381back from C<generate_svk_pair>).
284 382
285Returns the public key. 383Returns the public key.
286 384
287UNTESTED. 385UNTESTED.
288 386
289=cut 387=cut
290 388
291_txn insert_private_key => sub { 389$txn->(insert_private_key => sub {
292 my ($self, $privkey) = @_; 390 my ($self, $privkey) = @_;
293 391
294 $self->txn (invert_private_key => private => $privkey); 392 $self->txn (invert_private_key => private => $privkey);
295}; 393});
296 394
297=item $txn = $fcp->txn_get_size ($uri) 395=item $txn = $fcp->txn_get_size ($uri)
298 396
299=item $length = $fcp->get_size ($uri) 397=item $length = $fcp->get_size ($uri)
300 398
303 401
304UNTESTED. 402UNTESTED.
305 403
306=cut 404=cut
307 405
308_txn get_size => sub { 406$txn->(get_size => sub {
309 my ($self, $uri) = @_; 407 my ($self, $uri) = @_;
310 408
311 $self->txn (get_size => URI => $uri); 409 $self->txn (get_size => URI => $uri);
312}; 410});
313 411
314=item $txn = $fcp->txn_client_get ($uri [, $htl = 15 [, $removelocal = 0]]) 412=item $txn = $fcp->txn_client_get ($uri [, $htl = 15 [, $removelocal = 0]])
315 413
316=item ($metadata, $data) = @{ $fcp->client_get ($uri, $htl, $removelocal) 414=item ($metadata, $data) = @{ $fcp->client_get ($uri, $htl, $removelocal)
317 415
318Fetches a (small, as it should fit into memory) file from 416Fetches a (small, as it should fit into memory) file from
319freenet. C<$meta> is the metadata (as returned by C<parse_metadata> or 417freenet. C<$meta> is the metadata (as returned by C<parse_metadata> or
320C<undef>). 418C<undef>).
419
420The C<$uri> should begin with C<freenet:>, but the scheme is currently
421added, if missing.
321 422
322Due to the overhead, a better method to download big files should be used. 423Due to the overhead, a better method to download big files should be used.
323 424
324 my ($meta, $data) = @{ 425 my ($meta, $data) = @{
325 $fcp->client_get ( 426 $fcp->client_get (
327 ) 428 )
328 }; 429 };
329 430
330=cut 431=cut
331 432
332_txn client_get => sub { 433$txn->(client_get => sub {
333 my ($self, $uri, $htl, $removelocal) = @_; 434 my ($self, $uri, $htl, $removelocal) = @_;
334 435
335 $self->txn (client_get => URI => $uri, hops_to_live => ($htl || 15), remove_local => $removelocal*1); 436 $uri =~ s/^freenet://;
437 $uri = "freenet:$uri";
438
439 $self->txn (client_get => URI => $uri, hops_to_live => xeh (defined $htl ? $htl : 15),
440 remove_local_key => $removelocal ? "true" : "false");
336}; 441});
337 442
443=item $txn = $fcp->txn_client_put ($uri, $metadata, $data, $htl, $removelocal)
444
445=item my $uri = $fcp->client_put ($uri, $metadata, $data, $htl, $removelocal);
446
447Insert a new key. If the client is inserting a CHK, the URI may be
448abbreviated as just CHK@. In this case, the node will calculate the
449CHK.
450
451C<$meta> can be a reference or a string (ONLY THE STRING CASE IS IMPLEMENTED!).
452
453THIS INTERFACE IS UNTESTED AND SUBJECT TO CHANGE.
454
455=cut
456
457$txn->(client_put => sub {
458 my ($self, $uri, $meta, $data, $htl, $removelocal) = @_;
459
460 $self->txn (client_put => URI => $uri, xeh (defined $htl ? $htl : 15),
461 remove_local_key => $removelocal ? "true" : "false",
462 data => "$meta$data", metadata_length => xeh length $meta);
463});
464
465} # transactions
466
338=item MISSING: ClientPut 467=item MISSING: (ClientPut), InsertKey
339 468
340=back 469=back
341 470
342=head2 THE Net::FCP::Txn CLASS 471=head2 THE Net::FCP::Txn CLASS
343 472
344All requests (or transactions) are executed in a asynchroneous way (LIE: 473All requests (or transactions) are executed in a asynchronous way. For
345uploads are blocking). For each request, a C<Net::FCP::Txn> object is 474each request, a C<Net::FCP::Txn> object is created (worse: a tcp
346created (worse: a tcp connection is created, too). 475connection is created, too).
347 476
348For each request there is actually a different subclass (and it's possible 477For each request there is actually a different subclass (and it's possible
349to subclass these, although of course not documented). 478to subclass these, although of course not documented).
350 479
351The most interesting method is C<result>. 480The most interesting method is C<result>.
353=over 4 482=over 4
354 483
355=cut 484=cut
356 485
357package Net::FCP::Txn; 486package Net::FCP::Txn;
487
488use Fcntl;
489use Socket;
358 490
359=item new arg => val,... 491=item new arg => val,...
360 492
361Creates a new C<Net::FCP::Txn> object. Not normally used. 493Creates a new C<Net::FCP::Txn> object. Not normally used.
362 494
364 496
365sub new { 497sub new {
366 my $class = shift; 498 my $class = shift;
367 my $self = bless { @_ }, $class; 499 my $self = bless { @_ }, $class;
368 500
501 $self->{signal} = $EVENT->new_signal;
502
503 $self->{fcp}{txn}{$self} = $self;
504
369 my $attr = ""; 505 my $attr = "";
370 my $data = delete $self->{attr}{data}; 506 my $data = delete $self->{attr}{data};
371 507
372 while (my ($k, $v) = each %{$self->{attr}}) { 508 while (my ($k, $v) = each %{$self->{attr}}) {
373 $attr .= (Net::FCP::touc $k) . "=$v\012" 509 $attr .= (Net::FCP::touc $k) . "=$v\012"
374 } 510 }
375 511
376 if (defined $data) { 512 if (defined $data) {
377 $attr .= "DataLength=" . (length $data) . "\012"; 513 $attr .= sprintf "DataLength=%x\012", length $data;
378 $data = "Data\012$data"; 514 $data = "Data\012$data";
379 } else { 515 } else {
380 $data = "EndMessage\012"; 516 $data = "EndMessage\012";
381 } 517 }
382 518
383 my $fh = new IO::Socket::INET 519 socket my $fh, PF_INET, SOCK_STREAM, 0
384 PeerHost => $self->{fcp}{host}, 520 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"; 521 binmode $fh, ":raw";
522 fcntl $fh, F_SETFL, O_NONBLOCK;
523 connect $fh, (sockaddr_in $self->{fcp}{port}, inet_aton $self->{fcp}{host})
524 and !$!{EWOULDBLOCK}
525 and !$!{EINPROGRESS}
526 and Carp::croak "FCP::txn: unable to connect to $self->{fcp}{host}:$self->{fcp}{port}: $!\n";
389 527
390 if (0) { 528 $self->{sbuf} =
391 print 529 "\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", 530 . (Net::FCP::touc $self->{type})
400 $attr, 531 . "\012$attr$data";
401 $data;
402 532
403 #$fh->shutdown (1); # freenet buggy?, well, it's java... 533 #shutdown $fh, 1; # freenet buggy?, well, it's java...
404 534
405 $self->{fh} = $fh; 535 $self->{fh} = $fh;
406 536
407 $Net::FCP::regcb->($self); 537 $self->{w} = $EVENT->new_from_fh ($fh)->cb(sub { $self->fh_ready_w })->poll(0, 1, 1);
408 538
409 $self; 539 $self;
410} 540}
411 541
542=item $txn = $txn->cb ($coderef)
543
544Sets a callback to be called when the request is finished. The coderef
545will be called with the txn as it's sole argument, so it has to call
546C<result> itself.
547
548Returns the txn object, useful for chaining.
549
550Example:
551
552 $fcp->txn_client_get ("freenet:CHK....")
553 ->userdata ("ehrm")
554 ->cb(sub {
555 my $data = shift->result;
556 });
557
558=cut
559
560sub cb($$) {
561 my ($self, $cb) = @_;
562 $self->{cb} = $cb;
563 $self;
564}
565
566=item $txn = $txn->userdata ([$userdata])
567
568Set user-specific data. This is useful in progress callbacks. The data can be accessed
569using C<< $txn->{userdata} >>.
570
571Returns the txn object, useful for chaining.
572
573=cut
574
575sub userdata($$) {
576 my ($self, $data) = @_;
577 $self->{userdata} = $data;
578 $self;
579}
580
581=item $txn->cancel (%attr)
582
583Cancels the operation with a C<cancel> exception anf the given attributes
584(consider at least giving the attribute C<reason>).
585
586UNTESTED.
587
588=cut
589
590sub cancel {
591 my ($self, %attr) = @_;
592 $self->throw (Net::FCP::Exception->new (cancel => { %attr }));
593 $self->set_result;
594 $self->eof;
595}
596
412sub fh_ready { 597sub fh_ready_w {
598 my ($self) = @_;
599
600 my $len = syswrite $self->{fh}, $self->{sbuf};
601
602 if ($len > 0) {
603 substr $self->{sbuf}, 0, $len, "";
604 unless (length $self->{sbuf}) {
605 fcntl $self->{fh}, F_SETFL, 0;
606 $self->{w}->cb(sub { $self->fh_ready_r })->poll (1, 0, 1);
607 }
608 } elsif (defined $len) {
609 $self->throw (Net::FCP::Exception->new (network_error => { reason => "unexpected end of file while writing" }));
610 } else {
611 $self->throw (Net::FCP::Exception->new (network_error => { reason => "$!" }));
612 }
613}
614
615sub fh_ready_r {
413 my ($self) = @_; 616 my ($self) = @_;
414 617
415 if (sysread $self->{fh}, $self->{buf}, 65536, length $self->{buf}) { 618 if (sysread $self->{fh}, $self->{buf}, 65536, length $self->{buf}) {
416 for (;;) { 619 for (;;) {
417 if ($self->{datalen}) { 620 if ($self->{datalen}) {
621 #warn "expecting new datachunk $self->{datalen}, got ".(length $self->{buf})."\n";#d#
418 if (length $self->{buf} >= $self->{datalen}) { 622 if (length $self->{buf} >= $self->{datalen}) {
419 $self->rcv_data (substr $self->{buf}, 0, $self->{datalen}, ""); 623 $self->rcv_data (substr $self->{buf}, 0, delete $self->{datalen}, "");
420 } else { 624 } else {
421 last; 625 last;
422 } 626 }
423 } elsif ($self->{buf} =~ s/^DataChunk\015?\012Length=([0-9a-fA-F]+)\015?\012Data\015?\012//) { 627 } elsif ($self->{buf} =~ s/^DataChunk\015?\012Length=([0-9a-fA-F]+)\015?\012Data\015?\012//) {
424 $self->{datalen} = hex $1; 628 $self->{datalen} = hex $1;
629 #warn "expecting new datachunk $self->{datalen}\n";#d#
425 } elsif ($self->{buf} =~ s/^([a-zA-Z]+)\015?\012(?:(.+?)\015?\012)?EndMessage\015?\012//s) { 630 } elsif ($self->{buf} =~ s/^([a-zA-Z]+)\015?\012(?:(.+?)\015?\012)?EndMessage\015?\012//s) {
426 $self->rcv ($1, { 631 $self->rcv ($1, {
427 map { my ($a, $b) = split /=/, $_, 2; ((Net::FCP::tolc $a), $b) } 632 map { my ($a, $b) = split /=/, $_, 2; ((Net::FCP::tolc $a), $b) }
428 split /\015?\012/, $2 633 split /\015?\012/, $2
429 }); 634 });
430 } else { 635 } else {
431 last; 636 last;
432 } 637 }
433 } 638 }
434 } else { 639 } else {
435 $Net::FCP::unregcb->($self);
436 delete $self->{fh};
437 $self->eof; 640 $self->eof;
438 } 641 }
439}
440
441sub rcv_data {
442 my ($self, $chunk) = @_;
443
444 $self->{data} .= $chunk;
445} 642}
446 643
447sub rcv { 644sub rcv {
448 my ($self, $type, $attr) = @_; 645 my ($self, $type, $attr) = @_;
449 646
456 } else { 653 } else {
457 warn "received unexpected reply type '$type' for '$self->{type}', ignoring\n"; 654 warn "received unexpected reply type '$type' for '$self->{type}', ignoring\n";
458 } 655 }
459} 656}
460 657
658# used as a default exception thrower
659sub rcv_throw_exception {
660 my ($self, $attr, $type) = @_;
661 $self->throw (Net::FCP::Exception->new ($type, $attr));
662}
663
664*rcv_failed = \&Net::FCP::Txn::rcv_throw_exception;
665*rcv_format_error = \&Net::FCP::Txn::rcv_throw_exception;
666
667sub throw {
668 my ($self, $exc) = @_;
669
670 $self->{exception} = $exc;
671 $self->set_result;
672 $self->eof; # must be last to avoid loops
673}
674
461sub set_result { 675sub set_result {
462 my ($self, $result) = @_; 676 my ($self, $result) = @_;
463 677
464 $self->{result} = $result unless exists $self->{result}; 678 unless (exists $self->{result}) {
679 $self->{result} = $result;
680 $self->{cb}->($self) if exists $self->{cb};
681 $self->{signal}->send;
682 }
465} 683}
466 684
467sub eof { 685sub eof {
468 my ($self) = @_; 686 my ($self) = @_;
469 $self->set_result; 687
688 delete $self->{w};
689 delete $self->{fh};
690
691 delete $self->{fcp}{txn}{$self};
692
693 unless (exists $self->{result}) {
694 $self->throw (Net::FCP::Exception->new (short_data => {
695 reason => "unexpected eof or internal node error",
696 }));
697 }
698}
699
700sub progress {
701 my ($self, $type, $attr) = @_;
702
703 $self->{fcp}->progress ($self, $type, $attr);
470} 704}
471 705
472=item $result = $txn->result 706=item $result = $txn->result
473 707
474Waits until a result is available and then returns it. 708Waits until a result is available and then returns it.
475 709
476This waiting is (depending on your event model) not very efficient, as it 710This waiting is (depending on your event model) not very efficient, as it
477is done outside the "mainloop". 711is done outside the "mainloop". The biggest problem, however, is that it's
712blocking one thread of execution. Try to use the callback mechanism, if
713possible, and call result from within the callback (or after is has been
714run), as then no waiting is necessary.
478 715
479=cut 716=cut
480 717
481sub result { 718sub result {
482 my ($self) = @_; 719 my ($self) = @_;
483 720
484 $Net::FCP::waitcb->() while !exists $self->{result}; 721 $self->{signal}->wait while !exists $self->{result};
722
723 die $self->{exception} if $self->{exception};
485 724
486 return $self->{result}; 725 return $self->{result};
487}
488
489sub DESTROY {
490 $Net::FCP::unregcb->($_[0]);
491} 726}
492 727
493package Net::FCP::Txn::ClientHello; 728package Net::FCP::Txn::ClientHello;
494 729
495use base Net::FCP::Txn; 730use base Net::FCP::Txn;
515use base Net::FCP::Txn; 750use base Net::FCP::Txn;
516 751
517sub rcv_success { 752sub rcv_success {
518 my ($self, $attr) = @_; 753 my ($self, $attr) = @_;
519 754
520 $self->set_result ($attr); 755 $self->set_result ($attr->{uri});
521} 756}
522 757
523package Net::FCP::Txn::GenerateSVKPair; 758package Net::FCP::Txn::GenerateSVKPair;
524 759
525use base Net::FCP::Txn; 760use base Net::FCP::Txn;
526 761
527sub rcv_success { 762sub rcv_success {
528 my ($self, $attr) = @_; 763 my ($self, $attr) = @_;
529
530 $self->set_result ([$attr->{PublicKey}, $attr->{PrivateKey}]); 764 $self->set_result ([$attr->{PublicKey}, $attr->{PrivateKey}]);
531} 765}
532 766
533package Net::FCP::Txn::InvertPrivateKey; 767package Net::FCP::Txn::InsertPrivateKey;
534 768
535use base Net::FCP::Txn; 769use base Net::FCP::Txn;
536 770
537sub rcv_success { 771sub rcv_success {
538 my ($self, $attr) = @_; 772 my ($self, $attr) = @_;
539
540 $self->set_result ($attr->{PublicKey}); 773 $self->set_result ($attr->{PublicKey});
541} 774}
542 775
543package Net::FCP::Txn::GetSize; 776package Net::FCP::Txn::GetSize;
544 777
545use base Net::FCP::Txn; 778use base Net::FCP::Txn;
546 779
547sub rcv_success { 780sub rcv_success {
548 my ($self, $attr) = @_; 781 my ($self, $attr) = @_;
549
550 $self->set_result ($attr->{Length}); 782 $self->set_result (hex $attr->{Length});
783}
784
785package Net::FCP::Txn::GetPut;
786
787# base class for get and put
788
789use base Net::FCP::Txn;
790
791*rcv_uri_error = \&Net::FCP::Txn::rcv_throw_exception;
792*rcv_route_not_found = \&Net::FCP::Txn::rcv_throw_exception;
793
794sub rcv_restarted {
795 my ($self, $attr, $type) = @_;
796
797 delete $self->{datalength};
798 delete $self->{metalength};
799 delete $self->{data};
800
801 $self->progress ($type, $attr);
551} 802}
552 803
553package Net::FCP::Txn::ClientGet; 804package Net::FCP::Txn::ClientGet;
554 805
555use base Net::FCP::Txn; 806use base Net::FCP::Txn::GetPut;
807
808*rcv_data_not_found = \&Net::FCP::Txn::rcv_throw_exception;
809
810sub rcv_data {
811 my ($self, $chunk) = @_;
812
813 $self->{data} .= $chunk;
814
815 $self->progress ("data", { chunk => length $chunk, received => length $self->{data}, total => $self->{datalength} });
816
817 if ($self->{datalength} == length $self->{data}) {
818 my $data = delete $self->{data};
819 my $meta = Net::FCP::parse_metadata substr $data, 0, $self->{metalength}, "";
820
821 $self->set_result ([$meta, $data]);
822 $self->eof;
823 }
824}
556 825
557sub rcv_data_found { 826sub rcv_data_found {
558 my ($self, $attr) = @_; 827 my ($self, $attr, $type) = @_;
828
829 $self->progress ($type, $attr);
559 830
560 $self->{datalength} = hex $attr->{data_length}; 831 $self->{datalength} = hex $attr->{data_length};
561 $self->{metalength} = hex $attr->{metadata_length}; 832 $self->{metalength} = hex $attr->{metadata_length};
562} 833}
563 834
564sub rcv_restarted { 835package Net::FCP::Txn::ClientPut;
565 # nop, maybe feedback
566}
567 836
837use base Net::FCP::Txn::GetPut;
838
839*rcv_size_error = \&Net::FCP::Txn::rcv_throw_exception;
840*rcv_key_collision = \&Net::FCP::Txn::rcv_throw_exception;
841
842sub rcv_pending {
843 my ($self, $attr, $type) = @_;
844 $self->progress ($type, $attr);
845}
846
847sub rcv_success {
848 my ($self, $attr, $type) = @_;
849 $self->set_result ($attr);
850}
851
852=back
853
854=head2 The Net::FCP::Exception CLASS
855
856Any unexpected (non-standard) responses that make it impossible to return
857the advertised result will result in an exception being thrown when the
858C<result> method is called.
859
860These exceptions are represented by objects of this class.
861
862=over 4
863
864=cut
865
866package Net::FCP::Exception;
867
868use overload
869 '""' => sub {
870 "Net::FCP::Exception<<$_[0][0]," . (join ":", %{$_[0][1]}) . ">>";
871 };
872
873=item $exc = new Net::FCP::Exception $type, \%attr
874
875Create a new exception object of the given type (a string like
876C<route_not_found>), and a hashref containing additional attributes
877(usually the attributes of the message causing the exception).
878
879=cut
880
568sub eof { 881sub new {
882 my ($class, $type, $attr) = @_;
883
884 bless [Net::FCP::tolc $type, { %$attr }], $class;
885}
886
887=item $exc->type([$type])
888
889With no arguments, returns the exception type. Otherwise a boolean
890indicating wether the exception is of the given type is returned.
891
892=cut
893
894sub type {
569 my ($self) = @_; 895 my ($self, $type) = @_;
570 896
571 my $data = delete $self->{data}; 897 @_ >= 2
572 my $meta = Net::FCP::parse_metadata substr $data, 0, $self->{metalength}, ""; 898 ? $self->[0] eq $type
899 : $self->[0];
900}
573 901
574 $self->set_result ([$meta, $data]); 902=item $exc->attr([$attr])
903
904With no arguments, returns the attributes. Otherwise the named attribute
905value is returned.
906
907=cut
908
909sub attr {
910 my ($self, $attr) = @_;
911
912 @_ >= 2
913 ? $self->[1]{$attr}
914 : $self->[1];
575} 915}
576 916
577=back 917=back
578 918
579=head1 SEE ALSO 919=head1 SEE ALSO
587 Marc Lehmann <pcg@goof.com> 927 Marc Lehmann <pcg@goof.com>
588 http://www.goof.com/pcg/marc/ 928 http://www.goof.com/pcg/marc/
589 929
590=cut 930=cut
591 931
932package Net::FCP::Event::Auto;
933
934my @models = (
935 [Coro => Coro::Event::],
936 [Event => Event::],
937 [Glib => Glib::],
938 [Tk => Tk::],
939);
940
941sub AUTOLOAD {
942 $AUTOLOAD =~ s/.*://;
943
944 for (@models) {
945 my ($model, $package) = @$_;
946 if (defined ${"$package\::VERSION"}) {
947 $EVENT = "Net::FCP::Event::$model";
948 eval "require $EVENT"; die if $@;
949 goto &{"$EVENT\::$AUTOLOAD"};
950 }
951 }
952
953 for (@models) {
954 my ($model, $package) = @$_;
955 $EVENT = "Net::FCP::Event::$model";
956 if (eval "require $EVENT") {
957 goto &{"$EVENT\::$AUTOLOAD"};
958 }
959 }
960
961 die "No event module selected for Net::FCP and autodetect failed. Install any of these: Coro, Event, Glib or Tk.";
962}
963
5921; 9641;
593 965

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines