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.
@@ -0,0 +1,63 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+
3
+ module Fargo
4
+ module CLI
5
+ module Searches
6
+ delegate :search, :to => :client
7
+
8
+ def setup_console
9
+ super
10
+
11
+ add_completion(/^results\s+[^\s]+$/){ client.searches }
12
+
13
+ add_logger(:search_result) do |message|
14
+ obj = client.search_objects.detect{ |s| s.matches? message }
15
+ if obj
16
+ "New search result for: #{obj.query.inspect}"
17
+ else
18
+ 'New search result'
19
+ end
20
+ end
21
+ end
22
+
23
+ def results str = nil, opts = {}
24
+ str ||= client.searches.last
25
+ results = client.search_results(str)
26
+
27
+ if results.nil?
28
+ puts "No search results for: #{str.inspect}!"
29
+ return
30
+ end
31
+
32
+ results = results.map{ |r| r.dup }
33
+
34
+ results.each_with_index{ |r, i|
35
+ r[:file] = r[:file].gsub("\\", '/')
36
+ r[:file] = File.basename(r[:file]) unless opts[:full]
37
+ r[:index] = i
38
+ }
39
+
40
+ max_nick_size = results.map{ |r| r[:nick].size }.max
41
+
42
+ if opts[:sort] == 'size'
43
+ results = results.sort_by{ |r| r[:size] }
44
+ elsif !opts[:sort].nil?
45
+ puts "Unknown sort value: #{opts[:sort]}"
46
+ results = []
47
+ end
48
+
49
+ if opts[:grep]
50
+ results = results.select{ |r| r[:file].match opts[:grep] }
51
+ end
52
+
53
+ results.each do |r|
54
+ printf "%3d: %#{max_nick_size}s %9s -- %s\n", r[:index],
55
+ r[:nick], humanize_bytes(r[:size]), r[:file]
56
+ end
57
+
58
+ true
59
+ end
60
+
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,16 @@
1
+ module Fargo
2
+ module CLI
3
+ module Stats
4
+
5
+ def status
6
+ puts "User count: #{client.nicks.size}"
7
+ puts "Shared Directories:"
8
+ client.shared_directories.each do |dir|
9
+ puts "\t#{dir}"
10
+ end
11
+ puts "Sharing: #{humanize_bytes(client.share_size)}"
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -1,6 +1,9 @@
1
1
  require 'socket'
2
+ require 'base64'
2
3
  require 'active_support/core_ext/object/try'
3
4
  require 'active_support/callbacks'
5
+ require 'active_support/configurable'
6
+ require 'em-websocket'
4
7
 
5
8
  module Fargo
6
9
  class Client
@@ -22,14 +25,17 @@ module Fargo
22
25
 
23
26
  configure do |config|
24
27
  config.download_dir = '/tmp/fargo/downloads'
25
- config.config_dir = '/tmp/fargo/config'
26
- config.address = IPSocket.getaddress(Socket.gethostname)
28
+ config.config_dir = ENV['HOME'] + '/.fargo'
29
+ config.address =
30
+ (IPSocket.getaddress(Socket.gethostname) rescue '0.0.0.0')
27
31
  config.passive = false
28
32
  config.nick = 'fargo'
29
33
  config.hub_address = '127.0.0.1'
30
34
  config.hub_port = 7314
31
35
  config.active_port = 7315
32
36
  config.search_port = 7316
37
+ config.websocket_port = 9091
38
+ config.websocket_host = '127.0.0.1'
33
39
  config.download_slots = 4
34
40
  config.upload_slots = 4
35
41
  config.password = ''
@@ -49,15 +55,15 @@ module Fargo
49
55
  @channel.subscribe do |type, hash|
50
56
  Fargo.logger.debug "Channel received: #{type} - #{hash.inspect}"
51
57
  end
58
+
59
+ config_file = config.config_dir + '/config'
60
+ if File.exists? config_file
61
+ eval File.read(config_file)
62
+ end
52
63
  end
53
64
  end
54
65
 
55
66
  def connect
