@hot-updater/react-native 0.29.6 → 0.29.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -235,7 +235,7 @@ class OkHttpDownloadService : DownloadService {
|
|
|
235
235
|
|
|
236
236
|
// Verify file size
|
|
237
237
|
val finalSize = destination.length()
|
|
238
|
-
if (finalSize != totalSize) {
|
|
238
|
+
if (totalSize > 0 && finalSize != totalSize) {
|
|
239
239
|
Log.d(TAG, "Download incomplete: $finalSize / $totalSize bytes")
|
|
240
240
|
|
|
241
241
|
// Delete incomplete file
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
package com.hotupdater
|
|
2
|
+
|
|
3
|
+
import kotlinx.coroutines.runBlocking
|
|
4
|
+
import org.junit.Assert.assertArrayEquals
|
|
5
|
+
import org.junit.Assert.assertEquals
|
|
6
|
+
import org.junit.Assert.assertNull
|
|
7
|
+
import org.junit.Assert.assertTrue
|
|
8
|
+
import org.junit.Rule
|
|
9
|
+
import org.junit.Test
|
|
10
|
+
import org.junit.rules.TemporaryFolder
|
|
11
|
+
import java.io.File
|
|
12
|
+
import java.net.InetAddress
|
|
13
|
+
import java.net.ServerSocket
|
|
14
|
+
import java.net.Socket
|
|
15
|
+
import java.net.URL
|
|
16
|
+
import kotlin.concurrent.thread
|
|
17
|
+
|
|
18
|
+
class OkHttpDownloadServiceTest {
|
|
19
|
+
@get:Rule
|
|
20
|
+
val temporaryFolder = TemporaryFolder()
|
|
21
|
+
|
|
22
|
+
@Test
|
|
23
|
+
fun `downloadFile succeeds when content length is unknown`() =
|
|
24
|
+
runBlocking {
|
|
25
|
+
val payload = "bundle-content-without-content-length".repeat(512).toByteArray()
|
|
26
|
+
val server = ChunkedResponseServer(payload)
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
val destinationDir = temporaryFolder.newFolder("downloads")
|
|
30
|
+
val destination = File(destinationDir, "bundle.android.bundle")
|
|
31
|
+
var reportedSize: Long? = null
|
|
32
|
+
|
|
33
|
+
val result =
|
|
34
|
+
OkHttpDownloadService().downloadFile(
|
|
35
|
+
fileUrl = URL("http://127.0.0.1:${server.port}/bundle"),
|
|
36
|
+
destination = destination,
|
|
37
|
+
fileSizeCallback = { size -> reportedSize = size },
|
|
38
|
+
progressCallback = {},
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
assertTrue(result is DownloadResult.Success)
|
|
42
|
+
assertEquals(payload.size.toLong(), destination.length())
|
|
43
|
+
assertArrayEquals(payload, destination.readBytes())
|
|
44
|
+
assertNull(reportedSize)
|
|
45
|
+
} finally {
|
|
46
|
+
server.close()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private class ChunkedResponseServer(
|
|
51
|
+
private val payload: ByteArray,
|
|
52
|
+
) : AutoCloseable {
|
|
53
|
+
private val serverSocket = ServerSocket(0, 1, InetAddress.getByName("127.0.0.1"))
|
|
54
|
+
private val worker =
|
|
55
|
+
thread(start = true, isDaemon = true) {
|
|
56
|
+
serverSocket.use { socket ->
|
|
57
|
+
socket.accept().use(::respond)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
val port: Int = serverSocket.localPort
|
|
62
|
+
|
|
63
|
+
private fun respond(client: Socket) {
|
|
64
|
+
drainRequestHeaders(client)
|
|
65
|
+
|
|
66
|
+
client.getOutputStream().use { output ->
|
|
67
|
+
output.write(
|
|
68
|
+
(
|
|
69
|
+
"HTTP/1.1 200 OK\r\n" +
|
|
70
|
+
"Content-Type: application/octet-stream\r\n" +
|
|
71
|
+
"Transfer-Encoding: chunked\r\n" +
|
|
72
|
+
"Connection: close\r\n" +
|
|
73
|
+
"\r\n"
|
|
74
|
+
).toByteArray(),
|
|
75
|
+
)
|
|
76
|
+
output.write(payload.size.toString(16).toByteArray())
|
|
77
|
+
output.write("\r\n".toByteArray())
|
|
78
|
+
output.write(payload)
|
|
79
|
+
output.write("\r\n0\r\n\r\n".toByteArray())
|
|
80
|
+
output.flush()
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private fun drainRequestHeaders(client: Socket) {
|
|
85
|
+
val input = client.getInputStream()
|
|
86
|
+
var matched = 0
|
|
87
|
+
val terminator = byteArrayOf('\r'.code.toByte(), '\n'.code.toByte(), '\r'.code.toByte(), '\n'.code.toByte())
|
|
88
|
+
|
|
89
|
+
while (matched < terminator.size) {
|
|
90
|
+
val next = input.read()
|
|
91
|
+
if (next == -1) {
|
|
92
|
+
break
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
matched =
|
|
96
|
+
if (next.toByte() == terminator[matched]) {
|
|
97
|
+
matched + 1
|
|
98
|
+
} else if (next.toByte() == terminator[0]) {
|
|
99
|
+
1
|
|
100
|
+
} else {
|
|
101
|
+
0
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
override fun close() {
|
|
107
|
+
serverSocket.close()
|
|
108
|
+
worker.join(1_000)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/react-native",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.7",
|
|
4
4
|
"description": "React Native OTA solution for self-hosted",
|
|
5
5
|
"main": "lib/commonjs/index",
|
|
6
6
|
"module": "lib/module/index",
|
|
@@ -120,14 +120,14 @@
|
|
|
120
120
|
"react-native": "0.79.1",
|
|
121
121
|
"react-native-builder-bob": "^0.40.10",
|
|
122
122
|
"typescript": "^6.0.2",
|
|
123
|
-
"hot-updater": "0.29.
|
|
123
|
+
"hot-updater": "0.29.7"
|
|
124
124
|
},
|
|
125
125
|
"dependencies": {
|
|
126
126
|
"use-sync-external-store": "1.5.0",
|
|
127
|
-
"@hot-updater/cli-tools": "0.29.
|
|
128
|
-
"@hot-updater/
|
|
129
|
-
"@hot-updater/
|
|
130
|
-
"@hot-updater/core": "0.29.
|
|
127
|
+
"@hot-updater/cli-tools": "0.29.7",
|
|
128
|
+
"@hot-updater/js": "0.29.7",
|
|
129
|
+
"@hot-updater/plugin-core": "0.29.7",
|
|
130
|
+
"@hot-updater/core": "0.29.7"
|
|
131
131
|
},
|
|
132
132
|
"scripts": {
|
|
133
133
|
"build": "bob build && tsc -p plugin/tsconfig.build.json",
|