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