fargo 0.2.0 → 0.3.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.
@@ -1,32 +1,12 @@
1
- require 'zlib'
2
-
3
1
  module Fargo
4
2
  module Protocol
5
- class Download < EventMachine::Connection
6
-
7
- include Fargo::Utils
8
- include Fargo::Protocol::DC
9
-
10
- attr_accessor :download, :client
11
-
12
- def post_init
13
- super
3
+ module PeerDownload
14
4
 
15
- set_comm_inactivity_timeout 20
16
-
17
- @lock, @pk = generate_lock
18
- @handshake_step = 0
19
- end
20
-
21
- def send_lock
22
- @lock_sent = true
23
- send_message 'MyNick', @client.config.nick
24
- send_message 'Lock', "#{@lock} Pk=#{@pk}"
25
- end
5
+ attr_accessor :download
26
6
 
27
- def receive_data data
7
+ def receive_data_chunk data
28
8
  # only download if we're at the correct handshake step
29
- return super if @handshake_step != 6
9
+ return super if @handshake_step != 6 || @download.nil?
30
10
 
31
11
  if @zlib
32
12
  @inflator = Zlib::Inflate.new if @inflator.nil?
@@ -55,76 +35,30 @@ module Fargo
55
35
 
56
36
  download_finished! if @recvd == @length
57
37
  end
38
+
39
+ true
40
+ end
41
+
42
+ def parse_data?
43
+ @handshake_step != 6
58
44
  end
59
45
 
60
46
  def receive_message type, message
61
47
  case type
62
- when :mynick
63
- if @handshake_step == 0
64
- @handshake_step = 1
65
- @other_nick = message[:nick]
66
-
67
- client.channel << [:download_opened,
68
- publish_args.merge(:connection => self)]
69
- @download = @client.lock_next_download! @other_nick, self
70
-
71
- if @download.try(:file).nil?
72
- error "Nothing to download from:#{@other_nick}!"
73
- end
74
- else
75
- error 'Premature disconnect when mynick received'
76
- end
77
-
78
- when :lock
79
- if @handshake_step == 1
80
- @remote_lock = message[:lock]
81
- @handshake_step = 2
82
-
83
- send_lock unless @lock_sent
84
-
85
- send_message 'Supports', 'TTHF ADCGet ZLIG'
86
- send_message 'Direction', "Download #{@my_num = rand(10000)}"
87
- send_message 'Key', generate_key(@remote_lock)
88
- else
89
- error 'Premature disconnect when lock received'
90
- end
91
-
92
- when :supports
93
- if @handshake_step == 2
94
- @client_extensions = message[:extensions]
95
- @handshake_step = 3
96
- else
97
- error 'Premature disconnect when supports received'
98
- end
99
-
100
- when :direction
101
- if @handshake_step == 3 && message[:direction] == 'upload'
102
- @client_num = message[:number]
103
- @handshake_step = 4
104
- else
105
- error 'Premature disconnect when direction received'
106
- end
107
-
108
- when :key
109
- if @handshake_step == 4 && generate_key(@lock) == message[:key]
110
-
111
- FileUtils.mkdir_p File.dirname(download_path), :mode => 0755
112
-
113
- begin_download!
114
-
115
- else
116
- error 'Premature disconnect when key received'
117
- end
118
-
119
- when :file_length, :adcsnd
48
+ when :file_length, :adcsnd, :sending
120
49
  if @handshake_step == 5
121
50
  @recvd = 0
122
51
  @handshake_step = 6
123
52
 
124
- @zlib = message[:zlib]
53
+ @zlib = message[:zlib] unless @getblock_sent
125
54
  @length = message[:size]
126
55
 
127
- send_message 'Send' unless @client_extensions.include? 'ADCGet'
56
+ send_message 'Send' if @get_sent
57
+
58
+ if @zlib
59
+ Fargo.logger.debug(
60
+ "Enabling zlib compression on: #{@download.file}")
61
+ end
128
62
 
