Class: Lanet::FileTransfer

Inherits:
Object
  • Object
show all
Defined in:
lib/lanet/file_transfer.rb

Overview

FileTransfer handles secure file transmission over the network

Defined Under Namespace

Classes: Error

Constant Summary collapse

CHUNK_SIZE =

Use configuration constants

Config::CHUNK_SIZE
MAX_RETRIES =
Config::MAX_RETRIES
TIMEOUT =
Config::FILE_TRANSFER_TIMEOUT
FILE_HEADER =

Message types

"FH"
FILE_CHUNK =

File metadata

"FC"
FILE_END =

File data chunk

"FE"
FILE_ACK =

End of transfer

"FA"
FILE_ERROR =

Acknowledgment

"FR"

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(port = nil) ⇒ FileTransfer

Initialization



37
38
39
40
41
42
43
44
# File 'lib/lanet/file_transfer.rb', line 37

def initialize(port = nil)
  @port = port || 5001 # Default port for file transfers
  @progress = 0.0
  @file_size = 0
  @transferred_bytes = 0
  @sender = Lanet::Sender.new(@port) # Assumes Lanet::Sender is defined elsewhere
  @cancellation_requested = false
end

Instance Attribute Details

#file_sizeObject (readonly)

Attributes for tracking progress



34
35
36
# File 'lib/lanet/file_transfer.rb', line 34

def file_size
  @file_size
end

#progressObject (readonly)

Attributes for tracking progress



34
35
36
# File 'lib/lanet/file_transfer.rb', line 34

def progress
  @progress
end

#transferred_bytesObject (readonly)

Attributes for tracking progress



34
35
36
# File 'lib/lanet/file_transfer.rb', line 34

def transferred_bytes
  @transferred_bytes
end

Instance Method Details

#cancel_transferObject

Cancel Transfer



163
164
165
# File 'lib/lanet/file_transfer.rb', line 163

def cancel_transfer
  @cancellation_requested = true
end

#receive_file(output_dir, encryption_key = nil, public_key = nil, progress_callback = nil, &block) ⇒ Object

Receive File Method



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/lanet/file_transfer.rb', line 120

def receive_file(output_dir, encryption_key = nil, public_key = nil, progress_callback = nil, &block)
  # Use the block parameter if provided and progress_callback is nil
  progress_callback = block if block_given? && progress_callback.nil?

  FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)
  receiver = UDPSocket.new
  receiver.bind("0.0.0.0", @port)
  active_transfers = {}

  begin
    loop do
      data, addr = receiver.recvfrom(65_536) # Large buffer for chunks

      # Skip if we received nil data or address
      next if addr.nil? || data.nil?

      sender_ip = addr[3]
      result = Lanet::Encryptor.process_message(data, encryption_key, public_key)
      next unless result[:content]&.length&.> 2

      message_type = result[:content][0..1]
      message_data = result[:content][2..]

      case message_type
      when FILE_HEADER
        handle_file_header(sender_ip, message_data, active_transfers, encryption_key, progress_callback)
      when FILE_CHUNK
        handle_file_chunk(sender_ip, message_data, active_transfers, progress_callback, encryption_key)
      when FILE_END
        handle_file_end(sender_ip, message_data, active_transfers, output_dir, encryption_key, progress_callback)
      when FILE_ERROR
        handle_file_error(sender_ip, message_data, active_transfers, progress_callback)
      end
    end
  rescue Interrupt
    puts "\nFile receiver stopped."
  ensure
    cleanup_transfers(active_transfers)
    receiver.close
  end
end

#send_file(target_ip, file_path, encryption_key = nil, private_key = nil, progress_callback = nil) ⇒ Object

Send File Method



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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
112
113
114
115
116
117
# File 'lib/lanet/file_transfer.rb', line 47

def send_file(target_ip, file_path, encryption_key = nil, private_key = nil, progress_callback = nil)
  # Validate file
  unless File.exist?(file_path) && File.file?(file_path)
    raise Error, "File not found or is not a regular file: #{file_path}"
  end

  # Initialize transfer state
  @file_size = File.size(file_path)
  @transferred_bytes = 0
  @progress = 0.0
  @cancellation_requested = false
  transfer_id = SecureRandom.uuid
  chunk_index = 0

  receiver = nil

  begin
    # Send file header
    file_name = File.basename(file_path)
    file_checksum = calculate_file_checksum(file_path)
    header_data = {
      id: transfer_id,
      name: file_name,
      size: @file_size,
      checksum: file_checksum,
      timestamp: Time.now.to_i
    }.to_json
    header_message = Lanet::Encryptor.prepare_message("#{FILE_HEADER}#{header_data}", encryption_key, private_key)
    @sender.send_to(target_ip, header_message)

    # Wait for initial ACK
    receiver = UDPSocket.new
    receiver.bind("0.0.0.0", @port)
    wait_for_ack(receiver, target_ip, transfer_id, encryption_key, "initial")

    # Send file chunks
    File.open(file_path, "rb") do |file|
      until file.eof? || @cancellation_requested
        chunk = file.read(CHUNK_SIZE)
        chunk_data = {
          id: transfer_id,
          index: chunk_index,
          data: Base64.strict_encode64(chunk)
        }.to_json
        chunk_message = Lanet::Encryptor.prepare_message("#{FILE_CHUNK}#{chunk_data}", encryption_key, private_key)
        @sender.send_to(target_ip, chunk_message)

        chunk_index += 1
        @transferred_bytes += chunk.bytesize
        @progress = (@transferred_bytes.to_f / @file_size * 100).round(2)
        progress_callback&.call(@progress, @transferred_bytes, @file_size)

        sleep(0.01) # Prevent overwhelming the receiver
      end
    end

    # Send end marker and wait for final ACK
    unless @cancellation_requested
      end_data = { id: transfer_id, total_chunks: chunk_index }.to_json
      end_message = Lanet::Encryptor.prepare_message("#{FILE_END}#{end_data}", encryption_key, private_key)
      @sender.send_to(target_ip, end_message)
      wait_for_ack(receiver, target_ip, transfer_id, encryption_key, "final")
      true # Transfer successful
    end
  rescue StandardError => e
    send_error(target_ip, transfer_id, e.message, encryption_key, private_key)
    raise Error, "File transfer failed: #{e.message}"
  ensure
    receiver&.close
  end
end