ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Net-FCP/FCP.pm
Revision: 1.20
Committed: Mon Sep 15 00:05:32 2003 UTC (23 years ago) by root
Branch: MAIN
Changes since 1.19: +36 -2 lines
Log Message:
*** empty log message ***

File Contents

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