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