Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial commit.

  • Loading branch information...
commit c918e5947a48981f4b5863c46f187b2c0f9302b5 0 parents
@cbguder authored
5 .gitignore
@@ -0,0 +1,5 @@
+*.gem
+.bundle
+Gemfile.lock
+pkg/*
+doc/*
3  Gemfile
@@ -0,0 +1,3 @@
+source "http://rubygems.org"
+
+gemspec
19 LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2011 Can Berk Guder
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
26 README.rdoc
@@ -0,0 +1,26 @@
+= RUTorrent
+
+RUTorrent is a Ruby library for the remote management of µTorrent clients via
+the Web UI API.
+
+== Installation
+
+ gem install rutorrent
+
+== Synopsis
+
+ require 'rutorrent'
+
+ s = RUTorrent::Server.new('127.0.0.1', 6881, 'username', 'password')
+ t = s.torrents[0]
+ puts t.name
+
+Produces:
+
+ ubuntu-10.10-desktop-i386.iso
+
+== Caveat emptor
+
+As the version number implies, RUTorrent is undocumented and untested.
+Specifically, only read-only methods have been tested with µTorrent 2.2.1. Use
+it at your own risk.
21 Rakefile
@@ -0,0 +1,21 @@
+require 'bundler'
+require 'rake/rdoctask'
+require 'rake/testtask'
+
+Bundler::GemHelper.install_tasks
+
+Rake::TestTask.new
+
+Rake::RDocTask.new do |rd|
+ rd.main = 'README.rdoc'
+
+ rd.options << '--charset=utf8'
+
+ rd.rdoc_dir = 'doc'
+
+ rd.rdoc_files.include 'README.rdoc'
+ rd.rdoc_files.include 'LICENSE'
+ rd.rdoc_files.include 'lib/**/*.rb'
+
+ rd.title = 'RUTorrent'
+end
8 lib/rutorrent.rb
@@ -0,0 +1,8 @@
+require 'rutorrent/file'
+require 'rutorrent/helpers'
+require 'rutorrent/job_properties'
+require 'rutorrent/label'
+require 'rutorrent/rssfilter'
+require 'rutorrent/server'
+require 'rutorrent/settings'
+require 'rutorrent/torrent'
18 lib/rutorrent/file.rb
@@ -0,0 +1,18 @@
+module RUTorrent
+ class File
+ attr_reader :name, :size, :downloaded, :priority
+
+ def initialize(torrent, index, array)
+ @torrent = torrent
+ @index = index
+ @name, @size, @downloaded, @priority = array
+ end
+
+ def priority=(value)
+ raise 'Invalid priority.' unless (0..3).include?(value)
+
+ @torrent.server.request(:action => 'setprio', :hash => @torrent.hash, :p => value, :f => @index)
+ @priority = value
+ end
+ end
+end
1  lib/rutorrent/helpers.rb
@@ -0,0 +1 @@
+require 'rutorrent/helpers/url_helper'
26 lib/rutorrent/helpers/url_helper.rb
@@ -0,0 +1,26 @@
+module RUTorrent
+ module Helpers
+ module URLHelper
+ BASE_PATH = '/gui'
+ KEYS = [:token, :list, :cid, :action, :hash, :s, :v, :p, :f]
+
+ def self.path_for(args)
+ path = BASE_PATH.dup
+
+ case args
+ when String
+ path << args
+ when Hash
+ query = []
+
+ KEYS.each do |key|
+ query << "#{key}=#{args[key]}" if args.key?(key)
+ end
+
+ path << '/?'
+ path << query.join('&')
+ end
+ end
+ end
+ end
+end
44 lib/rutorrent/job_properties.rb
@@ -0,0 +1,44 @@
+module RUTorrent
+ class JobProperties
+ attr_reader :hash, :trackers, :ulrate, :dlrate, :superseed, :dht, :pex,
+ :seed_override, :seed_ratio, :seed_time, :ulslots
+
+ def initialize(hash)
+ @hash = hash['hash']
+ @trackers = hash['trackers']
+ @ulrate = hash['ulrate']
+ @dlrate = hash['dlrate']
+ @superseed = hash['superseed']
+ @dht = hash['dht']
+ @pex = hash['pex']
+ @seed_override = hash['seed_override']
+ @seed_ratio = hash['seed_ratio']
+ @seed_time = hash['seed_time']
+ @ulslots = hash['ulslots']
+ end
+
+ def superseed?
+ @superseed == 1
+ end
+
+ def dht?
+ @dht == 1
+ end
+
+ def pex?
+ @pex == 1
+ end
+
+ def seed_override?
+ @seed_override == 1
+ end
+
+ def seed_ratio
+ @seed_ratio / 1000.0
+ end
+
+ def trackers
+ @trackers.split("\r\n")
+ end
+ end
+end
9 lib/rutorrent/label.rb
@@ -0,0 +1,9 @@
+module RUTorrent
+ class Label
+ attr_reader :label, :torrent_count
+
+ def initialize(array)
+ @label, @torrent_count = array
+ end
+ end
+end
34 lib/rutorrent/rssfilter.rb
@@ -0,0 +1,34 @@
+module RUTorrent
+ class RSSFilter
+ QUALITIES = %w{HDTV TVRip DVDRip SVCD DSRip DVBRip PDTV HR.HDTV HR.PDTV DVDR
+ DVDScr 720p 1080i 1080p WebRip SatRip}
+
+ attr_reader :id, :flags, :name, :filter, :not_filter, :directory, :feed,
+ :quality, :label, :postpone_mode, :last_match, :smart_ep_filter,
+ :repack_ep_filter, :episode_filter_str, :episode_filter,
+ :resolving_candidate
+
+ def initialize(array)
+ @id, @flags, @name, @filter, @not_filter, @directory, @feed, @quality,
+ @label, @postpone_mode, @last_match, @smart_ep_filter, @repack_ep_filter,
+ @episode_filter_str, @episode_filter, @resolving_candidate = array
+ end
+
+ def enabled?; !@flags[0].zero? end
+ def matches_original_name?; !@flags[1].zero? end
+ def high_priority?; !@flags[2].zero? end
+ def smart_episode_filter?; !@flags[3].zero? end
+ def add_stopped?; !@flags[4].zero? end
+
+ def qualities
+ unless @qualities
+ @qualities = []
+ QUALITIES.each_with_index do |q,i|
+ @qualities << q unless @quality[i].zero?
+ end
+ end
+
+ @qualities
+ end
+ end
+end
105 lib/rutorrent/server.rb
@@ -0,0 +1,105 @@
+require 'rubygems'
+require 'nokogiri'
+require 'net/http'
+require 'json'
+
+module RUTorrent
+ class Server
+ TOKEN_PATH = '/gui/token.html'
+
+ attr_reader :host, :port, :user, :token, :build
+
+ def initialize(host, port, user, pass)
+ @host = host
+ @port = port
+ @user = user
+ @pass = pass
+
+ @token = nil
+ @build = nil
+
+ @http = Net::HTTP.new(@host, @port)
+
+ get_token
+ end
+
+ def settings
+ @settings ||= RUTorrent::Settings.new(self)
+ end
+
+ def labels
+ reload unless @labels
+ @labels
+ end
+
+ def torrents
+ reload unless @torrents
+ @torrents
+ end
+
+ def rssfilters
+ reload unless @rssfilters
+ @rssfilters
+ end
+
+ def request(args)
+ args[:token] ||= @token if @token
+
+ path = RUTorrent::Helpers::URLHelper.path_for(args)
+ resp = request_raw(path)
+
+ begin
+ json = JSON.parse(resp.body)
+ @build = json['build'] if json.has_key?('build')
+ ret = json
+ rescue
+ ret = resp.body
+ end
+
+ ret
+ end
+
+ def reload
+ json = request(:list => 1)
+
+ @labels = []
+ json['label'].each do |label|
+ @labels << Label.new(label)
+ end
+
+ @torrents = []
+ json['torrents'].each do |torrent|
+ @torrents << Torrent.new(self, torrent)
+ end
+
+ @rssfilters = []
+ json['rssfilters'].each do |rssfilter|
+ @rssfilters << RSSFilter.new(rssfilter)
+ end
+ end
+
+ def inspect
+ '#<%s:0x%8x %s@%s:%s>' % [self.class, object_id * 2, @user, @host, @port]
+ end
+
+ private
+
+ def request_raw(path, authenticate=true)
+ req = Net::HTTP::Get.new(path)
+ req.basic_auth @user, @pass if authenticate
+ @http.request(req)
+ end
+
+ def get_token
+ response = request_raw(TOKEN_PATH, false)
+
+ if response.code != '401' or response['WWW-Authenticate'] != 'Basic realm="uTorrent"'
+ raise "#{@host} is not a valid uTorrent server."
+ end
+
+ response = request_raw(TOKEN_PATH)
+ html = Nokogiri::HTML.parse(response.body)
+ @token = html.css("#token").text
+ end
+ end
+end
36 lib/rutorrent/settings.rb
@@ -0,0 +1,36 @@
+module RUTorrent
+ class Settings
+ def initialize(server)
+ @server = server
+ @settings = nil
+ @dirty = false
+ end
+
+ def reload
+ json = @server.request(:action => 'getsettings')
+ @settings = Hash[json['settings'].map{|s| [s[0], s[2]] }]
+ @dirty = false
+ end
+
+ def [](key)
+ reload_if_necessary
+ @settings[key]
+ end
+
+ def []=(key, value)
+ @server.request(:action => 'setsetting', :s => key, :v => value)
+ @dirty = true
+ end
+
+ def to_hash
+ reload_if_necessary
+ @settings.dup
+ end
+
+ private
+
+ def reload_if_necessary
+ reload if @dirty or @settings.nil?
+ end
+ end
+end
87 lib/rutorrent/torrent.rb
@@ -0,0 +1,87 @@
+module RUTorrent
+ class Torrent
+ attr_reader :hash, :status, :name, :size, :progress, :downloaded, :uploaded,
+ :ratio, :upload_speed, :download_speed, :eta, :label, :peers_connected,
+ :peers_in_swarm, :seeds_connected, :seeds_in_swarm, :availability,
+ :torrent_queue_order, :remaining
+
+ attr_reader :server
+
+ def initialize(server, array)
+ @server = server
+
+ @hash, @status, @name, @size, @progress, @downloaded, @uploaded, @ratio,
+ @upload_speed, @download_speed, @eta, @label, @peers_connected,
+ @peers_in_swarm, @seeds_connected, @seeds_in_swarm, @availability,
+ @torrent_queue_order, @remaining = array
+ end
+
+ def files
+ load_files unless @files
+ @files
+ end
+
+ def progress
+ @progress / 1000.0
+ end
+
+ def ratio
+ @ratio / 1000.0
+ end
+
+ def availability
+ @availability / 65536.0
+ end
+
+ def properties
+ load_properties unless @properties
+ @properties
+ end
+
+ def start; perform 'start' end
+ def stop; perform 'stop' end
+ def pause; perform 'pause' end
+ def unpause; perform 'unpause' end
+ def forcestart; perform 'forcestart' end
+ def recheck; perform 'recheck' end
+ def remove; perform 'remove' end
+ def removedata; perform 'removedata' end
+ def queuebottom; perform 'queuebottom' end
+ def queuedown; perform 'queuedown' end
+ def queuetop; perform 'queuetop' end
+ def queueup; perform 'queueup' end
+
+ def started?; !@status[0].zero? end
+ def checking?; !@status[1].zero? end
+ def start_after_check?; !@status[2].zero? end
+ def checked?; !@status[3].zero? end
+ def error?; !@status[4].zero? end
+ def paused?; !@status[5].zero? end
+ def queued?; !@status[6].zero? end
+ def loaded?; !@status[7].zero? end
+
+ def inspect
+ '#<%s:0x%8x %s>' % [self.class, object_id * 2, @name]
+ end
+
+ private
+
+ def load_files
+ json = @server.request(:action => 'getfiles', :hash => @hash)
+
+ @files = []
+ json['files'][1].each_with_index do |file,i|
+ @files << RUTorrent::File.new(@torrent, i, file)
+ end
+ end
+
+ def load_properties
+ json = @server.request(:action => 'getprops', :hash => @hash)
+ @properties = RUTorrent::JobProperties.new(json['props'][0])
+ end
+
+ def perform(action)
+ @server.request(:action => action, :hash => @hash)
+ end
+ end
+end
3  lib/rutorrent/version.rb
@@ -0,0 +1,3 @@
+module RUTorrent
+ VERSION = "0.0.1"
+end
27 rutorrent.gemspec
@@ -0,0 +1,27 @@
+# -*- encoding: utf-8 -*-
+$:.push File.expand_path("../lib", __FILE__)
+require "rutorrent/version"
+
+Gem::Specification.new do |s|
+ s.name = "rutorrent"
+ s.version = RUTorrent::VERSION
+ s.platform = Gem::Platform::RUBY
+
+ s.authors = ["Can Berk Güder"]
+ s.email = ["cbguder@gmail.com"]
+ s.homepage = "http://cbg.me/"
+
+ s.summary = %q{Library for the remote management of uTorrent clients}
+ s.description = <<-EOF.strip.gsub(/^\s+/, '')
+ RUTorrent is a Ruby library for the remote management of uTorrent clients
+ via the Web UI API.
+ EOF
+
+ s.files = `git ls-files`.split("\n")
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+
+ s.add_dependency("json", "~> 1.5.1")
+ s.add_dependency("nokogiri", "~> 1.4.4")
+
+ s.add_development_dependency("bundler")
+end
50 test/test_url_helper.rb
@@ -0,0 +1,50 @@
+require 'rutorrent'
+require 'test/unit'
+
+class TestURLHelper < Test::Unit::TestCase
+ def path_for(args)
+ args[:token] = 'TOKEN' if Hash === args
+ RUTorrent::Helpers::URLHelper::path_for(args)
+ end
+
+ def test_token_html
+ assert_equal '/gui/token.html', path_for('/token.html')
+ end
+
+ def test_list
+ assert_equal '/gui/?token=TOKEN&list=1', path_for(:list => 1)
+ end
+
+ def test_list_cid
+ assert_equal '/gui/?token=TOKEN&list=1&cid=CACHEID', path_for(:list => 1, :cid => 'CACHEID')
+ end
+
+ def test_action
+ assert_equal "/gui/?token=TOKEN&action=getsettings", path_for(:action => 'getsettings')
+ end
+
+ def test_action_s
+ assert_equal '/gui/?token=TOKEN&action=add-url&s=URL', path_for(:action => 'add-url', :s => 'URL')
+ end
+
+ def test_action_s_v
+ assert_equal '/gui/?token=TOKEN&action=setsetting&s=SETTING&v=VALUE', path_for(:action => 'setsetting', :s => 'SETTING', :v => 'VALUE')
+ end
+
+ def test_action_hash
+ actions = %q{getfiles getprops start stop pause unpause forcestart recheck
+ remove removedata queuebottom queuedown queuetop queueup}
+
+ actions.each do |action|
+ assert_equal "/gui/?token=TOKEN&action=#{action}&hash=HASH", path_for(:action => action, :hash => 'HASH')
+ end
+ end
+
+ def test_action_hash_s_v
+ assert_equal '/gui/?token=TOKEN&action=setprops&hash=HASH&s=PROPERTY&v=VALUE', path_for(:action => 'setprops', :hash => 'HASH', :s=> 'PROPERTY', :v => 'VALUE')
+ end
+
+ def test_action_hash_p_f
+ assert_equal '/gui/?token=TOKEN&action=setprio&hash=HASH&p=0&f=0', path_for(:action => 'setprio', :hash => 'HASH', :p=> 0, :f => 0)
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.