fargo 0.3.0 → 0.4.0

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