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