#!/usr/bin/perl use warnings; use strict; use utf8; use open qw(:std :utf8); our $VERSION='0.0.1'; our $PROGRAM='rtpg'; use RPC::XML; use RPC::XML::Client; use Data::Dumper; use Glib qw(TRUE FALSE); use Gtk2 qw(-init); use Gtk2::SimpleList; my $SIZE_BY_CHUNKS_LIMIT=1024**3; my %CONFIG= ( rpc_uri => 'http://apache/RPC2' ); # title - the title for list of torrents # type - the type for list # name - the internal name of item (for this script) # updater - function for update value (count value per item{hash}) # field - if 'updater' is not defined - name of field for autoupdate # my @torrents_list_info= ( { title => 'Name of torrent', name => 'name', type => 'text', field => 'name', }, { title => 'Size', name => 'size', type => 'text', field => 'human_size', }, { title => 'Done', name => 'done', type => 'text', field => 'human_done', } ); my ( $view, # current view mode $gui_list, # control: list of torrents $toolbar, # toolbar @tlist, # list of torrents ); $view='default'; # convert big numbers to small 1024 = 1K, etc sub human_size($) { my ($size, $sign)=(shift, 1); if ($size<0) { return '>2G'; } return 0 unless $size; my @suffixes=('', 'K', 'M', 'G', 'T', 'P', 'E'); my ($limit, $div)=(1024, 1); for (@suffixes) { if ($size<$limit || $_ eq $suffixes[-1]) { $size = sprintf '%1.1f', $sign*$size/$div; $size =~ s/\.0//; return "$size$_"; } $div = $limit; $limit *= 1024; } } sub get_percent_string($$) { my ($part, $full)=@_; return undef unless $full; return undef unless defined $part; return undef if $part<0; return undef if $full<0; return undef if $part>$full; my $percent=$part*100/$full; if ($percent<10) { $percent=sprintf '%1.2f', $percent; } else { $percent=sprintf '%1.1f', $percent; } s/0+$//, s/\.$// for $percent; return "$percent%"; } # execute rpc command sub rpc_command($;@) { my ($cmd, @args)=@_; print STDERR "Command: ", join(" ", $cmd, @args), "\n"; unless ($CONFIG{rpc}) { my $rpc=new RPC::XML::Client($CONFIG{rpc_uri}); die "$rpc\n" unless ref $rpc; $CONFIG{rpc}=$rpc; } my $rpc=$CONFIG{rpc}; my $resp = $rpc->send_request($cmd, @args); if (ref $resp) { die sprintf "%s: %s\n", $resp->value->{faultCode}, $resp->value->{faultString} if 'RPC::XML::fault' eq ref $resp; $resp = $resp->value; return $resp; } die "$resp\n"; return undef; } sub get_tlist() { my @iary= ( 'd.is_active', 'd.is_hash_checked', 'd.is_hash_checking', 'd.is_multi_file', 'd.is_open', 'd.is_pex_active', 'd.is_private', 'd.get_name', 'd.get_priority', 'd.get_ratio', 'd.get_size_bytes', 'd.get_hash', 'd.get_peers_connected', 'd.get_bytes_done', 'd.get_message', 'd.get_directory', 'd.get_up_total', 'd.get_complete', 'd.get_up_rate', 'd.get_down_rate', 'd.get_size_chunks', 'd.get_completed_chunks', 'd.get_chunk_size', ); my $list = rpc_command('d.multicall', $view, map { "$_=" } @iary); for (@$list) { my %info; for my $i (0 .. $#iary) { my $name=$iary[$i]; $name =~ s/^..(?:get_)?//; $info{$name}=$_->[$i]; } $_ = \%info; $_->{percent}=get_percent_string($_->{completed_chunks}, $_->{size_chunks})||0; my ($bytes_done, $size_bytes)= ( 1.0*$_->{completed_chunks}*$_->{chunk_size}, 1.0*$_->{size_chunks}*$_->{chunk_size} ); $_->{size_bytes}=$size_bytes if $size_bytes>$SIZE_BY_CHUNKS_LIMIT; $_->{bytes_done}=$bytes_done if $bytes_done>$SIZE_BY_CHUNKS_LIMIT; $_->{up_total}=1.0*$_->{bytes_done}*($_->{ratio}/1000); $_->{ratio}=sprintf '%1.2f', $_->{ratio}/1000; $_->{ratio}=~s/((\.00)|0)$//; $_->{human_size}=human_size $_->{size_bytes}; $_->{human_done}=human_size $_->{bytes_done}; $_->{human_up_total}=human_size $_->{up_total}; $_->{human_up_rate}=human_size $_->{up_rate}; $_->{human_down_rate}=human_size $_->{down_rate}; for ($_->{human_up_rate}, $_->{human_down_rate}) { next if $_ eq 0; $_ .= 'b/s'; } } @tlist=@$list; } sub update_tlist() { get_tlist; splice @{$gui_list->{data}}, $#tlist if @{$gui_list->{data}} > @tlist; for my $ino(0 .. $#tlist) { my $item=$tlist[$ino]; push @{$gui_list->{data}}, [$item->{name} ] if @{$gui_list->{data}}<=$ino; for my $rno(0 .. $#torrents_list_info) { my $row = $torrents_list_info[$rno]; if (defined $row->{field}) { my $name=$row->{field}; my $value=$item->{$name}; print Dumper [$name, $ino, $rno, $value]; $gui_list->{data}[$ino][$rno]=$value; } } } } sub create_main_window() { my $window = Gtk2::Window->new('toplevel'); $window->set_title($PROGRAM . ' v.' . $VERSION); $window->signal_connect(destroy => sub { Gtk2->main_quit; }); $toolbar=Gtk2::Toolbar->new(); $gui_list=Gtk2::SimpleList->new( map { ($_->{title} => $_->{type}) }@torrents_list_info ); update_tlist(); $gui_list->show; $window->add($gui_list); $window->show; } create_main_window; Gtk2->main;