ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-FCP/FCP.pm
Revision: 1.18
Committed: Thu Dec 3 19:07:57 2015 UTC (8 years, 5 months ago) by root
Branch: MAIN
CVS Tags: rel-0_4
Changes since 1.17: +7 -2 lines
Log Message:
0.4

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