Sub PrepareDownload(data As Map)
Dim filename As String = data.GetDefault("filename", "")
Dim session As String = data.GetDefault("session", "")
Dim ackRequired As Boolean = data.GetDefault("ackRequired", True)
' Define a command string for acknowledgment or logging purposes.
Dim cmd As String = "downloadFile"
' Check if the file exists before proceeding.
If filename <> "" And File.Exists(baseDir, filename) Then
Dim su As StringUtils
' Initialize RandomAccessFile to read the specified file.
Dim raf As RandomAccessFile
raf.Initialize(baseDir, filename, False)
' Obtain the file size for chunking and progress calculations.
Dim fileSize As Long = raf.Size
Log("File size: " & fileSize)
' Set the chunk size in bytes (for instance, 10 MB here).
Dim chunkSize As Int = 10 * 1024 * 1024
' This offset tracks our position in the file as we read in chunks.
Dim offset As Long = 0
' First pass: calculate the MD5 hash of the entire file (also in chunks to prevend memory exceptions).
Dim jo As JavaObject
jo.InitializeStatic("java.security.MessageDigest")
jo = jo.RunMethod("getInstance", Array("MD5"))
' Loop through the file in chunks to update the MD5 digest.
Do While offset < fileSize
Dim remaining As Long = fileSize - offset
Dim actualSize As Int = chunkSize
If remaining < chunkSize Then actualSize = remaining
Dim buffer(actualSize) As Byte
raf.ReadBytes(buffer, 0, actualSize, offset)
' Update the MD5 digest with the current chunk's bytes.
jo.RunMethod("update", Array(buffer))
' Advance the offset by the number of bytes read.
offset = offset + actualSize
Loop
' Retrieve the final MD5 hash as a byte array, then encode it in Base64.
Dim finalHash() As Byte = jo.RunMethod("digest", Null)
Dim fileHash As String = su.EncodeBase64(finalHash)
' Reset offset for the second pass where we actually send chunks.
offset = 0
' Calculate the total number of chunks for reference.
Dim totalChunks As Int = Ceil(fileSize / chunkSize)
' Second pass: read and publish file data chunk by chunk.
Do While offset < fileSize
Dim remaining As Long = fileSize - offset
Dim actualSize As Int = chunkSize
If remaining < chunkSize Then actualSize = remaining
Dim buffer(actualSize) As Byte
raf.ReadBytes(buffer, 0, actualSize, offset)
' Increment offset before we build the MQTT message.
offset = offset + actualSize
' Encode the current chunk's data in Base64 for transmission.
Dim chunkBase64 As String = su.EncodeBase64(buffer)
' Build a JSON map containing chunk data and metadata.
Dim mp As Map
mp.Initialize
mp.Put("filename", filename)
mp.Put("data", chunkBase64)
mp.Put("chunkIndex", (offset / chunkSize) - IIf(remaining < chunkSize, 0, 0))
mp.Put("totalChunks", totalChunks)
mp.Put("hash", fileHash)
mp.Put("session", session)
' Convert the map to a JSON string, then to a byte array for publishing.
Dim Payload() As Byte = mp.As(JSON).ToCompactString.GetBytes("UTF8")
mqtt.Publish(Tools.deviceID & "/download", Payload)
Log("publish chunk #" & (offset / chunkSize))
Loop
' Close the file reader once all chunks are sent.
raf.Close
' Optionally send an acknowledgment if required.
If ackRequired Then
SendAcknowledgment(session, cmd, CreateMap("filename": filename), True, "")
End If
Else
' If the file does not exist, optionally send a negative acknowledgment.
If ackRequired Then
SendAcknowledgment(session, cmd, CreateMap("filename": filename), False, "File not found")
End If
End If
End Sub