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, 7 months ago) by root
Branch: MAIN
CVS Tags: HEAD
Changes since 1.27: +2 -8 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 =head1 NAME
2
3 AnyEvent::FCP - freenet client protocol 2.0
4
5 =head1 SYNOPSIS
6
7 use AnyEvent::FCP;
8
9 my $fcp = new AnyEvent::FCP;
10
11 # transactions return condvars
12 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
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 See L<https://wiki.freenetproject.org/FCP> for a description of what the
24 messages do.
25
26 The module uses L<AnyEvent> to find a suitable event module.
27
28 Only very little is implemented, ask if you need more, and look at the
29 example program later in this section.
30
31 =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 $fcp->watch_global (1, 0);
41 my $req = $fcp->list_persistent_requests;
42
43 TODO
44 for my $req (values %$req) {
45 if ($req->{filename} =~ /a/) {
46 $fcp->modify_persistent_request (1, $req->{identifier}, undef, 0);
47 }
48 }
49
50 =head2 IMPORT TAGS
51
52 Nothing much can be "imported" from this module right now.
53
54 =head1 THE AnyEvent::FCP CLASS
55
56 =over 4
57
58 =cut
59
60 package AnyEvent::FCP;
61
62 use common::sense;
63
64 use Carp;
65
66 our $VERSION = 0.5;
67
68 use Scalar::Util ();
69
70 use AnyEvent;
71 use AnyEvent::Handle;
72 use AnyEvent::Util ();
73
74 our %TOLC; # tolc cache
75
76 sub touc($) {
77 local $_ = shift;
78 1 while s/((?:^|_)(?:svk|chk|uri|fcp|ds|mime|dda)(?:_|$))/\U$1/;
79 s/(?:^|_)(.)/\U$1/g;
80 $_
81 }
82
83 sub tolc($) {
84 local $_ = shift;
85 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 s/(?<=[a-z])(?=[A-Z])/_/g;
88 lc
89 }
90
91 =item $fcp = new AnyEvent::FCP key => value...;
92
93 Create a new FCP connection to the given host and port (default
94 127.0.0.1:9481, or the environment variables C<FREDHOST> and C<FREDPORT>).
95
96 If no C<name> was specified, then AnyEvent::FCP will generate a
97 (hopefully) unique client name for you.
98
99 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 =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 =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 =item on_failure => $callback->($fcp, $type, $args, $backtrace, $error)
142
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 =cut
149
150 sub new {
151 my $class = shift;
152
153 my $rand = join "", map chr 0x21 + rand 94, 1..40; # ~ 262 bits entropy
154
155 my $self = bless {
156 host => $ENV{FREDHOST} || "127.0.0.1",
157 port => $ENV{FREDPORT} || 9481,
158 timeout => 3600 * 2,
159 keepalive => 9 * 60,
160 name => time.rand.rand.rand, # lame
161 @_,
162 queue => [],
163 req => {},
164 prefix => "..:aefcpid:$rand:",
165 idseq => "a0",
166 }, $class;
167
168 {
169 Scalar::Util::weaken (my $self = $self);
170
171 $self->{kw} = AE::timer $self->{keepalive}, $self->{keepalive}, sub {
172 $self->{hdl}->push_write ("\n");
173 };
174
175 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 or return $self->fatal ("protocol error, expected message end, got $1\n");
190
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 $self->{hdl} = new AnyEvent::Handle
241 connect => [$self->{host} => $self->{port}],
242 timeout => $self->{timeout},
243 on_read => $on_read,
244 on_eof => sub {
245 if ($self->{on_eof}) {
246 $self->{on_eof}($self);
247 } else {
248 $self->fatal ("EOF");
249 }
250 },
251 on_error => sub {
252 $self->fatal ($_[2]);
253 },
254 ;
255
256 Scalar::Util::weaken ($self->{hdl}{fcp} = $self);
257 }
258
259 $self->send_msg (client_hello =>
260 name => $self->{name},
261 expected_version => "2.0",
262 );
263
264 $self
265 }
266
267 sub fatal {
268 my ($self, $msg) = @_;
269
270 $self->{hdl}->push_shutdown if $self->{hdl};
271 delete $self->{kw};
272
273 if ($self->{on_error}) {
274 $self->{on_error}->($self, $msg);
275 } else {
276 die "AnyEvent::FCP($self->{host}:$self->{port}): $msg";
277 }
278 }
279
280 sub identifier {
281 $_[0]{prefix} . ++$_[0]{idseq}
282 }
283
284 sub send_msg {
285 my ($self, $type, %kv) = @_;
286
287 my $data = delete $kv{data};
288
289 if (exists $kv{id_cb}) {
290 my $id = $kv{identifier} ||= $self->identifier;
291 $self->{id}{$id} = delete $kv{id_cb};
292 }
293
294 my $msg = (touc $type) . "\012"
295 . join "", map +(touc $_) . "=$kv{$_}\012", keys %kv;
296
297 if (defined $data) {
298 $msg .= "DataLength=" . (length $data) . "\012"
299 . "Data\012$data";
300 } else {
301 $msg .= "EndMessage\012";
302 }
303
304 $self->{hdl}->push_write ($msg);
305 }
306
307 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 # 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 $self->recv (request_changed => $kv, $type, @extra);
372 }
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 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 }
399 }
400
401 =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 You can think of the underscore as a kind of continuation indicator - the
425 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 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
440 This is a popular choice, but it makes handling errors hard - make sure
441 you never generate protocol errors!
442
443 In the failure case, if an C<on_failure> hook exists, it will be invoked
444 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
449 on_failure => sub {
450 my ($fcp, $request_type, $orig_args, $backtrace, $error_object) = @_;
451
452 warn "FCP failure ($type @$args), $error_object->{code_description} ($error_object->{extra_description})$backtrace";
453 exit 1;
454 },
455
456 =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 The C<$failure> callback will be invoked with the error object from the
470 server.
471
472 =item C<undef>
473
474 This is the same thing as specifying C<sub { }> as callback, i.e. on
475 success, the results are ignored, while on failure, the C<on_failure> hook
476 is invoked or the module dies with a backtrace.
477
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 our $NOP_CB = sub { };
486
487 sub _txn {
488 my ($name, $sub) = @_;
489
490 *{$name} = sub {
491 my $cv = AE::cv;
492
493 splice @_, 1, 0, $cv, sub { $cv->croak ($_[0]{extra_description}) };
494 &$sub;
495 $cv->recv
496 };
497
498 *{"$name\_"} = sub {
499 my ($ok, $err) = pop;
500
501 if (ARRAY:: eq ref $ok) {
502 ($ok, $err) = @$ok;
503 } elsif (UNIVERSAL::isa $ok, AnyEvent::CondVar::) {
504 $err = sub { $ok->croak ($_[0]{extra_description}) };
505 } else {
506 my $bt = Carp::longmess "AnyEvent::FCP request $name";
507 Scalar::Util::weaken (my $self = $_[0]);
508 my $args = [@_]; shift @$args;
509 $err = sub {
510 if ($self->{on_failure}) {
511 $self->{on_failure}($self, $name, $args, $bt, $_[0]);
512 } else {
513 die "$_[0]{code_description} ($_[0]{extra_description})$bt";
514 }
515 };
516 }
517
518 $ok ||= $NOP_CB;
519
520 splice @_, 1, 0, $ok, $err;
521 &$sub;
522 };
523 }
524
525 =over 4
526
527 =item $peers = $fcp->list_peers ([$with_metdata[, $with_volatile]])
528
529 =cut
530
531 _txn list_peers => sub {
532 my ($self, $ok, undef, $with_metadata, $with_volatile) = @_;
533
534 my @res;
535
536 $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 $ok->(\@res);
544 1
545 } else {
546 push @res, $kv;
547 0
548 }
549 },
550 );
551 };
552
553 =item $notes = $fcp->list_peer_notes ($node_identifier)
554
555 =cut
556
557 _txn list_peer_notes => sub {
558 my ($self, $ok, undef, $node_identifier) = @_;
559
560 $self->send_msg (list_peer_notes =>
561 node_identifier => $node_identifier,
562 id_cb => sub {
563 my ($self, $type, $kv, $rdata) = @_;
564
565 $ok->($kv);
566 1
567 },
568 );
569 };
570
571 =item $fcp->watch_global ($enabled[, $verbosity_mask])
572
573 =cut
574
575 _txn watch_global => sub {
576 my ($self, $ok, $err, $enabled, $verbosity_mask) = @_;
577
578 $self->send_msg (watch_global =>
579 enabled => $enabled ? "true" : "false",
580 defined $verbosity_mask ? (verbosity_mask => $verbosity_mask+0) : (),
581 );
582
583 $ok->();
584 };
585
586 =item $reqs = $fcp->list_persistent_requests
587
588 =cut
589
590 _txn list_persistent_requests => sub {
591 my ($self, $ok, $err) = @_;
592
593 $self->serialise (list_persistent_requests => sub {
594 my ($self, $guard) = @_;
595
596 my @res;
597
598 $self->send_msg ("list_persistent_requests");
599
600 $self->on (sub {
601 my ($self, $type, $kv, $rdata) = @_;
602
603 $guard if 0;
604
605 if ($type eq "end_list_persistent_requests") {
606 $ok->(\@res);
607 return;
608 } else {
609 my $id = $kv->{identifier};
610
611 if ($type =~ /^persistent_(get|put|put_dir)$/) {
612 push @res, [$type, $kv];
613 }
614 }
615
616 1
617 });
618 });
619 };
620
621 =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
627 =cut
628
629 _txn modify_persistent_request => sub {
630 my ($self, $ok, $err, $global, $identifier, $client_token, $priority_class) = @_;
631
632 $self->serialise ($identifier => sub {
633 my ($self, $guard) = @_;
634
635 $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
642 $self->on (sub {
643 my ($self, $type, $kv, @extra) = @_;
644
645 $guard if 0;
646
647 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
657 1
658 });
659 });
660 };
661
662 =item $info = $fcp->get_plugin_info ($name, $detailed)
663
664 =cut
665
666 _txn get_plugin_info => sub {
667 my ($self, $ok, $err, $name, $detailed) = @_;
668
669 my $id = $self->identifier;
670
671 $self->send_msg (get_plugin_info =>
672 identifier => $id,
673 plugin_name => $name,
674 detailed => $detailed ? "true" : "false",
675 );
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
688 1
689 });
690 };
691
692 =item $status = $fcp->client_get ($uri, $identifier, %kv)
693
694 %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 my ($self, $ok, $err, $uri, $identifier, %kv) = @_;
704
705 $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
729 1
730 });
731 });
732 };
733
734 =item $status = $fcp->remove_request ($identifier[, $global])
735
736 Remove the request with the given identifier. Returns true if successful,
737 false on error.
738
739 =cut
740
741 _txn remove_request => sub {
742 my ($self, $ok, $err, $identifier, $global) = @_;
743
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 $guard if 0;
755
756 if ($kv->{identifier} eq $identifier) {
757 if ($type eq "persistent_request_removed") {
758 $ok->(1);
759 return;
760 } elsif ($type eq "protocol_error") {
761 $err->($kv);
762 return;
763 }
764 }
765
766 1
767 });
768 });
769 };
770
771 =item ($can_read, $can_write) = $fcp->test_dda ($local_directory, $remote_directory, $want_read, $want_write))
772
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 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 C<$can_write> indicate whether you can read or write to freenet via the
792 directory.
793
794 =cut
795
796 _txn test_dda => sub {
797 my ($self, $ok, $err, $local, $remote, $want_read, $want_write) = @_;
798
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 my ($self, $type, $kv) = @_;
809
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 my ($self, $type, $kv) = @_;
844
845 $guard if 0; # reference
846
847 if ($type eq "test_dda_complete") {
848 $ok->(
849 $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 $err->($kv->{extra_description});
854 return;
855 }
856
857 1
858 });
859
860 return;
861 } elsif ($type eq "protocol_error" && $kv->{identifier} eq $remote) {
862 $err->($kv);
863 return;
864 }
865
866 1
867 });
868 });
869 };
870
871 =back
872
873 =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 C<list_persistent_requests>.
923
924 $fcp->watch_global_; # do not wait
925 $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 =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 $fcp->watch_global_ (1);
1007
1008 # list them, synchronously
1009 my $req = $fcp->list_persistent_requests;
1010
1011 # go through all requests
1012 TODO
1013 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 $fcp->get_plugin_info ("dummy");
1044
1045 =head1 SEE ALSO
1046
1047 L<http://wiki.freenetproject.org/FreenetFCPSpec2Point0>, L<Net::FCP>.
1048
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