56
- EventMachine.error_handler{ |e|
57
- Fargo.logger.debug "Error raised during event loop: #{e.message}"
58
- Fargo.logger.debug e.backtrace.join("\n")
59
- }
60
-
61
67
  run_callbacks :connect do
62
68
  EventMachine.connect config.hub_address, config.hub_port,
63
69
  Protocol::Hub do |conn|
@@ -77,10 +83,29 @@ module Fargo
77
83
  end
78
84
  end
79
85
  end
86
+
87
+ start_websocket_service
88
+ end
89
+
90
+ def start_websocket_service
91
+ EventMachine.start_server(config.websocket_host, config.websocket_port,
92
+ EventMachine::WebSocket::Connection, {}) do |ws|
93
+ ws.onopen {
94
+ Fargo.logger.debug('ws connected')
95
+
96
+ sid = channel.subscribe do |type, hash|
97
+ if type != :download_opened
98
+ ws.send Base64.encode64(Marshal.dump([type, hash]))
99
+ end
100
+ end
101
+
102
+ ws.onclose{ channel.unsubscribe sid }
103
+ }
104
+ end
80
105
  end
81
106
 
82
107
  def connected?
83
- EventMachine.reactor_running?
108
+ EventMachine.reactor_running? && !hub.error?
84
109
  end
85
110
 
86
111
  def disconnect
@@ -0,0 +1,33 @@
1
+ require 'irb'
2
+
3
+ # See url below for where this came from
4
+ # http://jameskilton.com/2009/04/02/embedding-irb-into-your-ruby-application/
5
+ module IRB
6
+ def self.start_session(binding)
7
+ unless @__initialized
8
+ args = ARGV
9
+ ARGV.replace(ARGV.dup)
10
+ IRB.setup(nil)
11
+ ARGV.replace(args)
12
+ @__initialized = true
13
+ end
14
+
15
+ workspace = WorkSpace.new(binding)
16
+
17
+ irb = Irb.new(workspace)
18
+
19
+ trap('SIGINT') do
20
+ irb.signal_handle
21
+ end
22
+
23
+ @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
24
+ @CONF[:MAIN_CONTEXT] = irb.context
25
+ @CONF[:PROMPT][@CONF[:PROMPT_MODE]][:RETURN].replace ''
26
+
27
+ yield if block_given?
28
+
29
+ catch(:IRB_EXIT) do
30
+ irb.eval_input
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,18 @@
1
+ require 'readline'
2
+
3
+ if Readline::VERSION =~ /editline/i
4
+ $stderr.puts "Sorry, fargo CLI requires a ruby compiled against the actual"
5
+ $stderr.puts "Readline library. Your version is: '#{Readline::VERSION}'"
6
+ exit 1
7
+ end
8
+
9
+ require 'readline/extra_utils'
10
+
11
+ module Readline
12
+ def self.above_prompt
13
+ Readline.clear_rl
14
+ yield
15
+ ensure
16
+ Readline.restore
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+ class Struct
2
+ def to_h
3
+ {}.tap do |h|
4
+ members.zip(entries).each{ |k, v| h[k] = v }
5
+ end
6
+ end
7
+ end
@@ -21,12 +21,14 @@ module Fargo
21
21
  download_finished!
22
22
  else
23
23
  percent = @recvd.to_f / @length
24
+ @download.percent = percent
25
+
24
26
  if percent - @last_published > 0.05
25
27
  @file.flush
26
28
  @client.channel << [:download_progress, {:percent => percent,
27
29
  :file => download_path,
28
30
  :nick => @other_nick,
29
- :download => @download,
31
+ :download => @download.to_h,
30
32
  :size => @recvd,
31
33
  :compressed => @zlib}]
32
34
 
@@ -61,7 +63,8 @@ module Fargo
61
63
  end
62
64
 
63
65
  @client.channel << [:download_started, {:file => download_path,
64
- :download => @download,
66
+ :download => @download.to_h,
67
+ :length => @length,
65
68
  :nick => @other_nick}]
66
69
  else
