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

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines