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