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