The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl
#$Id: dcget 939 2011-11-10 09:37:32Z pro $ $URL: svn://svn.setun.net/dcppp/trunk/examples/dcget $

=head1 NAME

 UNFINISHED!!! [auto] get [popular] files

=head1 SYNOPSIS

 ./dcget dchub://hub [adc://hub] [tth] [-file] ...

 TODO:topath/name:TTH:size

=head1 CONFIGURE 

 create config.pl:
 $config{dc}{host} = 'myhub.net';

=cut
use 5.10.0;
use strict;
use Data::Dumper;
$Data::Dumper::Sortkeys = $Data::Dumper::Useqq = $Data::Dumper::Indent = 1;
use Time::HiRes qw(time sleep);
#use Encode;
use lib::abs '../lib';
#use lib '../TigerHash/lib';
#use lib './stat/pslib';
our ( %config, %work );
#use psmisc;
use Net::DirectConnect::pslib::psmisc;
psmisc->import(qw(:log));
#*config = *main::config;
#require Net::DirectConnect::pslib::psmisc;
#psmisc->import qw(:config :log printlog);
#use pssql;
use Net::DirectConnect;
use Net::DirectConnect::filelist;
#$config{disconnect_after}     //= 10;
#$config{disconnect_after_inf} //= 0;
psmisc::configure();    #psmisc::lib_init();
psmisc::lib_init();     #for die handler
#$config{'auto_get_best'}      //= 1;
$config{'hit_to_ask'}         //= 5;
$config{'queue_recalc_every'} //= 100;
$config{'get_every'}          //= 60;
#$config{'get_dir'}            //= './downloads/';
$config{ 'log_' . $_ } //= 0 for qw (dmp dcdmp dcdbg);
psmisc::printlog("usage: $1 [adc|dchub://]host[:port] [hub..]\n"), exit
  if !$ARGV[0] and !( $config{dc}{host} || $config{dcget}{host} );    # and !$config{dc}{hosts};