67
70
  error "Premature disconnect when #{message[:type]} received"
@@ -157,7 +160,7 @@ module Fargo
157
160
  reset_download
158
161
 
159
162
  @client.channel << [:download_failed, opts.merge(:nick => @other_nick,
160
- :download => download,
163
+ :download => download.to_h,
161
164
  :file => path,
162
165
  :last_error => msg)]
163
166
  end
@@ -172,7 +175,7 @@ module Fargo
172
175
  reset_download
173
176
 
174
177
  @client.channel << [:download_finished,
175
- {:file => path, :download => download, :nick => @other_nick}]
178
+ {:file => path, :download => download.to_h, :nick => @other_nick}]
176
179
 
177
180
  close_connection_after_writing if download.file_list?
178
181
  end
@@ -5,6 +5,7 @@ module Fargo
5
5
 
6
6
  included do
7
7
  set_callback :initialization, :after, :initialize_chats
8
+ set_callback :connect, :after, :periodically_remove_chats
8
9
  end
9
10
 
10
11
  def messages
@@ -15,8 +16,22 @@ module Fargo
15
16
  @chats[nick]
16
17
  end
17
18
 
19
+ def send_chat text
20
+ hub.send_data "<#{config.nick}> #{text}|"
21
+ end
22
+
18
23
  protected
19
24
 
25
+ def periodically_remove_chats
26
+ EventMachine.add_periodic_timer 60 do
27
+ @public_chats = @public_chats[0..100]
28
+
29
+ @chats.each_pair do |k, v|
30
+ @chats[k] = v[0..100]
31
+ end
32
+ end
33
+ end
34
+
20
35
  def initialize_chats
21
36
  @public_chats = []
22
37
  @chats = Hash.new{ |h, k| h[k] = [] }
@@ -19,7 +19,11 @@ module Fargo
19
19
  :failed_downloads, :trying, :timed_out
20
20
 
21
21
  def has_download_slot?
22
- @current_downloads.size + @trying.size < config.download_slots
22
+ open_download_slots > 0
23
+ end
24
+
25
+ def open_download_slots
26
+ config.download_slots - @trying.size - @current_downloads.size
23
27
  end
24
28
 
25
29
  def clear_failed_downloads
@@ -1,5 +1,5 @@
1
1
  require 'bzip2'
2
- require 'libxml'
2
+ require 'nokogiri'
3
3
  require 'active_support/core_ext/module/synchronization'
4
4
 
5
5
  module Fargo
@@ -8,7 +8,7 @@ module Fargo
8
8
  extend ActiveSupport::Concern
9
9
  include TTH
10
10
 
11
- attr_reader :local_file_list
11
+ attr_reader :local_file_list, :shared_directories
12
12
 
13
13
  included do
14
14
  set_callback :initialization, :after, :initialize_upload_lists
@@ -16,17 +16,14 @@ module Fargo
16
16
  end
17
17
 
18
18
  def share_directory dir
19
- @shared_directories << dir unless @shared_directories.include? dir
19
+ shared_directories << dir unless shared_directories.include? dir
20
20
 
