ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Net-FCP/bin/fmd
Revision: 1.19
Committed: Mon Dec 3 01:06:07 2007 UTC (18 years, 9 months ago) by root
Branch: MAIN
Changes since 1.18: +25 -14 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 #!/opt/bin/perl
2    
3 root 1.14 system "echo $$ >fmd.pid";
4 root 1.19 use Coro::Debug; our $server = new_unix_server Coro::Debug "debug";
5 root 1.14
6 root 1.1 =head1 fmd - the freenet mass downloader
7    
8     Fmd is at a very early stage of development (and very hackish, too), as I
9     am learning the basics of freenet myself.
10    
11     However, I use it in production, and since it verifies everything it
12     decodes etc., it seems to be quite safe to use, that is, if you know how
13     to debug perl :)
14    
15     =head2 FEATURES
16    
17     - decoding is done "in place", i.e. all non-checkblock blocks are
18     stored in-place and will not be moved, only checkblocks will
19     be moved to their final position in the file before decoding.
20     - high resistance against failures of fred or fmd
21     - extremely persistent retry behaviour - there is no such thing
22     as a permanent failure.
23     - handles hundreds of simultaneous downloads with grace.
24    
25     =head2 ENVIRONMENT
26    
27     Set FMD_HOME to a directory (default ~/fmd) where fmd will store it's
28     files. It will not store your porn files or other freenet data outside
29     that directory.
30    
31     The subdirectory db will contain a database (soon to go away), while tmp
32     contains queue files and partial splitfiles. All finished files will be
33     moved to the done subdir.
34    
35     FREDHOST and FREDPORT do the obvious. Fix these docs if you disagree.
36    
37     Also edit the fmd executable for the number of threads and other
38 root 1.14 non-useful constants. The default number (250) works for me, probably not
39 root 1.1 for you.
40    
41     =head2 COMMANDS
42    
43     =over 4
44    
45     =item CHK@... (space or "/") filename
46    
47     Just pasting a CHK and a filename sperated by one or more spaces or
48     slashes will add files to the queue. The line can have leading garbage,
49     i.e. you can paste full uris.
50    
51     =item <attachment>...</attachment>
52    
53     =item <attach>...</attach>
54    
55     =item <attached>...</attached>
56    
57     Will add frosts mentally deranged pseudo-xml format, soon to be replaced
58     by something even more horrible.
59    
60     =item l
61    
62     List all jobs by number.
63    
64     =item <number> (optionally trailing command)
65    
66     Selects the job with the given number for further comamnds that require a current job.
67    
68     =item s
69    
70     Show the current job. Bot useful right now.
71    
72     =item k
73    
74     Kill the current job. This will keep the temp. file around. Sorry. (But it
75     could be used to reconstruct the download.. hmm..)
76    
77     =item pri<number>
78    
79 root 1.2 Set the current job priority to <pri>. The default is 1. A job with higher
80     priority will (on average) get more requests.
81 root 1.1
82 root 1.2 The useful range is probably 0..100, but be careful and limit yourself to
83     small values (<10), otherwise your other downloads might starve!
84 root 1.1
85     =item q
86    
87     Kill the command prompt. Yupp.
88    
89     =back
90    
91     =cut
92    
93 root 1.10 use strict;
94    
95 root 1.19 use EV;
96     use Coro::EV;
97    
98 root 1.1 use Net::FCP;
99 root 1.19 use Storable ();
100 root 1.1 use Time::HiRes;
101 root 1.19 use Coro 4.23;
102 root 1.15 use Coro::Channel;
103 root 1.1 use Coro::Handle;
104     use Coro::Signal;
105     use Coro::Timer;
106 root 1.9 use Coro::AIO;
107 root 1.19 use Coro::Storable ();
108 root 1.1 use List::Util;
109     use Digest::SHA1;
110     use Algorithm::FEC;
111     use Digest::SHA1;
112     use Net::FCP::Util;
113     use POSIX ();
114    
115 root 1.9 Coro::AIO::max_poll_time 0.01;
116     Coro::AIO::min_parallel 1;
117 root 1.19 Coro::AIO::max_parallel 32;
118 root 1.9
119 root 1.1 $|=1;
120    
121 root 1.17 our $MAX_TXN = 400; # use max. this many transactions in parallel
122 root 1.19 our @HTL = (3,5,10,15,20);
123 root 1.1 our $VERIFY_CHK = 1; # verify all blocks, again and again (only useful to debugging).
124     our $FMD_HOME = $ENV{FMD_HOME} || "$ENV{HOME}/fmd";
125    
126     our $FCP = new Net::FCP;
127    
128     defined $FMD_HOME
129     or die "you currently must define FMD_HOME to a persistent directory";
130    
131     mkdir $FMD_HOME, 0700;
132    
133 root 1.17 our $QUEUE_HOME = "$FMD_HOME/job";
134 root 1.1 mkdir $QUEUE_HOME, 0700;
135 root 1.17 our $DATA_HOME = "$FMD_HOME/tmp";
136     mkdir $DATA_HOME, 0700;
137 root 1.1 our $DONE_HOME = "$FMD_HOME/done";
138     mkdir $DONE_HOME, 0700;
139    
140     our %job;
141    
142     sub push_key {
143     my ($key, $title) = @_;
144    
145     for my $job (values %job) {
146     if ($job->{p}{key} eq $key) {
147     warn "job $job->{id} already works on this, not adding";
148     return;
149     }
150     }
151    
152     my $job = job->new_from_key ($key, $title);
153     warn "added as job $job->{id}\n";
154     $job;
155     }
156    
157     sub cmdline {
158     my ($i, $o) = @_;
159    
160     my $cmd = async {
161     my $job;
162     while (print $o "> " and defined ($_ = <$i>)) {
163     chomp;
164     if (/<attach(?:ment|ed|)>(.*) \* (CHK[^<]+)<\/attach/) {
165     $job = push_key $2, $1;
166 root 1.7 } elsif (/(CHK\@[a-zA-Z0-9,~\-]{54})[\/ ]+(.*?)\s*$/) {
167 root 1.1 $job = push_key $1, $2;
168     } elsif (s/^(\d+)//) {
169     if ($job = $job{$1}) {
170     print $o delete $job->{log};
171     if ($job->{input}) {
172     }
173     }
174     redo;
175     } elsif (/^q/) {
176     close $i;
177     close $o;
178     } elsif (/^l/) {
179     for my $job (sort { $a->{id} <=> $b->{id} } values %job) {
180     print $o "$_ $job->{id}: $job->{p}{key} $job->{p}{title} $job->{status}\n";
181     }
182 root 1.16 # } elsif (/^pri\s*(\d+)/) {
183     # if ($job) {
184     # $job->{p}{pri} = $1;
185     # $job->save;
186     # }
187 root 1.1 } elsif (/^s$/) {
188     print $o $job->show if $job;
189     } elsif (/^k$/) {
190     $job->kill if $job;
191 root 1.14 } elsif (/^p(.*)/) {
192 root 1.16 print $o +(join " ", eval $1), "\n";
193 root 1.1 } elsif (/\S/) {
194     #
195     } else {
196     print $o "?\n";
197     }
198     for my $job (sort { $a->{id} <=> $b->{id} } values %job) {
199     if ($job->{_input}) {
200     print $o "> $job->{id} $job->{title} $job->{status}\n";
201     }
202     }
203     }
204     };
205     }
206    
207     package job;
208    
209 root 1.10 use strict;
210    
211 root 1.1 use Coro;
212 root 1.9 use Coro::AIO;
213 root 1.1 use Fcntl;
214     use IO::Handle;
215 root 1.6 use Array::Heap2;
216 root 1.1
217 root 1.14 our $count = 0;
218 root 1.1
219     sub new {
220     my $class = shift;
221    
222     my $self = bless { @_ }, $class;
223    
224 root 1.17 for my $job (values %job) {
225     if ($job->{p}{key} eq $self->{p}{key}) {
226     warn "job $job->{id} already works on this, not adding";
227 root 1.19 aio_unlink $self->{job};
228 root 1.17 return;
229     }
230     }
231    
232 root 1.9 $self->{p}{title} !~ /\//
233     or die "$self->{p}{title}: malformed key\n";
234    
235 root 1.13 $self->{p}{title} =~ s/\s+$//;
236     $self->{p}{title} =~ s/\n/_/g;
237    
238 root 1.1 $self->{id} = ++$count;
239     $self->{job} ||= "$QUEUE_HOME/" . Time::HiRes::time . ":$count.j";
240    
241     $job{$self->{id}} = $self;
242    
243     $self->start;
244 root 1.19 $self
245 root 1.1 }
246    
247     sub new_from_key {
248     my ($class, $key, $title) = @_;
249     $class->new (p => { key => $key, title => $title, state => "examine" });
250     }
251    
252     sub new_from_file {
253     my ($class, $path) = @_;
254     $class->new (job => $path, p => Storable::retrieve $path);
255     }
256    
257     sub save {
258     my ($self) = @_;
259 root 1.19 if (my $fh = aio_open "$self->{job}~", O_CREAT|O_TRUNC|O_WRONLY, 0600) {
260     aio_write $fh, 0, undef, Coro::Storable::blocking_nfreeze $self->{p}, 0;
261     aio_fsync $fh;
262     aio_close $fh;
263     aio_rename "$self->{job}~", $self->{job};
264     aio_pathsync $QUEUE_HOME;
265     }
266 root 1.1 }
267    
268     sub clean {
269     my ($self) = @_;
270    
271     delete $job{$self->{id}};
272     $self->save;
273 root 1.19 system "mv", $self->{job}, "$DONE_HOME/$self->{p}{title}.job";
274 root 1.12 unlink $self->{job};
275 root 1.1 }
276    
277     sub kill {
278     my ($self) = @_;
279    
280     $self->clean;
281     $self->{coro}->cancel;
282     }
283    
284 root 1.14 our @queue;
285     our $queue_change = new Coro::Signal;
286     our $queue_alloc = 0;
287 root 1.1
288     async {
289     for (;;) {
290     while (@queue
291     and (($queue[0][0] > 10 and $queue_alloc < $MAX_TXN)
292     or ($queue[0][0] > 1 and $queue_alloc < $MAX_TXN - 3)
293     or $queue_alloc < $MAX_TXN - 5)) {
294     (pop_heap @queue)->[1]->send;
295     $queue_alloc++;
296 root 1.4 Coro::Timer::sleep 0.05;
297 root 1.1 }
298     $queue_change->wait;
299     }
300     };
301    
302     sub txn_begin {
303     my ($pri) = @_;
304     my $sig = new Coro::Signal;
305    
306     #warn "txn_begin $pri\n";#d#
307     push_heap @queue, [$pri, $sig];
308     $queue_change->send;
309     $sig->wait;
310     }
311    
312     sub txn_end {
313     $queue_alloc--;
314     $queue_change->send;
315 root 1.15 }
316    
317     sub txn_client_get {
318     my %arg = @_;
319    
320     txn_begin $arg{pri};
321     $FCP->txn_client_get ($arg{uri}, $arg{htl})->cb (unblock_sub {
322     txn_end;
323    
324     $arg{cb}->(@_);
325     });
326 root 1.1 }
327    
328     sub fetch_uri {
329     my ($pri, $uri) = @_;
330    
331     for(my $count = 1; ; $count += 0.3) {
332     for my $htl (@HTL) {
333 root 1.4 txn_begin time + $htl + $count;
334 root 1.1 my ($meta, $data) = eval { @{ $FCP->client_get ($uri, $htl) } };
335     txn_end;
336     if ($@) {
337     if (UNIVERSAL::isa ($@, Net::FCP::Exception::)) {
338     if ($@->type ("data_not_found")
339     || $@->type ("route_not_found")) {
340     next;
341     }
342     if ($@->type ("short_data")) {
343     warn "(short_data, redo)\n";
344     redo;
345     }
346     die;
347     }
348     }
349     if (defined $data) {
350     return ($meta, $data);
351     }
352     }
353     }
354    
355     die;
356     }
357    
358     sub log {
359     my ($self, $text) = @_;
360     my $time = POSIX::strftime "%H:%M:%S", localtime time;
361 root 1.3 warn "$time $self->{id},$self->{p}{pri}: $text\n";
362 root 1.1 $self->{log} .= "$time $text\n";
363     }
364    
365     sub feedback {
366     my ($self, $prompt) = @_;
367     $self->{input} = [$Coro::current, $prompt];
368     Coro::schedule;
369     }
370    
371     sub show {
372     my ($self) = @_;
373    
374     "ID: $self->{id}\n"
375     . "Title: $self->{p}{title}\n"
376 root 1.18 . "Blocks#: " . @{$self->{p}{blk} || []} . "\n"
377 root 1.1 . "Blocks: " . (join "", map {
378     $_->{done} ? "+" : "-"
379 root 1.17 } @{$self->{p}{blk} || []}) . "\n" .
380 root 1.11 ""
381 root 1.1 }
382    
383 root 1.14 our $id;
384 root 1.1
385     sub MAXSEG (){ 128*1024*1024 }
386     sub MINSEG (){ 6* 128*1024 }
387    
388     sub blocksize($) {
389     return
390     $_[0] >= 64*1024*1024 ? 1024*1024
391     : $_[0] >= 32*1024*1024 ? 512*1024
392     : $_[0] >= 1024*1024 ? 256*1024
393     : 128*1024;
394     }
395    
396     sub start {
397     my ($self) = @_;
398    
399     $self->{p}{pri} ||= 1;
400     $self->{p}{state} ||= "examine";
401    
402 root 1.17 $self->{job} =~ /\/([^\/]*)\.j$/ or die "$self->{job}: missing .j";
403     $self->{file} = "$DATA_HOME/$1.d";
404 root 1.19 sysopen $self->{fh}, $self->{file}, O_RDWR|O_CREAT, 0600
405     # $self->{fh} = aio_open $self->{file}, O_RDWR|O_CREAT, 0600
406 root 1.1 or die "$self->{file}: $!";
407    
408     $self->{status} = "starting";
409     $self->{coro} = async {
410 root 1.19 $self->save;
411    
412 root 1.1 for(;;) {
413     my ($state, @args) = ref $self->{p}{state} ? @{$self->{p}{state}} : $self->{p}{state};
414     my $next = eval { $self->can ("state_$state")->($self, @args) };
415     if ($@) {
416     $self->log ($@);
417     $next = $self->feedback ("continue with state: ");
418     }
419 root 1.10 $self->log ($self->{status} = "STATE CHANGE: ". join ", ", ref $next ? @$next : $next);
420 root 1.1 $self->{p}{state} = $next;
421     $self->save;
422     }
423     };
424     }
425    
426     sub state_finish {
427     my ($self, $save) = @_;
428    
429     if ($save) {
430 root 1.10 aio_fsync $self->{fh};
431 root 1.1 close $self->{fh};
432    
433 root 1.10 aio_unlink "$DONE_HOME/$self->{p}{title}";
434     aio_link $self->{file}, "$DONE_HOME/$self->{p}{title}"
435     and die "link: $self->{file} => $DONE_HOME/$self->{p}{title}: $!";
436 root 1.19 aio_pathsync $DONE_HOME;
437 root 1.1 }
438     $self->clean;
439    
440 root 1.10 aio_unlink $self->{file};
441 root 1.1
442     $self->{status} = "finished";
443     $self->feedback ("finished");
444     terminate;
445     }
446    
447     sub state_examine {
448     my ($self) = @_;
449     my $p = $self->{p};
450    
451     $self->{status} = "initial fetch";
452    
453     for (;;) {
454     $self->log ("fetching $p->{key} (=$p->{title})");
455    
456     ($p->{meta}, $p->{data}) = fetch_uri 100, "freenet:$p->{key}";
457     $self->save;
458     #use PApp::Util; print STDERR PApp::Util::dumpval [keys %{$meta->{document}[0]{split_file}}];
459     $self->log ("type $p->{meta}{document}[0]{info}{format}");
460    
461     if (my $splitfile = $p->{meta}{document}[0]{split_file}) {
462     return "splitfile";
463     } elsif ((defined $p->{data}) and (length $p->{data})) {
464 root 1.8 syswrite $self->{fh}, delete $p->{data};
465 root 1.12 aio_fsync $self->{fh};
466 root 1.1 return ["finish", 1];
467     }
468    
469     $self->log ("EMPTY, retrying in an hour");
470     Coro::Timer::sleep 3600;
471     }
472    
473     }
474    
475     sub state_splitfile {
476     my ($self) = @_;
477     my $p = $self->{p};
478    
479     my $splitfile = $p->{meta}{document}[0]{split_file};
480     my $filesize = hex $splitfile->{size};
481    
482     if ($splitfile->{algo_name} eq "OnionFEC_a_1_2") {
483     my $data_packets = hex $splitfile->{block_count};
484     my $check_packets = hex $splitfile->{check_block_count};
485    
486     my $blk = ($p->{blk} ||= []);
487    
488     unless (@$blk) {
489     for (1..$data_packets) {
490     push @$blk, {
491     uri => $splitfile->{block}{sprintf "%x", $_},
492     };
493     }
494     for (1..$check_packets) {
495     push @$blk, {
496     uri => $splitfile->{check_block}{sprintf "%x", $_},
497     };
498     }
499     }
500    
501     my @segments;
502     my $segments = 0;
503    
504     {
505     # that is a horrible algorithm :(, these freenet freaks are... java-disabled
506     # hardcoding lots of magic parameters is soo dumb.
507     my $size = $filesize;
508     my $offset = 0;
509     my $offset2 = ($filesize & ~(1024*1024-1)) + 1024*1024; # leave enough space after last data block
510     my $idx = 0;
511     my $idx2 = $data_packets;
512     my @redundandy = (0,1,2); # maybe OnionFAC_a_1_2 means 1/2 redundancy(?)
513    
514     while ($size > 0) {
515     my $segsize = $size >= MAXSEG ? MAXSEG : $size <= MINSEG ? MINSEG : $size;
516     my $blksize = blocksize $segsize;
517     my $seg =
518     {
519     id => $segments++,
520     todo => int (($segsize + $blksize - 1) / $blksize),
521     done => 0,
522     blk => [],
523     blksize => $blksize,
524     };
525    
526     push @segments, $seg;
527     $size -= $segsize;
528    
529     while ($segsize > 0) {
530     push @{$seg->{blk}}, $idx;
531     for ($blk->[$idx++]) {
532     $_->{offset} = $offset;
533     #$_->{size} = $blksize > $segsize ? $segsize : $blksize; # WRONG
534     $_->{size} = $blksize;
535     $_->{seg} = $seg;
536     }
537    
538     $segsize -= $blksize;
539     $offset += $blksize;
540    
541     if (($redundandy[0] += $redundandy[1]) >= $redundandy[2]) {
542     $redundandy[0] -= $redundandy[2];
543    
544     push @{$seg->{blk}}, $idx2;
545     for ($blk->[$idx2++]) {
546     $_->{offset} = $offset2;
547     $_->{size} = $blksize;
548     $_->{seg} = $seg;
549     }
550    
551     $offset2 += $blksize;
552     }
553     }
554     }
555    
556     $idx == $data_packets
557     or die "$self->{id}/$p->{tile} $self->{job}\nidx $idx != data_packets $data_packets";
558     $idx2 == $data_packets + $check_packets
559     or die "$self->{id}/$p->{tile} $self->{job}\nidx2 $idx2 != data_packets $data_packets + check_packets $check_packets";
560     }
561    
562     for (@$blk) {
563     ++$_->{seg}{done} if $_->{done};
564     delete $_->{htl};
565     }
566    
567     my $fail = 0;
568     my $sig = new Coro::Signal;
569    
570     $self->{status} = "splitfile fetch (" . @$blk . " blocks)";
571    
572     my @txn;
573    
574     for (;;) {
575     for my $id (0 .. $#$blk) {
576     my $blk = $blk->[$id];
577    
578     next if $txn[$id] || $blk->{done} || $blk->{seg}{todo} <= $blk->{seg}{done};
579    
580     my $htl = $HTL[$blk->{htl}++ % @HTL];
581 root 1.5 my $pri = int time + $htl / 20 * 7200 * rand;
582 root 1.1
583 root 1.5 #warn $self->{id} . ", GET<$htl, $pri>\n";#d#
584 root 1.15 $txn[$id] ||= txn_client_get pri => $pri, uri => $blk->{uri}, htl => $htl, cb => sub {
585 root 1.1 undef $txn[$id];
586    
587     my $seg = $blk->{seg};
588    
589     my ($meta, $data) = eval { @{ $_[0]->result } };
590    
591     if (defined $data) {
592     $blk->{size} == length $data
593     or die sprintf "block $id expected size %d, got %d\n", $blk->{size}, length $data;
594    
595 root 1.9 (length $data) == (aio_write $self->{fh}, $blk->{offset}, (length $data), $data, 0)
596 root 1.1 or die "unable to write chunk to disk, not setting valid flag";
597 root 1.9 aio_fsync $self->{fh};
598 root 1.1
599     $blk->{done} = 1;
600     $blk->{meta} = $meta->{raw} if length $meta->{raw};#d#
601     $seg->{done}++;
602     $self->save;
603    
604     $::htl_sum += $htl;
605     $::htl_cnt++;
606    
607 root 1.4 $self->log (sprintf "got block $seg->{id}.$id %d ($seg->{done}/$seg->{todo}) at htl $htl (%f) and pri $pri",
608     length $data, $::htl_sum / $::htl_cnt);
609 root 1.1 } else {
610     if ($@) {
611     if ($@->type ("data_not_found")) {
612     # nop
613     } elsif ($@->type ("network_error")) {
614     $self->log ("$@, retrying in 1s");
615     CORE::sleep 1;
616     } else {
617     $self->log ("$@");
618     }
619     }
620     ++$fail;
621     }
622     $self->{status} = "splitfile fetch ($seg->{done}/$seg->{todo}, $fail failed)";
623    
624     $sig->send;
625 root 1.15 };
626 root 1.1 }
627    
628     for my $seg (@segments) {
629     if ($seg->{done} >= $seg->{todo} && !$seg->{finished}) {
630    
631     $self->log ("segment done, cancelling segment $seg->{id}");
632     for my $id (@{$seg->{blk}}) {
633     (delete $txn[$id])->cancel if $txn[$id];
634     }
635    
636 root 1.10 my $verify;
637 root 1.1 for my $id (@{$seg->{blk}}) {
638     my $blk = $blk->[$id];
639     if ($blk->{done}) {
640 root 1.10 aio_read $self->{fh}, $blk->{offset}, $blk->{size}, my $buf, 0;
641 root 1.1
642     my $k1 = Net::FCP::Util::extract_chk_hash $blk->{uri};
643     my $k2 = Net::FCP::Util::generate_chk_hash $blk->{meta}, $buf;
644    
645     if ($k1 ne $k2) {
646 root 1.10 $verify .= "v";
647 root 1.1 #warn sprintf "$p->{title} block $id BROKEN (%s != %s)", (unpack "H*", $k1), (unpack "H*", $k2);
648     $blk->{done} = 0;
649     $seg->{done}--;
650     $self->save;
651     } else {
652 root 1.10 $verify .= "V";
653 root 1.1 }
654     }
655     }
656 root 1.10 $self->log ("verified segment $seg->{id}");
657     $self->log ($verify);
658 root 1.1
659     if ($seg->{done} >= $seg->{todo} && !$seg->{finished}) {
660     $self->log ("verified segment OK $seg->{id}");
661     $seg->{finished}++;
662     $segments--;
663     } else {
664     $self->log ("verified segment NOT OK $seg->{id}");
665     }
666     }
667     }
668    
669     last unless $segments;
670    
671     $sig->wait;
672     }
673    
674     $self->log ("decoding < $self->{job} $self->{file} $filesize >");
675    
676     for my $seg (@segments) {
677     my @part;
678     my @idx;
679     my @blk = map $blk->[$_], sort { $a <=> $b } @{$seg->{blk}};
680    
681     for my $id (0 .. $#blk) {
682     my $blk = $blk[$id];
683     next unless $blk->{done};
684    
685     push @part, [$self->{fh}, $blk->{offset}];
686     push @idx, $id;
687    
688     last if @idx == $seg->{todo};
689     }
690    
691     my $fec = new Algorithm::FEC
692     $seg->{todo},
693     scalar @blk,
694     $seg->{blksize};
695    
696     $fec->shuffle (\@part, \@idx);
697    
698     # now copy check blocks to their destination position
699     for my $i (0 .. $#idx) {
700     next if $idx[$i] == $i;
701    
702     my $src = $part[$i];
703     $part[$i] = [$self->{fh}, $blk[$i]{offset}];
704     $fec->copy ($src, $part[$i]);
705     }
706    
707     $fec->set_decode_blocks (\@part, \@idx);
708     $fec->decode;
709     }
710    
711     my $sha1 = new Digest::SHA1;
712     open my $dd, "-|", "head -c$filesize \Q$self->{file}\E"
713     or die "DD: $!";
714     #$dd = Coro::Handle::unblock $dd;
715     $sha1->addfile ($dd);
716     $sha1 = $sha1->hexdigest;
717    
718     if (exists $p->{meta}{document}[0]{info}{checksum}
719     and $p->{meta}{document}[0]{info}{checksum} ne $sha1) {
720     $self->log ("META: $p->{meta}{document}[0]{info}{checksum} and real checksum $sha1 for $filesize DIFFER");
721 root 1.9 # $self->feedback ("CHECKSUM ERROR");
722     # terminate;
723 root 1.1 }
724    
725     truncate $self->{fh}, $filesize;
726     sysseek $self->{fh}, 0, 0;
727    
728     return ["finish", 1];
729     } else {
730     $self->log ("splitfile algo '$splitfile->{algo_name}' unknown");
731     $self->feedback ("algo unknown");
732     terminate;
733     }
734     }
735    
736     package main;
737    
738     $|=1;
739    
740 root 1.17 for (<\Q$QUEUE_HOME\E/*.j>) {
741     job->new_from_file ($_);
742     print "J";
743     }
744     print "\n";
745 root 1.1
746     open my $stdin , "<&0" or die;
747     open my $stdout, ">&1" or die;
748     cmdline unblock $stdin, unblock $stdout;
749    
750 root 1.19 EV::loop;
751 root 1.1