Rtorrent web interface

May 25, 2009

Me and alka wrote a web application in ruby which serves as an interface to rtorrent. It has been written in ruby using the ramaze and sequel frameworks. It
is currently tested on debian stable with mostly apt-get installed gems,
for more info check the README file included (the only one external to
the distribution should be the xmlrpc gem which is on another post in
this blog).
It supports authentication and accesses rtorrent through the unix socket
interface, allowing to run the server as a user process.
An internal copy of the information from rtorrent is stored in a
in-memory sqlite database (maybe it can be totaly avoided, but that was how we planned it in the beggining) to avoid to access too much times to rtorrent
along with the caching of pages in ramaze.

Currently may lack some feature, but it’s doing it’s job, the main
missing feature is the user interface. If someone can write a
CSS and maybe do some cleanup in the interface any help would be
appreciated.

As usual in the code page everything can be found or directly here
git://github.com/mellon85/rtorrent-wrb.git

It is currently no more developed as we switched torrent client. Patches or comment are welcomed


XMLRPCS gem published

June 26, 2008

I have just published on rubyforge the gem for my xmlrpc modifications.
It’s called xmlrpcs and contains the class discussed in the previous post.

xmlrpcs Rubyforge homepage
Online RDoc

The changes are that now it has full rdoc comments and the path to include it is ‘xmlrpc/xmlrpcs’
I would like to relase my upnp binding as a gem too, but that will come later.
To install it just do

gem install xmlrpcs

and it’s done


Ruby XMLRPC sockets changes

June 25, 2008

[Continues: Ruby xml-talking to rtorrent]

I have rewritten how i changed the socket in XMLRPC are modified in a more Ruby style way.

A new class, in XMLRPC module, called ClientS (in a file called xmlrpcs.rb). It is a subclass of the Client class to ease the implementation of other kind of trasport system if the Net::HTTP based one doesn’t fit your needs.

require 'xmlrpc/client'

class XMLRPC::ClientS < XMLRPC::Client
        def initialize(info)
            @info = info
        end

        private

        # create new socket
        def new_socket(info,async)
            raise "Must be subclassed"
        end

        # write xmlrpc request in the previously created socket
        def write_request(socket,request)
          if socket.write(request) != request.length then
              raise "Not all the data has been sent"
          end
        end

        # read response from the socket
        def read_response(socket)
            socket.read()
        end

        # do_rpc working with custom sockets
        def do_rpc( request, async )
          sock = new_socket(@info,async)
          write_request(sock,request)
          return read_response(sock)
        end
    end
end

This makes it very simple to extend the class by just subclassing ClientS or just redefining the function new_socket.
To avoid to close the socket at each request, which may be not the right choice for every code, you can redefine the do_rpc
The info field is used to pass some arguments to create the socket, for instance

require 'xmlrpcs'
require 'socket'

class XMLClient < XMLRPC::ClientS
    def new_socket( info, async )
        UNIXSocket.new(info)
    end
end

c=XMLClient.new("/path/to/sock")
c.call(..)

The code can be found in the code page

[Continued by: XMLRPCs gem published]


Ruby xml-talking to rtorrent

June 23, 2008

I haven’t had very much time to write some ruby code to communicate with rtorrent through the local domain socket. But i got something that starts to work.

The main problem is that Ruby XMLRPC library is unfriendly with user with special needs. There is no way to get the xml data, send it the way i want and get it back and let the library parse it!
To solve this i had to modify the library, with a patch that when is ready i’ll subit to the ruby developers.
I have added a class that represents a socket factory! the easy way to solve the problem. In less then 30 lines it just works. There is a new method to initialize the Client class too, and even the hashed (new3) method will work if the field socket is specified.

  class SocketFactory
      def issue() # returns a new socket to be used with the library
          raise "This class must be subclassed"
      end
  end
  def new4( socket )
      self.new(nil,nil,nil,nil,nil,nil,nil,nil,nil,socket)
  end
  alias new_with_socket new4

After these modifications to the initialization code i had to modify the private method do_rpc adding this at the beginning of the method

      data = nil
      if @socket != nil then # using user socket
          # must do the same things, send the same things
          # but to the socket! not to a http/https server
          sock = @socket.issue()
          if sock.write(request) != request.length then
              raise "Not all the data has been sent"
          end
          # read the whole data until the socket is closed
          data = sock.read()
          # after any answer the socket is closed
          return data # return data to the parser
      else

and an end just after the return statement of the original method. There is an additional check to verify that the socket object is a subclass of the SocketFactory class.

I wrote a simple socket factory to test the communications with rtorrent itself in the real world. I had to write to it using the SCGI protocol, which obviously is supported in ruby, but it doesn’t support local domain socket, for no real reasons, since it is indistinguishable from any other kind of socket, but that was the statement in rdoc

One thing that SCGI doesn‘t support is using UNIX Domain sockets in addition to TCP/IP sockets. This isn‘t really needed, but it is handy in a shared hosting situation where you don‘t want others connecting to your processes or if you have to request open ports. Sorry, no UNIX Domain sockets in SCGI.

It’s crazy… so i wrote a simple scgi wrapper. which is incredibly simple

    def self.wrap( content, uri, method="POST" )
        null=""
        header = "CONTENT_LENGTH #{content.length} SCGI#{null}1
                  REQUEST_METHOD #{method} REQUEST_URI #{uri} "
        return "#{header.length}:#{header},#{content}"
    end

There are some missing chars (many “\0″), in the string header and the null string are just “\0″, but can’t be shown here as in any my previous post, the files in the git repository are ok.

To use this wrapper i have to write a socket factory that will create sockets that will wrap the xmldata in a scgi header.
So i wrote a class SCGIFactory, which is still incomplete (no error checking) but is working.

    def issue()
        if @method != nil
            SCGIWrappedSocket.new(UNIXSocket.new(@path),@uri,@method)
        else
            SCGIWrappedSocket.new(UNIXSocket.new(@path),@uri)
        end
    end
    def write(x)
        @sock.write(SCGI.wrap(x,@uri,@method))
    end 

    def read()
        data = @sock.read()
        # receiving an html response (very dumb parsing)
        # divide in 2
        # 1 -> status + headers
        # 2 -> data
        return data.split("\r\n\r\n").last
    end

As you can see the read method need many enhancements. I need a way to parse http data from a string in any default library, but no default library do this. If anybody knows hot to do that, please tell me.

I wrote this test

require 'xmlrpc/client'
require 'SCGIFactory'

f=SCGIFactory::Unix.new("/rtorrent.sock","/RPC2")
c=XMLRPC::Client.new_with_socket(f)
puts "#{c.call("system.listMethods")}"

And I can get the whole list of the supported methods directly from the program itself

All the code is available in a git repository, different from the ruby upnp one.. Git repository can be found in the code page

[Continued by: XMLRPC sockets changes]