21
- if connected?
22
- EventMachine.defer {
21
+ EventMachine.schedule { # Make sure we run in the reactor
22
+ EventMachine.defer { # This takes awhile so don't block the reactor
23
23
  update_tth dir
24
24
  write_file_list
25
25
  }
26
- else
27
- update_tth dir
28
- write_file_list
29
- end
26
+ }
30
27
  end
31
28
 
32
29
  def share_size
@@ -38,11 +35,11 @@ module Fargo
38
35
  end
39
36
 
40
37
  def local_listings
41
- collect_local_listings @local_file_list, [], nil
38
+ collect_local_listings local_file_list, [], nil
42
39
  end
43
40
 
44
41
  def search_local_listings search
45
- collect_local_listings @local_file_list, [], search
42
+ collect_local_listings local_file_list, [], search
46
43
  end
47
44
 
48
45
  def listing_for query
@@ -56,6 +53,10 @@ module Fargo
56
53
 
57
54
  protected
58
55
 
56
+ def cache_file_list_path
57
+ File.join config.config_dir, 'file_cache'
58
+ end
59
+
59
60
  def collect_local_listings hash, arr, search
60
61
  hash.each_pair do |k, v|
61
62
  if v.is_a?(Listing)
@@ -69,18 +70,24 @@ module Fargo
69
70
  end
70
71
 
71
72
  def write_file_list
72
- doc = LibXML::XML::Document.new
73
- doc.root = LibXML::XML::Node.new 'FileListing'
74
- doc.root['Version'] = '1'
75
- doc.root['Base'] = '/'
76
- doc.root['Generator'] = "fargo #{VERSION}"
77
-
78
- create_entities @local_file_list, doc.root
73
+ builder = Nokogiri::XML::Builder.new(:encoding => 'utf-8') do |xml|
74
+ attrs = ActiveSupport::OrderedHash.new
75
+ attrs[:Base] = '/'
76
+ attrs[:Version] = '1'
77
+ attrs[:Generator] = "fargo #{VERSION}"
78
+ xml.FileListing(attrs) {
79
+ create_entities local_file_list, xml
80
+ }
81
+ end
79
82
 
80
83
  FileUtils.mkdir_p config.config_dir
81
84
  Bzip2::Writer.open(local_file_list_path, 'w') do |f|
82
- f << doc.to_s(:indent => false)
85
+ f << builder.to_xml
83
86
  end
87
+
88
+ File.open(cache_file_list_path, 'w'){ |f|
89
+ f << Marshal.dump([local_file_list, shared_directories, share_size])
90
+ }
84
91
  end
85
92
 
86
93
  def update_tth root, directory = nil, hash = nil
@@ -89,7 +96,7 @@ module Fargo
89
96
  root = File.dirname(root)
90
97
  end
91
98
 
92
- hash ||= (@local_file_list[File.basename(directory)] ||= {})
99
+ hash ||= (local_file_list[File.basename(directory)] ||= {})
93
100
 
94
101
  Pathname.glob(directory + '/*').each do |path|
95
102
  if path.directory?
@@ -125,27 +132,24 @@ module Fargo
125
132
 
126
133
  synchronize :update_tth, :with => :@update_lock
127
134
 
128
- def create_entities entity, node
135
+ def create_entities entity, xml
129
136
  entity.each_pair do |k, v|
130
137
  if v.is_a? Hash
131
- dir = LibXML::XML::Node.new 'Directory'
132
- dir['Name'] = k
133
- create_entities v, dir
134
- node << dir
138
+ xml.Directory(:Name => k) { create_entities v, xml }
135
139
  else
136
- file = LibXML::XML::Node.new 'File'
137
- file['Name'] = k
138
- file['Size'] = v.size.to_s
139
- file['TTH'] = v.tth
140
-
141
- node << file
140
+ # Make sure they always show up in this order
141
+ attrs = ActiveSupport::OrderedHash.new
142
+ attrs[:Name] = k
143
+ attrs[:Size] = v.size.to_s
144
+ attrs[:TTH] = v.tth
145
+ xml.File attrs
142
146
  end
143
147
  end
144
148
  end
145
149
 
146
150
  def schedule_update
147
151
  EventMachine::Timer.new(60) do
148
- @shared_directories.each{ |d| update_tth d }
152
+ shared_directories.each{ |d| update_tth d }
149
153
 
150
154
  write_file_list
151
155
  schedule_update
@@ -153,10 +157,12 @@ module Fargo
153
157
  end
154
158
 
155
159
  def initialize_upload_lists
156
- @shared_directories = []
157
- @local_file_list = {}
158
- @share_size = 0
159
- @update_lock = Mutex.new
160
+ @local_file_list, @shared_directories, @share_size = begin
161
+ Marshal.load File.read(cache_file_list_path)
162
+ rescue
163
+ [{}, [], 0]
164
+ end
165
+ @update_lock = Mutex.new
160
166
  end
161
167
 
162
168
  end