ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-FCP/FCP.pm
Revision: 1.11
Committed: Fri Aug 7 01:54:00 2015 UTC (8 years, 9 months ago) by root
Branch: MAIN
Changes since 1.10: +271 -108 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 =head1 NAME
2    
3     AnyEvent::FCP - freenet client protocol 2.0
4    
5     =head1 SYNOPSIS
6    
7 root 1.3 use AnyEvent::FCP;
8 root 1.1
9 root 1.3 my $fcp = new AnyEvent::FCP;
10 root 1.1
11 root 1.7 # transactions return condvars
12 root 1.3 my $lp_cv = $fcp->list_peers;
13     my $pr_cv = $fcp->list_persistent_requests;
14    
15     my $peers = $lp_cv->recv;
16     my $reqs = $pr_cv->recv;
17 root 1.1
18     =head1 DESCRIPTION
19    
20     This module implements the freenet client protocol version 2.0, as used by
21     freenet 0.7. See L<Net::FCP> for the earlier freenet 0.5 version.
22    
23 root 1.3 See L<http://wiki.freenetproject.org/FreenetFCPSpec2Point0> for a
24     description of what the messages do.
25 root 1.1
26     The module uses L<AnyEvent> to find a suitable event module.
27    
28 root 1.3 Only very little is implemented, ask if you need more, and look at the
29     example program later in this section.
30    
31 root 1.7 =head2 EXAMPLE
32    
33     This example fetches the download list and sets the priority of all files
34     with "a" in their name to "emergency":
35    
36     use AnyEvent::FCP;
37    
38     my $fcp = new AnyEvent::FCP;
39    
40 root 1.11 $fcp->watch_global (1, 0);
41     my $req = $fcp->list_persistent_requests;
42 root 1.7
43 root 1.11 TODO
44 root 1.7 for my $req (values %$req) {
45     if ($req->{filename} =~ /a/) {
46 root 1.11 $fcp->modify_persistent_request (1, $req->{identifier}, undef, 0);
47 root 1.7 }
48     }
49    
50 root 1.1 =head2 IMPORT TAGS
51    
52     Nothing much can be "imported" from this module right now.
53    
54 root 1.11 =head1 THE AnyEvent::FCP CLASS
55 root 1.1
56     =over 4
57    
58     =cut
59    
60     package AnyEvent::FCP;
61    
62 root 1.2 use common::sense;
63    
64 root 1.1 use Carp;
65    
66 root 1.7 our $VERSION = '0.3';
67 root 1.1
68 root 1.2 use Scalar::Util ();
69 root 1.1
70     use AnyEvent;
71 root 1.2 use AnyEvent::Handle;
72 root 1.9 use AnyEvent::Util ();
73 root 1.1
74     sub touc($) {
75     local $_ = shift;
76 root 1.9 1 while s/((?:^|_)(?:svk|chk|uri|fcp|ds|mime|dda)(?:_|$))/\U$1/;
77 root 1.1 s/(?:^|_)(.)/\U$1/g;
78     $_
79     }
80    
81     sub tolc($) {
82     local $_ = shift;
83 root 1.11 1 while s/(SVK|CHK|URI|FCP|DS|MIME|DDA)([^_])/$1\_$2/;
84     1 while s/([^_])(SVK|CHK|URI|FCP|DS|MIME|DDA)/$1\_$2/;
85 root 1.1 s/(?<=[a-z])(?=[A-Z])/_/g;
86     lc
87     }
88    
89 root 1.11 =item $fcp = new AnyEvent::FCP [host => $host][, port => $port][, name => $name]
90 root 1.1
91     Create a new FCP connection to the given host and port (default
92 root 1.2 127.0.0.1:9481, or the environment variables C<FREDHOST> and C<FREDPORT>).
93 root 1.1
94 root 1.3 If no C<name> was specified, then AnyEvent::FCP will generate a
95     (hopefully) unique client name for you.
96    
97     =cut
98 root 1.1
99     sub new {
100     my $class = shift;
101 root 1.11 my $self = bless {
102     host => $ENV{FREDHOST} || "127.0.0.1",
103     port => $ENV{FREDPORT} || 9481,
104     timeout => 3600 * 2,
105     name => time.rand.rand.rand, # lame
106     @_,
107     queue => [],
108     req => {},
109     id => "a0",
110     }, $class;
111 root 1.2
112     {
113     Scalar::Util::weaken (my $self = $self);
114    
115     $self->{hdl} = new AnyEvent::Handle
116     connect => [$self->{host} => $self->{port}],
117     timeout => $self->{timeout},
118     on_error => sub {
119 root 1.8 warn "@_\n";#d#
120 root 1.2 exit 1;
121     },
122     on_read => sub { $self->on_read (@_) },
123     on_eof => $self->{on_eof} || sub { };
124    
125     Scalar::Util::weaken ($self->{hdl}{fcp} = $self);
126     }
127    
128 root 1.11 $self->send_msg (client_hello =>
129     name => $self->{name},
130 root 1.2 expected_version => "2.0",
131     );
132 root 1.1
133     $self
134     }
135    
136 root 1.2 sub send_msg {
137     my ($self, $type, %kv) = @_;
138 root 1.1
139 root 1.2 my $data = delete $kv{data};
140 root 1.1
141 root 1.2 if (exists $kv{id_cb}) {
142 root 1.9 my $id = $kv{identifier} ||= ++$self->{id};
143 root 1.2 $self->{id}{$id} = delete $kv{id_cb};
144 root 1.1 }
145    
146 root 1.2 my $msg = (touc $type) . "\012"
147     . join "", map +(touc $_) . "=$kv{$_}\012", keys %kv;
148 root 1.1
149 root 1.2 sub id {
150     my ($self) = @_;
151 root 1.1
152    
153 root 1.2 }
154 root 1.1
155     if (defined $data) {
156 root 1.2 $msg .= "DataLength=" . (length $data) . "\012"
157     . "Data\012$data";
158 root 1.1 } else {
159 root 1.2 $msg .= "EndMessage\012";
160 root 1.1 }
161    
162 root 1.2 $self->{hdl}->push_write ($msg);
163 root 1.1 }
164    
165 root 1.9 sub on {
166     my ($self, $cb) = @_;
167    
168     # cb return undef - message eaten, remove cb
169     # cb return 0 - message eaten
170     # cb return 1 - pass to next
171    
172     push @{ $self->{on} }, $cb;
173     }
174    
175     sub _push_queue {
176     my ($self, $queue) = @_;
177    
178     shift @$queue;
179     $queue->[0]($self, AnyEvent::Util::guard { $self->_push_queue ($queue) })
180     if @$queue;
181     }
182    
183     # lock so only one $type (arbitrary string) is in flight,
184     # to work around horribly misdesigned protocol.
185     sub serialise {
186     my ($self, $type, $cb) = @_;
187    
188     my $queue = $self->{serialise}{$type} ||= [];
189     push @$queue, $cb;
190     $cb->($self, AnyEvent::Util::guard { $self->_push_queue ($queue) })
191     unless $#$queue;
192     }
193    
194 root 1.11 # how to merge these types into $self->{persistent}
195     our %PERSISTENT_TYPE = (
196     persistent_get => sub { %{ $_[1] } = (type => "persistent_get" , %{ $_[2] }) },
197     persistent_put => sub { %{ $_[1] } = (type => "persistent_put" , %{ $_[2] }) },
198     persistent_put_dir => sub { %{ $_[1] } = (type => "persistent_put_dir", %{ $_[2] }) },
199     persistent_request_modified => sub { %{ $_[1] } = (%{ $_[1] }, %{ $_[2] }) },
200     persistent_request_removed => sub { delete $_[0]{req}{$_[2]{identifier}} },
201    
202     simple_progress => sub { $_[1]{simple_progress} = $_[2] }, # get/put
203    
204     uri_generated => sub { $_[1]{uri_generated} = $_[2] }, # put
205     generated_metadata => sub { $_[1]{generated_metadata} = $_[2] }, # put
206     started_compression => sub { $_[1]{started_compression} = $_[2] }, # put
207     finished_compression => sub { $_[1]{finished_compression} = $_[2] }, # put
208     put_fetchable => sub { $_[1]{put_fetchable} = $_[2] }, # put
209     put_failed => sub { $_[1]{put_failed} = $_[2] }, # put
210     put_successful => sub { $_[1]{put_successful} = $_[2] }, # put
211    
212     sending_to_network => sub { $_[1]{sending_to_network} = $_[2] }, # get
213     compatibility_mode => sub { $_[1]{compatibility_mode} = $_[2] }, # get
214     expected_hashes => sub { $_[1]{expected_hashes} = $_[2] }, # get
215     expected_mime => sub { $_[1]{expected_mime} = $_[2] }, # get
216     expected_data_length => sub { $_[1]{expected_data_length} = $_[2] }, # get
217     get_failed => sub { $_[1]{get_failed} = $_[2] }, # get
218     data_found => sub { $_[1]{data_found} = $_[2] }, # get
219     enter_finite_cooldown => sub { $_[1]{enter_finite_cooldown} = $_[2] }, # get
220     );
221    
222     sub recv {
223     my ($self, $type, $kv, @extra) = @_;
224    
225     if (my $cb = $PERSISTENT_TYPE{$type}) {
226     my $id = $kv->{identifier};
227     my $req = $_[0]{req}{$id} ||= {};
228     $cb->($self, $req, $kv);
229     $self->recv (request_change => $kv, $type, @extra);
230     }
231    
232     my $on = $self->{on};
233     for (0 .. $#$on) {
234     unless (my $res = $on->[$_]($self, $type, $kv, @extra)) {
235     splice @$on, $_, 1 unless defined $res;
236     return;
237     }
238     }
239    
240     if (my $cb = $self->{queue}[0]) {
241     $cb->($self, $type, $kv, @extra)
242     and shift @{ $self->{queue} };
243     } else {
244     $self->default_recv ($type, $kv, @extra);
245     }
246     }
247    
248 root 1.2 sub on_read {
249 root 1.1 my ($self) = @_;
250    
251 root 1.2 my $type;
252     my %kv;
253     my $rdata;
254    
255     my $hdr_cb; $hdr_cb = sub {
256     if ($_[1] =~ /^([^=]+)=(.*)$/) {
257     my ($k, $v) = ($1, $2);
258     my @k = split /\./, tolc $k;
259     my $ro = \\%kv;
260    
261     while (@k) {
262     my $k = shift @k;
263     if ($k =~ /^\d+$/) {
264     $ro = \$$ro->[$k];
265 root 1.1 } else {
266 root 1.2 $ro = \$$ro->{$k};
267 root 1.1 }
268     }
269    
270 root 1.2 $$ro = $v;
271 root 1.1
272 root 1.2 $_[0]->push_read (line => $hdr_cb);
273     } elsif ($_[1] eq "Data") {
274     $_[0]->push_read (chunk => delete $kv{data_length}, sub {
275     $rdata = \$_[1];
276 root 1.11 $self->recv ($type, \%kv, $rdata);
277 root 1.2 });
278     } elsif ($_[1] eq "EndMessage") {
279 root 1.11 $self->recv ($type, \%kv);
280 root 1.2 } else {
281     die "protocol error, expected message end, got $_[1]\n";#d#
282     }
283     };
284 root 1.1
285 root 1.2 $self->{hdl}->push_read (line => sub {
286     $type = tolc $_[1];
287     $_[0]->push_read (line => $hdr_cb);
288     });
289     }
290 root 1.1
291 root 1.2 sub default_recv {
292     my ($self, $type, $kv, $rdata) = @_;
293    
294     if ($type eq "node_hello") {
295     $self->{node_hello} = $kv;
296     } elsif (exists $self->{id}{$kv->{identifier}}) {
297     $self->{id}{$kv->{identifier}}($self, $type, $kv, $rdata)
298     and delete $self->{id}{$kv->{identifier}};
299 root 1.1 }
300     }
301    
302 root 1.11 our $NOP_CB = sub { };
303    
304 root 1.2 sub _txn {
305     my ($name, $sub) = @_;
306 root 1.1
307 root 1.2 *{$name} = sub {
308     splice @_, 1, 0, (my $cv = AnyEvent->condvar);
309     &$sub;
310 root 1.11 $cv->recv
311 root 1.2 };
312 root 1.1
313 root 1.11 *{"$name\_"} = sub {
314     splice @_, 1, 0, pop || $NOP_CB;
315 root 1.2 &$sub;
316     };
317 root 1.1 }
318    
319 root 1.11 =item $peers = $fcp->list_peers ([$with_metdata[, $with_volatile]])
320 root 1.3
321     =cut
322    
323 root 1.2 _txn list_peers => sub {
324     my ($self, $cv, $with_metadata, $with_volatile) = @_;
325 root 1.1
326 root 1.2 my @res;
327 root 1.1
328 root 1.2 $self->send_msg (list_peers =>
329     with_metadata => $with_metadata ? "true" : "false",
330     with_volatile => $with_volatile ? "true" : "false",
331     id_cb => sub {
332     my ($self, $type, $kv, $rdata) = @_;
333    
334     if ($type eq "end_list_peers") {
335     $cv->(\@res);
336     1
337     } else {
338     push @res, $kv;
339     0
340     }
341     },
342     );
343     };
344 root 1.1
345 root 1.11 =item $notes = $fcp->list_peer_notes ($node_identifier)
346 root 1.3
347     =cut
348    
349 root 1.2 _txn list_peer_notes => sub {
350     my ($self, $cv, $node_identifier) = @_;
351 root 1.1
352 root 1.2 $self->send_msg (list_peer_notes =>
353     node_identifier => $node_identifier,
354     id_cb => sub {
355     my ($self, $type, $kv, $rdata) = @_;
356    
357     $cv->($kv);
358     1
359     },
360     );
361     };
362 root 1.1
363 root 1.11 =item $fcp->watch_global ($enabled[, $verbosity_mask])
364 root 1.3
365     =cut
366    
367 root 1.2 _txn watch_global => sub {
368     my ($self, $cv, $enabled, $verbosity_mask) = @_;
369 root 1.1
370 root 1.2 $self->send_msg (watch_global =>
371     enabled => $enabled ? "true" : "false",
372     defined $verbosity_mask ? (verbosity_mask => $verbosity_mask+0) : (),
373     );
374 root 1.1
375 root 1.2 $cv->();
376     };
377 root 1.1
378 root 1.11 =item $reqs = $fcp->list_persistent_requests
379 root 1.3
380     =cut
381    
382 root 1.2 _txn list_persistent_requests => sub {
383     my ($self, $cv) = @_;
384 root 1.1
385 root 1.10 $self->serialise (list_persistent_requests => sub {
386     my ($self, $guard) = @_;
387    
388 root 1.11 my @res;
389 root 1.1
390 root 1.10 $self->send_msg ("list_persistent_requests");
391 root 1.1
392 root 1.10 $self->on (sub {
393     my ($self, $type, $kv, $rdata) = @_;
394 root 1.2
395 root 1.10 $guard if 0;
396 root 1.2
397 root 1.10 if ($type eq "end_list_persistent_requests") {
398 root 1.11 $cv->(\@res);
399 root 1.10 return;
400 root 1.2 } else {
401 root 1.10 my $id = $kv->{identifier};
402    
403     if ($type =~ /^persistent_(get|put|put_dir)$/) {
404 root 1.11 push @res, [$type, $kv];
405 root 1.10 }
406 root 1.2 }
407 root 1.10
408     1
409     });
410     });
411 root 1.2 };
412 root 1.1
413 root 1.11 =item $status = $fcp->remove_request ($global, $identifier)
414 root 1.3
415     =cut
416    
417 root 1.2 _txn remove_request => sub {
418     my ($self, $cv, $global, $identifier) = @_;
419 root 1.1
420 root 1.2 $self->send_msg (remove_request =>
421     global => $global ? "true" : "false",
422     identifier => $identifier,
423     id_cb => sub {
424     my ($self, $type, $kv, $rdata) = @_;
425    
426     $cv->($kv);
427     1
428     },
429     );
430     };
431 root 1.1
432 root 1.11 =item $sync = $fcp->modify_persistent_request ($global, $identifier[, $client_token[, $priority_class]])
433 root 1.3
434     =cut
435    
436 root 1.2 _txn modify_persistent_request => sub {
437     my ($self, $cv, $global, $identifier, $client_token, $priority_class) = @_;
438 root 1.1
439 root 1.2 $self->send_msg (modify_persistent_request =>
440     global => $global ? "true" : "false",
441     defined $client_token ? (client_token => $client_token ) : (),
442     defined $priority_class ? (priority_class => $priority_class) : (),
443 root 1.4 identifier => $identifier,
444 root 1.2 id_cb => sub {
445     my ($self, $type, $kv, $rdata) = @_;
446    
447     $cv->($kv);
448     1
449     },
450     );
451     };
452 root 1.1
453 root 1.11 =item $info = $fcp->get_plugin_info ($name, $detailed)
454 root 1.3
455     =cut
456    
457 root 1.2 _txn get_plugin_info => sub {
458     my ($self, $cv, $name, $detailed) = @_;
459 root 1.1
460 root 1.2 $self->send_msg (get_plugin_info =>
461     plugin_name => $name,
462     detailed => $detailed ? "true" : "false",
463     id_cb => sub {
464     my ($self, $type, $kv, $rdata) = @_;
465    
466     $cv->($kv);
467     1
468     },
469     );
470 root 1.4 };
471    
472 root 1.11 =item $status = $fcp->client_get ($uri, $identifier, %kv)
473 root 1.1
474 root 1.4 %kv can contain (L<http://wiki.freenetproject.org/FCP2p0ClientGet>).
475    
476     ignore_ds, ds_only, verbosity, max_size, max_temp_size, max_retries,
477     priority_class, persistence, client_token, global, return_type,
478     binary_blob, allowed_mime_types, filename, temp_filename
479    
480     =cut
481    
482     _txn client_get => sub {
483     my ($self, $cv, $uri, $identifier, %kv) = @_;
484    
485     $self->send_msg (client_get =>
486     %kv,
487     uri => $uri,
488     identifier => $identifier,
489 root 1.11 );
490     };
491    
492     =item $status = $fcp->remove_request ($identifier[, $global])
493    
494     Remove the request with the given isdentifier. Returns true if successful,
495     false on error.
496    
497     =cut
498    
499     _txn remove_request => sub {
500     my ($self, $cv, $identifier, $global) = @_;
501    
502     $self->serialise ($identifier => sub {
503     my ($self, $guard) = @_;
504    
505     $self->send_msg (remove_request =>
506     identifier => $identifier,
507     global => $global ? "true" : "false",
508     );
509     $self->on (sub {
510     my ($self, $type, $kv, @extra) = @_;
511    
512     if ($kv->{identifier} eq $identifier) {
513     if ($type eq "persistent_request_removed") {
514     $cv->(1);
515     return;
516     } elsif ($type eq "protocol_error") {
517     $cv->(undef);
518     return;
519     }
520     }
521 root 1.4
522     1
523 root 1.11 });
524     });
525 root 1.2 };
526 root 1.1
527 root 1.11 =item ($can_read, $can_write) = $fcp->test_dda ($local_directory, $remote_directory, $want_read, $want_write))
528 root 1.9
529     The DDA test in FCP is probably the single most broken protocol - only
530     one directory test can be outstanding at any time, and some guessing and
531     heuristics are involved in mangling the paths.
532    
533     This function combines C<TestDDARequest> and C<TestDDAResponse> in one
534 root 1.11 request, handling file reading and writing as well, and tries very hard to
535     do the right thing.
536    
537     Both C<$local_directory> and C<$remote_directory> must specify the same
538     directory - C<$local_directory> is the directory path on the client (where
539     L<AnyEvent::FCP> runs) and C<$remote_directory> is the directory path on
540     the server (where the freenet node runs). When both are running on the
541     same node, the paths are generally identical.
542    
543     C<$want_read> and C<$want_write> should be set to a true value when you
544     want to read (get) files or write (put) files, respectively.
545    
546     On error, an exception is thrown. Otherwise, C<$can_read> and
547     C<$can_write> indicate whether you can reaqd or write to freenet via the
548     directory.
549 root 1.9
550     =cut
551    
552     _txn test_dda => sub {
553     my ($self, $cv, $local, $remote, $want_read, $want_write) = @_;
554    
555     $self->serialise (test_dda => sub {
556     my ($self, $guard) = @_;
557    
558     $self->send_msg (test_dda_request =>
559     directory => $remote,
560     want_read_directory => $want_read ? "true" : "false",
561     want_write_directory => $want_write ? "true" : "false",
562     );
563     $self->on (sub {
564 root 1.10 my ($self, $type, $kv) = @_;
565 root 1.9
566     if ($type eq "test_dda_reply") {
567     # the filenames are all relative to the server-side directory,
568     # which might or might not match $remote anymore, so we
569     # need to rewrite the paths to be relative to $local
570     for my $k (qw(read_filename write_filename)) {
571     my $f = $kv->{$k};
572     for my $dir ($kv->{directory}, $remote) {
573     if ($dir eq substr $f, 0, length $dir) {
574     substr $f, 0, 1 + length $dir, "";
575     $kv->{$k} = $f;
576     last;
577     }
578     }
579     }
580    
581     my %response = (directory => $remote);
582    
583     if (length $kv->{read_filename}) {
584     if (open my $fh, "<:raw", "$local/$kv->{read_filename}") {
585     sysread $fh, my $buf, -s $fh;
586     $response{read_content} = $buf;
587     }
588     }
589    
590     if (length $kv->{write_filename}) {
591     if (open my $fh, ">:raw", "$local/$kv->{write_filename}") {
592     syswrite $fh, $kv->{content_to_write};
593     }
594     }
595    
596     $self->send_msg (test_dda_response => %response);
597    
598     $self->on (sub {
599 root 1.10 my ($self, $type, $kv) = @_;
600 root 1.9
601     $guard if 0; # reference
602    
603     if ($type eq "test_dda_complete") {
604     $cv->(
605     $kv->{read_directory_allowed} eq "true",
606     $kv->{write_directory_allowed} eq "true",
607     );
608     } elsif ($type eq "protocol_error" && $kv->{identifier} eq $remote) {
609     $cv->croak ($kv->{extra_description});
610     return;
611     }
612    
613     1
614     });
615    
616     return;
617     } elsif ($type eq "protocol_error" && $kv->{identifier} eq $remote) {
618     $cv->croak ($kv->{extra_description});
619     return;
620     }
621    
622     1
623     });
624     });
625     };
626    
627 root 1.1 =back
628    
629 root 1.11 =head2 REQUEST CACHE
630    
631     The C<AnyEvent::FCP> class keeps a request cache, where it caches all
632     information from requests.
633    
634     For these messages, it will store a copy of the key-value pairs, together with a C<type> slot,
635     in C<< $fcp->{req}{$identifier} >>:
636    
637     persistent_get
638     persistent_put
639     persistent_put_dir
640    
641     This message updates the stored data:
642    
643     persistent_request_modified
644    
645     This message will remove this entry:
646    
647     persistent_request_removed
648    
649     These messages get merged into the cache entry, under their
650     type, i.e. a C<simple_progress> message will be stored in C<<
651     $fcp->{req}{$identifier}{simple_progress} >>:
652    
653     simple_progress # get/put
654    
655     uri_generated # put
656     generated_metadata # put
657     started_compression # put
658     finished_compression # put
659     put_failed # put
660     put_fetchable # put
661     put_successful # put
662    
663     sending_to_network # get
664     compatibility_mode # get
665     expected_hashes # get
666     expected_mime # get
667     expected_data_length # get
668     get_failed # get
669     data_found # get
670     enter_finite_cooldown # get
671    
672     In addition, an event (basically a fake message) of type C<request_changed> is generated
673     on every change, which will be called as C<< $cb->($fcp, $kv, $type) >>, where C<$type>
674     is the type of the original message triggering the change,
675    
676     To fill this cache with the global queue and keep it updated,
677     call C<watch_global> to subscribe to updates, followed by
678     C<list_persistent_requests_sync>.
679    
680     $fcp->watch_global_sync_; # do not wait
681     $fcp->list_persistent_requests; # wait
682    
683     To get a better idea of what is stored in the cache, here is an example of
684     what might be stored in C<< $fcp->{req}{"Frost-gpl.txt"} >>:
685    
686     {
687     identifier => "Frost-gpl.txt",
688     uri => 'CHK@Fnx5kzdrfE,EImdzaVyEWl,AAIC--8/gpl.txt',
689     binary_blob => "false",
690     global => "true",
691     max_retries => -1,
692     max_size => 9223372036854775807,
693     persistence => "forever",
694     priority_class => 3,
695     real_time => "false",
696     return_type => "direct",
697     started => "true",
698     type => "persistent_get",
699     verbosity => 2147483647,
700     sending_to_network => {
701     identifier => "Frost-gpl.txt",
702     global => "true",
703     },
704     compatibility_mode => {
705     identifier => "Frost-gpl.txt",
706     definitive => "true",
707     dont_compress => "false",
708     global => "true",
709     max => "COMPAT_1255",
710     min => "COMPAT_1255",
711     },
712     expected_hashes => {
713     identifier => "Frost-gpl.txt",
714     global => "true",
715     hashes => {
716     ed2k => "d83596f5ee3b7...",
717     md5 => "e0894e4a2a6...",
718     sha1 => "...",
719     sha256 => "...",
720     sha512 => "...",
721     tth => "...",
722     },
723     },
724     expected_mime => {
725     identifier => "Frost-gpl.txt",
726     global => "true",
727     metadata => { content_type => "application/rar" },
728     },
729     expected_data_length => {
730     identifier => "Frost-gpl.txt",
731     data_length => 37576,
732     global => "true",
733     },
734     simple_progress => {
735     identifier => "Frost-gpl.txt",
736     failed => 0,
737     fatally_failed => 0,
738     finalized_total => "true",
739     global => "true",
740     last_progress => 1438639282628,
741     required => 372,
742     succeeded => 102,
743     total => 747,
744     },
745     data_found => {
746     identifier => "Frost-gpl.txt",
747     completion_time => 1438663354026,
748     data_length => 37576,
749     global => "true",
750     metadata => { content_type => "image/jpeg" },
751     startup_time => 1438657196167,
752     },
753     }
754    
755 root 1.3 =head1 EXAMPLE PROGRAM
756    
757     use AnyEvent::FCP;
758    
759     my $fcp = new AnyEvent::FCP;
760    
761     # let us look at the global request list
762 root 1.11 $fcp->watch_global_ (1);
763 root 1.3
764     # list them, synchronously
765 root 1.11 my $req = $fcp->list_persistent_requests;
766 root 1.3
767     # go through all requests
768 root 1.11 TODO
769 root 1.3 for my $req (values %$req) {
770     # skip jobs not directly-to-disk
771     next unless $req->{return_type} eq "disk";
772     # skip jobs not issued by FProxy
773     next unless $req->{identifier} =~ /^FProxy:/;
774    
775     if ($req->{data_found}) {
776     # file has been successfully downloaded
777    
778     ... move the file away
779     (left as exercise)
780    
781     # remove the request
782    
783     $fcp->remove_request (1, $req->{identifier});
784     } elsif ($req->{get_failed}) {
785     # request has failed
786     if ($req->{get_failed}{code} == 11) {
787     # too many path components, should restart
788     } else {
789     # other failure
790     }
791     } else {
792     # modify priorities randomly, to improve download rates
793     $fcp->modify_persistent_request (1, $req->{identifier}, undef, int 6 - 5 * (rand) ** 1.7)
794     if 0.1 > rand;
795     }
796     }
797    
798     # see if the dummy plugin is loaded, to ensure all previous requests have finished.
799     $fcp->get_plugin_info_sync ("dummy");
800    
801 root 1.1 =head1 SEE ALSO
802    
803 root 1.2 L<http://wiki.freenetproject.org/FreenetFCPSpec2Point0>, L<Net::FCP>.
804 root 1.1
805     =head1 BUGS
806    
807     =head1 AUTHOR
808    
809     Marc Lehmann <schmorp@schmorp.de>
810     http://home.schmorp.de/
811    
812     =cut
813    
814     1
815