module Rmega::Nodes::Downloadable

Public Instance Methods

allocate(path) click to toggle source

Creates the local file allocating filesize-n bytes (of /dev/zero) for it. Opens the local file to start writing from the beginning of it.

# File lib/rmega/nodes/downloadable.rb, line 9
def allocate(path)
  unless allocated?(path)
    `dd if=/dev/zero of="#{path}" bs=1 count=0 seek=#{filesize} > /dev/null 2>&1`
    raise "Unable to allocate space for file #{path}" if ::File.size(path) != filesize
  end

  @file = ::File.open(path, 'r+b')
  @file.rewind
end
allocated?(path) click to toggle source
# File lib/rmega/nodes/downloadable.rb, line 24
def allocated?(path)
  ::File.exist?(path) and ::File.size(path) == filesize
end
calculate_chunck_mac(data) click to toggle source
# File lib/rmega/nodes/downloadable.rb, line 63
def calculate_chunck_mac(data)
  mac_iv = @node_key.ctr_nonce * 2
  return aes_cbc_mac(@node_key.aes_key, data, mac_iv)
end
decrypt_chunk(start, data) click to toggle source
# File lib/rmega/nodes/downloadable.rb, line 58
def decrypt_chunk(start, data)
  iv = @node_key.ctr_nonce + [start/0x1000000000, start/0x10].pack('l>*')
  return aes_ctr_decrypt(@node_key.aes_key, data, iv)
end
download(path) click to toggle source
# File lib/rmega/nodes/downloadable.rb, line 68
def download(path)
  path = ::File.expand_path(path)
  path = Dir.exist?(path) ? ::File.join(path, name) : path

  progress = Progress.new(filesize, caption: 'Allocate', filename: self.name)
  pool = Pool.new

  @resumed_download = allocated?(path)
  allocate(path)
  @node_key = NodeKey.load(decrypted_file_key)

  chunk_macs = {}

  each_chunk do |start, size|
    pool.process do
      data = @resumed_download ? read_chunk(start, size) : nil

      if data
        chunk_macs[start] = calculate_chunck_mac(data) if options.file_integrity_check
        progress.increment(size, real: false, caption: "Verify")
      else
        data = decrypt_chunk(start, download_chunk(start, size))
        chunk_macs[start] = calculate_chunck_mac(data) if options.file_integrity_check
        write_chunk(start, data)
        progress.increment(size, caption: "Download")
      end
    end
  end

  # waits for the last running threads to finish
  pool.wait_done

  if options.file_integrity_check
    file_mac = aes_cbc_mac(@node_key.aes_key, chunk_macs.sort.map(&:last).join, "\x0"*16)

    if Utils.compact_to_8_bytes(file_mac) != @node_key.meta_mac
      raise("Checksum failed. File corrupted?")
    end
  end

  return nil
ensure
  @file.close rescue nil
end
download_chunk(start, size) click to toggle source

Downloads a part of the remote file, starting from the start-n byte and ending after size-n bytes.

# File lib/rmega/nodes/downloadable.rb, line 47
def download_chunk(start, size)
  stop = start + size - 1
  url = "#{storage_url}/#{start}-#{stop}"

  survive do
    data = http_get_content(url)
    raise("Unexpected data length") if data.size != size
    return data
  end
end
file_io_synchronize(&block) click to toggle source
# File lib/rmega/nodes/downloadable.rb, line 19
def file_io_synchronize(&block)
  @file_io_mutex ||= Mutex.new
  @file_io_mutex.synchronize(&block)
end
read_chunk(start, size) click to toggle source
# File lib/rmega/nodes/downloadable.rb, line 36
def read_chunk(start, size)
  file_io_synchronize do
    @file.seek(start)
    data = @file.read(size)
    @file.seek(start)
    return (data == "\x0"*size) ? nil : data
  end
end
write_chunk(start, buffer) click to toggle source

Writes a buffer in the local file, starting from the start-n byte.

# File lib/rmega/nodes/downloadable.rb, line 29
def write_chunk(start, buffer)
  file_io_synchronize do
    @file.seek(start)
    @file.write(buffer)
  end
end