Module: Rmega::Nodes::Downloadable

Includes:
Rmega::Net, Options
Included in:
File
Defined in:
lib/rmega/nodes/downloadable.rb

Instance Method Summary collapse

Methods included from Options

included, #options

Methods included from Rmega::Net

#http_get_content, #http_post, #survive

Methods included from Loggable

included, #logger

Instance Method Details

#allocate(path) ⇒ Object

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.



9
10
11
12
13
14
15
16
17
# 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) ⇒ Boolean

Returns:

  • (Boolean)


24
25
26
# File 'lib/rmega/nodes/downloadable.rb', line 24

def allocated?(path)
  ::File.exists?(path) and ::File.size(path) == filesize
end

#calculate_chunck_mac(data) ⇒ Object



63
64
65
66
# 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) ⇒ Object



58
59
60
61
# 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) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/rmega/nodes/downloadable.rb', line 68

def download(path)
  path = ::File.expand_path(path)
  path = Dir.exists?(path) ? ::File.join(path, name) : path

  progress = Progress.new(filesize, caption: 'Download', 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)
      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)
      end
    end
  end

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

  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) ⇒ Object

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



47
48
49
50
51
52
53
54
55
56
# 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) ⇒ Object



19
20
21
22
# 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) ⇒ Object



36
37
38
39
40
41
42
43
# 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) ⇒ Object

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



29
30
31
32
33
34
# 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