File Coverage

blib/lib/AnyEvent/FCP.pm
Criterion Covered Total %
statement 23 149 15.4
branch 0 42 0.0
condition 0 23 0.0
subroutine 7 30 23.3
pod 1 11 9.0
total 31 255 12.1


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             AnyEvent::FCP - freenet client protocol 2.0
4              
5             =head1 SYNOPSIS
6              
7             use AnyEvent::FCP;
8              
9             my $fcp = new AnyEvent::FCP;
10              
11             # transactions return condvars
12             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              
18             =head1 DESCRIPTION
19              
20             This module implements the freenet client protocol version 2.0, as used by
21             freenet 0.7. See L for the earlier freenet 0.5 version.
22              
23             See L for a
24             description of what the messages do.
25              
26             The module uses L to find a suitable event module.
27              
28             Only very little is implemented, ask if you need more, and look at the
29             example program later in this section.
30              
31             =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             $fcp->watch_global (1, 0);
41             my $req = $fcp->list_persistent_requests;
42              
43             TODO
44             for my $req (values %$req) {
45             if ($req->{filename} =~ /a/) {
46             $fcp->modify_persistent_request (1, $req->{identifier}, undef, 0);
47             }
48             }
49              
50             =head2 IMPORT TAGS
51              
52             Nothing much can be "imported" from this module right now.
53              
54             =head1 THE AnyEvent::FCP CLASS
55              
56             =over 4
57              
58             =cut
59              
60             package AnyEvent::FCP;
61              
62 1     1   821 use common::sense;
  1         7  
  1         3  
63              
64 1     1   37 use Carp;
  1         2  
  1         63  
65              
66             our $VERSION = 0.5;
67              
68 1     1   3 use Scalar::Util ();
  1         4  
  1         11  
69              
70 1     1   832 use AnyEvent;
  1         3593  
  1         25  
71 1     1   619 use AnyEvent::Handle;
  1         14042  
  1         27  
72 1     1   4 use AnyEvent::Util ();
  1         1  
  1         3811  
