fargo 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +49 -1
- data/bin/fargo +5 -0
- data/ext/fargo/base32.c +0 -25
- data/ext/fargo/base32.h +0 -8
- data/ext/fargo/tiger.c +0 -24
- data/ext/fargo/tiger.h +0 -8
- data/ext/fargo/tigertree.c +0 -36
- data/ext/fargo/tigertree.h +0 -6
- data/ext/fargo/tth.h +0 -8
- data/ext/readline/extconf.rb +9 -0
- data/ext/readline/fargo_cli.c +28 -0
- data/ext/readline/screen.c +77 -0
- data/ext/readline/screen.h +3 -0
- data/lib/fargo.rb +3 -1
- data/lib/fargo/cli.rb +84 -0
- data/lib/fargo/cli/completion.rb +41 -0
- data/lib/fargo/cli/downloads.rb +57 -0
- data/lib/fargo/cli/help.rb +80 -0
- data/lib/fargo/cli/info.rb +55 -0
- data/lib/fargo/cli/logging.rb +87 -0
- data/lib/fargo/cli/nick_browser.rb +159 -0
- data/lib/fargo/cli/searches.rb +63 -0
- data/lib/fargo/cli/stats.rb +16 -0
- data/lib/fargo/client.rb +33 -8
- data/lib/fargo/ext/irb.rb +33 -0
- data/lib/fargo/ext/readline.rb +18 -0
- data/lib/fargo/ext/struct.rb +7 -0
- data/lib/fargo/protocol/peer_download.rb +7 -4
- data/lib/fargo/supports/chat.rb +15 -0
- data/lib/fargo/supports/downloads.rb +5 -1
- data/lib/fargo/supports/local_file_list.rb +42 -36
- data/lib/fargo/supports/remote_file_list.rb +9 -7
- data/lib/fargo/supports/searches.rb +6 -0
- data/lib/fargo/version.rb +1 -1
- data/spec/fargo/protocol/hub_spec.rb +1 -1
- data/spec/fargo/protocol/peer_upload_spec.rb +1 -1
- data/spec/fargo/search_spec.rb +6 -1
- data/spec/fargo/supports/chat_spec.rb +7 -0
- data/spec/fargo/supports/local_file_list_spec.rb +32 -1
- data/spec/spec_helper.rb +6 -3
- metadata +65 -26
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'fargo/ext/readline'
|
2
|
+
|
3
|
+
module Fargo
|
4
|
+
module CLI
|
5
|
+
module Completion
|
6
|
+
|
7
|
+
def setup_console
|
8
|
+
old_proc = Readline.completion_proc
|
9
|
+
Readline.basic_word_break_characters = " \t\n\\'"
|
10
|
+
Readline.basic_quote_characters = ''
|
11
|
+
|
12
|
+
Readline.completion_proc = lambda { |str|
|
13
|
+
input = Readline.get_input
|
14
|
+
|
15
|
+
candidates, data = [], nil
|
16
|
+
regex, proc = @completions.detect{ |k, _|
|
17
|
+
data = input.match(k)
|
18
|
+
}
|
19
|
+
if data
|
20
|
+
data = data.to_a
|
21
|
+
data.shift
|
22
|
+
candidates = proc.call *data
|
23
|
+
end
|
24
|
+
|
25
|
+
if candidates.empty?
|
26
|
+
old_proc.call str
|
27
|
+
else
|
28
|
+
str = str.gsub /^"/, ''
|
29
|
+
candidates.select{ |n| n.start_with? str }.map{ |s| s.inspect }
|
30
|
+
end
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_completion regex, &block
|
35
|
+
@completions ||= {}
|
36
|
+
@completions[regex] = block
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Fargo
|
2
|
+
module CLI
|
3
|
+
module Downloads
|
4
|
+
|
5
|
+
def setup_console
|
6
|
+
super
|
7
|
+
|
8
|
+
add_completion(/^(download|get)\s+\d+,\s*[^\s]*$/) do
|
9
|
+
client.searches
|
10
|
+
end
|
11
|
+
|
12
|
+
add_logger(:download_started) do |message|
|
13
|
+
"Download of #{message[:download][:file]} " +
|
14
|
+
"(#{humanize_bytes message[:length]}) " +
|
15
|
+
"from #{message[:nick]} started"
|
16
|
+
end
|
17
|
+
|
18
|
+
add_logger(:download_finished) do |message|
|
19
|
+
"Download of #{message[:download][:file]} " +
|
20
|
+
"finished into #{message[:file]}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def download index, search = nil
|
25
|
+
search ||= client.searches[0]
|
26
|
+
|
27
|
+
if search.nil?
|
28
|
+
puts "Nothing to download!"
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
item = client.search_results(search)[index]
|
33
|
+
|
34
|
+
if item.nil?
|
35
|
+
puts 'That is not something to download!'
|
36
|
+
else
|
37
|
+
client.download item[:nick], item[:file], item[:tth], item[:size]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def transfers
|
42
|
+
max_nick = client.current_downloads.keys.map(&:size).max
|
43
|
+
client.current_downloads.each_pair do |nick, download|
|
44
|
+
printf "%#{max_nick}s %10s (%.2f%%) -- %s\n", nick,
|
45
|
+
humanize_bytes(download.size), 100 * download.percent,
|
46
|
+
download.file
|
47
|
+
end
|
48
|
+
|
49
|
+
puts "Upload slots avail: " +
|
50
|
+
"#{client.open_upload_slots}/#{client.config.upload_slots} " +
|
51
|
+
"Download slots avail: " +
|
52
|
+
"#{client.open_download_slots}/#{client.config.download_slots}"
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Fargo
|
2
|
+
module CLI
|
3
|
+
module Help
|
4
|
+
|
5
|
+
def setup_console
|
6
|
+
super
|
7
|
+
|
8
|
+
add_completion(/^man\s+[^\s]*$/){
|
9
|
+
['man', 'results', 'search', 'ls', 'pwd', 'cwd', 'cd', 'browse',
|
10
|
+
'download', 'get', 'who', 'say', 'send_chat', 'transfers']
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
def man cmd = nil
|
15
|
+
cmd ||= 'man'
|
16
|
+
|
17
|
+
case cmd.to_sym
|
18
|
+
when :man
|
19
|
+
puts "Usage: man 'cmd'"
|
20
|
+
when :results
|
21
|
+
puts "Usage: results ['search string' | index], opts = {}"
|
22
|
+
puts ""
|
23
|
+
puts " Show the results for a previous search. The search can be"
|
24
|
+
puts " identified by its search string or its index of the search."
|
25
|
+
puts " If no search string or index is given, the last search"
|
26
|
+
puts " results are displayed if they exist."
|
27
|
+
puts ""
|
28
|
+
puts " Recognized options:"
|
29
|
+
puts " :full - if true, show full filenames instead of just base"
|
30
|
+
puts " :sort - if 'size', sort results by size"
|
31
|
+
puts " :grep - A regexp to filter results by"
|
32
|
+
when :search
|
33
|
+
puts "Usage: search ['string' | Search]"
|
34
|
+
puts ""
|
35
|
+
puts " Search the hub for something. Either a string or a Search"
|
36
|
+
puts " object can be specified."
|
37
|
+
when :ls
|
38
|
+
puts "Usage: ls ['dir']"
|
39
|
+
puts ""
|
40
|
+
puts " Lists a directory. If no directory is given, lists the"
|
41
|
+
puts " current one."
|
42
|
+
when :pwd, :cwd
|
43
|
+
puts "Usage: pwd/cwd"
|
44
|
+
puts ""
|
45
|
+
puts " Show your current directory when browsing a user"
|
46
|
+
when :cd
|
47
|
+
puts "Usage: cd ['dir']"
|
48
|
+
puts ""
|
49
|
+
puts " Works just like on UNIX. No argument means go to root"
|
50
|
+
when :browse
|
51
|
+
puts "Usage: browse 'nick'"
|
52
|
+
puts ""
|
53
|
+
puts " Begin browisng a nick. If no file list has been downloaded,"
|
54
|
+
puts " one is queued for download and you will be notified when"
|
55
|
+
puts " browsing is ready."
|
56
|
+
when :download, :get
|
57
|
+
puts "Usage: "
|
58
|
+
when :who
|
59
|
+
puts "Usage: who ['nick' | 'size' | 'name']"
|
60
|
+
puts ""
|
61
|
+
puts " If no argument is given, shows all users on the hub. If a"
|
62
|
+
puts " name is given, shows that user on the hub. If 'size' or"
|
63
|
+
puts " 'name' is given, the users are sorted by that attribute."
|
64
|
+
when :say, :send_chat
|
65
|
+
puts "Usage: say/send_chat 'msg'"
|
66
|
+
puts ""
|
67
|
+
puts " Send a message to the hub"
|
68
|
+
when :transfers
|
69
|
+
puts "Usage: transfers"
|
70
|
+
puts ""
|
71
|
+
puts " Show some statistics about transfers happening."
|
72
|
+
else
|
73
|
+
puts "Unknown commnand: #{cmd}"
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
2
|
+
|
3
|
+
module Fargo
|
4
|
+
module CLI
|
5
|
+
module Info
|
6
|
+
|
7
|
+
delegate :send_chat, :to => :client
|
8
|
+
alias :say :send_chat
|
9
|
+
|
10
|
+
def setup_console
|
11
|
+
super
|
12
|
+
|
13
|
+
add_completion(/^who\s+[^\s]*$/) { client.nicks + ['size', 'name'] }
|
14
|
+
end
|
15
|
+
|
16
|
+
def who sort_by = nil
|
17
|
+
print_nick = lambda{ |p|
|
18
|
+
printf "%10s %s\n", humanize_bytes(p[1]), p[0]
|
19
|
+
}
|
20
|
+
|
21
|
+
if client.nicks.include? sort_by
|
22
|
+
info = client.info(sort_by)
|
23
|
+
key_len = info.keys.map{ |k| k.to_s.length }.max
|
24
|
+
|
25
|
+
info.each_pair do |k, v|
|
26
|
+
next if k == :type
|
27
|
+
printf "%#{key_len}s: %s\n", k,
|
28
|
+
v.is_a?(Numeric) ? humanize_bytes(v) : v
|
29
|
+
end
|
30
|
+
elsif sort_by.nil?
|
31
|
+
client.nicks.each do |n|
|
32
|
+
print_nick.call [n, client.info(n)[:sharesize]]
|
33
|
+
end
|
34
|
+
else
|
35
|
+
pairs = client.nicks.map{ |n|
|
36
|
+
[n, client.info(n)[:sharesize]]
|
37
|
+
}
|
38
|
+
|
39
|
+
if sort_by == 'name'
|
40
|
+
pairs = pairs.sort_by{ |p| p[0] }
|
41
|
+
elsif sort_by == 'size'
|
42
|
+
pairs = pairs.sort_by{ |p| p[1] }
|
43
|
+
else
|
44
|
+
pairs = []
|
45
|
+
puts "Unknown sorting by: #{sort_by.inspect}"
|
46
|
+
end
|
47
|
+
pairs.each &print_nick
|
48
|
+
end
|
49
|
+
|
50
|
+
true
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'drb'
|
2
|
+
require 'em-http-request'
|
3
|
+
|
4
|
+
module Fargo
|
5
|
+
module CLI
|
6
|
+
module Logging
|
7
|
+
|
8
|
+
attr_writer :client
|
9
|
+
|
10
|
+
def setup_console
|
11
|
+
super
|
12
|
+
|
13
|
+
add_logger(:chat) do |message|
|
14
|
+
"<#{message[:from]}>: #{message[:text]}"
|
15
|
+
end
|
16
|
+
|
17
|
+
add_logger(:hub_disconnected) do |_|
|
18
|
+
puts "Hub disconnected, exiting..."
|
19
|
+
exit
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_logger type, &block
|
24
|
+
@logging[type.to_s] << block
|
25
|
+
end
|
26
|
+
|
27
|
+
def client
|
28
|
+
@client ||= DRbObject.new_with_uri 'druby://127.0.0.1:8082'
|
29
|
+
end
|
30
|
+
|
31
|
+
def log_published_messages
|
32
|
+
@logging = Hash.new{ |h, k| h[k] = [] }
|
33
|
+
|
34
|
+
streamer = proc {
|
35
|
+
host = "ws://#{client.config.websocket_host}" +
|
36
|
+
":#{client.config.websocket_port}/"
|
37
|
+
|
38
|
+
ws = EventMachine::HttpRequest.new(host).get(:timeout => 0)
|
39
|
+
|
40
|
+
ws.disconnect {
|
41
|
+
Readline.above_prompt{ puts "Stopping logging stream." }
|
42
|
+
}
|
43
|
+
|
44
|
+
ws.callback {
|
45
|
+
Readline.above_prompt{ puts "Streaming logging messages." }
|
46
|
+
}
|
47
|
+
|
48
|
+
ws.stream { |msg|
|
49
|
+
to_log = nil
|
50
|
+
type, message = Marshal.load(Base64.decode64(msg))
|
51
|
+
|
52
|
+
@logging[type.to_s].each{ |l|
|
53
|
+
to_log = l.call message
|
54
|
+
Readline.above_prompt{ puts to_log } unless to_log.nil?
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
|
59
|
+
if EventMachine.reactor_running?
|
60
|
+
EventMachine.schedule streamer
|
61
|
+
else
|
62
|
+
Thread.start{ EventMachine.run streamer }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
protected
|
67
|
+
|
68
|
+
def humanize_bytes bytes
|
69
|
+
suffix = 'B'
|
70
|
+
while bytes > 1024
|
71
|
+
suffix = case suffix
|
72
|
+
when 'B' then 'K'
|
73
|
+
when 'K' then 'M'
|
74
|
+
when 'M' then 'G'
|
75
|
+
when 'G' then 'T'
|
76
|
+
when 'T' then break
|
77
|
+
end
|
78
|
+
|
79
|
+
bytes /= 1024.0
|
80
|
+
end
|
81
|
+
|
82
|
+
'%.2f %s' % [bytes, suffix]
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Fargo
|
4
|
+
module CLI
|
5
|
+
module NickBrowser
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
alias :get :download
|
10
|
+
end
|
11
|
+
|
12
|
+
module InstanceMethods
|
13
|
+
def setup_console
|
14
|
+
super
|
15
|
+
|
16
|
+
@fixed_completions = {}
|
17
|
+
|
18
|
+
add_completion(/^browse\s+[^\s]*$/) { client.nicks }
|
19
|
+
|
20
|
+
file_regex = /(?:\s+(?:[^\s,]*))+/
|
21
|
+
add_completion(/^(?:get|download)#{file_regex}$/) { completion true }
|
22
|
+
add_completion(/^(?:ls|cd)#{file_regex}$/) { completion }
|
23
|
+
|
24
|
+
add_logger(:download_finished) do |message|
|
25
|
+
if message[:file].end_with? 'files.xml.bz2'
|
26
|
+
begin_browsing message[:nick]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def download file, other = nil
|
32
|
+
if file.is_a?(String)
|
33
|
+
resolved = resolve(file).to_s
|
34
|
+
listing = drilldown resolved, @file_list
|
35
|
+
|
36
|
+
if listing.nil?
|
37
|
+
puts "No file to download!: #{file}"
|
38
|
+
elsif listing.is_a? Hash
|
39
|
+
# Recursively download the entire directory
|
40
|
+
listing.keys.each do |k|
|
41
|
+
download File.join(resolved, k)
|
42
|
+
end
|
43
|
+
else
|
44
|
+
client.download listing
|
45
|
+
end
|
46
|
+
else
|
47
|
+
super
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def browse nick
|
53
|
+
@browsing = nick
|
54
|
+
@file_list = nil
|
55
|
+
list = client.file_list nick
|
56
|
+
begin_browsing nick, false if list.is_a?(Hash)
|
57
|
+
end
|
58
|
+
|
59
|
+
def cd dir = '/'
|
60
|
+
cwd = resolve(dir)
|
61
|
+
if drilldown(cwd, @file_list).nil?
|
62
|
+
puts "#{dir.inspect} doesn't exist!"
|
63
|
+
else
|
64
|
+
@cwd = cwd
|
65
|
+
pwd
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def pwd
|
70
|
+
puts @cwd.to_s
|
71
|
+
end
|
72
|
+
|
73
|
+
alias :cwd :pwd
|
74
|
+
|
75
|
+
def ls dir = ''
|
76
|
+
if @cwd.nil? || @file_list.nil?
|
77
|
+
puts "Note browsing any nick!"
|
78
|
+
return
|
79
|
+
end
|
80
|
+
|
81
|
+
hash = drilldown(resolve(dir), @file_list)
|
82
|
+
|
83
|
+
hash.keys.sort_by(&:downcase).
|
84
|
+
sort_by{ |k| hash[k].is_a?(Hash) ? 0 : 1 }.each do |key|
|
85
|
+
if hash[key].is_a?(Hash)
|
86
|
+
puts "#{key}/"
|
87
|
+
else
|
88
|
+
printf "%10s -- %s\n", humanize_bytes(hash[key].size), key
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
true
|
93
|
+
end
|
94
|
+
|
95
|
+
def begin_browsing nick, above_prompt = true
|
96
|
+
@cwd = Pathname.new '/'
|
97
|
+
@file_list = client.file_list(@browsing)
|
98
|
+
|
99
|
+
if above_prompt
|
100
|
+
Readline.above_prompt{ puts "#{@browsing} ready for browsing" }
|
101
|
+
else
|
102
|
+
puts "#{@browsing} ready for browsing"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
protected
|
107
|
+
|
108
|
+
def completion include_files = false
|
109
|
+
if @browsing
|
110
|
+
all_input = Readline.get_input
|
111
|
+
dirs = []
|
112
|
+
while tmp = all_input.slice!(/[^\s]+?\s+/)
|
113
|
+
dirs << tmp.rstrip.gsub!(/"/, '')
|
114
|
+
end
|
115
|
+
dirs.shift # original command
|
116
|
+
|
117
|
+
resolved = resolve dirs.join('/'), false
|
118
|
+
hash = drilldown resolved, @file_list
|
119
|
+
|
120
|
+
keys = hash.keys rescue []
|
121
|
+
keys = keys.select{ |k|
|
122
|
+
include_files || k == '..' || hash[k].is_a?(Hash)
|
123
|
+
}
|
124
|
+
keys << '..' unless keys.empty? && dirs.size != 0
|
125
|
+
|
126
|
+
keys.map{ |k|
|
127
|
+
suffix = hash[k].is_a?(Hash) ? '/' : ''
|
128
|
+
|
129
|
+
# Readline doesn't like completing words with spaces in the file
|
130
|
+
# name, so just display them as periods when in actuality we'll
|
131
|
+
# convert back to a space later
|
132
|
+
(k.gsub(' ', '.') + suffix).tap do |str|
|
133
|
+
key = @cwd.join(*dirs).join(str).expand_path.to_s
|
134
|
+
@fixed_completions[key] = resolved.join k
|
135
|
+
end
|
136
|
+
}
|
137
|
+
else
|
138
|
+
[]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def resolve dir, clear_cache = true
|
143
|
+
return '' if @cwd.nil?
|
144
|
+
|
145
|
+
res = @fixed_completions[@cwd.join(dir).expand_path.to_s] ||
|
146
|
+
@cwd.join(dir).expand_path
|
147
|
+
@fixed_completions.clear if clear_cache
|
148
|
+
res.expand_path
|
149
|
+
end
|
150
|
+
|
151
|
+
def drilldown path, list
|
152
|
+
path.to_s.gsub(/^\//, '').split('/').inject(list) { |hash, part|
|
153
|
+
hash ? hash[part] : nil
|
154
|
+
}
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|