@@ -20,7 +20,6 @@ import androidx.core.app.NotificationCompat
2020import com.margelo.nitro.core.Promise
2121import okhttp3.*
2222import okhttp3.MediaType.Companion.toMediaType
23- import okhttp3.RequestBody.Companion.toRequestBody
2423import java.io.File
2524import java.io.RandomAccessFile
2625import java.util.concurrent.TimeUnit
@@ -601,15 +600,11 @@ class NitroCloudUploader(
601600 try {
602601 println (" 📤 Uploading part ${part.partNumber} /${state.totalChunks} (attempt ${retries + 1 } )" )
603602
604- // ✅ Read chunk
605- val chunkData = readChunk(filePath, part.offset, part.size)
606-
607- // ✅ Upload chunk
603+ // Stream the chunk from disk to avoid allocating the full part in memory.
608604 val request = Request .Builder ()
609605 .url(part.url)
610- .put(chunkData.toRequestBody( " application/octet-stream " .toMediaType() ))
606+ .put(streamingFileChunkBody(filePath, part.offset, part.size ))
611607 .addHeader(" Content-Type" , " application/octet-stream" )
612- .addHeader(" Content-Length" , part.size.toString())
613608 .build()
614609
615610 val response = httpClient.newCall(request).execute()
@@ -722,21 +717,40 @@ class NitroCloudUploader(
722717 false
723718 }
724719
725- private fun readChunk (filePath : String , offset : Long , size : Long ): ByteArray {
726- return RandomAccessFile (File (filePath), " r" ).use { raf ->
727- if (offset + size > raf.length()) {
728- throw IllegalArgumentException (" Invalid chunk bounds: offset=$offset , size=$size , file=${raf.length()} " )
729- }
730-
731- raf.seek(offset)
732- val buffer = ByteArray (size.toInt())
733- val bytesRead = raf.read(buffer)
734-
735- if (bytesRead != size.toInt()) {
736- throw Exception (" Read $bytesRead bytes, expected $size " )
720+ /* *
721+ * Streams a byte range from disk into an OkHttp RequestBody.
722+ *
723+ * Each write uses a fixed-size buffer instead of allocating the full chunk.
724+ * The file is reopened for each write so the body can be replayed if OkHttp retries.
725+ */
726+ private fun streamingFileChunkBody (filePath : String , offset : Long , size : Long ): RequestBody {
727+ val mediaType = " application/octet-stream" .toMediaType()
728+ return object : RequestBody () {
729+ override fun contentType () = mediaType
730+ override fun contentLength () = size
731+ override fun writeTo (sink : okio.BufferedSink ) {
732+ RandomAccessFile (File (filePath), " r" ).use { raf ->
733+ if (offset + size > raf.length()) {
734+ throw IllegalArgumentException (
735+ " Invalid chunk bounds: offset=$offset , size=$size , file=${raf.length()} " ,
736+ )
737+ }
738+ raf.seek(offset)
739+ val buffer = ByteArray (256 * 1024 ) // 256 KB
740+ var remaining = size
741+ while (remaining > 0 ) {
742+ val toRead = minOf(buffer.size.toLong(), remaining).toInt()
743+ val read = raf.read(buffer, 0 , toRead)
744+ if (read <= 0 ) {
745+ throw java.io.IOException (
746+ " Unexpected EOF at offset ${offset + (size - remaining)} " ,
747+ )
748+ }
749+ sink.write(buffer, 0 , read)
750+ remaining - = read
751+ }
752+ }
737753 }
738-
739- buffer
740754 }
741755 }
742756
0 commit comments