#!/usr/bin/env ruby require "date" require "bencode" class TorrentFile include Bencode class FileSegment attr_accessor :sha1_hash def initialize(sha1_hash) @sha1_hash = sha1_hash end end attr_accessor :file_location, :file_data, :data, :info, :announce_list attr_accessor :encoding, :creation_date, :created_by, :comment, :announce #inside of info attr_accessor :name, :pieces, :piece_length, :length, :private #single file attr_accessor :md5sum #multi file attr_accessor :files def initialize(file_location) @file_data = File.read(file_location) @file_location = file_location end # # Returns an array of the file names # def file_list parsed_data = @data ||= Bencode.decode(@file_data) if is_single_file? [parsed_data["info"]["name"]] else files = Bencode.decode(parsed_data["info"]["files"]) end end # # True if there is only one file # def is_single_file? parsed_data = @data ||= Bencode.decode(@file_data) !parsed_data["info"].has_key?("files") end # # True if there is more then one file # def is_multi_file? parsed_data = @data ||= Bencode.decode(@file_data) parsed_data["info"].has_key?("files") end # # Returns the info hash # def info parsed_data = @data ||= Bencode.decode(@file_data) parsed_data["info"] end # # returns array of back up trackers. # this is an extention to the official specification, # which is also backwards compatible. # This key is used to implement lists of backup trackers. # def announce_list parsed_data = @data ||= Bencode.decode(@file_data) parsed_data["announce-list"].flatten end # # returns a string of an annouce # def announce parsed_data = @data ||= Bencode.decode(@file_data) parsed_data["announce"] end # # returns the string of the encoding # def encoding parsed_data = @data ||= Bencode.decode(@file_data) parsed_data["encoding"] end # # Returns the data the torrent was created # def creation_date parsed_data = @data ||= Bencode.decode(@file_data) Time.at(parsed_data["creation date"]) end # # returns the created by string # def created_by parsed_data = @data ||= Bencode.decode(@file_data) parsed_data["created by"] end # # returns the comment string # def comment parsed_data = @data ||= Bencode.decode(@file_data) end # # returns the file name # def name parsed_data = @data ||= Bencode.decode(@file_data) parsed_data["info"]["name"] end # # returns the pieces data # def pieces unless @parsed_peices parsed_data = @data ||= Bencode.decode(@file_data) @parsed_peices = parsed_data["info"]["pieces"].unpack("H*").first.scan(/.{20}/).map{|hash| FileSegment.new(hash)} end @parsed_peices end # # returns the piece_length # def piece_length parsed_data = @data ||= Bencode.decode(@file_data) parsed_data["info"]["piece length"] end # # returns the length # def length parsed_data = @data ||= Bencode.decode(@file_data) parsed_data["info"]["length"] end # # returns the private flag # def private parsed_data = @data ||= Bencode.decode(@file_data) parsed_data["info"]["private"] end end if __FILE__ == $0 require 'test/unit' class TorrentFileTest < Test::Unit::TestCase def setup @test_file = File.join(File.dirname(__FILE__),"test.torrent") end def test_torrent_single_file tf = TorrentFile.new(@test_file) assert_equal("UTF-8",tf.encoding) assert(tf.info["name"]) assert_equal("College Basketball - ''Colorado State at Utah'' (Recorded Jan 17, 2009, MTN).mpg",tf.name) assert_equal(1232424648,tf.creation_date.to_i) assert_equal(["http://tracker.thepiratebay.org/announce","udp://tracker.thepiratebay.org:80/announce"],tf.announce_list) assert_equal(["College Basketball - ''Colorado State at Utah'' (Recorded Jan 17, 2009, MTN).mpg"],tf.file_list) assert_equal(1692,tf.pieces.size) end end end