Class: RubyTorrent::Piece

Inherits:
Object show all
Includes:
EventSource
Defined in:
lib/rubytorrent/package.rb

Overview

a Piece is the basic unit of the .torrent metainfo file (though not of the bittorrent protocol). Pieces store their data directly on disk, so many operations here will be slow. each Piece stores data in one or more file pointers.

unlike Blocks and Packages, which are either complete or incomplete, a Piece can be complete but not valid, if the SHA1 check fails. thus, a call to piece.complete? is not sufficient to determine whether the data is ok to use or not.

Pieces handle all the trickiness involved with Blocks: taking in Blocks from arbitrary locations, writing them out to the correct set of file pointers, keeping track of which sections of the data have been filled, claimed but not filled, etc.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from EventSource

append_features, #on_event, #relay_event, #send_event, #unregister_events

Constructor Details

#initialize(index, sha1, start, length, files, validity_assumption = nil) ⇒ Piece

Returns a new instance of Piece.



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/rubytorrent/package.rb', line 267

def initialize(index, sha1, start, length, files, validity_assumption=nil)
  @index = index
  @sha1 = sha1
  @start = start
  @length = length
  @files = files # array of [file pointer, mutex, file length]
  @valid = nil

  ## calculate where we start and end in terms of the file pointers.
  @start_index = 0
  sum = 0
  while(sum + @files[@start_index][2] <= @start)
    sum += @files[@start_index][2]
    @start_index += 1
  end
  ## now sum + @files[@start_index][2] > start, and sum <= start
  @start_offset = @start - sum

  ## sections of the data we have
  @have = Covering.new(AwesomeRange.new(0 ... @length)).complete!
  @valid = validity_assumption
  @have.empty! unless valid?

  ## sections of the data someone has laid claim to but hasn't yet
  ## provided. a super-set of @have.
  @claimed = Covering.new(AwesomeRange.new(0 ... @length))

  ## protects @claimed, @have
  @state_m = Mutex.new
end

Instance Attribute Details

#indexObject (readonly)

Returns the value of attribute index.



264
265
266
# File 'lib/rubytorrent/package.rb', line 264

def index
  @index
end

#lengthObject (readonly)

Returns the value of attribute length.



264
265
266
# File 'lib/rubytorrent/package.rb', line 264

def length
  @length
end

#startObject (readonly)

Returns the value of attribute start.



264
265
266
# File 'lib/rubytorrent/package.rb', line 264

def start
  @start
end

Instance Method Details

#add_block(b) ⇒ Object

we don’t do any checking that this block has been claimed or not.



379
380
381
382
383
384
385
386
387
388
389
390
# File 'lib/rubytorrent/package.rb', line 379

def add_block(b)
  @valid = nil
  write = false
  new_have = @state_m.synchronize { @have.fill AwesomeRange.new(b.begin ... (b.begin + b.length)) }
  if new_have != @have
    @have = new_have
    write = true
  end
  
  write_bytes(b.begin, b.data) if write
  send_event(:complete) if complete?
end

#claim_block(b) ⇒ Object



356
357
358
359
360
# File 'lib/rubytorrent/package.rb', line 356

def claim_block(b)
  @state_m.synchronize do
    @claimed = @claimed.fill AwesomeRange.new(b.begin ... (b.begin + b.length))
  end
end

#complete?Boolean

Returns:

  • (Boolean)


302
# File 'lib/rubytorrent/package.rb', line 302

def complete?; @have.complete?; end

#discardObject

discard all data



305
306
307
308
309
310
311
# File 'lib/rubytorrent/package.rb', line 305

def discard # discard all data
  @state_m.synchronize do
    @have.empty!
    @claimed.empty!
  end
  @valid = false
end

#each_empty_block(max_length) ⇒ Object



348
349
350
351
352
353
354
# File 'lib/rubytorrent/package.rb', line 348

def each_empty_block(max_length)
  raise "no empty blocks in a complete piece" if complete?

  each_gap(@have, max_length) do |start, len|
    yield Block.new(@index, start, len)
  end
end

#each_unclaimed_block(max_length) ⇒ Object



340
341
342
343
344
345
346
# File 'lib/rubytorrent/package.rb', line 340

def each_unclaimed_block(max_length)
  raise "no unclaimed blocks in a complete piece" if complete?

  each_gap(@claimed, max_length) do |start, len|
    yield Block.new(@index, start, len)
  end
end

#empty_bytesObject



331
332
333
334
335
# File 'lib/rubytorrent/package.rb', line 331

def empty_bytes
  r = 0
  each_gap(@have) { |start, len| r += len }
  r
end

#get_complete_block(beginn, length) ⇒ Object

for a complete Piece, returns a complete Block of specified size and location.



370
371
372
373
374
375
376
# File 'lib/rubytorrent/package.rb', line 370

def get_complete_block(beginn, length)
  raise "can't make block from incomplete piece" unless complete?
  raise "invalid parameters #{beginn}, #{length}" unless (length > 0) && (beginn + length) <= @length

  b = Block.new(@index, beginn, length)
  b.add_chunk read_bytes(beginn, length) # returns b
end

#percent_claimedObject



337
# File 'lib/rubytorrent/package.rb', line 337

def percent_claimed; 100.0 * (@length.to_f - unclaimed_bytes) / @length; end

#percent_doneObject



338
# File 'lib/rubytorrent/package.rb', line 338

def percent_done; 100.0 * (@length.to_f - empty_bytes) / @length; end

#started?Boolean

Returns:

  • (Boolean)


303
# File 'lib/rubytorrent/package.rb', line 303

def started?; !@claimed.empty? || !@have.empty?; end

#to_sObject



298
299
300
# File 'lib/rubytorrent/package.rb', line 298

def to_s
  "<piece #@index: #@start + #@length #{(complete? ? 'cmp' : 'inc')}>"
end

#unclaim_block(b) ⇒ Object



362
363
364
365
366
# File 'lib/rubytorrent/package.rb', line 362

def unclaim_block(b)
  @state_m.synchronize do
    @claimed = @claimed.poke AwesomeRange.new(b.begin ... (b.begin + b.length))
  end
end

#unclaimed_bytesObject



325
326
327
328
329
# File 'lib/rubytorrent/package.rb', line 325

def unclaimed_bytes
  r = 0
  each_gap(@claimed) { |start, len| r += len }
  r
end

#valid?Boolean

Returns:

  • (Boolean)


313
314
315
316
317
318
319
320
321
322
323
# File 'lib/rubytorrent/package.rb', line 313

def valid?
  return @valid unless @valid.nil?
  return (@valid = false) unless complete?

  data = read_bytes(0, @length)
  if (data.length != @length)
    @valid = false
  else
    @valid = (Digest::SHA1.digest(data) == @sha1)
  end
end