psmisc::printlog( 'info', 'started:', $^X, $0, join ' ', @ARGV );
#$SIG{INT} = $SIG{KILL} = sub { printlog 'exiting', exit; };
#printlog 'dev', 'mkdir', $config{'get_dir'},
#mkdir $config{'get_dir'};
#exit;
#use Net::DirectConnect::adc;
my $log = sub (@) {
  my $dc = ref $_[0] ? shift : {};
  #print Dumper @_;
  psmisc::printlog shift(), "[$dc->{'number'}]", @_,;
};
my @todl = grep { /^[A-Z0-9]{39}$/ or /^-/ } @ARGV;
s/^-// for @todl;
@ARGV = grep { !( /^[A-Z0-9]{39}$/ or /^-/ ) } @ARGV;
$SIG{PIPE} = sub { printlog( 'sig', 'PIPE' ) };
my @dirs = grep { -d } @ARGV;
#printlog('dev', 'started', @ARGV),
my $filelist = shift @ARGV if $ARGV[0] ~~ 'filelist';
@ARGV = grep { !-d } @ARGV;
my @hosts = grep { m{^\w+://} } @ARGV;
Net::DirectConnect::filelist->new( log => $log, %{ $config{dc} || {} } )->filelist_make(@dirs), exit
  if ( $filelist and !caller )
  or ( !@hosts and !( $config{dc}{host} || $config{dcget}{host} ) );
my @dc;
@dc = map {
  Net::DirectConnect->new(
    #modules            => ['filelist'],
    share              => \@dirs,
    'filelist_builder' => ( join ' ', $^X, $0, 'filelist' ),
    #'filelist_builder' => ( join ' ', $^X, 'dcshare', 'filelist' ),
    #SUPAD        => { H => { PING => 1 } },
    #botinfo      => 'devperlpinger',
    #auto_GetINFO => 1,
    #Nick         => 'perlgetterrr',
    auto_connect => 1,
    auto_say     => 1,
    #dev_http     => 1,
    'log'     => $log,
    'handler' => {
      'Search' => sub {    #_parse_aft
        my $dc = shift;
        #printlog 'sch', Dumper @_ if $dc->{adc};
        my $who    = shift if $dc->{adc};
        my $search = shift if $dc->{nmdc};
        my $s = $_[0] || {};
        $s = pop if $dc->{adc};
        return if $dc->{nmdc} and $s->{'nick'} eq $dc->{'Nick'};
        #my $q = $s->{'tth'} || $s->{'string'} || $s->{'TR'} || $s->{'AN'} || return;
        my $q = $s->{'tth'} || $s->{'TR'} || return;
        ++$work{'ask'}{$q};
        ++$work{'stat'}{'Search'};
      },
      'SR' => sub {    #_parse_aft
        my $dc = shift;
        #printlog 'SRh', @_;
        my %s = %{ $_[1] || return };
        #printlog 'SRparsed:', Dumper \%s;
        #$db->insert_hash( 'results', \%s );
        ++$work{'filename'}{ $s{tth} }{ $s{filename} };
        $work{'tthfrom'}{ $s{tth} }{ $s{nick} } = \%s;
        #++$work{'stat'}{'SR'};
      },
      'UPSR' => sub {    #_parse_aft
        my $dc = shift;
        #my %s = %{ $_[1] || return };
        #printlog 'UPSRparsed:', $dc, ':', @_;#Dumper \%s;
        #$db->insert_hash( 'results', \%s );
        #++$work{'filename'}{ $s{tth} }{ $s{filename} };
        #$work{'tthfrom'}{ $s{tth} }{ $s{nick} } = \%s;
        #++$work{'stat'}{'SR'};
      },
      'RES' => sub {     #_parse_aft
        my $dc = shift;
        #psmisc::printlog 'RESparsed:', Dumper( \@_ );
        my ( $dst, $peercid ) = @{ $_[0] };
        my $s = pop || return;    #%{ $_[1] || return };
                                  #$db->insert_hash( 'results', \%s );
                                  #my ($file) = $s->{FN} =~ m{([^\\/]+)$};
                                  #++$work{'filename'}{ $s->{TR} }{$file}; # $self->{'want_download_filename'}
                                  #$s->{CID} = $peercid;
                                  #$work{'tthfrom'}{ $s->{TR} }{$peercid} = $s; # $self->{'want_download_tth_from'}
                                  #++$work{'stat'}{'RES'};
      },
    },
    #auto_work
    worker => sub {
      my $dc = shift;
      #printlog('worker',  keys %{$dc->{parent}||{}}    );
      $dc->{'handler'}{'SCH'} ||= $dc->{'handler'}{'Search'};    #_parse_aft _parse_aft
      psmisc::schedule(
        $config{'queue_recalc_every'},
        our $queuerecalc_ ||= sub {
          my $dc   = shift;
          my $time = int time;
          $work{'toask'} = [ (
              sort { $work{'ask'}{$a} <=> $work{'ask'}{$b} } grep {
                      $work{'ask'}{$_} >= $config{'hit_to_ask'}
                  and !exists $work{'asked'}{$_}
                  and !exists $dc->{share_full}{$_}
                } keys %{ $work{'ask'} }
            )
          ];
          psmisc::printlog(
            'info', "queue len=", scalar @{ $work{'toask'} },
            " first hits=", $work{'ask'}{ $work{'toask'}[0] },
            "asks=", scalar keys %{ $work{'ask'} },
            $work{'toask'}[0]
          );
        },
        $dc
      );
      psmisc::schedule(
        [ 3600, 3600 ],
        our $hashes_cleaner_ ||= sub {
          my $dc = shift;
          #my $min = scalar keys %{ $work{'hubs'} || {} };
          my $min = scalar @dc;
          psmisc::printlog( 'info', "queue clear min[$min] now", scalar %{ $work{'ask'} || {} } );
          delete $work{'ask'}{$_} for grep { $work{'ask'}{$_} <= $min } keys %{ $work{'ask'} || {} };
          psmisc::printlog( 'info', "queue clear ok now", scalar %{ $work{'ask'} || {} } );
        },
        $dc
      );
      if ( $config{'auto_get_best'} ) {
        psmisc::schedule(
          $dc->{'search_every'},
          $dc->{'___queueask_'} ||= sub {
            my $dc = shift;
            return unless $dc->active();
            my $q;
            #psmisc::printlog( 'dev', "getbest run");
            while ( $q = shift @{ $work{'toask'} } or return ) {
              #psmisc::printlog( 'dev', "getbest:", $q);
              psmisc::printlog( 'dev', 'already in share', $q ), next if $dc->{share_full}{$q};
              last if !exists $work{'asked'}{$q};
#$work{'ask_db'}{$q} = $work{'asked'}{$q} = $r->{'time'}, next                  if $r and $r->{'time'};    # + $config{'ask_retry'} > time;
#$work{'ask_db'}{$q} = 0;
#last;
            }
            return unless length $q;
            #psmisc::printlog( 'dev', "getbest todo [$dc->{'search_todo'}]", );
            if ( !$dc->{'search_todo'} ) {
              $work{'asked'}{$q} = int time;
              psmisc::printlog( 'info', "search", $q, 'on', $dc->{'host'} );
              #$dc->search($q);
              $dc->download($q);
            } else {
              unshift @{ $work{'toask'} }, $q;
            }
          },
          $dc
        );
      }
      #printlog 'getev' ,$config{'get_every'};
      psmisc::schedule(
        [ 10, 99999999 ],
        our $se_sub__ ||= sub {
          my $dc = shift;
          #$dc->search('lost');
          #$dc->search($_) for @ARGV;
        },
        $dc
      );
      psmisc::schedule(
        [ 10, 0, 1 ],
        our $update_queue_sub__ ||= sub {
          #my $self = $dc;
          my $self = shift;
          #$self->log('dev', 'dlsub once!!');
          my %partial;
          unless ( $self->{no_auto_load_partial} ) {
            my $dir = $self->{'partial_prefix'};
            #$self->log('dev', 'look', $self->{'partial_prefix'});
            for my $file (<$dir*>) {
              #$self->log('dev', $_);
              local $_ = $file;
              $_ = Encode::decode $self->{charset_fs}, $_, Encode::FB_WARN if $self->{charset_fs};
              #$self->log('dev', 'U', $_);
              next unless s/$self->{'partial_ext'}$//i;
              next unless s/^$self->{'partial_prefix'}//i;
              #$self->log('dev', $_);
              next unless s/\.([A-Z0-9]{39})$//;
              my ($tth) = $1;
              $self->log( 'warn', 'removing already downloaded partial', $_, $tth ), unlink($file), next
                if $self->{'share_full'}{$tth};
              $self->log( 'warn', 'removing already downloading partial', $_, $tth ), unlink($file), next
                if $partial{$tth}{size} > -s $file;
				$self->log( 'warn', 'removing already downloading partial2', $_, $tth , $partial{$tth}{size}),
			  unlink($partial{$tth}{file}) if $partial{$tth}{size} and $partial{$tth}{size} < -s $file;
              $self->log( 'dev', 'adding to queue', $_, $tth, -s $file, $partial{$tth} );
              #$self->{'want_download'}{$tth} //= {};
              push @todl, $tth unless $partial{$tth};
              $partial{$tth}{size} = -s $file;
              $partial{$tth}{file} = $file;
              $self->{'want_download_filename'}{$tth} = { $_ => 100 };
            }
            #$self->dumper();
          }
        },
        $dc
      );
      psmisc::schedule(
        [ 10, 11 ],
        our $dl_sub__ ||= sub {
          return unless @todl;
          #$dc->search('lost');
          #$dc->search($_) for @ARGV;
          $dc->download( shift @todl );
        },
        $dc
      );
    },
    %{ $config{dc}    || {} },
    %{ $config{dcget} || {} },
    #( $_ ? ( 'host' => $_ ) : () ),
    #( $ARGV[0] ? ( 'host' => $ARGV[0] ) : () ),
    'host' => $_,
    )    # for ( @ARGV, @{ $config{dc}{hosts} || [] } );
  } (
  grep {
    $_
    }    #psmisc::array($config{dc}{host}), #ref $config{dc}{host} eq 'ARRAY' ? @{ $config{dc}{host} } : $config{dc}{host},
         #@ARGV
    @hosts ? @hosts : psmisc::array( $config{dc}{host} || $config{dcget}{host} )
  );
$_->{__work} ||= \%work for    #for dumper
  grep { $_->{dev_auto_dump} } @dc;
while ( @_ = grep { $_ and $_->active() } @dc ) {
  $_->work() for @_;
  #Time::HiRes::sleep 0.1;
}

#psmisc::printlog 'dev', 'endst', join ', ', map {$_->status }@dc