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