From: Ralf Ertzinger Date: Wed, 10 Dec 2008 19:47:01 +0000 (+0100) Subject: - Initial checkin X-Git-Url: https://git.camperquake.de/gitweb.cgi?p=xmlrtorrent.git;a=commitdiff_plain;h=65a4e435c4cc6e72c0f852b4abc4374747fb1a55 - Initial checkin --- 65a4e435c4cc6e72c0f852b4abc4374747fb1a55 diff --git a/xmlrtorrent.pl b/xmlrtorrent.pl new file mode 100644 index 0000000..2089ce8 --- /dev/null +++ b/xmlrtorrent.pl @@ -0,0 +1,365 @@ +# control an rTorrent client via XMLRPC, +# and collect rtorrent files from IRC for later download +# +# (c) 2007-2008 by Ralf Ertzinger +# licensed under GNU GPL v2 + +use strict; +use Irssi 20020324 qw (command_bind command_runsub signal_add_first signal_add_last); +use vars qw($VERSION %IRSSI); +use XML::Simple; +use Data::Dumper; +use File::Spec; +use xmlrtorrent; + +my $conf; +my $conffile = File::Spec->catfile(Irssi::get_irssi_dir(), 'xmlrtorrent.xml'); +my $scriptdir = File::Spec->catfile(Irssi::get_irssi_dir(), 'scripts'); +my %torrentlist = (); +my $torrentindex = 1; +my $rtorrent; + +my @outputstack = (undef); + +my $PARAMS = { + 'XMLURL' => 'http://localhost/RPC2', +}; + +# activate debug here +my $debug = 0; + +# "message public", SERVER_REC, char *msg, char *nick, char *address, char *target +signal_add_last("message public" => sub {check_for_link(\@_,1,4,2,0);}); +# "message own_public", SERVER_REC, char *msg, char *target +signal_add_last("message own_public" => sub {check_for_link(\@_,1,2,-1,0);}); + +# "message private", SERVER_REC, char *msg, char *nick, char *address +signal_add_last("message private" => sub {check_for_link(\@_,1,-1,2,0);}); +# "message own_private", SERVER_REC, char *msg, char *target, char *orig_target +signal_add_last("message own_private" => sub {check_for_link(\@_,1,2,-1,0);}); + +# "message irc action", SERVER_REC, char *msg, char *nick, char *address, char *target +signal_add_last("message irc action" => sub {check_for_link(\@_,1,4,2,0);}); +# "message irc own_action", SERVER_REC, char *msg, char *target +signal_add_last("message irc own_action" => sub {check_for_link(\@_,1,2,-1,0);}); + +# For tab completion +signal_add_first('complete word', \&sig_complete); + +my $xmlrtorrent_commands = { + 'save' => sub { + cmd_save(); + }, + + 'set' => sub { + cmd_set(@_); + }, + + 'show' => sub { + cmd_show(@_); + }, + + 'help' => sub { + cmd_help(@_); + }, + + 'queue' => sub { + cmd_queue(@_); + }, + + 'remote' => sub { + cmd_remote(@_); + }, + + 'debug' => sub { + $debug = 1; + write_irssi('Enabled debugging'); + }, + + 'nodebug' => sub { + $debug = 0; + write_irssi('Disabled debugging'); + }, +}; + +sub write_irssi { + my @text = @_; + my $output = $outputstack[0]; + + $text[0] = 'xmlrtorrent: ' . $text[0]; + + if (defined($output) and ref($output)) { + $output->print(sprintf(shift(@text), @text), MSGLEVEL_CLIENTCRAP); + } else { + Irssi::print(sprintf(shift(@text), @text)); + } + +} + +sub push_output { + unshift(@outputstack, shift); +} + +sub pop_output { + shift(@outputstack); +} + +sub write_debug { + if ($debug) { + write_irssi(@_); + } +} + +# This is shamelessly stolen from pythons urlgrabber +sub format_number { + my $number = shift; + my $SI = shift || 0; + my @symbols = ('', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'); + my $step = $SI?1000:1024; + my $thresh = 999; + my $depth = 0; + my $max_depth = $#symbols; + my $format; + + while (($number > $thresh) and ($depth < $max_depth)) { + $depth += 1; + $number /= $step; + } + + if ($number =~ /^[+-]?\d+$/) { + # Integer. + $format = '%i%s'; + } elsif ($number < 9.95) { + $format = '%.1f%s'; + } else { + $format = '%.0f%s'; + } + return sprintf($format, $number, $symbols[$depth]); +} + + + +sub check_for_link { + my ($signal,$parammessage,$paramchannel,$paramnick,$paramserver) = @_; + my $server = $signal->[$paramserver]; + my $target = $signal->[$paramchannel]; + my $message = ($parammessage == -1) ? '' : $signal->[$parammessage]; + my $nick = ($paramnick == -1)?defined($server)?$server->{'nick'}:'':$signal->[$paramnick]; + my $g; + my $m; + my $p; + + my $witem; + if (defined $server) { + $witem = $server->window_item_find($target); + } else { + $witem = Irssi::window_item_find($target); + } + + # Look if we should ignore this line + if ($message =~ m,(?:\s|^)/nosave(?:\s|$),) { + return; + } + + push_output($witem); + + # Look if there is a torrent link in there + $message =~ m|(http://\S*\.torrent)|; + $m = $1; + while (defined($m)) { + write_debug('Torrent-URL: %s', $m); + $torrentlist{$torrentindex++} = {'CHANNEL' => $target, 'NICK' => $nick, 'URL' => $m}; + + # Remove the matched part from the message and try again (there may be + # more!) + $message =~ s/$m//; + + $message =~ m|(http://.*\.torrent)|; + $m = $1; + } + + pop_output(); +} + +# Handle the queue of unhandled torrents +sub cmd_queue { + my ($subcmd, $id, @params) = @_; + + if ('remove' eq $subcmd) { + if (defined($id)) { + delete($torrentlist{$id}); + } + } elsif ('clear' eq $subcmd) { + %torrentlist = (); + } elsif ('confirm' eq $subcmd) { + my $u; + return unless(defined($id) and exists($torrentlist{$id})); + + $u = $torrentlist{$id}->{'URL'}; + + write_debug('Sending %s to rtorrent', $u); + unless(defined($rtorrent->load_start($u))) { + write_irssi('Error sending URL %s: %s', $u, $rtorrent->errstr()); + } else { + delete($torrentlist{$id}); + } + } elsif (('list' eq $subcmd) or !defined($subcmd)) { + write_irssi('List of queued torrents'); + foreach (sort(keys(%torrentlist))) { + write_irssi(' %d: %s@%s: %s', $_, + $torrentlist{$_}->{'NICK'}, + $torrentlist{$_}->{'CHANNEL'}, + $torrentlist{$_}->{'URL'}); + } + } +} + +# Handle the remote rtorrent queue +sub cmd_remote { + my ($subcmd, $id, @params) = @_; + my $rqueue; + + if ('queue' eq $subcmd) { + unless(defined($rqueue = $rtorrent->download_list())) { + write_irssi('Error getting list of downloads: %s', $rtorrent->errstr()); + return; + } + + foreach (@{$rqueue}) { + write_irssi('%s%s: %sB/%sB done, %sb/s up, %sb/s down', + $_->[6]?'*':' ', + $_->[0], + format_number($_->[2]), + format_number($_->[1]), + format_number($_->[3]), + format_number($_->[4])); + } + } +} + + +sub cmd_save { + + eval { + open(CONF, '>'.$conffile) or die 'Could not open config file'; + print CONF XML::Simple::XMLout($conf, KeepRoot => 1, KeyAttr => {'config' => 'module', 'option' => 'key'}); + close(CONF); + }; + if ($@) { + write_irssi('Could not save config to %s: %s', ($conffile, $@)); + } else { + write_irssi('configuration saved to %s', $conffile); + } +} + +sub cmd_set { + my $target = shift; + my $key = shift; + my $val = shift; + + if ('global' eq $target) { + if(exists($PARAMS->{$key})) { + $conf->{'xmlrtorrent'}->{$key} = $val; + if ('XMLURL' eq $key) { + unless(defined($rtorrent = xmlrtorrent->new('XMLURL' => $conf->{'xmlrtorrent'}->{'XMLURL'}))) { + write_irssi('Could not initialize XMLRPC instance'); + return; + } + } + } else { + write_irssi('Key %s does not exist', $key); + } + } +} + + +sub cmd_show { + my $target = shift; + my $p; + my $e; +} + +sub cmd_help { + my $target = shift; + my $p; + + write_irssi(<<'EOT'); +Supported commands: + save: Save the current configuration + help: Display this help + debug: enable debugging messages + nodebug: disable debugging messages +EOT +} + + +# save on unload +sub sig_command_script_unload { + my $script = shift; + if ($script =~ /(.*\/)?xmlrtorrent(\.pl)?$/) { + cmd_save(); + } +} + +sub init_xmlrtorrent { + + my $bindings = shift; + my $p; + + unless(-r $conffile && defined($conf = XML::Simple::XMLin($conffile, ForceArray => ['config', 'option'], KeepRoot => 1, KeyAttr => {'config' => 'module', 'option' => 'key'}))) { + # No config, start with an empty one + write_debug('No config found, using defaults'); + $conf = { 'xmlrtorrent' => { }}; + } + foreach (keys(%{$PARAMS})) { + unless (exists($conf->{'xmlrtorrent'}->{$_})) { + $conf->{'xmlrtorrent'}->{$_} = $PARAMS->{$_}; + } + } + + unless(defined($rtorrent = xmlrtorrent->new('XMLURL' => $conf->{'xmlrtorrent'}->{'XMLURL'}))) { + write_irssi('Could not initialize XMLRPC instance'); + return; + } + + if ($bindings) { + + Irssi::signal_add_first('command script load', 'sig_command_script_unload'); + Irssi::signal_add_first('command script unload', 'sig_command_script_unload'); + Irssi::signal_add('setup saved', 'cmd_save'); + + + Irssi::command_bind('torrent' => \&cmdhandler); + } + + write_irssi('xmlrtorrent initialized'); +} + +sub sig_complete { + my ($complist, $window, $word, $linestart, $want_space) = @_; + my @matches; + + if ($linestart !~ m|^/torrent\b|) { + return; + } + + ${$want_space} = 0; + + Irssi::signal_stop(); +} + +sub cmdhandler { + my ($data, $server, $witem) = @_; + my ($cmd, @params) = split(/\s+/, $data); + + push_output($witem); + + if (exists($xmlrtorrent_commands->{$cmd})) { + $xmlrtorrent_commands->{$cmd}->(@params); + } + + pop_output(); +} + +unshift(@INC, $scriptdir); +init_xmlrtorrent(1); diff --git a/xmlrtorrent.pm b/xmlrtorrent.pm new file mode 100644 index 0000000..92cd1aa --- /dev/null +++ b/xmlrtorrent.pm @@ -0,0 +1,70 @@ +package xmlrtorrent; + +use strict; +use RPC::XML; +use RPC::XML::Client; +use Data::Dumper; + +sub new { + my $class = shift; + my $self = {@_}; + + unless(exists($self->{'XMLURL'}) && defined($self->{'XMLURL'})) { + return undef; + } + + $self->{'__RPCClient'} = RPC::XML::Client->new($self->{'XMLURL'}); + + return bless($self, $class); +} + +sub load_start{ + my $self = shift; + my $URL = shift; + my $res; + + $res = $self->{'__RPCClient'}->send_request('load_start', $URL); + unless(ref($res)) { + $self->{'__ERROR'} = $res; + return undef + } + + if ($res->is_fault()) { + $self->{'__ERROR'} = $res->value()->{'faultString'}; + return undef; + } + + return 1; +} + +sub download_list { + my $self = shift; + my $res; + + $res = $self->{'__RPCClient'}->send_request('d.multicall', '', + 'd.get_name=', + 'd.get_size_bytes=', + 'd.get_bytes_done=', + 'd.get_up_rate=', + 'd.get_down_rate=', + 'd.is_active='); + unless(ref($res)) { + $self->{'__ERROR'} = $res; + return undef + } + + if ($res->is_fault()) { + $self->{'__ERROR'} = $res->value()->{'faultString'}; + return undef; + } + + return $res->value(); +} + +sub errstr { + my $self = shift; + + return $self->{'__ERROR'}; +} + +1;