# TODO: serialization trhu Queue prolly #!/usr/bin/perl use strict; use warnings; use lib "/home/dabreegster/lab/hexedui"; use include; if (@ARGV and shift() eq "--stfu") { open STDERR, ">>/var/log/bewarez" or die "can't open stderr log: $!\n"; print STDERR "-" x 80, "\n"; } #sub POE::Kernel::ASSERT_DEFAULT () { 1 } use POE ("Component::Server::TCP"); use PoCoDownload; use HexedUI; use Queue; use Storable; # Set up the queue. my $dlqueue = new Queue; my $entry = forge_template( ["", "(%)", " MB of MB", "[ K/s]", "eta {}"], Name => { Format => "-", }, Percent => { Format => "100.0", Filter => sub { my $obj = shift; $obj->{Total} // return 0; sprintf("%.1f", $obj->{SoFar} / $obj->{Total} * 100) } }, SoFar => { Format => "1024.42", Filter => sub { my $obj = shift; $obj->{SoFar} // return 0; sprintf("%.2f", $obj->{SoFar} / 1024 ** 2) } }, Total => { Format => "1024.42", Filter => sub { my $obj = shift; $obj->{Total} // return 0; sprintf("%.2f", $obj->{Total} / 1024 ** 2) } }, Speed => { Format => "100.0", Filter => sub { my $obj = shift; $obj->{Speed} // return 0; sprintf("%.1f", $obj->{Speed}) } }, ETA => { Format => "xx::yy::zz" } ); # Set up the interface. my $ui = cast HexedUI BeWarez => { Title => { Type => "Fixed", At => [0, 0], Size => [3, "100%"], Border => 1 }, Main => { Type => "Menu", At => [3, 0], Size => ["100% - 3", "100%"], Border => 1, Pad => 1, Queue => $dlqueue, KeyHandler => sub { my (undef, $key) = @_; if ($key eq "p") { POE::Kernel->post("leecher", "toggle"); } elsif ($key eq "") { POE::Kernel->post("leecher", "delete_dl"); } }, ShiftKeys => 1, DeleteKey => 0 # We want to do preprocessing on it ourselves }, Shutdown => sub { POE::Kernel->call("leecher", "pause"); POE::Kernel->post("leech", "shutdown"); POE::Kernel->post("server", "shutdown"); delete @{$dlqueue}{qw(Heap Hooks)}; store($dlqueue, ".dlqueue"); } }; # Set up the session to handle commands. POE::Component::Server::TCP->new( Port => 31008, Alias => "server", ClientInput => sub { my ($cmd, $arg) = split(" ", $_[ARG0], 2); if ($cmd eq "add") { POE::Kernel->post("leecher", "add_dload", $arg); } else { croak "I don't know the command $cmd!"; } } ); # Set up the session to talk HTTP. POE::Component::Client::UserAgent->new(alias => "leech"); # Set up the session to handle downloading. POE::Session->create( inline_states => { _start => sub { $_[KERNEL]->alias_set("leecher"); $_[HEAP]{Alive} = 0; $ui->{BeWarez}{Title}->title(0, "Be Warez... I pwn your warez."); $ui->{BeWarez}{Title}->draw; $_[HEAP]{Queue} = $dlqueue; $dlqueue->hook(sub { my $queue = shift; my $cmd = shift; if ($cmd eq "shift") { my ($oldid, $newid) = @_; # We only need to do something if the first queue spot has been tampered with. return unless $oldid == 0 or $newid == 0; my $heap = $queue->{Heap}{Leecher}; # And preserve our status (if we're active or not). my $status = $heap->{Alive}; # A progress event might beat our pause event. Shouldn't because we call() pause # ASAP but just in case. $heap->{Alive} = 0; # Pause the old file if it was active. my $pauseme = $oldid || $newid; # Pause the old '0'. POE::Kernel->call("leecher", "pause", $pauseme) if $status; # And start up the new one unless we were already paused. POE::Kernel->post("leecher", "resume") if $status; } # Save the new state of the queue in case we exit abnormally. if ($cmd eq "add" or $cmd eq "del" or $cmd eq "shift") { my $heap = delete $dlqueue->{Heap}; my $hooks = delete $dlqueue->{Hooks}; my $fh = delete $dlqueue->{Queue}[0]{File} if $dlqueue->all; # SAVE THE QUEUE! AND THE QUEEN! #store($dlqueue, ".dlqueue"); ($dlqueue->{Hooks}, $dlqueue->{Heap}) = ($hooks, $heap); $dlqueue->{Queue}[0]{File} = $fh if $fh; } }, Leecher => $_[HEAP]); if ($startup) { $dlqueue->set($_, MenuFilter => $entry) foreach 0 .. $#{ $dlqueue->{Queue} }; $ui->{BeWarez}{Main}->draw; POE::Kernel->post("leecher", "resume"); } }, add_dload => sub { my $url = $_[ARG0]; return unless $url; my $queue = $_[HEAP]{Queue}; my $fname; if ($url =~ m/youtube\.com/) { ($url, $fname) = clive_scrape($url); } else { $fname = basename($url); # Ew, nasty URL encoding. Spaces aren't that much better, but eh... $fname =~ s/%20/ /g; } my $dl = { Name => $fname, URL => $url, MenuFilter => $entry }; # Same file's already in the queue. debug("already in queue"), return if $queue->get($dl->{Name}); $queue->add($dl->{Name}, %$dl); # Do we start it now or not? if ($queue->all == 1) { #$queue->set($dl->{Name}, Menu => "Starting download: $dl->{Name}..."); $_[KERNEL]->yield("resume"); } else { #$queue->set($dl->{Name}, Menu => "Queued download: $dl->{Name}..."); } }, resume => sub { $_[HEAP]{Alive} = 1; return unless my $dl = $_[HEAP]{Queue}->get(0); $dl->{SoFar} = -s $dl->{Name} // 0; open $dl->{File}, ">>$dl->{Name}" or croak "Can't append to $dl->{Name}: $!"; $dl->{Request} = HTTP::Request->new("GET", $dl->{URL}, ["Range" => "bytes=$dl->{SoFar}-"]); $dl->{LastTime} = time; $dl->{LastSize} = $dl->{SoFar}; $dl->{Speed} = 0; $dl->{ETA} = "Never"; $_[KERNEL]->post(leech => "request", request => $dl->{Request}, response => $_[SESSION]->postback("response"), callback => \&progress, #chunksize => 1024 ); }, pause => sub { my $id = $_[ARG0] // 0; return unless my $dl = $_[HEAP]{Queue}->get($id); $_[KERNEL]->post("leech", "cancel"); $_[HEAP]->{Alive} = 0; close $dl->{File} if $dl->{File}; delete @{$dl}{qw(SoFar File Request Total LastTime LastSize Speed)} }, toggle => sub { $_[KERNEL]->yield( $_[HEAP]->{Alive} ? "pause" : "resume" ); }, response => sub { # We get this when a download is finished. my $response = $_[ARG1][1]; my $http_code = $response->code; if ($response->is_success or $http_code == 416) { # 416 means we've already got the whole file. my $queue = $_[HEAP]{Queue}; close $queue->get(0)->{File}; $queue->del(0); $_[HEAP]{Alive} = 0; # Go to the next download... if it's there. $_[KERNEL]->yield("resume") if $queue->all; } elsif ($response->is_error) { debug "SCREAM! error $http_code" } elsif ($response->is_redirect) { debug "redirecting..."; # Just update the request object so progress() doesn't freak out $_[HEAP]{Queue}->get(0)->{Request} = $response->{_request}; } else { debug "Well wtf IS the response, then?!"; } }, delete_dl => sub { my $queue = $_[HEAP]{Queue}; my $dl = $queue->get($queue->active); return unless my $opt = $ui->choose("Do you really want to delete $dl->{Name}?" => "Just kidding.", "Cancel the download.", "Cancel the download and delete the file." ); # Irregardless, remove the download from the queue. my $alive = $_[HEAP]{Alive}; POE::Kernel->call("leecher", "pause") if $queue->active == 0; $queue->del($queue->active); POE::Kernel->call("leecher", "resume") if $alive; unlink($dl->{Name}) if $opt == 2; # Delete it, whatever... } } ); $poe_kernel->run; exit; # Handle a chunk of data. sub progress { my ($data, $response, undef) = @_; my $heap = $dlqueue->{Heap}{Leecher}; debug("got a chunk, not not alive"), return unless $heap->{Alive}; my $dl = $dlqueue->get(0); # Meaningful data from the response object. my $http_code = $response->code; my $http_req = $response->request; my $http_total = $response->header("content-length"); # When we shift the queue, a cancel request is sent to the server. But it might not # process in time. So we want to check to make sure our current download matches the # response we got. But some redirects screw up and we have to match previous headers. debug("response got and request expected mismatch, old request"), return if "$dl->{Request}" ne "$http_req" and "$response->{_previous}{_request}" ne "$dl->{Request}"; # The below block executes when we first start a download. unless ($dl->{Total}) { # Errors and stuff are handled by Poco::Client::UserAgent, so all we have to deal with # is good ol' data. :P debug "scary http code $http_code" unless $http_code == 206 or $http_code == 200; if ($http_total) { $dl->{Total} = $dl->{SoFar} + $http_total; } else { debug "no content-length header. stupid server. bugginess expected."; $dl->{Total} = -1; } } debug "sofar=total but got data" if $dl->{SoFar} == $dl->{Total} and $dl->{Total}; debug "empty chunk received" unless $data; my $fh = $dl->{File}; print $fh $data; $dl->{SoFar} += length($data); # Calculate speed my ($time, $size) = (time, $dl->{SoFar}); if ($time - $dl->{LastTime} > 1) { $dl->{Speed} = ($size - $dl->{LastSize}) / ($time - $dl->{LastTime}) / 1024; ($dl->{LastTime}, $dl->{LastSize}) = ($time, $size); # Recalculate ETA my $secs = ($dl->{Total} - $dl->{SoFar}) / 1024 / $dl->{Speed}; my $ok = sprintf("%02d:%02d:%02d", int($secs / 3600), ($secs % 3600) / 60, ($secs %3600) % 60); $dlqueue->set($dl->{ID}, ETA => $ok); } $dlqueue->set($dl->{ID}, dummy => "just to update menufilter"); return 1; } # Gimme URL from youtube or similar sub clive_scrape { my $rawurl = shift; chomp(my $out = (`clive --emit $rawurl`)[-1]); my (undef, $url, $fname, undef) = split(",", $out); $url =~ s/"//g; $fname =~ s/"//g; return ($url, $fname); }