fargo 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|