73              
74             our %TOLC; # tolc cache
75              
76             sub touc($) {
77 0     0 0 0 local $_ = shift;
78 0         0 1 while s/((?:^|_)(?:svk|chk|uri|fcp|ds|mime|dda)(?:_|$))/\U$1/;
79 0         0 s/(?:^|_)(.)/\U$1/g;
80 0         0 $_
81             }
82              
83             sub tolc($) {
84 0     0 0 0 local $_ = shift;
85 0         0 1 while s/(SVK|CHK|URI|FCP|DS|MIME|DDA)([^_])/$1\_$2/;
86 0         0 1 while s/([^_])(SVK|CHK|URI|FCP|DS|MIME|DDA)/$1\_$2/;
87 0         0 s/(?<=[a-z])(?=[A-Z])/_/g;
88             lc
89 0         0 }
90              
91             =item $fcp = new AnyEvent::FCP key => value...;
92              
93             Create a new FCP connection to the given host and port (default
94             127.0.0.1:9481, or the environment variables C and C).
95              
96             If no C was specified, then AnyEvent::FCP will generate a
97             (hopefully) unique client name for you.
98              
99             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             =item on_eof => $callback->($fcp)
132              
133             Invoked when the underlying L signals EOF, currently
134             regardless of whether the EOF was expected or not.
135              
136             =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             =item on_failure => $callback->($fcp, $type, $backtrace, $args, $error)
142              
143             Invoked when an FCP request fails that didn't have a failure callback. See
144             L for details.
145              
146             =back
147              
148             =cut
149              
150             sub new {
151 0     0 1 0 my $class = shift;
152              
153 0         0 my $rand = join "", map chr 0x21 + rand 94, 1..40; # ~ 262 bits entropy
154              
155             my $self = bless {
156             host => $ENV{FREDHOST} || "127.0.0.1",
157 0   0     0 port => $ENV{FREDPORT} || 9481,
      0        
158             timeout => 3600 * 2,
159             keepalive => 9 * 60,
160             name => time.rand.rand.rand, # lame
161             @_,
162             queue => [],
163             req => {},
164             prefix => "..:aefcpid:$rand:",
165             idseq => "a0",
166             }, $class;
167              
168             {
169 0         0 Scalar::Util::weaken (my $self = $self);
  0         0  
170              
171             $self->{kw} = AE::timer $self->{keepalive}, $self->{keepalive}, sub {
172 0     0   0 $self->{hdl}->push_write ("\n");
173 0         0 };
174              
175 0         0 our $ENDMESSAGE = qr<\012(EndMessage|Data)\012>;
176              
177             # these are declared here for performance reasons
178 0         0 my ($k, $v, $type);
179 0         0 my $rdata;
180            
181             my $on_read = sub {
182 0     0   0 my ($hdl) = @_;
183              
184             # we only carve out whole messages here
185 0         0 while ($hdl->{rbuf} =~ /\012(EndMessage|Data)\012/) {
186             # remember end marker
187 0 0 0     0 $rdata = $1 eq "Data"
188             or $1 eq "EndMessage"
189             or return $self->fatal ("protocol error, expected message end, got $1\n");
190              
191 0         0 my @lines = split /\012/, substr $hdl->{rbuf}, 0, $-[0];
192              
193 0         0 substr $hdl->{rbuf}, 0, $+[0], ""; # remove pkg
194              
195 0         0 $type = shift @lines;
196 0   0     0 $type = ($TOLC{$type} ||= tolc $type);
197              
198 0         0 my %kv;
199              
200 0         0 for (@lines) {
201 0         0 ($k, $v) = split /=/, $_, 2;
202 0   0     0 $k = ($TOLC{$k} ||= tolc $k);
203            
204 0 0       0 if ($k =~ /\./) {
205             # generic, slow case
206 0         0 my @k = split /\./, $k;
207 0         0 my $ro = \\%kv;
208              
209 0         0 while (@k) {
210 0         0 $k = shift @k;
211 0 0       0 if ($k =~ /^\d+$/) {
212 0         0 $ro = \$$ro->[$k];
213             } else {
214 0         0 $ro = \$$ro->{$k};
215             }
216             }
217              
218 0         0 $$ro = $v;
219              
220 0         0 next;
221             }
222              
223             # special comon case, for performance only
224 0         0 $kv{$k} = $v;
225             }
226            
227 0 0       0 if ($rdata) {
228             $_[0]->push_read (chunk => delete $kv{data_length}, sub {
229 0         0 $rdata = \$_[1];
230 0         0 $self->recv ($type, \%kv, $rdata);
231 0         0 });
232              
233 0         0 last; # do not tgry to parse more messages
234             } else {
235 0         0 $self->recv ($type, \%kv);
236             }
237             }
238 0         0 };
239              
240             $self->{hdl} = new AnyEvent::Handle
241             connect => [$self->{host} => $self->{port}],
242             timeout => $self->{timeout},
243             on_read => $on_read,
244             on_eof => sub {
245 0 0   0   0 if ($self->{on_eof}) {
246 0         0 $self->{on_eof}($self);
247             } else {
248 0         0 $self->fatal ("EOF");
249             }
250             },
251             on_error => sub {
252 0     0   0 $self->fatal ($_[2]);
253             },
254 0         0 ;
255              
256 0         0 Scalar::Util::weaken ($self->{hdl}{fcp} = $self);
257             }
258              
259             $self->send_msg (client_hello =>
260             name => $self->{name},
261 0         0 expected_version => "2.0",
262             );
263              
264 0         0 $self
265             }
266              
267             sub fatal {
268 0     0 0 0 my ($self, $msg) = @_;
269              
270 0         0 $self->{hdl}->shutdown;
271 0         0 delete $self->{kw};
272            
273 0 0       0 if ($self->{on_error}) {
274 0         0 $self->{on_error}->($self, $msg);
275             } else {
276 0         0 die $msg;
277             }
278             }
279              
280             sub identifier {
281             $_[0]{prefix} . ++$_[0]{idseq}
282 0     0 0 0 }
283              
284             sub send_msg {
285 0     0 0 0 my ($self, $type, %kv) = @_;
286              
287 0         0 my $data = delete $kv{data};
288              
289 0 0       0 if (exists $kv{id_cb}) {
290 0   0     0 my $id = $kv{identifier} ||= $self->identifier;
291 0         0 $self->{id}{$id} = delete $kv{id_cb};
292             }
293              
294 0         0 my $msg = (touc $type) . "\012"
295             . join "", map +(touc $_) . "=$kv{$_}\012", keys %kv;
296              
297             sub id {
298 0     0 0 0 my ($self) = @_;
299              
300              
301             }
302              
303 0 0       0 if (defined $data) {
304 0         0 $msg .= "DataLength=" . (length $data) . "\012"
305             . "Data\012$data";
306             } else {
307 0         0 $msg .= "EndMessage\012";
308             }
309              
310 0         0 $self->{hdl}->push_write ($msg);
311             }
312              
313             sub on {
314 0     0 0 0 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 0         0 push @{ $self->{on} }, $cb;
  0         0  
321             }
322              
323             sub _push_queue {
324 0     0   0 my ($self, $queue) = @_;
325              
326 0         0 shift @$queue;
327 0     0   0 $queue->[0]($self, AnyEvent::Util::guard { $self->_push_queue ($queue) })
328 0 0       0 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 0     0 0 0 my ($self, $type, $cb) = @_;
335              
336 0   0     0 my $queue = $self->{serialise}{$type} ||= [];
337 0         0 push @$queue, $cb;
338 0     0   0 $cb->($self, AnyEvent::Util::guard { $self->_push_queue ($queue) })
339 0 0       0 unless $#$queue;
340             }
341              
342             # 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 0     0 0 0 my ($self, $type, $kv, @extra) = @_;
372              
373 0 0       0 if (my $cb = $PERSISTENT_TYPE{$type}) {
374 0         0 my $id = $kv->{identifier};
375 0   0     0 my $req = $_[0]{req}{$id} ||= {};
376 0         0 $cb->($self, $req, $kv);
377 0         0 $self->recv (request_changed => $kv, $type, @extra);
378             }
379              
380 0         0 my $on = $self->{on};
381 0         0 for (0 .. $#$on) {
382 0 0       0 unless (my $res = $on->[$_]($self, $type, $kv, @extra)) {
383 0 0       0 splice @$on, $_, 1 unless defined $res;
384 0         0 return;
385             }
386             }
387              
388 0 0       0 if (my $cb = $self->{queue}[0]) {
389             $cb->($self, $type, $kv, @extra)
390 0 0       0 and shift @{ $self->{queue} };
  0         0  
391             } else {
392 0         0 $self->default_recv ($type, $kv, @extra);
393             }
394             }
395              
396             sub default_recv {
397 0     0 0 0 my ($self, $type, $kv, $rdata) = @_;
398              
399 0 0       0 if ($type eq "node_hello") {
    0          
400 0         0 $self->{node_hello} = $kv;
401             } elsif (exists $self->{id}{$kv->{identifier}}) {
402             $self->{id}{$kv->{identifier}}($self, $type, $kv, $rdata)
403 0 0       0 and delete $self->{id}{$kv->{identifier}};
404             }
405             }
406              
407             =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 - 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             error, it will invoke the C 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              
446             This is a popular choice, but it makes handling errors hard - make sure
447             you never generate protocol errors!
448              
449             If an C hook exists, it will be invoked with the FCP object,
450             the request type (the name of the method), a (textual) backtrace as
451             generated by C, 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              
455             on_failure => sub {
456             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             },
461              
462             =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             The C<$failure> callback will be invoked with the error object from the
476             server.
477              
478             =item C
479              
480             This is the same thing as specifying C as callback, i.e. on
481             success, the results are ignored, while on failure, the C hook
482             is invoked or the module dies with a backtrace.
483              
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             our $NOP_CB = sub { };
492              
493             sub _txn {
494 9     9   10 my ($name, $sub) = @_;
495              
496 9         24 *{$name} = sub {
497 0     0   0 my $cv = AE::cv;
498              
499 0     0   0 splice @_, 1, 0, $cv, sub { $cv->croak ($_[0]{extra_description}) };
  0         0  
500 0         0 &$sub;
501 0         0 $cv->recv
502 9         12 };
503              
504 9         17 *{"$name\_"} = sub {
505 0     0     my ($ok, $err) = pop;
506              
507 0 0         if (ARRAY:: eq ref $ok) {
    0          
508 0           ($ok, $err) = @$ok;
509             } elsif (UNIVERSAL::isa $ok, AnyEvent::CondVar::) {
510 0     0     $err = sub { $ok->croak ($_[0]{extra_description}) };
  0            
511             } else {
512 0           my $bt = Carp::longmess "AnyEvent::FCP request $name";
513 0           Scalar::Util::weaken (my $self = $_[0]);
514 0           my $args = [@_]; shift @$args;
  0            
515             $err = sub {
516 0 0   0     if ($self->{on_failure}) {
517 0           $self->{on_failure}($self, $name, $args, $bt, $_[0]);
518             } else {
519 0           die "$_[0]{code_description} ($_[0]{extra_description})$bt";
520             }
521 0           };
522             }
523              
524 0   0       $ok ||= $NOP_CB;
525              
526 0           splice @_, 1, 0, $ok, $err;
527 0           &$sub;
528 9         19 };
529             }
530              
531             =over 4
532              
533             =item $peers = $fcp->list_peers ([$with_metdata[, $with_volatile]])
534              
535             =cut
536              
537             _txn list_peers => sub {
538             my ($self, $ok, undef, $with_metadata, $with_volatile) = @_;
539              
540             my @res;
541              
542             $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             $ok->(\@res);
550             1
551             } else {
552             push @res, $kv;
553             0
554             }
555             },
556             );
557             };
558              
559             =item $notes = $fcp->list_peer_notes ($node_identifier)
560              
561             =cut
562              
563             _txn list_peer_notes => sub {
564             my ($self, $ok, undef, $node_identifier) = @_;
565              
566             $self->send_msg (list_peer_notes =>
567             node_identifier => $node_identifier,
568             id_cb => sub {
569             my ($self, $type, $kv, $rdata) = @_;
570              
571             $ok->($kv);
572             1
573             },
574             );
575             };
576              
577             =item $fcp->watch_global ($enabled[, $verbosity_mask])
578              
579             =cut
580              
581             _txn watch_global => sub {
582             my ($self, $ok, $err, $enabled, $verbosity_mask) = @_;
583              
584             $self->send_msg (watch_global =>
585             enabled => $enabled ? "true" : "false",
586             defined $verbosity_mask ? (verbosity_mask => $verbosity_mask+0) : (),
587             );
588              
589             $ok->();
590             };
591              
592             =item $reqs = $fcp->list_persistent_requests
593              
594             =cut
595              
596             _txn list_persistent_requests => sub {
597             my ($self, $ok, $err) = @_;
598              
599             $self->serialise (list_persistent_requests => sub {
600             my ($self, $guard) = @_;
601              
602             my @res;
603              
604             $self->send_msg ("list_persistent_requests");
605              
606             $self->on (sub {
607             my ($self, $type, $kv, $rdata) = @_;
608              
609             $guard if 0;
610              
611             if ($type eq "end_list_persistent_requests") {
612             $ok->(\@res);
613             return;
614             } else {
615             my $id = $kv->{identifier};
616              
617             if ($type =~ /^persistent_(get|put|put_dir)$/) {
618             push @res, [$type, $kv];
619             }
620             }
621              
622             1
623             });
624             });
625             };
626              
627             =item $sync = $fcp->modify_persistent_request ($global, $identifier[, $client_token[, $priority_class]])
628              
629             Update either the C or C 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.
632              
633             =cut
634              
635             _txn modify_persistent_request => sub {
636             my ($self, $ok, $err, $global, $identifier, $client_token, $priority_class) = @_;
637              
638             $self->serialise ($identifier => sub {
639             my ($self, $guard) = @_;
640              
641             $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              
648             $self->on (sub {
649             my ($self, $type, $kv, @extra) = @_;
650              
651             $guard if 0;
652              
653             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              
663             1
664             });
665             });
666             };
667              
668             =item $info = $fcp->get_plugin_info ($name, $detailed)
669              
670             =cut
671              
672             _txn get_plugin_info => sub {
673             my ($self, $ok, $err, $name, $detailed) = @_;
674              
675             my $id = $self->identifier;
676              
677             $self->send_msg (get_plugin_info =>
678             identifier => $id,
679             plugin_name => $name,
680             detailed => $detailed ? "true" : "false",
681             );
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              
694             1
695             });
696             };
697              
698             =item $status = $fcp->client_get ($uri, $identifier, %kv)
699              
700             %kv can contain (L).
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             my ($self, $ok, $err, $uri, $identifier, %kv) = @_;
710              
711             $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              
735             1
736             });
737             });
738             };
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             my ($self, $ok, $err, $identifier, $global) = @_;
749              
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             $guard if 0;
761              
762             if ($kv->{identifier} eq $identifier) {
763             if ($type eq "persistent_request_removed") {
764             $ok->(1);
765             return;
766             } elsif ($type eq "protocol_error") {
767             $err->($kv);
768             return;
769             }
770             }
771              
772             1
773             });
774             });
775             };
776              
777             =item ($can_read, $can_write) = $fcp->test_dda ($local_directory, $remote_directory, $want_read, $want_write))
778              
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 and C in one
784             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 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              
800             =cut
801              
802             _txn test_dda => sub {
803             my ($self, $ok, $err, $local, $remote, $want_read, $want_write) = @_;
804              
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             my ($self, $type, $kv) = @_;
815              
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             my ($self, $type, $kv) = @_;
850              
851             $guard if 0; # reference
852              
853             if ($type eq "test_dda_complete") {
854             $ok->(
855             $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             $err->($kv->{extra_description});
860             return;
861             }
862              
863             1
864             });
865              
866             return;
867             } elsif ($type eq "protocol_error" && $kv->{identifier} eq $remote) {
868             $err->($kv);
869             return;
870             }
871              
872             1
873             });
874             });
875             };
876              
877             =back
878              
879             =head2 REQUEST CACHE
880              
881             The C 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 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 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 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 to subscribe to updates, followed by
928             C.
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             =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             $fcp->watch_global_ (1);
1013              
1014             # list them, synchronously
1015             my $req = $fcp->list_persistent_requests;
1016              
1017             # go through all requests
1018             TODO
1019             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             =head1 SEE ALSO
1052              
1053             L, L.
1054              
1055             =head1 BUGS
1056              
1057             =head1 AUTHOR
1058              
1059             Marc Lehmann
1060             http://home.schmorp.de/
1061              
1062             =cut
1063              
1064             1
1065