ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-FCP/FCP.pm
Revision: 1.21
Committed: Sun Jun 12 01:48:05 2016 UTC (7 years, 11 months ago) by root
Branch: MAIN
CVS Tags: rel-0_5
Changes since 1.20: +23 -9 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 =head1 NAME
2    
3     AnyEvent::FCP - freenet client protocol 2.0
4    
5     =head1 SYNOPSIS
6    
7 root 1.3 use AnyEvent::FCP;
8 root 1.1
9 root 1.3 my $fcp = new AnyEvent::FCP;
10 root 1.1
11 root 1.7 # transactions return condvars
12 root 1.3 my $lp_cv = $fcp->list_peers;
13     my $pr_cv = $fcp->list_persistent_requests;
14    
15     my $peers = $lp_cv->recv;
16     my $reqs = $pr_cv->recv;
17 root 1.1
18     =head1 DESCRIPTION
19    
20     This module implements the freenet client protocol version 2.0, as used by
21     freenet 0.7. See L<Net::FCP> for the earlier freenet 0.5 version.
22    
23 root 1.3 See L<http://wiki.freenetproject.org/FreenetFCPSpec2Point0> for a
24     description of what the messages do.
25 root 1.1
26     The module uses L<AnyEvent> to find a suitable event module.
27    
28 root 1.3 Only very little is implemented, ask if you need more, and look at the
29     example program later in this section.
30    
31 root 1.7 =head2 EXAMPLE
32    
33     This example fetches the download list and sets the priority of all files
34     with "a" in their name to "emergency":
35    
36     use AnyEvent::FCP;
37    
38     my $fcp = new AnyEvent::FCP;
39    
40 root 1.11 $fcp->watch_global (1, 0);
41     my $req = $fcp->list_persistent_requests;
42 root 1.7
43 root 1.11 TODO
44 root 1.7 for my $req (values %$req) {
45     if ($req->{filename} =~ /a/) {
46 root 1.11 $fcp->modify_persistent_request (1, $req->{identifier}, undef, 0);
47 root 1.7 }
48     }
49    
50 root 1.1 =head2 IMPORT TAGS
51    
52     Nothing much can be "imported" from this module right now.
53    
54 root 1.11 =head1 THE AnyEvent::FCP CLASS
55 root 1.1
56     =over 4
57    
58     =cut
59    
60     package AnyEvent::FCP;
61    
62 root 1.2 use common::sense;
63    
64 root 1.1 use Carp;
65    
66 root 1.21 our $VERSION = 0.5;
67 root 1.1
68 root 1.2 use Scalar::Util ();
69 root 1.1
70     use AnyEvent;
71 root 1.2 use AnyEvent::Handle;
72 root 1.9 use AnyEvent::Util ();
73 root 1.1
74 root 1.16 our %TOLC; # tolc cache
75    
76 root 1.1 sub touc($) {
77     local $_ = shift;
78 root 1.9 1 while s/((?:^|_)(?:svk|chk|uri|fcp|ds|mime|dda)(?:_|$))/\U$1/;
79 root 1.1 s/(?:^|_)(.)/\U$1/g;
80     $_
81     }
82    
83     sub tolc($) {
84     local $_ = shift;
85 root 1.11 1 while s/(SVK|CHK|URI|FCP|DS|MIME|DDA)([^_])/$1\_$2/;
86     1 while s/([^_])(SVK|CHK|URI|FCP|DS|MIME|DDA)/$1\_$2/;
87 root 1.1 s/(?<=[a-z])(?=[A-Z])/_/g;
88     lc
89     }
90    
91 root 1.20 =item $fcp = new AnyEvent::FCP key => value...;
92 root 1.1
93     Create a new FCP connection to the given host and port (default
94 root 1.2 127.0.0.1:9481, or the environment variables C<FREDHOST> and C<FREDPORT>).
95 root 1.1
96 root 1.3 If no C<name> was specified, then AnyEvent::FCP will generate a
97     (hopefully) unique client name for you.
98    
99 root 1.20 The following keys can be specified (they are all optional):
100    
101     =over 4
102    
103     =item name => $string
104    
105     A unique name to identify this client. If none is specified, a randomly
106     generated name will be used.
107    
108     =item host => $hostname
109    
110     The hostname or IP address of the freenet node. Default is C<$ENV{FREDHOST}>
111     or C<127.0.0.1>.
112    
113     =item port => $portnumber
114    
115     The port number of the FCP port. Default is C<$ENV{FREDPORT}> or C<9481>.
116    
117     =item timeout => $seconds
118    
119     The timeout, in seconds, after which a connection error is assumed when
120     there is no activity. Default is C<7200>, i.e. two hours.
121    
122     =item keepalive => $seconds
123    
124     The interval, in seconds, at which keepalive messages will be
125     sent. Default is C<540>, i.e. nine minutes.
126    
127     These keepalive messages are useful both to detect that a connection is
128     no longer working and to keep any (home) routers from expiring their
129     masquerading entry.
130    
131 root 1.21 =item on_eof => $callback->($fcp)
132    
133     Invoked when the underlying L<AnyEvent::Handle> signals EOF, currently
134     regardless of whether the EOF was expected or not.
135    
136 root 1.20 =item on_error => $callback->($fcp, $message)
137    
138     Invoked on any (fatal) errors, such as unexpected connection close. The
139     callback receives the FCP object and a textual error message.
140    
141 root 1.21 =item on_failure => $callback->($fcp, $type, $backtrace, $args, $error)
142 root 1.20
143     Invoked when an FCP request fails that didn't have a failure callback. See
144     L<FCP REQUESTS> for details.
145    
146     =back
147    
148 root 1.3 =cut
149 root 1.1
150     sub new {
151     my $class = shift;
152 root 1.14
153     my $rand = join "", map chr 0x21 + rand 94, 1..40; # ~ 262 bits entropy
154    
155 root 1.11 my $self = bless {
156     host => $ENV{FREDHOST} || "127.0.0.1",
157     port => $ENV{FREDPORT} || 9481,
158     timeout => 3600 * 2,
159 root 1.18 keepalive => 9 * 60,
160 root 1.11 name => time.rand.rand.rand, # lame
161     @_,
162     queue => [],
163     req => {},
164 root 1.15 prefix => "..:aefcpid:$rand:",
165 root 1.14 idseq => "a0",
166 root 1.11 }, $class;
167 root 1.2
168     {
169     Scalar::Util::weaken (my $self = $self);
170    
171 root 1.18 $self->{kw} = AE::timer $self->{keepalive}, $self->{keepalive}, sub {
172     $self->{hdl}->push_write ("\n");
173     };
174    
175 root 1.17 our $ENDMESSAGE = qr<\012(EndMessage|Data)\012>;
176    
177     # these are declared here for performance reasons
178     my ($k, $v, $type);
179     my $rdata;
180    
181     my $on_read = sub {
182     my ($hdl) = @_;
183    
184     # we only carve out whole messages here
185     while ($hdl->{rbuf} =~ /\012(EndMessage|Data)\012/) {
186     # remember end marker
187     $rdata = $1 eq "Data"
188     or $1 eq "EndMessage"
189 root 1.19 or return $self->fatal ("protocol error, expected message end, got $1\n");
190 root 1.17
191     my @lines = split /\012/, substr $hdl->{rbuf}, 0, $-[0];
192    
193     substr $hdl->{rbuf}, 0, $+[0], ""; # remove pkg
194    
195     $type = shift @lines;
196     $type = ($TOLC{$type} ||= tolc $type);
197    
198     my %kv;
199    
200     for (@lines) {
201     ($k, $v) = split /=/, $_, 2;
202     $k = ($TOLC{$k} ||= tolc $k);
203    
204     if ($k =~ /\./) {
205     # generic, slow case
206     my @k = split /\./, $k;
207     my $ro = \\%kv;
208    
209     while (@k) {
210     $k = shift @k;
211     if ($k =~ /^\d+$/) {
212     $ro = \$$ro->[$k];
213     } else {
214     $ro = \$$ro->{$k};
215     }
216     }
217    
218     $$ro = $v;
219    
220     next;
221     }
222    
223     # special comon case, for performance only
224     $kv{$k} = $v;
225     }
226    
227     if ($rdata) {
228     $_[0]->push_read (chunk => delete $kv{data_length}, sub {
229     $rdata = \$_[1];
230     $self->recv ($type, \%kv, $rdata);
231     });
232    
233     last; # do not tgry to parse more messages
234     } else {
235     $self->recv ($type, \%kv);
236     }
237     }
238     };
239    
240 root 1.2 $self->{hdl} = new AnyEvent::Handle
241     connect => [$self->{host} => $self->{port}],
242     timeout => $self->{timeout},
243 root 1.19 on_read => $on_read,
244 root 1.21 on_eof => sub {
245     if ($self->{on_eof}) {
246     $self->{on_eof}($self);
247     } else {
248     $self->fatal ("EOF");
249     }
250     },
251 root 1.2 on_error => sub {
252 root 1.19 $self->fatal ($_[2]);
253 root 1.2 },
254 root 1.17 ;
255 root 1.2
256     Scalar::Util::weaken ($self->{hdl}{fcp} = $self);
257     }
258    
259 root 1.11 $self->send_msg (client_hello =>
260     name => $self->{name},
261 root 1.2 expected_version => "2.0",
262     );
263 root 1.1
264     $self
265     }
266    
267 root 1.19 sub fatal {
268     my ($self, $msg) = @_;
269    
270     $self->{hdl}->shutdown;
271     delete $self->{kw};
272    
273     if ($self->{on_error}) {
274 root 1.20 $self->{on_error}->($self, $msg);
275 root 1.19 } else {
276     die $msg;
277     }
278     }
279    
280 root 1.14 sub identifier {
281     $_[0]{prefix} . ++$_[0]{idseq}
282     }
283    
284 root 1.2 sub send_msg {
285     my ($self, $type, %kv) = @_;
286 root 1.1
287 root 1.2 my $data = delete $kv{data};
288 root 1.1
289 root 1.2 if (exists $kv{id_cb}) {
290 root 1.14 my $id = $kv{identifier} ||= $self->identifier;
291 root 1.2 $self->{id}{$id} = delete $kv{id_cb};
292 root 1.1 }
293    
294 root 1.2 my $msg = (touc $type) . "\012"
295     . join "", map +(touc $_) . "=$kv{$_}\012", keys %kv;
296 root 1.1
297 root 1.2 sub id {
298     my ($self) = @_;
299 root 1.1
300    
301 root 1.2 }
302 root 1.1
303     if (defined $data) {
304 root 1.2 $msg .= "DataLength=" . (length $data) . "\012"
305     . "Data\012$data";
306 root 1.1 } else {
307 root 1.2 $msg .= "EndMessage\012";
308 root 1.1 }
309    
310 root 1.2 $self->{hdl}->push_write ($msg);
311 root 1.1 }
312    
313 root 1.9 sub on {
314     my ($self, $cb) = @_;
315    
316     # cb return undef - message eaten, remove cb
317     # cb return 0 - message eaten
318     # cb return 1 - pass to next
319    
320     push @{ $self->{on} }, $cb;
321     }
322    
323     sub _push_queue {
324     my ($self, $queue) = @_;
325    
326     shift @$queue;
327     $queue->[0]($self, AnyEvent::Util::guard { $self->_push_queue ($queue) })
328     if @$queue;
329     }
330    
331     # lock so only one $type (arbitrary string) is in flight,
332     # to work around horribly misdesigned protocol.
333     sub serialise {
334     my ($self, $type, $cb) = @_;
335    
336     my $queue = $self->{serialise}{$type} ||= [];
337     push @$queue, $cb;
338     $cb->($self, AnyEvent::Util::guard { $self->_push_queue ($queue) })
339     unless $#$queue;
340     }
341    
342 root 1.11 # how to merge these types into $self->{persistent}
343     our %PERSISTENT_TYPE = (
344     persistent_get => sub { %{ $_[1] } = (type => "persistent_get" , %{ $_[2] }) },
345     persistent_put => sub { %{ $_[1] } = (type => "persistent_put" , %{ $_[2] }) },
346     persistent_put_dir => sub { %{ $_[1] } = (type => "persistent_put_dir", %{ $_[2] }) },
347     persistent_request_modified => sub { %{ $_[1] } = (%{ $_[1] }, %{ $_[2] }) },
348     persistent_request_removed => sub { delete $_[0]{req}{$_[2]{identifier}} },
349    
350     simple_progress => sub { $_[1]{simple_progress} = $_[2] }, # get/put
351    
352     uri_generated => sub { $_[1]{uri_generated} = $_[2] }, # put
353     generated_metadata => sub { $_[1]{generated_metadata} = $_[2] }, # put
354     started_compression => sub { $_[1]{started_compression} = $_[2] }, # put
355     finished_compression => sub { $_[1]{finished_compression} = $_[2] }, # put
356     put_fetchable => sub { $_[1]{put_fetchable} = $_[2] }, # put
357     put_failed => sub { $_[1]{put_failed} = $_[2] }, # put
358     put_successful => sub { $_[1]{put_successful} = $_[2] }, # put
359    
360     sending_to_network => sub { $_[1]{sending_to_network} = $_[2] }, # get
361     compatibility_mode => sub { $_[1]{compatibility_mode} = $_[2] }, # get
362     expected_hashes => sub { $_[1]{expected_hashes} = $_[2] }, # get
363     expected_mime => sub { $_[1]{expected_mime} = $_[2] }, # get
364     expected_data_length => sub { $_[1]{expected_data_length} = $_[2] }, # get
365     get_failed => sub { $_[1]{get_failed} = $_[2] }, # get
366     data_found => sub { $_[1]{data_found} = $_[2] }, # get
367     enter_finite_cooldown => sub { $_[1]{enter_finite_cooldown} = $_[2] }, # get
368     );
369    
370     sub recv {
371     my ($self, $type, $kv, @extra) = @_;
372    
373     if (my $cb = $PERSISTENT_TYPE{$type}) {
374     my $id = $kv->{identifier};
375     my $req = $_[0]{req}{$id} ||= {};
376     $cb->($self, $req, $kv);
377 root 1.16 $self->recv (request_changed => $kv, $type, @extra);
378 root 1.11 }
379    
380     my $on = $self->{on};
381     for (0 .. $#$on) {
382     unless (my $res = $on->[$_]($self, $type, $kv, @extra)) {
383     splice @$on, $_, 1 unless defined $res;
384     return;
385     }
386     }
387    
388     if (my $cb = $self->{queue}[0]) {
389     $cb->($self, $type, $kv, @extra)
390     and shift @{ $self->{queue} };
391     } else {
392     $self->default_recv ($type, $kv, @extra);
393     }
394     }
395    
396 root 1.2 sub default_recv {
397     my ($self, $type, $kv, $rdata) = @_;
398    
399     if ($type eq "node_hello") {
400     $self->{node_hello} = $kv;
401     } elsif (exists $self->{id}{$kv->{identifier}}) {
402     $self->{id}{$kv->{identifier}}($self, $type, $kv, $rdata)
403     and delete $self->{id}{$kv->{identifier}};
404 root 1.1 }
405     }
406    
407 root 1.12 =back
408    
409     =head2 FCP REQUESTS
410    
411     The following methods implement various requests. Most of them map
412     directory to the FCP message of the same name. The added benefit of
413     these over sending requests yourself is that they handle the necessary
414     serialisation, protocol quirks, and replies.
415    
416     All of them exist in two versions, the variant shown in this manpage, and
417     a variant with an extra C<_> at the end, and an extra C<$cb> argument. The
418     version as shown is I<synchronous> - it will wait for any replies, and
419     either return the reply, or croak with an error. The underscore variant
420     returns immediately and invokes one or more callbacks or condvars later.
421    
422     For example, the call
423    
424     $info = $fcp->get_plugin_info ($name, $detailed);
425    
426     Also comes in this underscore variant:
427    
428     $fcp->get_plugin_info_ ($name, $detailed, $cb);
429    
430     You can thinbk of the underscore as a kind of continuation indicator - the
431     normal function waits and returns with the data, the C<_> indicates that
432     you pass the continuation yourself, and the continuation will be invoked
433     with the results.
434    
435     This callback/continuation argument (C<$cb>) can come in three forms itself:
436    
437     =over 4
438    
439     =item A code reference (or rather anything not matching some other alternative)
440    
441     This code reference will be invoked with the result on success. On an
442 root 1.20 error, it will invoke the C<on_failure> callback of the FCP object, or,
443     if none was defined, will die (in the event loop) with a backtrace of the
444     call site.
445 root 1.12
446     This is a popular choice, but it makes handling errors hard - make sure
447     you never generate protocol errors!
448    
449 root 1.20 If an C<on_failure> hook exists, it will be invoked with the FCP object,
450 root 1.21 the request type (the name of the method), a (textual) backtrace as
451     generated by C<Carp::longmess>, and arrayref containing the arguments from
452     the original request invocation and the error object from the server, in
453     this order, e.g.:
454 root 1.20
455     on_failure => sub {
456 root 1.21 my ($fcp, $request_type, $backtrace, $orig_args, $error_object) = @_;
457    
458     warn "FCP failure ($type), $error_object->{code_description} ($error_object->{extra_description})$backtrace";
459     exit 1;
460 root 1.20 },
461    
462 root 1.12 =item A condvar (as returned by e.g. C<< AnyEvent->condvar >>)
463    
464     When a condvar is passed, it is sent (C<< $cv->send ($results) >>) the
465     results when the request has finished. Should an error occur, the error
466     will instead result in C<< $cv->croak ($error) >>.
467    
468     This is also a popular choice.
469    
470     =item An array with two callbacks C<[$success, $failure]>
471    
472     The C<$success> callback will be invoked with the results, while the
473     C<$failure> callback will be invoked on any errors.
474    
475 root 1.20 The C<$failure> callback will be invoked with the error object from the
476     server.
477    
478 root 1.12 =item C<undef>
479    
480     This is the same thing as specifying C<sub { }> as callback, i.e. on
481 root 1.20 success, the results are ignored, while on failure, the C<on_failure> hook
482     is invoked or the module dies with a backtrace.
483 root 1.12
484     This is good for quick scripts, or when you really aren't interested in
485     the results.
486    
487     =back
488    
489     =cut
490    
491 root 1.11 our $NOP_CB = sub { };
492    
493 root 1.2 sub _txn {
494     my ($name, $sub) = @_;
495 root 1.1
496 root 1.2 *{$name} = sub {
497 root 1.12 my $cv = AE::cv;
498    
499 root 1.14 splice @_, 1, 0, $cv, sub { $cv->croak ($_[0]{extra_description}) };
500 root 1.2 &$sub;
501 root 1.11 $cv->recv
502 root 1.2 };
503 root 1.1
504 root 1.11 *{"$name\_"} = sub {
505 root 1.12 my ($ok, $err) = pop;
506    
507     if (ARRAY:: eq ref $ok) {
508     ($ok, $err) = @$ok;
509     } elsif (UNIVERSAL::isa $ok, AnyEvent::CondVar::) {
510 root 1.14 $err = sub { $ok->croak ($_[0]{extra_description}) };
511 root 1.12 } else {
512 root 1.20 my $bt = Carp::longmess "AnyEvent::FCP request $name";
513     Scalar::Util::weaken (my $self = $_[0]);
514     my $args = [@_]; shift @$args;
515 root 1.12 $err = sub {
516 root 1.20 if ($self->{on_failure}) {
517 root 1.21 $self->{on_failure}($self, $name, $args, $bt, $_[0]);
518 root 1.20 } else {
519     die "$_[0]{code_description} ($_[0]{extra_description})$bt";
520     }
521 root 1.12 };
522     }
523    
524     $ok ||= $NOP_CB;
525    
526     splice @_, 1, 0, $ok, $err;
527 root 1.2 &$sub;
528     };
529 root 1.1 }
530    
531 root 1.12 =over 4
532    
533 root 1.11 =item $peers = $fcp->list_peers ([$with_metdata[, $with_volatile]])
534 root 1.3
535     =cut
536    
537 root 1.2 _txn list_peers => sub {
538 root 1.12 my ($self, $ok, undef, $with_metadata, $with_volatile) = @_;
539 root 1.1
540 root 1.2 my @res;
541 root 1.1
542 root 1.2 $self->send_msg (list_peers =>
543     with_metadata => $with_metadata ? "true" : "false",
544     with_volatile => $with_volatile ? "true" : "false",
545     id_cb => sub {
546     my ($self, $type, $kv, $rdata) = @_;
547    
548     if ($type eq "end_list_peers") {
549 root 1.12 $ok->(\@res);
550 root 1.2 1
551     } else {
552     push @res, $kv;
553     0
554     }
555     },
556     );
557     };
558 root 1.1
559 root 1.11 =item $notes = $fcp->list_peer_notes ($node_identifier)
560 root 1.3
561     =cut
562    
563 root 1.2 _txn list_peer_notes => sub {
564 root 1.12 my ($self, $ok, undef, $node_identifier) = @_;
565 root 1.1
566 root 1.2 $self->send_msg (list_peer_notes =>
567     node_identifier => $node_identifier,
568     id_cb => sub {
569     my ($self, $type, $kv, $rdata) = @_;
570    
571 root 1.12 $ok->($kv);
572 root 1.2 1
573     },
574     );
575     };
576 root 1.1
577 root 1.11 =item $fcp->watch_global ($enabled[, $verbosity_mask])
578 root 1.3
579     =cut
580    
581 root 1.2 _txn watch_global => sub {
582 root 1.12 my ($self, $ok, $err, $enabled, $verbosity_mask) = @_;
583 root 1.1
584 root 1.2 $self->send_msg (watch_global =>
585     enabled => $enabled ? "true" : "false",
586     defined $verbosity_mask ? (verbosity_mask => $verbosity_mask+0) : (),
587     );
588 root 1.1
589 root 1.12 $ok->();
590 root 1.2 };
591 root 1.1
592 root 1.11 =item $reqs = $fcp->list_persistent_requests
593 root 1.3
594     =cut
595    
596 root 1.2 _txn list_persistent_requests => sub {
597 root 1.12 my ($self, $ok, $err) = @_;
598 root 1.1
599 root 1.10 $self->serialise (list_persistent_requests => sub {
600     my ($self, $guard) = @_;
601    
602 root 1.11 my @res;
603 root 1.1
604 root 1.10 $self->send_msg ("list_persistent_requests");
605 root 1.1
606 root 1.10 $self->on (sub {
607     my ($self, $type, $kv, $rdata) = @_;
608 root 1.2
609 root 1.10 $guard if 0;
610 root 1.2
611 root 1.10 if ($type eq "end_list_persistent_requests") {
612 root 1.12 $ok->(\@res);
613 root 1.10 return;
614 root 1.2 } else {
615 root 1.10 my $id = $kv->{identifier};
616    
617     if ($type =~ /^persistent_(get|put|put_dir)$/) {
618 root 1.11 push @res, [$type, $kv];
619 root 1.10 }
620 root 1.2 }
621 root 1.10
622     1
623     });
624     });
625 root 1.2 };
626 root 1.1
627 root 1.12 =item $sync = $fcp->modify_persistent_request ($global, $identifier[, $client_token[, $priority_class]])
628    
629     Update either the C<client_token> or C<priority_class> of a request
630     identified by C<$global> and C<$identifier>, depending on which of
631     C<$client_token> and C<$priority_class> are not C<undef>.
632 root 1.3
633     =cut
634    
635 root 1.12 _txn modify_persistent_request => sub {
636     my ($self, $ok, $err, $global, $identifier, $client_token, $priority_class) = @_;
637 root 1.1
638 root 1.12 $self->serialise ($identifier => sub {
639     my ($self, $guard) = @_;
640 root 1.2
641 root 1.12 $self->send_msg (modify_persistent_request =>
642     global => $global ? "true" : "false",
643     identifier => $identifier,
644     defined $client_token ? (client_token => $client_token ) : (),
645     defined $priority_class ? (priority_class => $priority_class) : (),
646     );
647 root 1.1
648 root 1.12 $self->on (sub {
649     my ($self, $type, $kv, @extra) = @_;
650 root 1.3
651 root 1.13 $guard if 0;
652    
653 root 1.12 if ($kv->{identifier} eq $identifier) {
654     if ($type eq "persistent_request_modified") {
655     $ok->($kv);
656     return;
657     } elsif ($type eq "protocol_error") {
658     $err->($kv);
659     return;
660     }
661     }
662 root 1.1
663 root 1.2 1
664 root 1.12 });
665     });
666 root 1.2 };
667 root 1.1
668 root 1.11 =item $info = $fcp->get_plugin_info ($name, $detailed)
669 root 1.3
670     =cut
671    
672 root 1.2 _txn get_plugin_info => sub {
673 root 1.12 my ($self, $ok, $err, $name, $detailed) = @_;
674 root 1.1
675 root 1.15 my $id = $self->identifier;
676    
677 root 1.2 $self->send_msg (get_plugin_info =>
678 root 1.15 identifier => $id,
679 root 1.2 plugin_name => $name,
680     detailed => $detailed ? "true" : "false",
681 root 1.15 );
682     $self->on (sub {
683     my ($self, $type, $kv) = @_;
684    
685     if ($kv->{identifier} eq $id) {
686     if ($type eq "get_plugin_info") {
687     $ok->($kv);
688     } else {
689     $err->($kv, $type);
690     }
691     return;
692     }
693 root 1.2
694 root 1.15 1
695     });
696 root 1.4 };
697    
698 root 1.11 =item $status = $fcp->client_get ($uri, $identifier, %kv)
699 root 1.1
700 root 1.4 %kv can contain (L<http://wiki.freenetproject.org/FCP2p0ClientGet>).
701    
702     ignore_ds, ds_only, verbosity, max_size, max_temp_size, max_retries,
703     priority_class, persistence, client_token, global, return_type,
704     binary_blob, allowed_mime_types, filename, temp_filename
705    
706     =cut
707    
708     _txn client_get => sub {
709 root 1.12 my ($self, $ok, $err, $uri, $identifier, %kv) = @_;
710 root 1.4
711 root 1.13 $self->serialise ($identifier => sub {
712     my ($self, $guard) = @_;
713    
714     $self->send_msg (client_get =>
715     %kv,
716     uri => $uri,
717     identifier => $identifier,
718     );
719    
720     $self->on (sub {
721     my ($self, $type, $kv, @extra) = @_;
722    
723     $guard if 0;
724    
725     if ($kv->{identifier} eq $identifier) {
726     if ($type eq "persistent_get") {
727     $ok->($kv);
728     return;
729     } elsif ($type eq "protocol_error") {
730     $err->($kv);
731     return;
732     }
733     }
734 root 1.12
735 root 1.13 1
736     });
737     });
738 root 1.11 };
739    
740     =item $status = $fcp->remove_request ($identifier[, $global])
741    
742     Remove the request with the given isdentifier. Returns true if successful,
743     false on error.
744    
745     =cut
746    
747     _txn remove_request => sub {
748 root 1.12 my ($self, $ok, $err, $identifier, $global) = @_;
749 root 1.11
750     $self->serialise ($identifier => sub {
751     my ($self, $guard) = @_;
752    
753     $self->send_msg (remove_request =>
754     identifier => $identifier,
755     global => $global ? "true" : "false",
756     );
757     $self->on (sub {
758     my ($self, $type, $kv, @extra) = @_;
759    
760 root 1.13 $guard if 0;
761    
762 root 1.11 if ($kv->{identifier} eq $identifier) {
763     if ($type eq "persistent_request_removed") {
764 root 1.12 $ok->(1);
765 root 1.11 return;
766     } elsif ($type eq "protocol_error") {
767 root 1.12 $err->($kv);
768 root 1.11 return;
769     }
770     }
771 root 1.4
772     1
773 root 1.11 });
774     });
775 root 1.2 };
776 root 1.1
777 root 1.11 =item ($can_read, $can_write) = $fcp->test_dda ($local_directory, $remote_directory, $want_read, $want_write))
778 root 1.9
779     The DDA test in FCP is probably the single most broken protocol - only
780     one directory test can be outstanding at any time, and some guessing and
781     heuristics are involved in mangling the paths.
782    
783     This function combines C<TestDDARequest> and C<TestDDAResponse> in one
784 root 1.11 request, handling file reading and writing as well, and tries very hard to
785     do the right thing.
786    
787     Both C<$local_directory> and C<$remote_directory> must specify the same
788     directory - C<$local_directory> is the directory path on the client (where
789     L<AnyEvent::FCP> runs) and C<$remote_directory> is the directory path on
790     the server (where the freenet node runs). When both are running on the
791     same node, the paths are generally identical.
792    
793     C<$want_read> and C<$want_write> should be set to a true value when you
794     want to read (get) files or write (put) files, respectively.
795    
796     On error, an exception is thrown. Otherwise, C<$can_read> and
797     C<$can_write> indicate whether you can reaqd or write to freenet via the
798     directory.
799 root 1.9
800     =cut
801    
802     _txn test_dda => sub {
803 root 1.12 my ($self, $ok, $err, $local, $remote, $want_read, $want_write) = @_;
804 root 1.9
805     $self->serialise (test_dda => sub {
806     my ($self, $guard) = @_;
807    
808     $self->send_msg (test_dda_request =>
809     directory => $remote,
810     want_read_directory => $want_read ? "true" : "false",
811     want_write_directory => $want_write ? "true" : "false",
812     );
813     $self->on (sub {
814 root 1.10 my ($self, $type, $kv) = @_;
815 root 1.9
816     if ($type eq "test_dda_reply") {
817     # the filenames are all relative to the server-side directory,
818     # which might or might not match $remote anymore, so we
819     # need to rewrite the paths to be relative to $local
820     for my $k (qw(read_filename write_filename)) {
821     my $f = $kv->{$k};
822     for my $dir ($kv->{directory}, $remote) {
823     if ($dir eq substr $f, 0, length $dir) {
824     substr $f, 0, 1 + length $dir, "";
825     $kv->{$k} = $f;
826     last;
827     }
828     }
829     }
830    
831     my %response = (directory => $remote);
832    
833     if (length $kv->{read_filename}) {
834     if (open my $fh, "<:raw", "$local/$kv->{read_filename}") {
835     sysread $fh, my $buf, -s $fh;
836     $response{read_content} = $buf;
837     }
838     }
839    
840     if (length $kv->{write_filename}) {
841     if (open my $fh, ">:raw", "$local/$kv->{write_filename}") {
842     syswrite $fh, $kv->{content_to_write};
843     }
844     }
845    
846     $self->send_msg (test_dda_response => %response);
847    
848     $self->on (sub {
849 root 1.10 my ($self, $type, $kv) = @_;
850 root 1.9
851     $guard if 0; # reference
852    
853     if ($type eq "test_dda_complete") {
854 root 1.12 $ok->(
855 root 1.9 $kv->{read_directory_allowed} eq "true",
856     $kv->{write_directory_allowed} eq "true",
857     );
858     } elsif ($type eq "protocol_error" && $kv->{identifier} eq $remote) {
859 root 1.12 $err->($kv->{extra_description});
860 root 1.9 return;
861     }
862    
863     1
864     });
865    
866     return;
867     } elsif ($type eq "protocol_error" && $kv->{identifier} eq $remote) {
868 root 1.12 $err->($kv);
869 root 1.9 return;
870     }
871    
872     1
873     });
874     });
875     };
876    
877 root 1.1 =back
878    
879 root 1.11 =head2 REQUEST CACHE
880    
881     The C<AnyEvent::FCP> class keeps a request cache, where it caches all
882     information from requests.
883    
884     For these messages, it will store a copy of the key-value pairs, together with a C<type> slot,
885     in C<< $fcp->{req}{$identifier} >>:
886    
887     persistent_get
888     persistent_put
889     persistent_put_dir
890    
891     This message updates the stored data:
892    
893     persistent_request_modified
894    
895     This message will remove this entry:
896    
897     persistent_request_removed
898    
899     These messages get merged into the cache entry, under their
900     type, i.e. a C<simple_progress> message will be stored in C<<
901     $fcp->{req}{$identifier}{simple_progress} >>:
902    
903     simple_progress # get/put
904    
905     uri_generated # put
906     generated_metadata # put
907     started_compression # put
908     finished_compression # put
909     put_failed # put
910     put_fetchable # put
911     put_successful # put
912    
913     sending_to_network # get
914     compatibility_mode # get
915     expected_hashes # get
916     expected_mime # get
917     expected_data_length # get
918     get_failed # get
919     data_found # get
920     enter_finite_cooldown # get
921    
922     In addition, an event (basically a fake message) of type C<request_changed> is generated
923     on every change, which will be called as C<< $cb->($fcp, $kv, $type) >>, where C<$type>
924     is the type of the original message triggering the change,
925    
926     To fill this cache with the global queue and keep it updated,
927     call C<watch_global> to subscribe to updates, followed by
928     C<list_persistent_requests_sync>.
929    
930     $fcp->watch_global_sync_; # do not wait
931     $fcp->list_persistent_requests; # wait
932    
933     To get a better idea of what is stored in the cache, here is an example of
934     what might be stored in C<< $fcp->{req}{"Frost-gpl.txt"} >>:
935    
936     {
937     identifier => "Frost-gpl.txt",
938     uri => 'CHK@Fnx5kzdrfE,EImdzaVyEWl,AAIC--8/gpl.txt',
939     binary_blob => "false",
940     global => "true",
941     max_retries => -1,
942     max_size => 9223372036854775807,
943     persistence => "forever",
944     priority_class => 3,
945     real_time => "false",
946     return_type => "direct",
947     started => "true",
948     type => "persistent_get",
949     verbosity => 2147483647,
950     sending_to_network => {
951     identifier => "Frost-gpl.txt",
952     global => "true",
953     },
954     compatibility_mode => {
955     identifier => "Frost-gpl.txt",
956     definitive => "true",
957     dont_compress => "false",
958     global => "true",
959     max => "COMPAT_1255",
960     min => "COMPAT_1255",
961     },
962     expected_hashes => {
963     identifier => "Frost-gpl.txt",
964     global => "true",
965     hashes => {
966     ed2k => "d83596f5ee3b7...",
967     md5 => "e0894e4a2a6...",
968     sha1 => "...",
969     sha256 => "...",
970     sha512 => "...",
971     tth => "...",
972     },
973     },
974     expected_mime => {
975     identifier => "Frost-gpl.txt",
976     global => "true",
977     metadata => { content_type => "application/rar" },
978     },
979     expected_data_length => {
980     identifier => "Frost-gpl.txt",
981     data_length => 37576,
982     global => "true",
983     },
984     simple_progress => {
985     identifier => "Frost-gpl.txt",
986     failed => 0,
987     fatally_failed => 0,
988     finalized_total => "true",
989     global => "true",
990     last_progress => 1438639282628,
991     required => 372,
992     succeeded => 102,
993     total => 747,
994     },
995     data_found => {
996     identifier => "Frost-gpl.txt",
997     completion_time => 1438663354026,
998     data_length => 37576,
999     global => "true",
1000     metadata => { content_type => "image/jpeg" },
1001     startup_time => 1438657196167,
1002     },
1003     }
1004    
1005 root 1.3 =head1 EXAMPLE PROGRAM
1006    
1007     use AnyEvent::FCP;
1008    
1009     my $fcp = new AnyEvent::FCP;
1010    
1011     # let us look at the global request list
1012 root 1.11 $fcp->watch_global_ (1);
1013 root 1.3
1014     # list them, synchronously
1015 root 1.11 my $req = $fcp->list_persistent_requests;
1016 root 1.3
1017     # go through all requests
1018 root 1.11 TODO
1019 root 1.3 for my $req (values %$req) {
1020     # skip jobs not directly-to-disk
1021     next unless $req->{return_type} eq "disk";
1022     # skip jobs not issued by FProxy
1023     next unless $req->{identifier} =~ /^FProxy:/;
1024    
1025     if ($req->{data_found}) {
1026     # file has been successfully downloaded
1027    
1028     ... move the file away
1029     (left as exercise)
1030    
1031     # remove the request
1032    
1033     $fcp->remove_request (1, $req->{identifier});
1034     } elsif ($req->{get_failed}) {
1035     # request has failed
1036     if ($req->{get_failed}{code} == 11) {
1037     # too many path components, should restart
1038     } else {
1039     # other failure
1040     }
1041     } else {
1042     # modify priorities randomly, to improve download rates
1043     $fcp->modify_persistent_request (1, $req->{identifier}, undef, int 6 - 5 * (rand) ** 1.7)
1044     if 0.1 > rand;
1045     }
1046     }
1047    
1048     # see if the dummy plugin is loaded, to ensure all previous requests have finished.
1049     $fcp->get_plugin_info_sync ("dummy");
1050    
1051 root 1.1 =head1 SEE ALSO
1052    
1053 root 1.2 L<http://wiki.freenetproject.org/FreenetFCPSpec2Point0>, L<Net::FCP>.
1054 root 1.1
1055     =head1 BUGS
1056    
1057     =head1 AUTHOR
1058    
1059     Marc Lehmann <schmorp@schmorp.de>
1060     http://home.schmorp.de/
1061    
1062     =cut
1063    
1064     1
1065