Created
August 14, 2018 07:04
-
-
Save iomonad/289521a705ede167488594d4079e9b5b to your computer and use it in GitHub Desktop.
Binge - curses client for transmission-daemon
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binge | |
===== | |
Another curses client for transmission-daemon. Not fully featured, | |
but sufficient for my daily torrents. Minimal and experimental. | |
Screenshot: http://i.imgur.com/2X82oyS.png | |
Dependencies | |
============ | |
apt-get install libcurses-perl libwww-perl libcpanel-json-xs-perl \ | |
libscalar-list-utils-perl libmath-round-perl libjson-maybexs-perl | |
Key Bindings | |
============ | |
u/d change up/download speed* | |
l change upload ratio* | |
R delete and remove* | |
r remove* | |
p start/stop* | |
J/K move up/down torrent queue* | |
Shft-PgUp/PgDwn move to top/bottom* | |
s sort torrent queue | |
ctrl-c quit | |
* the currently focused torrent | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl | |
## Another minimal curses client for transmission-daemon. | |
## Not fully featured, but sufficient for my daily torrents. | |
## 6 May, 2017 | |
use v5.24 ; | |
use strictures ; | |
use warnings ; | |
use feature qw(signatures) ; | |
no warnings qw(experimental::signatures) ; | |
use Curses ; | |
use LWP::UserAgent ; | |
use JSON::MaybeXS ; | |
use List::Util qw( none all any ) ; | |
use Math::Round qw( round ) ; | |
## Access tr-daemon via rpc | |
my $ua = LWP::UserAgent->new( agent => 'Transmission-Client' ) ; | |
my $url = 'http://localhost:9091/transmission/rpc' ; | |
my $trd_timeout = 10 ; | |
## Torrent data found via the rpc interface. | |
my $torrents = {} ; | |
my $trackerinfo = {} ; | |
my @queued ; | |
my %selected = ( id => -1, qpos => -1 ) ; | |
my $hilight = 0 ; | |
my $update_cnt = 0 ; | |
## Initial curses setup. | |
initscr(); | |
noecho() ; ## Don't echo() while we do getch | |
cbreak() ; ## line buffering disabled | |
keypad(1) ; ## we get f1, f2 etc... | |
timeout(900) ; ## getch exits after .9 sec | |
curs_set(0) ; ## hide cursor | |
start_color() ; | |
## 0 black, 1 red, 2 green, 3 yellow, 4 blue, 5 magenta, 6 cyan, 7 white | |
init_pair 1, COLOR_RED, COLOR_BLACK ; | |
init_pair 2, COLOR_GREEN, COLOR_BLACK ; | |
init_pair 4, COLOR_BLUE, COLOR_BLACK ; | |
init_pair 5, COLOR_MAGENTA, COLOR_BLACK ; | |
init_pair 7, COLOR_BLACK, COLOR_WHITE ; | |
my $CP_DIVIDER = COLOR_PAIR(1); | |
my $CP_DIM1 = COLOR_PAIR(2); | |
my $CP_STD = COLOR_PAIR(4); | |
my $CP_BRIGHT = COLOR_PAIR(5); | |
my $CP_HILIGHT = COLOR_PAIR(7); | |
## Display initial torrent data | |
get_torrent_data() ; | |
display() ; | |
## MAIN LOOP | |
while ( 1 ) { | |
## Get latest torrent data. | |
for (my $cnt = 0 ; $cnt <= $trd_timeout ; $cnt++) { | |
last if get_torrent_data() ; | |
sleep .2 ; | |
die "Connection not found" if $cnt == $trd_timeout ; | |
} | |
my $ch = getchar() ; | |
if (defined $ch) { | |
timeout(900) ; | |
$hilight = 1 ; | |
my @updownkeys = qw( u d U D ) ; | |
my @movekeys = ( KEY_SHOME, KEY_SEND, "J", "K" ) ; | |
my @navkeys = ( KEY_DOWN, KEY_UP, KEY_HOME, KEY_END, KEY_PPAGE, KEY_NPAGE, "j", "k" ) ; | |
## If Esc, then no torrent is focused. | |
if ($ch eq "\e" or not scalar @queued) { %selected = ( id => -1, qpos => -1) ; | |
## Modify the upload ratio of the focused torrent | |
} elsif ($ch eq "L" or $ch eq "l") { change_ratio() ; | |
## Modify the up/download rate of the focused torrent | |
} elsif ( any { $_ eq $ch } @updownkeys ) { change_rate($ch) ; | |
## Navigate torrent queue with keys. | |
} elsif ( any { $_ eq $ch } @navkeys ) { keyselect_torrent($ch) ; | |
## Move the focused torrent up/down the torrent queue. | |
} elsif ( any { $_ eq $ch } @movekeys ) { move_torrent($ch) ; | |
## Remove the focused torrent | |
} elsif ($ch eq "r" or $ch eq "R") { remove_torrent($ch) ; next ; | |
## Start/stop the focused torrent. | |
} elsif ($ch eq "p" ) { toggle_pause($ch) ; next ; | |
## Sort the torrent queue. | |
} elsif ($ch eq "s" ) { queuesort() ; next ; | |
## if key char $ch not recognized, then... | |
} else { | |
timeout(2500) ; | |
$hilight = 0 ; | |
} | |
## if key char $ch undefined, then... | |
} elsif ($hilight) { | |
timeout(900) ; | |
$hilight-- ; | |
} else { | |
timeout(2500) ; | |
} | |
display() ; | |
} | |
say STDERR "Exiting..." ; | |
endwin() ; | |
#### SUBROUTINES | |
## Change upload/download rate for the focused torrent. | |
sub change_rate($ch) { | |
my @updownkeys = qw( u d U D ) ; | |
return 0 if $selected{id} == -1 or none { $_ eq $ch } @updownkeys ; | |
my ($key1, $key2, $msg) ; | |
($key1, $key2, $msg) = ( "uploadLimit", "uploadLimited", "Upload") if $ch =~ /u/i ; | |
($key1, $key2, $msg) = ("downloadLimit", "downloadLimited", "Download") if $ch =~ /d/i ; | |
my %args = ( ids => [ $selected{id} ], fields => [ $key1, $key2 ] ) ; | |
my $data = rpc('torrent-get', %args) ; | |
return 0 if not $data ; | |
for ($key1, $key2) { return 0 unless defined $data->{torrents}->[0]->{$_} } ; | |
my $foo = $data->{torrents}->[0]->{$key1} ; | |
my $bar = $data->{torrents}->[0]->{$key2} ; | |
my $rate = $bar ? $foo : -1 ; | |
while ( 1 ) { | |
$rate = -1 if $rate < 0 ; | |
my $string = sprintf("%s %s%5d", $msg, "speed:", $rate) ; | |
clrtoeol($LINES - 1, 0) ; | |
addstring($LINES - 1, 2, "$string") ; | |
my $ch = getchar() ; | |
next unless defined $ch ; | |
if ($ch eq KEY_UP) { $rate = $rate >= 0 ? $rate + 100 : 100 ; | |
} elsif ($ch eq KEY_DOWN) { $rate = $rate - 100 > 0 ? $rate - 100 : -1 ; | |
} elsif ($ch eq KEY_RIGHT) { $rate = $rate >= 0 ? $rate + 10 : 10 ; | |
} elsif ($ch eq KEY_LEFT) { $rate = $rate - 10 > 0 ? $rate - 10 : -1 ; | |
} elsif ($ch eq "\e" or $ch eq "q") { return 0 | |
} elsif ($ch eq "\n") { last | |
} | |
} | |
%args = ( ids => [ $selected{id} ], $key2 => 0 ) if $rate == -1 ; | |
%args = ( ids => [ $selected{id} ], $key1 => $rate, $key2 => 1 ) if $rate != -1 ; | |
rpc('torrent-set', %args) ; | |
return 1 ; | |
} | |
## Asks the user a yes/no question. | |
sub confirm($msg) { | |
clrtoeol($LINES - 1, 0) ; | |
addstring($LINES - 1, 2, $msg) ; | |
while (1) { | |
my $ch = getchar() ; | |
next unless defined $ch ; | |
if ($ch eq "y") { | |
clrtoeol($LINES - 1, 0) ; | |
refresh() ; | |
return 1 ; | |
} elsif ($ch eq "n" or $ch eq "\e" or $ch eq "q") { | |
clrtoeol($LINES - 1, 0) ; | |
refresh() ; | |
return 0 ; | |
} | |
} | |
} | |
## Shows message on bottom line for 1 second. | |
sub shortmessage($msg) { | |
my $row = $LINES - 1 ; | |
clrtoeol($row, 0) ; | |
addstring($row, 2, $msg) ; | |
refresh() ; | |
sleep 1 ; | |
clrtoeol($row, 0) ; | |
refresh() ; | |
return 1 ; | |
} | |
## Remove (and sometimes delete) the focused torrent. | |
sub remove_torrent($ch) { | |
timeout(0) ; | |
my ($id, $qpos) = @selected{qw( id qpos )} ; | |
if ($ch eq "r" and confirm("Remove torrent? y/n")) { | |
my %args = (ids => [ $id ], ) ; | |
rpc('torrent-remove', %args ) ; | |
shortmessage("Removed.") ; | |
return 1 ; | |
} elsif ($ch eq "R" and confirm("Remove and delete torrent? y/n")) { | |
my %args = (ids => [ $id ], "delete-local-data" => 1) ; | |
rpc('torrent-remove', %args ) ; | |
shortmessage("Removed.") ; | |
return 1 ; | |
} else { | |
return 0 ; | |
} | |
} | |
## Move the focused torrent up/down the torrent queue. | |
sub move_torrent($ch) { | |
my $method = 0 ; | |
$method = "queue-move-up" if $ch eq "K" ; | |
$method = "queue-move-down" if $ch eq "J" ; | |
$method = "queue-move-top" if $ch eq KEY_SHOME ; | |
$method = "queue-move-bottom" if $ch eq KEY_SEND ; | |
return 0 unless $method ; | |
$hilight = 2 ; | |
timeout(0) ; | |
return rpc("$method", ( ids => [ $selected{id} ] ) ) ; | |
} | |
## Pause/unpause the focused torrent. | |
sub toggle_pause($ch) { | |
$hilight = 2 ; | |
timeout(0) ; | |
my ($id, $qpos) = @selected{qw( id qpos )} ; | |
my $status = $queued[$qpos]->{status} ; | |
if ($queued[$qpos]->{id} == $id) { | |
rpc('torrent-start', ids => $id) if $status == 0 and $ch eq "p" ; | |
rpc('torrent-stop', ids => $id) if $status != 0 and $ch eq "p" ; | |
} | |
} | |
## Change seeding ratio of the focused torrent | |
sub change_ratio { | |
my $qpos = $selected{qpos} ; | |
my $ratio = $queued[$qpos]->{seedRatioLimit} ; | |
while ( 1 ) { | |
$ratio = -1 if $ratio < 0 ; | |
my $foo = $ratio <= 0 ? sprintf("%4d", $ratio) : sprintf("%4.1f", $ratio) ; | |
clrtoeol($LINES - 1, 0) ; | |
addstring($LINES - 1, 2, "Upload ratio: $foo") ; | |
my $ch = getchar() ; | |
if (defined $ch ) { | |
if ($ch eq KEY_UP) { $ratio++ | |
} elsif ($ch eq KEY_DOWN) { $ratio-- | |
} elsif ($ch eq KEY_RIGHT) { $ratio += .1 | |
} elsif ($ch eq KEY_LEFT) { $ratio -= .1 | |
} elsif ($ch eq "\e" or $ch eq "q") { return 0 | |
} elsif ($ch eq "\n") { last | |
} | |
} | |
} | |
my %args = ( | |
ids => [ $selected{id} ], | |
seedRatioLimit => $ratio, | |
seedRatioMode => 1, | |
) ; | |
rpc('torrent-set', %args) ; | |
return 1 ; | |
} | |
## Use the nav keys to focus a torrent. | |
sub keyselect_torrent($ch) { | |
$selected{qpos}++ if ($ch eq KEY_DOWN or $ch eq "j") and $selected{qpos} < $#queued ; | |
$selected{qpos}-- if ($ch eq KEY_UP or $ch eq "k") and $selected{qpos} >= 0 ; | |
$selected{qpos} = 0 if $ch eq KEY_HOME ; | |
$selected{qpos} = $#queued if $ch eq KEY_END ; | |
if ($ch eq KEY_PPAGE) { | |
my $qpos = $selected{qpos} - $LINES + 2 ; | |
$selected{qpos} = $qpos >= 0 ? $qpos : 0 ; | |
} | |
if ($ch eq KEY_NPAGE) { | |
my $qpos = $selected{qpos} + $LINES - 2 ; | |
$selected{qpos} = $qpos > $#queued ? $#queued : $qpos ; | |
} | |
my $qpos = $selected{qpos} ; | |
$selected{id} = $qpos >= 0 ? $queued[$qpos]->{id} : -1 ; | |
} | |
sub queuesort { | |
sub by_active_seeds { | |
my ($aa, $bb) ; | |
my $astatus = $a->{status} ; my $bstatus = $b->{status} ; | |
if ($astatus == 6 and $bstatus == 6) { | |
$aa = $a->{rateUpload} ; | |
$bb = $b->{rateUpload} ; | |
} else { | |
$aa = 0 ; $bb = 0 ; | |
} | |
$bb <=> $aa ; | |
} | |
sub by_status { | |
my %tr = ( 4 => 6, 3 => 5, 6 => 4, 5 => 3, 0 => 2, 1 => 1, 2 => 0 ) ; | |
my $aa = $a->{status} ; my $bb = $b->{status} ; | |
my $astatus = $tr{$aa} ; my $bstatus = $tr{$bb} ; | |
$bstatus <=> $astatus ; | |
} | |
sub by_date { | |
my $aa = defined($a->{addedDate}) ? $a->{addedDate} : 0 ; | |
my $bb = defined($b->{addedDate}) ? $b->{addedDate} : 0 ; | |
$bb <=> $aa ; | |
#$aa <=> $bb ; | |
} | |
my @sorted = sort by_active_seeds | |
sort by_status | |
sort by_date @queued ; | |
for (my $num = 0 ; $num <= $#sorted ; $num++ ) { | |
my %args = ( ids => [ $sorted[$num]->{id} ], queuePosition => $num ) ; | |
rpc('torrent-set', %args) ; | |
} | |
timeout(0) ; | |
shortmessage("Sorting...") ; | |
%selected = ( id => -1, qpos => -1) ; | |
return 1 ; | |
} | |
#### DISPLAY SUBROUTINES | |
## Update/refresh all displayed torrents. | |
sub display() { | |
## Compute range of torrents to display. | |
my ($first, $last) ; | |
my $total = $LINES - 2 ; | |
if ( $#queued < $total ) { | |
$first = 0 ; | |
$last = $#queued ; | |
} elsif ( $selected{qpos} <= int($total/2) ) { | |
$first = 0 ; ## first is an index into @queued | |
$last = $total - 1 ; ## last is an index in @queued | |
} elsif ( ($#queued - $selected{qpos}) < int($total/2) ) { | |
$first = $#queued - $total + 1 ; | |
$last = $#queued ; | |
} else { | |
my $half = int( $total/2 ) ; | |
$first = $selected{qpos} - $half ; | |
$last = $selected{qpos} - $half + $total -1 ; | |
} | |
## The top/bottom lines are blanked. | |
clrtoeol(0, 0) ; | |
clrtoeol($LINES - 1, 0) ; | |
my $row = 1 ; | |
for (my $qpos = $first ; $qpos <= $last ; $qpos++, $row++ ) { | |
my $col = 0 ; | |
my $name_width = $COLS - 64 ; | |
addstring($row, $col, " ") ; $col += 2 ; | |
$col += print_name($row, $col, $qpos, $name_width) ; | |
$col += print_div($row, $col, $qpos) ; | |
$col += print_sizeWhenDone($row, $col, $qpos) ; | |
$col += print_div($row, $col, $qpos) ; | |
$col += print_percentDone($row, $col, $qpos) ; | |
$col += print_div($row, $col, $qpos) ; | |
$col += print_ratio($row, $col, $qpos) ; | |
$col += print_div($row, $col, $qpos) ; | |
$col += print_peers($row, $col, $qpos,"seederCount") ; | |
$col += print_div($row, $col, $qpos) ; | |
$col += print_peers($row, $col, $qpos,"leecherCount") ; | |
$col += print_div($row, $col, $qpos) ; | |
$col += print_connected($row, $col, $qpos) ; | |
$col += print_div($row, $col, $qpos) ; | |
$col += print_eta($row, $col, $qpos) ; | |
$col += print_div($row, $col, $qpos) ; | |
$col += print_rateDownload($row, $col, $qpos) ; | |
$col += print_div($row, $col, $qpos) ; | |
$col += print_rateUpload($row, $col, $qpos) ; | |
addstring($row, $col, " ") ; | |
} | |
for ( ; $row < $total + 1 ; $row++ ) { clrtoeol($row, 0) } ; | |
} | |
sub print_name($row,$col,$qpos,$width,) { | |
my $status = $queued[$qpos]->{status} ; | |
my $name = sprintf( "%-${width}s", substr($queued[$qpos]->{name}, 0, $width) ) ; | |
attrset(A_BOLD) if $qpos == $selected{qpos} ; | |
if ($status == 4) { attron($CP_BRIGHT) ; | |
} elsif ($status == 6) { attron($CP_STD) ; | |
} else { attron($CP_DIM1) ; | |
} | |
attrset($CP_HILIGHT) if $qpos == $selected{qpos} and $hilight ; | |
addstring($row, $col, $name) ; | |
standend() ; | |
return $width ; | |
} ; | |
sub print_sizeWhenDone($row,$col,$qpos) { | |
my ($num, $unit) = format_number($queued[$qpos]->{sizeWhenDone}) ; | |
my $output = sprintf "%3s%s", $num, $unit ; | |
attrset(A_BOLD) if $qpos == $selected{qpos} ; | |
if ($unit eq "G") { attron($CP_BRIGHT) ; | |
} else { attron($CP_STD) ; | |
} | |
attrset($CP_HILIGHT) if $qpos == $selected{qpos} and $hilight ; | |
addstring($row, $col, "$output") ; | |
standend() ; | |
return 4 ; | |
} | |
sub print_ratio($row,$col,$qpos) { | |
my $ratio = $queued[$qpos]->{uploadRatio} ; | |
my $status = $queued[$qpos]->{status} ; | |
my $fmt1 = "%4.2f" ; my $fmt2 = "%4.1f" ; my $fmt3 = "%4.0f" ; | |
my $output ; | |
if ($ratio < 0) { $output = " " ; | |
} elsif ($ratio < 10) { $output = sprintf($fmt1, $ratio) ; | |
} elsif ($ratio < 100) { $output = sprintf($fmt2, $ratio) ; | |
} else { $output = sprintf($fmt3, $ratio) ; | |
} ; | |
attrset(A_BOLD) if $qpos == $selected{qpos} ; | |
if ($ratio < 0) { attron($CP_DIM1) ; | |
} elsif ($ratio < 1) { attron($CP_BRIGHT) ; | |
} elsif ($ratio > 1 and $status != 0) { attron($CP_STD) ; | |
} else { attron($CP_DIM1) ; | |
} | |
attrset($CP_HILIGHT) if $qpos == $selected{qpos} and $hilight ; | |
addstring($row, $col, "$output") ; | |
standend() ; | |
return 4 ; | |
} | |
sub print_peers($row,$col,$qpos,$key) { | |
my $peers = $queued[$qpos]->{$key} ; | |
my $output ; | |
if ($peers == -1) { $output = " " ; | |
} elsif ($peers < 1000) { $output = sprintf "%3d", $peers ; | |
} else { $output = sprintf "%2dK", round($peers/1000) ; | |
} ; | |
attrset(A_BOLD) if $qpos == $selected{qpos} ; | |
if ($peers < 1000) { attron($CP_STD) ; | |
} else { attron($CP_BRIGHT) ; | |
} | |
attrset($CP_HILIGHT) if $qpos == $selected{qpos} and $hilight ; | |
addstring($row, $col, "$output") ; | |
standend() ; | |
return 3 ; | |
} | |
sub print_connected($row,$col,$qpos) { | |
my $peers = $queued[$qpos]->{peersConnected} ; | |
my $output = $peers ? sprintf("%2d", $peers) : " " ; | |
attrset(A_BOLD) if $qpos == $selected{qpos} ; | |
if ($peers >= 50) { attron($CP_BRIGHT) ; | |
} else { attron($CP_STD) ; | |
} | |
attrset($CP_HILIGHT) if $qpos == $selected{qpos} and $hilight ; | |
addstring($row, $col, "$output") ; | |
standend() ; | |
return 2 ; | |
} | |
sub print_percentDone($row,$col,$qpos) { | |
my $status = $queued[$qpos]->{status} ; | |
my $perc = $queued[$qpos]->{percentDone} * 100 ; | |
my $elvis = sprintf "%3d%%", $perc ; | |
attrset(A_BOLD) if $qpos == $selected{qpos} ; | |
if ($perc == 100) { attron($CP_DIM1) ; | |
} elsif ($status == 4) { attron($CP_BRIGHT) ; | |
} else { attron($CP_STD) ; | |
} | |
attrset($CP_HILIGHT) if $qpos == $selected{qpos} and $hilight ; | |
addstring($row, $col, "$elvis") ; | |
standend() ; | |
return 4 ; | |
} | |
sub print_eta($row,$col,$qpos) { | |
my $eta = $queued[$qpos]->{eta} ; | |
my $status = $queued[$qpos]->{status} ; | |
my $output ; | |
attrset(A_BOLD) if $qpos == $selected{qpos} ; | |
if ($status == 4) { | |
if ($eta > 604800) { $output = sprintf "%2d%s", round($eta/604800), "w" ; | |
} elsif ($eta > 86400) { $output = sprintf "%2d%s", round($eta/86400), "d" ; | |
} elsif ($eta > 3600) { $output = sprintf "%2d%s", round($eta/3600), "h" ; | |
} elsif ($eta > 600) { $output = sprintf "%2d%s", round($eta/60), "m" ; | |
} elsif ($eta > 60) { $output = sprintf "%2d%s", round($eta/60), "m" ; | |
} elsif ($eta > 0) { $output = sprintf "%2d%s", $eta, "s" ; | |
} else { $output = " " ; | |
} | |
if ($hilight and $qpos == $selected{qpos}) { attron($CP_HILIGHT) ; | |
} elsif ($eta > 3600) { attron($CP_DIM1) ; | |
} elsif ($eta > 600) { attron($CP_STD) ; | |
} elsif ($eta > 0) { attron($CP_BRIGHT) ; | |
} | |
} else { $output = " " ; | |
} | |
attrset($CP_HILIGHT) if $qpos == $selected{qpos} and $hilight ; | |
addstring($row, $col, "$output") ; | |
standend() ; | |
return 3 ; | |
} | |
sub print_rateDownload($row,$col,$qpos) { | |
my $output ; | |
my $status = $queued[$qpos]->{status} ; | |
my $rate = $queued[$qpos]->{rateDownload} ; | |
attrset(A_BOLD) if $qpos == $selected{qpos} ; | |
if ($status != 4 or $rate <= 0) { | |
$output = " " ; | |
} else { | |
my ($num, $unit) = format_number($rate) ; | |
$output = sprintf "%5s", "+$num$unit" ; | |
if ($rate > 512000) { attron($CP_BRIGHT) ; | |
} else { attron($CP_STD) ; | |
} | |
} | |
attrset($CP_HILIGHT) if $qpos == $selected{qpos} and $hilight ; | |
addstring($row, $col, $output) ; | |
standend() ; | |
return 5 ; | |
} | |
sub print_rateUpload($row,$col,$qpos) { | |
my $output ; | |
my $status = $queued[$qpos]->{status} ; | |
my $rate = $queued[$qpos]->{rateUpload} ; | |
attrset(A_BOLD) if $qpos == $selected{qpos} ; | |
if ($status !~ /4|6/ or $rate <= 0) { | |
$output = " }" ; | |
} else { | |
my ($num, $unit) = format_number($rate) ; | |
$output = sprintf "%5s", "-$num$unit" ; | |
if ($rate > 512000) { attron($CP_BRIGHT) ; | |
} else { attron($CP_STD) ; | |
} | |
} | |
attrset($CP_HILIGHT) if $qpos == $selected{qpos} and $hilight ; | |
addstring($row, $col, $output) ; | |
standend() ; | |
return 5 ; | |
} | |
sub print_div($row,$col,$qpos) { | |
attrset(A_BOLD) if $qpos == $selected{qpos} ; | |
attron($CP_DIVIDER) ; | |
attrset($CP_HILIGHT) if $qpos == $selected{qpos} and $hilight ; | |
addch($row,$col," ") ; | |
addch(ACS_VLINE) ; | |
addch(" ") ; | |
standend() ; | |
return 3 ; | |
} | |
sub format_number { | |
my $num = shift ; | |
$num = 0 if $num <= 0 ; | |
if ($num < 1000) { return $num , "B" ; | |
} elsif ($num < 1022976) { return int($num/1024) , "K" ; | |
} elsif ($num < 10276044) { return sprintf("%.1f", $num/1048576) , "M" ; | |
} elsif ($num < 1047527424) { return int($num/1048576) , "M" ; | |
} elsif ($num < 10522669875) { return sprintf("%.1f", $num/1073741824) , "G" ; | |
} else { return int($num/1073741824) , "G" ; | |
} | |
} | |
#### DATA/RPC SUBROUTINES | |
sub get_torrent_data { | |
## Interrogate tr-daemon for various data fields. | |
my $data ; | |
my @fields = qw( | |
eta id name sizeWhenDone seedRatioLimit queuePosition status | |
uploadRatio rateUpload rateDownload percentDone peersConnected | |
) ; | |
my %args = ( fields => \@fields ) ; | |
if ($update_cnt++%6 == 0) { | |
push @fields, "trackerStats" ; | |
%args = ( fields => \@fields ) ; | |
$data = rpc('torrent-get', %args) ; | |
} else { | |
$data = rpc('torrent-get', %args) ; | |
} | |
## Sort torrents by queuePosition. | |
@queued = () ; | |
return 0 if not $data ; | |
@queued = @{ $data->{torrents} } ; | |
@queued = sort { $a->{queuePosition} <=> $b->{queuePosition} } @queued ; | |
## Iterate over trackers to find seeds/peers, | |
foreach my $t ( @queued ) { | |
my $id = $t->{id} ; | |
if (defined $t->{trackerStats}->[0]) { | |
my ($seeds, $leeches) = (-1) x 2 ; | |
foreach my $stats (@{ $t->{trackerStats} }) { | |
$seeds = $stats->{seederCount} if $stats->{seederCount} > $seeds ; | |
$leeches = $stats->{leecherCount} if $stats->{leecherCount} > $leeches ; | |
} | |
## Assign to the highest values found. | |
$trackerinfo->{$id}->{seederCount} = $seeds ; | |
$trackerinfo->{$id}->{leecherCount} = $leeches ; | |
} ; | |
## Default seederCount and leecherCount to -1. | |
$trackerinfo->{$id}->{seederCount} = -1 unless defined $trackerinfo->{$id}->{seederCount} ; | |
$trackerinfo->{$id}->{leecherCount} = -1 unless defined $trackerinfo->{$id}->{leecherCount} ; | |
## Write %trackerinfo into @queued torrents. | |
$t->{seederCount} = $trackerinfo->{$id}->{seederCount} ; | |
$t->{leecherCount} = $trackerinfo->{$id}->{leecherCount} ; | |
} | |
## Store torrents by "id" instead of "queuePosition". | |
$torrents = {} ; | |
foreach my $t (@queued) { my $id = $t->{id} ; $torrents->{$id} = $t ; } ; | |
## Store current focused torrent in %selected. | |
my $id = $selected{id} ; | |
my $qpos = $selected{qpos} ; | |
if ($id != -1 and defined $torrents->{$id}) { | |
$selected{qpos} = $torrents->{$id}->{queuePosition} ; | |
} elsif ($qpos != -1) { | |
$qpos = $#queued if $qpos > $#queued ; | |
$selected{id} = $queued[$qpos]->{id} ; | |
$selected{qpos} = $qpos ; | |
} else { | |
%selected = ( id => -1, qpos => -1 ) ; | |
} | |
return 1 ; | |
} | |
## Partially cribbed from Transmission::Curses. | |
## https://trac.transmissionbt.com/browser/trunk/extras/rpc-spec.txt | |
## https://trac.transmissionbt.com/browser/trunk/libtransmission/transmission.h | |
sub rpc { | |
my $method = shift or return ; | |
my %args = @_ ; | |
my $nested = delete $args{_nested}; # internal flag | |
my($tag, $res, $post); | |
if (ref $args{ids} eq 'ARRAY') { | |
for my $id (@{ $args{ids} }) { $id += 0 if $id =~ /^\d+$/ ; } | |
} | |
$tag = int rand 2*16 - 1; | |
$post = JSON::MaybeXS->new->encode({ | |
method => $method, tag => $tag, arguments => \%args, | |
}) ; | |
$res = $ua->post( $url, Content => $post ) ; | |
unless ( $res->is_success ) { | |
if ($res->code == 409 and ! $nested) { | |
my $sid = $res->header('X-Transmission-Session-Id') ; | |
$ua->default_header('X-Transmission-Session-Id' => $sid) ; | |
return rpc($method => %args, _nested => 1) ; | |
} else { | |
return 0 ; | |
} ; | |
} | |
$res = JSON::MaybeXS->new->decode( $res->content ) ; | |
return 0 unless $res->{tag} = $tag ; | |
return 0 if $res->{result} ne 'success' ; | |
return $res->{'arguments'} ; | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
! the colors im using with binge. | |
*color0: #171717 | |
*color1: #333333 | |
*color2: #595959 | |
*color4: #8C8C8C | |
*color5: #CCCCCC | |
*color7: #8C8C8C | |
*color8: #171717 | |
*color9: #333333 | |
*color10: #595959 | |
*color12: #8C8C8C | |
*color13: #CCCCCC | |
*color15: #8C8C8C |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment