-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
22 changed files
with
693 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,13 @@ | ||
AllCops: | ||
NewCops: enable | ||
TargetRubyVersion: 3.0 | ||
|
||
Style/StringLiterals: | ||
EnforcedStyle: double_quotes | ||
|
||
Style/StringLiteralsInInterpolation: | ||
EnforcedStyle: double_quotes | ||
Documentation: | ||
Enabled: false | ||
Style/FrozenStringLiteralComment: | ||
Enabled: false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
#!/usr/bin/env ruby | ||
require 'rutorrent' | ||
puts Rutorrent::VERSION | ||
|
||
require "rutorrent" | ||
|
||
Rutorrent::CLI.start |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,7 @@ | ||
# frozen_string_literal: true | ||
|
||
require_relative "rutorrent/version" | ||
Dir["#{__dir__}/rutorrent/**/*.rb"].each { |f| require f } | ||
|
||
module Rutorrent | ||
class Error < StandardError; end | ||
# Your code goes here... | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
module Rutorrent | ||
class AvailablePiece | ||
attr_accessor :payload | ||
|
||
def initialize(payload) | ||
@payload = payload.chars.map!(&:to_i) | ||
end | ||
|
||
def available_pieces | ||
payload.each_index.select { |i| payload[i] == 1 } | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
require "tty-prompt" | ||
require "bencode" | ||
require "bytesize" | ||
|
||
module Rutorrent | ||
class CLI | ||
def self.start | ||
puts Rutorrent::MESSAGES[:no_torrents_available] if Dir["#{Dir.home}/**/*.torrent"].empty? && exit | ||
|
||
selected_torrent_path = prompt.select(Rutorrent::MESSAGES[:select_torrent], Dir["#{Dir.home}/**/*.torrent"]) | ||
decoded_torrent = BEncode.load_file(selected_torrent_path) | ||
torrent_files = prompt.multi_select(Rutorrent::MESSAGES[:instructions], | ||
format_torrent_files_with_size(decoded_torrent)) | ||
|
||
torrent_files = format_torrent_files_with_size(decoded_torrent, show_byte_size: false) if torrent_files.empty? | ||
|
||
Rutorrent::Downloader.new(decoded_torrent, torrent_files).start | ||
end | ||
|
||
def self.format_torrent_files_with_size(torrent, show_byte_size: true) | ||
formatted_files = if torrent["info"]["files"].nil? | ||
[torrent["info"]["name"]] | ||
else | ||
torrent["info"]["files"].map { |f| f["path"].join("/") } | ||
end | ||
|
||
if show_byte_size | ||
formatted_files = formatted_files.map do |file| | ||
return "#{file} (#{ByteSize.new(torrent["info"]["length"])})" if torrent["info"]["files"].nil? | ||
|
||
index = torrent["info"]["files"].find_index { |f| f["path"].join("/") == file } | ||
"#{file} (#{ByteSize.new(torrent["info"]["files"][index]["length"])})" | ||
end | ||
end | ||
|
||
formatted_files | ||
end | ||
|
||
def self.prompt | ||
@prompt ||= TTY::Prompt.new | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
module Rutorrent | ||
MESSAGES = { | ||
select_torrent: "Please select a .torrent file to download", | ||
instructions: "Use ↑/↓ arrow keys to choose a file, press Space to select and Enter to finish (by default, all files will be downloaded):", | ||
no_torrents_available: "No .torrent files found in your home directory. Please download at least one and try again." | ||
}.freeze | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
module Rutorrent | ||
module Constants | ||
PEER_MESSAGES_MAPPING = { | ||
0x0 => "choke", | ||
0x1 => "unchoke", | ||
0x2 => "interested", | ||
0x3 => "not interested", | ||
0x4 => "have", | ||
0x5 => "bitfield", | ||
0x6 => "request", | ||
0x7 => "piece", | ||
0x8 => "cancel" | ||
}.freeze | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
module Rutorrent | ||
class Downloader | ||
def initialize(torrent, torrent_files) | ||
@torrent = torrent | ||
@torrent_files = torrent_files | ||
end | ||
|
||
def start | ||
tracker.connect | ||
end | ||
|
||
private | ||
|
||
def tracker | ||
@tracker ||= Rutorrent::Tracker.new(torrent, torrent_files) | ||
end | ||
|
||
attr_reader :torrent, :torrent_files | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
require "digest/sha1" | ||
|
||
class FileSaver | ||
attr_reader :expected_hashes, :pieces, :total_pieces, :piece_length, :file_path, :file | ||
|
||
def initialize(file_path, piece_length, total_pieces, expected_hashes) | ||
@file_path = file_path | ||
@file = File.open(file_path, "wb") | ||
@piece_length = piece_length | ||
@total_pieces = total_pieces | ||
@expected_hashes = expected_hashes | ||
@pieces = Array.new(total_pieces) { "" } | ||
end | ||
|
||
def save_block(piece_index, begin_offset, block_data) | ||
@pieces[piece_index] ||= "" | ||
@pieces[piece_index][begin_offset, block_data.length] = block_data | ||
|
||
@file.seek(piece_index * @piece_length) | ||
@file.write(@pieces[piece_index]) | ||
verify_piece(piece_index) | ||
@pieces[piece_index] = nil | ||
end | ||
|
||
def save_piece_to_file(piece_index) | ||
@file.seek(piece_index * piece_length) | ||
@file.write(pieces[piece_index]) | ||
puts "Verified and saved piece #{piece_index}" | ||
end | ||
|
||
def expected_final_piece_size | ||
@total_length - (@piece_length * (@total_pieces - 1)) | ||
end | ||
|
||
def verify_piece(piece_index) | ||
piece_data = @pieces[piece_index] | ||
piece_hash = Digest::SHA1.digest(piece_data) | ||
expected_hash = @expected_hashes[piece_index] | ||
|
||
return save_piece_to_file(piece_index) | ||
end | ||
|
||
def close | ||
file.close | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
require "net/http" | ||
|
||
module Rutorrent | ||
class HTTPConnection | ||
attr_reader :torrent, :options, :torrent_files | ||
|
||
def initialize(torrent, torrent_files, options = {}) | ||
@torrent = torrent | ||
@options = options | ||
@torrent_files = torrent_files | ||
|
||
create_methods_by_options | ||
end | ||
|
||
def connect | ||
uri = build_uri | ||
response = fetch_response(uri) | ||
handle_response(response) | ||
end | ||
|
||
private | ||
|
||
def request_params | ||
{ | ||
info_hash: info_hash, | ||
peer_id: peer_id, | ||
port: port, | ||
uploaded: uploaded, | ||
downloaded: downloaded, | ||
left: left, | ||
compact: compact, | ||
event: event | ||
} | ||
end | ||
|
||
def build_uri | ||
URI("#{torrent["announce"]}?#{URI.encode_www_form(request_params)}") | ||
end | ||
|
||
def create_methods_by_options | ||
options.each do |method_name, method_value| | ||
define_singleton_method(method_name) { method_value } | ||
end | ||
end | ||
|
||
def fetch_response(uri) | ||
Net::HTTP.get_response(uri) | ||
rescue StandardError => e | ||
puts "Something went wrong: #{e}" | ||
exit | ||
end | ||
|
||
def handle_response(response) | ||
return if response.nil? | ||
|
||
response = BEncode.load(response.body) | ||
|
||
peers = response["peers"].scan(/.{6}/) | ||
puts "There are #{peers.size} peers available" | ||
|
||
unpacked_peers = unpack_peers(peers) | ||
peer_classes = [] | ||
|
||
unpacked_peers.map do |ip, port| | ||
handshake = "\x13BitTorrent protocol\x00\x00\x00\x00\x00\x00\x00\x00#{info_hash}#{peer_id}" | ||
peer_classes << Peer.new(ip, port, handshake, info_hash) | ||
end | ||
end | ||
|
||
def unpack_peers(peers) | ||
peers.map { |peer| peer.unpack("a4n") } | ||
end | ||
end | ||
end |
Oops, something went wrong.