129
63
  @client.channel << [:download_started, {:file => download_path,
130
64
  :download => @download,
@@ -147,11 +81,11 @@ module Fargo
147
81
  # This wasn't handled by us, proxy it on up to the client
148
82
  else
149
83
  super
150
-
151
84
  end
152
85
  end
153
86
 
154
87
  def begin_download!
88
+ FileUtils.mkdir_p File.dirname(download_path), :mode => 0755
155
89
  @file = File.open download_path, 'wb'
156
90
 
157
91
  @file.seek @download.offset
@@ -174,20 +108,45 @@ module Fargo
174
108
  download_query = 'TTH/' + @download.tth
175
109
  end
176
110
 
177
- zlig = ''
178
- if @client_extensions.include? 'ZLIG'
179
- zlig = 'ZL1'
180
- Fargo.logger.debug "Enabling zlib compression on: #{@download.file}"
181
- end
111
+ zlig = @client_extensions.include?('ZLIG') ? ' ZL1' : ''
112
+
113
+ send_message 'ADCGET', "file #{download_query} #{@download.offset} #{@download.size}#{zlig}"
114
+
115
+ # See http://www.teamfair.info/wiki/index.php?title=XmlBZList for
116
+ # what the $Supports extensions mean for the U?GetZ?Block commands
117
+ elsif @client_extensions.include? 'GetZBlock'
118
+ @getblock_sent = true
119
+ @zlib = true
120
+ send_message 'UGetZBlock',
121
+ "#{@download.offset} #{@download.size} #{@download.file}"
122
+ elsif @client_extensions.include? 'XmlBZList'
123
+ @getblock_sent = true
124
+ @zlib = false
125
+ send_message 'UGetBlock',
126
+ "#{@download.offset} #{@download.size} #{@download.file}"
182
127
 
183
- send_message 'ADCGET', "file #{download_query} #{@download.offset} #{@download.size} #{zlig}"
184
128
  else
129
+ @get_sent = true
185
130
  send_message 'Get', "#{@download.file}$#{@download.offset + 1}"
186
131
  end
187
132
 
188
133
  Fargo.logger.debug "#{self}: Beginning download of #{@download}"
189
134
  end
190
135
 
136
+ def unbind
137
+ super
138
+
139
+ Fargo.logger.debug "#{self} Disconnected from: #{@other_nick}"
140
+
141
+ if @download
142
+ download_failed! @last_error, :recvd => @recvd, :length => @length
143
+ end
144
+
145
+ reset_download
146
+ end
147
+
148
+ protected
149
+
191
150
  def download_failed! msg, opts = {}
192
151
  Fargo.logger.debug "#{self}: #{msg} #{@download}"
193
152
 
@@ -218,24 +177,6 @@ module Fargo
218
177
  close_connection_after_writing if download.file_list?
219
178
  end
220
179
 
221
- def publish_args
222
- {:nick => @other_nick}
223
- end
224
-
225
- def unbind
226
- super
227
-
228
- Fargo.logger.debug "#{self} Disconnected from: #{@other_nick}"
229
-
230
- if @download
231
- download_failed! @last_error, :recvd => @recvd, :length => @length
232
- end
233
-
234
- reset_download
235
- end
236
-
237
- private
238
-
239
180
  def reset_download
240
181
  @file.close unless @file.nil? || @file.closed?
241
182
 
@@ -245,6 +186,7 @@ module Fargo
245
186
 
246
187
  # clear out these variables
247
188
  @inflator = @file_path = @zlib = @download = @length = @recvd = nil
189
+ @get_sent = @getblock_sent = false
248
190
 
249
191
  # Go back to the get step
250
192
  @handshake_step = 5
@@ -270,12 +212,6 @@ module Fargo
270
212
  end
271
213
  end
272
214
 
273
- def error message
274
- Fargo.logger.warn @last_error = message
275
-
276
- close_connection
277
- end
278
-
279
215
  end
280
216
  end
281
217
  end
@@ -0,0 +1,160 @@
1
+ module Fargo
2
+ module Protocol
3
+ module PeerUpload
4
+
5
+ CHUNKSIZE = 16 * 1024
6
+
7
+ def receive_message type, message
8
+ case type
9
+ when :adcget, :getblock, :get
10
+ if @handshake_step == 5
11
+ if message[:file] == 'files.xml.bz2'
12
+ @listing = 'filelist'
13
+ else
14
+ @listing = @client.listing_for message[:file].gsub("\\", '/')
15
+ end
16
+
17
+ if message[:size] == -1
18
+ if @listing == 'filelist'
19
+ @size = File.size @client.local_file_list_path
20
+ else
21
+ @size = @listing.try :size
22
+ end
23
+ else
24
+ @size = message[:size]
25
+ end
26
+
27
+ @offset = message[:offset]
28
+ @zlib = message[:zlib]
29
+
30
+ if @listing.nil?
31
+ if type == :getblock
32
+ send_message 'Failed', 'File Not Available'
33
+ else
34
+ send_message 'Error', 'File Not Available'
35
+ end
36
+ elsif @client.open_upload_slots == 0 && @listing != 'filelist'
37
+ send_message 'MaxedOut'
38
+ elsif type == :adcget
39
+ zl = @zlib ? ' ZL1' : ''
40
+ send_message 'ADCSND',
41
+ "#{message[:kind]} #{message[:file]} #{@offset} #{@size}#{zl}"
42
+
43
+ begin_streaming
44
+ elsif type == :getblock
45
+ if message[:size] == -1
46
+ send_message 'Sending'
47
+ else
48
+ send_message 'Sending', @size
49
+ end
50
+
51
+ begin_streaming
52
+ else
53
+ @handshake_step = 10
54
+
55
+ send_message 'FileLength', @size
56
+ end
57
+ else
58
+ error "Premature disconnect when #{type} received"
59
+ end
60
+
61
+ when :send
62
+ if @handshake_step == 10
63
+ begin_streaming
64
+ else
65
+ error "Premature disconnect when #{type} received"
66
+ end
67
+
68
+ when :cancel
69
+ if @handshake_step == 11
70
+ cancel_streaming
71
+ else
72
+ error "Premature disconnect when cancel received"
73
+ end
74
+
75
+ else
76
+ super
77
+ end
78
+ end
79
+
80
+ def unbind
81
+ super
82
+
83
+ if @listing
84
+ Fargo.logger.debug "Upload disconnected"
85
+ finish_streaming
86
+ end
87
+ end
88
+
89
+ protected
90
+
91
+ def begin_streaming
92
+ @handshake_step = 11
93
+
94
+ if @listing == 'filelist'
95
+ @file = File.open @client.local_file_list_path, 'rb'
96
+ else
97
+ @client.take_slot!
98
+ @file = File.open File.join(@listing.root, @listing.name), 'rb'
99
+ end
100
+
101
+ @file.seek @offset
102
+ @deflator = Zlib::Deflate.new if @zlib
103
+ @sent = 0
104
+ @looping = true
105
+
106
+ stream_file
107
+ end
108
+
109
+ def finish_streaming
110
+ @file.try :close
111
+
112
+ if @listing == 'filelist'
113
+ close_connection_after_writing
114
+ else
115
+ @client.release_slot!
116
+ end
117
+
118
+ @deflator = @file = @sent = @size = @offset = @listing = @zlib = nil
119
+ @looping = @canceled = false
120
+ @handshake_step = 5
121
+ end
122
+
123
+ def cancel_streaming
124
+ @looping = false
125
+ @canceled = true
126
+ end
127
+
128
+ def stream_file
129
+ while @looping do
130
+ if @sent < @size
131
+ if get_outbound_data_size > 4 * CHUNKSIZE
132
+ EventMachine.next_tick{ stream_file }
133
+ break
134
+ else
135
+ to_send = [CHUNKSIZE, @size - @sent].min
136
+ @sent += to_send
137
+
138
+ data = @file.read to_send
139
+ if @zlib
140
+ flush_flag = (@sent == @size ? Zlib::FINISH : Zlib::NO_FLUSH)
141
+ data = @deflator.deflate data, flush_flag
142
+ end
143
+
144
+ send_data data
145
+ end
146
+ else
147
+ finish_streaming
148
+ break
149
+ end
150
+ end
151
+
152
+ if @canceled
153
+ send_message 'Canceled'
154
+ finish_streaming
155
+ end
156
+ end
157
+
158
+ end
159
+ end
160
+ end
data/lib/fargo/search.rb CHANGED
@@ -6,8 +6,21 @@ module Fargo
6
6
  COMPRESSED = 3
7
7
  DOCUMENT = 4
8
8
  EXECUTABLE = 5
9
+ PICTURE = 6
9
10
  VIDEO = 7
10
11
  FOLDER = 8
12
+ TTH = 9
13
+
14
+ # See http://www.teamfair.info/wiki/index.php?title=$Search for the
15
+ # extensions
16
+ EXTENSIONS = {
17
+ AUDIO => [/mp(2|3)/, 'wav', 'au', /(r|s)m/, 'mid', 'flac', 'm4a'],
18
+ COMPRESSED => ['zip', 'arj', 'rar', 'lzh', 'gz', 'z', 'arc', 'pak'],
19
+ DOCUMENT => [/docx?/, 'txt', 'wri', 'pdf', 'ps', 'tex'],
20
+ EXECUTABLE => ['pm', 'exe', 'bat', 'com'],
21
+ PICTURE => ['gif', /jpe?g/, 'bmp', 'pcx', 'png', 'wmf', 'psd'],
22
+ VIDEO => [/mpe?g/, 'avi', 'asf', 'mov', 'mkv']
23
+ }
11
24
 
12
25
  attr_accessor :size_restricted, :is_minimum_size, :size, :filetype, :pattern
13
26
 
@@ -36,14 +49,34 @@ module Fargo
36
49
  pattern.gsub('$', ' ')
37
50
  end
38
51
 
39
- def matches_result? map
40
- file = map[:file].downcase
52
+ def matches? map
53
+ if map.is_a?(Listing)
54
+ listing = map
55
+ map = {
56
+ :file => listing.name,
57
+ :size => listing.size,
58
+ :tth => listing.tth
59
+ }
60
+ end
61
+
62
+ file = map[:file].try(:downcase) || ''
41
63
 
42
- matches_query = queries.inject(true) do |last, word|
43
- last && file.index(word.downcase)
64
+ if @filetype == TTH
65
+ matches_query = (@pattern =~ /^TTH:(\w+)$/ && map[:tth] == $1)
66
+ else
67
+ matches_query = queries.inject(true) do |last, word|
68
+ last && file.index(word.downcase)
69
+ end
70
+
71
+ patterns = EXTENSIONS[@filetype]
72
+
73
+ if patterns && matches_query
74
+ ext = File.extname file
75
+ matches_query = patterns.any?{ |p| ext =~ /^\.#{p}$/i }
76
+ end
44
77
  end
45
78
 
46
- if size_restricted == 'T'
79
+ if size_restricted
47
80
  if is_minimum_size
48
81
  matches_query && map[:size] > size
49
82
  else
@@ -1,16 +1,16 @@
1
1
  module Fargo
2
+ class Download < Struct.new(:nick, :file, :tth, :size, :offset)
3
+ attr_accessor :percent, :status
4
+
5
+ def file_list?
6
+ file == 'files.xml.bz2'
7
+ end
8
+ end
9
+
2
10
  module Supports
3
11
  module Downloads
4
12
  extend ActiveSupport::Concern
5
13
 
6
- class Download < Struct.new(:nick, :file, :tth, :size, :offset)
7
- attr_accessor :percent, :status
8
-
9
- def file_list?
10
- file == 'files.xml.bz2'
11
- end
12
- end
13
-
14
14
  included do
15
15
  set_callback :initialization, :after, :initialize_download_lists
16
16
  end
@@ -30,22 +30,22 @@ module Fargo
30
30
  finished_downloads.clear
31
31
  end
32
32
 
33
- def download nick, file=nil, tth=nil, size=-1, offset=0
33
+ def download nick, file = nil, tth = nil, size = -1, offset = 0
34
34
  raise ConnectionException.new 'Not connected yet!' unless hub
35
35
 
36
- if nick.is_a?(Supports::FileList::Listing)
37
- listing = nick
38
- nick = listing.nick
39
- file = listing.name
40
- tth = listing.tth
41
- size = listing.size
42
- elsif nick.is_a?(Download)
36
+ if nick.is_a?(Download)
43
37
  dl = nick
44
38
  nick = dl.nick
45
39
  file = dl.file
46
40
  tth = dl.tth
47
41
  size = dl.size || -1
48
42
  offset = dl.offset || 0
43
+ elsif nick.is_a?(Listing) # i.e. a listing
44
+ listing = nick
45
+ nick = listing.nick
46
+ file = listing.name
47
+ tth = listing.tth
48
+ size = listing.size
49
49
  end
50
50
 
51
51
  raise 'File must not be nil!' if file.nil?
@@ -177,7 +177,7 @@ module Fargo
177
177
  download.percent = 1
178
178
  download.status = 'finished'
179
179
  download_finished! user, false
180
- elsif type == :download_failed || type == :download_disconnected
180
+ elsif type == :download_failed || type == :peer_disconnected
181
181
  channel.unsubscribe subscribed_id
182
182
  download.status = 'failed'
183
183
  download_finished! user, true
@@ -0,0 +1,164 @@
1
+ require 'bzip2'
2
+ require 'libxml'
3
+ require 'active_support/core_ext/module/synchronization'
4
+
5
+ module Fargo
6
+ module Supports
7
+ module LocalFileList
8
+ extend ActiveSupport::Concern
9
+ include TTH
10
+
11
+ attr_reader :local_file_list
12
+
13
+ included do
14
+ set_callback :initialization, :after, :initialize_upload_lists
15
+ set_callback :connect, :after, :schedule_update
16
+ end
17
+
18
+ def share_directory dir
19
+ @shared_directories << dir unless @shared_directories.include? dir
20
+
21
+ if connected?
22
+ EventMachine.defer {
23
+ update_tth dir
24
+ write_file_list
25
+ }
26
+ else
27
+ update_tth dir
28
+ write_file_list
29
+ end
30
+ end
31
+
32
+ def share_size
33
+ config.override_share_size || @share_size
34
+ end
35
+
36
+ def local_file_list_path
37
+ File.join config.config_dir, 'files.xml.bz2'
38
+ end
39
+
40
+ def local_listings
41
+ collect_local_listings @local_file_list, [], nil
42
+ end
43
+
44
+ def search_local_listings search
45
+ collect_local_listings @local_file_list, [], search
46
+ end
47
+
48
+ def listing_for query
49
+ if query =~ /^TTH\/(\w+)$/
50
+ tth = $1
51
+ local_listings.detect{ |l| l.tth = tth }
52
+ else
53
+ local_listings.detect{ |l| l.name == query }
54
+ end
55
+ end
56
+
57
+ protected
58
+
59
+ def collect_local_listings hash, arr, search
60
+ hash.each_pair do |k, v|
61
+ if v.is_a?(Listing)
62
+ arr << v if search.nil? || search.matches?(v)
63
+ else
64
+ collect_local_listings v, arr, search
65
+ end
66
+ end
67
+
68
+ arr
69
+ end
70
+
71
+ 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
79
+
80
+ FileUtils.mkdir_p config.config_dir
81
+ Bzip2::Writer.open(local_file_list_path, 'w') do |f|
82
+ f << doc.to_s(:indent => false)
83
+ end
84
+ end
85
+
86
+ def update_tth root, directory = nil, hash = nil
87
+ if directory.nil?
88
+ directory = root
89
+ root = File.dirname(root)
90
+ end
91
+
92
+ hash ||= (@local_file_list[File.basename(directory)] ||= {})
93
+
94
+ Pathname.glob(directory + '/*').each do |path|
95
+ if path.directory?
96
+ update_tth_without_synchronization root, path.to_s,
97
+ hash[path.basename.to_s] ||= {}
98
+ elsif hash[path.basename.to_s].nil? ||
99
+ path.mtime > hash[path.basename.to_s].mtime
100
+ hash[path.basename.to_s] = Listing.new(
101
+ file_tth(path.to_s),
102
+ path.size,
103
+ path.to_s.gsub(root + '/', ''),
104
+ config.nick,
105
+ path.mtime,
106
+ root
107
+ )
108
+
109
+ @share_size += path.size
110
+ end
111
+ end
112
+
113
+ to_remove = []
114
+
115
+ hash.each_pair do |k, v|
116
+ file = directory + '/' + k
117
+ unless File.exists?(file)
118
+ to_remove << k
119
+ @share_size -= File.size file if File.file?(file)
120
+ end
121
+ end
122
+
123
+ to_remove.each{ |k| hash.delete k }
124
+ end
125
+
126
+ synchronize :update_tth, :with => :@update_lock
127
+
128
+ def create_entities entity, node
129
+ entity.each_pair do |k, v|
130
+ if v.is_a? Hash
131
+ dir = LibXML::XML::Node.new 'Directory'
132
+ dir['Name'] = k
133
+ create_entities v, dir
134
+ node << dir
135
+ 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
142
+ end
143
+ end
144
+ end
145
+
146
+ def schedule_update
147
+ EventMachine::Timer.new(60) do
148
+ @shared_directories.each{ |d| update_tth d }
149
+
150
+ write_file_list
151
+ schedule_update
152
+ end
153
+ end
154
+
155
+ def initialize_upload_lists
156
+ @shared_directories = []
157
+ @local_file_list = {}
158
+ @share_size = 0
159
+ @update_lock = Mutex.new
160
+ end
161
+
162
+ end
163
+ end
164
+ end