@hot-updater/react-native 1.0.7 → 1.1.0
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.
- package/HotUpdater.podspec +14 -2
- package/Package.swift +24 -3
- package/android/build.gradle +2 -6
- package/android/hot-updater-fingerprint.gradle +135 -0
- package/android/src/main/cpp/HotUpdaterRecovery.cpp +291 -21
- package/android/src/main/java/com/hotupdater/ActivityStartGate.kt +176 -15
- package/android/src/main/java/com/hotupdater/ArtifactFileResolver.kt +20 -8
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +145 -31
- package/android/src/main/java/com/hotupdater/CohortService.kt +10 -2
- package/android/src/main/java/com/hotupdater/FileManagerService.kt +3 -1
- package/android/src/main/java/com/hotupdater/HotUpdaterConfig.kt +249 -21
- package/android/src/main/java/com/hotupdater/HotUpdaterException.kt +8 -4
- package/android/src/main/java/com/hotupdater/HotUpdaterHeldContent.kt +92 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +319 -113
- package/android/src/main/java/com/hotupdater/HotUpdaterInstance.kt +87 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterInstanceEnvironment.kt +323 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterMainThread.kt +46 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterProcess.kt +30 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryAttribution.kt +492 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryManager.kt +1541 -159
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoverySlots.kt +137 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterRecoveryState.kt +4 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterRuntimeInstance.kt +53 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterUpdateCheck.kt +149 -0
- package/android/src/main/java/com/hotupdater/InstallIdentityService.kt +12 -2
- package/android/src/main/java/com/hotupdater/NativeConfigUtils.kt +5 -6
- package/android/src/main/java/com/hotupdater/NativeUpdateCheck.kt +172 -0
- package/android/src/main/java/com/hotupdater/NativeUpdateChecker.kt +1007 -0
- package/android/src/main/java/com/hotupdater/NativeUpdateEventQueue.kt +111 -0
- package/android/src/main/java/com/hotupdater/OkHttpUpdateCheckTransport.kt +88 -0
- package/android/src/main/java/com/hotupdater/ReleaseCatalog.kt +491 -0
- package/android/src/main/java/com/hotupdater/ReleaseCatalogClient.kt +405 -0
- package/android/src/main/java/com/hotupdater/ReleaseSelection.kt +474 -0
- package/android/src/main/java/com/hotupdater/SignatureVerifier.kt +32 -10
- package/android/src/main/java/com/hotupdater/StrictJson.kt +392 -0
- package/android/src/main/java/com/hotupdater/VersionedPreferencesService.kt +24 -9
- package/android/src/main/java/com/hotupdater/WeakIdentitySet.kt +76 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/BitReader.java +314 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/BrotliError.java +47 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/BrotliInputStream.java +174 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/BrotliRuntimeException.java +21 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Context.java +58 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Decode.java +1574 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Decoder.java +72 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Dictionary.java +103 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/DictionaryData.java +82 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Huffman.java +143 -0
- package/{LICENSE → android/src/main/java/com/hotupdater/vendor/brotli/dec/LICENSE} +6 -20
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/README.md +39 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/State.java +100 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Transform.java +240 -0
- package/android/src/main/java/com/hotupdater/vendor/brotli/dec/Utils.java +163 -0
- package/android/src/main/resources/META-INF/LICENSE-brotli +19 -0
- package/android/src/newarch/HotUpdater.kt +53 -15
- package/android/src/newarch/HotUpdaterModule.kt +42 -17
- package/android/src/newarch/JavaScriptExceptionHooks.kt +45 -8
- package/android/src/oldarch/HotUpdater.kt +51 -12
- package/android/src/oldarch/HotUpdaterModule.kt +43 -17
- package/android/src/oldarch/HotUpdaterSpec.kt +2 -0
- package/android/src/oldarch/JavaScriptExceptionHooks.kt +25 -16
- package/ios/HotUpdater/Interface/HotUpdaterInterface.h +13 -0
- package/ios/HotUpdater/Internal/ArtifactFileResolver.swift +38 -7
- package/ios/HotUpdater/Internal/BuiltInAssetResolver.swift +10 -4
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +186 -48
- package/ios/HotUpdater/Internal/CohortService.swift +8 -0
- package/ios/HotUpdater/Internal/HotUpdater.mm +821 -71
- package/ios/HotUpdater/Internal/HotUpdaterAsync.swift +7 -0
- package/ios/HotUpdater/Internal/HotUpdaterConfig.swift +208 -25
- package/ios/HotUpdater/Internal/HotUpdaterFingerprintHash.m +10 -0
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +940 -261
- package/ios/HotUpdater/Internal/HotUpdaterInstanceEnvironment.swift +492 -0
- package/ios/HotUpdater/Internal/HotUpdaterInstanceRouter.swift +83 -0
- package/ios/HotUpdater/Internal/HotUpdaterInterface.swift +28 -1
- package/ios/HotUpdater/Internal/HotUpdaterLaunchRecovery.swift +1553 -0
- package/ios/HotUpdater/Internal/HotUpdaterNativeUpdates.swift +353 -0
- package/ios/HotUpdater/Internal/LaunchPreparationLock.swift +54 -0
- package/ios/HotUpdater/Internal/NativeUpdateCheck.swift +2051 -0
- package/ios/HotUpdater/Internal/ReleaseCatalog.swift +634 -0
- package/ios/HotUpdater/Internal/ReleaseCatalogCacheService.swift +1 -1
- package/ios/HotUpdater/Internal/ReleaseSelection.swift +482 -0
- package/ios/HotUpdater/Internal/SignatureVerifier.swift +27 -10
- package/ios/HotUpdater/Internal/StrictJson.swift +363 -0
- package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +4 -2
- package/ios/HotUpdater/Internal/UpdateFailure.swift +2 -0
- package/ios/HotUpdater/Package.swift +11 -0
- package/ios/HotUpdater/Public/HotUpdater/HotUpdater.h +4 -0
- package/ios/HotUpdater/Public/HotUpdater.h +21 -2
- package/ios/HotUpdater/Recovery/HotUpdaterRecoveryHooks.mm +66 -0
- package/ios/Plugins/HotUpdaterFingerprintPlugin/HotUpdaterFingerprintPlugin.swift +45 -0
- package/ios/scripts/generate-fingerprint-hash.zsh +85 -0
- package/lib/commonjs/checkForUpdate.js +3 -1
- package/lib/commonjs/checkForUpdate.js.map +1 -1
- package/lib/commonjs/index.js +9 -12
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/native.js +24 -79
- package/lib/commonjs/native.js.map +1 -1
- package/lib/commonjs/nativeUpdateEvents.js +66 -0
- package/lib/commonjs/nativeUpdateEvents.js.map +1 -0
- package/lib/commonjs/sdkVersion.js +1 -1
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -1
- package/lib/module/checkForUpdate.js +3 -1
- package/lib/module/checkForUpdate.js.map +1 -1
- package/lib/module/index.js +10 -13
- package/lib/module/index.js.map +1 -1
- package/lib/module/native.js +19 -73
- package/lib/module/native.js.map +1 -1
- package/lib/module/nativeUpdateEvents.js +61 -0
- package/lib/module/nativeUpdateEvents.js.map +1 -0
- package/lib/module/sdkVersion.js +1 -1
- package/lib/module/specs/NativeHotUpdater.js.map +1 -1
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +4 -14
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/native.d.ts +9 -46
- package/lib/typescript/commonjs/native.d.ts.map +1 -1
- package/lib/typescript/commonjs/nativeUpdateEvents.d.ts +12 -0
- package/lib/typescript/commonjs/nativeUpdateEvents.d.ts.map +1 -0
- package/lib/typescript/commonjs/sdkVersion.d.ts +1 -1
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts +19 -3
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -1
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +4 -14
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/native.d.ts +9 -46
- package/lib/typescript/module/native.d.ts.map +1 -1
- package/lib/typescript/module/nativeUpdateEvents.d.ts +12 -0
- package/lib/typescript/module/nativeUpdateEvents.d.ts.map +1 -0
- package/lib/typescript/module/sdkVersion.d.ts +1 -1
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +19 -3
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/checkForUpdate.ts +7 -1
- package/src/index.ts +9 -17
- package/src/native.ts +19 -91
- package/src/nativeUpdateEvents.ts +77 -0
- package/src/sdkVersion.ts +1 -1
- package/src/specs/NativeHotUpdater.ts +20 -3
- package/android/libs/README.md +0 -50
- package/android/libs/hot-updater-brotli-dec-1.2.0.jar +0 -0
- package/android/src/bridge/BridgeReactInstance.kt +0 -38
- package/android/src/bridgeless/BridgeReactInstance.kt +0 -24
- package/android/src/main/java/com/hotupdater/ReactIntegrationManagerBase.kt +0 -34
- package/android/src/newarch/ReactIntegrationManager.kt +0 -185
- package/android/src/oldarch/ReactIntegrationManager.kt +0 -120
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
package com.hotupdater
|
|
2
|
+
|
|
3
|
+
import org.json.JSONArray
|
|
4
|
+
import org.json.JSONObject
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Parses exactly one JSON value (RFC 8259) as `JSON.parse` does, into
|
|
8
|
+
* `org.json` values. `org.json` also accepts comments, single quotes, unquoted
|
|
9
|
+
* keys, trailing commas, and other non-JSON syntax, and Android's
|
|
10
|
+
* `JSONObject` rejects a number that overflows to infinity, which `JSON.parse`
|
|
11
|
+
* accepts, so update responses are parsed here instead. Iterative, so deep
|
|
12
|
+
* nesting cannot overflow the stack. A value nested deeper than
|
|
13
|
+
* [MAX_BUILD_DEPTH], far deeper than any field Hot Updater reads, is checked
|
|
14
|
+
* but not built, as on iOS: it parses as `JSONObject.NULL`, so a deep unknown
|
|
15
|
+
* field costs no memory beyond the text.
|
|
16
|
+
*/
|
|
17
|
+
internal object StrictJson {
|
|
18
|
+
/** The deepest nesting of arrays and objects that is built. */
|
|
19
|
+
const val MAX_BUILD_DEPTH = 128
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Stands for a number that overflows to infinity, which `JSONObject`
|
|
23
|
+
* cannot hold. It is neither a number, a string, nor null, so a known
|
|
24
|
+
* field that reads it fails as `Infinity` fails in JavaScript.
|
|
25
|
+
*/
|
|
26
|
+
object NonFiniteNumber
|
|
27
|
+
|
|
28
|
+
fun isValid(text: String): Boolean = Checker(text).check()
|
|
29
|
+
|
|
30
|
+
/** The value of [text], or null when [text] is not exactly one JSON value. */
|
|
31
|
+
fun parse(text: String): Any? {
|
|
32
|
+
if (!isValid(text)) return null
|
|
33
|
+
return Builder(text).build()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Builds the value of a text [Checker] accepted. */
|
|
37
|
+
private class Builder(
|
|
38
|
+
private val text: String,
|
|
39
|
+
) {
|
|
40
|
+
private var index = 0
|
|
41
|
+
|
|
42
|
+
private class Container(
|
|
43
|
+
val value: Any,
|
|
44
|
+
) {
|
|
45
|
+
var key = ""
|
|
46
|
+
|
|
47
|
+
fun add(item: Any) {
|
|
48
|
+
when (value) {
|
|
49
|
+
is JSONObject -> value.put(key, item)
|
|
50
|
+
is JSONArray -> value.put(item)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** [create] within the build depth; past it, a placeholder that keeps nothing. */
|
|
56
|
+
private fun buildable(
|
|
57
|
+
depth: Int,
|
|
58
|
+
create: () -> Any,
|
|
59
|
+
): Any = if (depth >= MAX_BUILD_DEPTH) JSONObject.NULL else create()
|
|
60
|
+
|
|
61
|
+
fun build(): Any {
|
|
62
|
+
val containers = ArrayDeque<Container>()
|
|
63
|
+
var expectValue = true
|
|
64
|
+
while (true) {
|
|
65
|
+
skipWhitespace()
|
|
66
|
+
if (expectValue) {
|
|
67
|
+
val value: Any =
|
|
68
|
+
when (text[index]) {
|
|
69
|
+
'{' -> {
|
|
70
|
+
index++
|
|
71
|
+
skipWhitespace()
|
|
72
|
+
if (text[index] == '}') {
|
|
73
|
+
index++
|
|
74
|
+
JSONObject()
|
|
75
|
+
} else {
|
|
76
|
+
containers.addLast(Container(buildable(containers.size) { JSONObject() }).apply { key = member() })
|
|
77
|
+
continue
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
'[' -> {
|
|
82
|
+
index++
|
|
83
|
+
skipWhitespace()
|
|
84
|
+
if (text[index] == ']') {
|
|
85
|
+
index++
|
|
86
|
+
JSONArray()
|
|
87
|
+
} else {
|
|
88
|
+
containers.addLast(Container(buildable(containers.size) { JSONArray() }))
|
|
89
|
+
continue
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
'"' -> {
|
|
94
|
+
string()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
't' -> {
|
|
98
|
+
literal("true", true)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
'f' -> {
|
|
102
|
+
literal("false", false)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
'n' -> {
|
|
106
|
+
literal("null", JSONObject.NULL)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
else -> {
|
|
110
|
+
number()
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
expectValue = false
|
|
114
|
+
val container = containers.lastOrNull() ?: return value
|
|
115
|
+
container.add(value)
|
|
116
|
+
} else {
|
|
117
|
+
val container = containers.last()
|
|
118
|
+
when (text[index++]) {
|
|
119
|
+
',' -> {
|
|
120
|
+
if (container.value is JSONObject) {
|
|
121
|
+
skipWhitespace()
|
|
122
|
+
container.key = member()
|
|
123
|
+
}
|
|
124
|
+
expectValue = true
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
else -> {
|
|
128
|
+
// The closing bracket the checker matched.
|
|
129
|
+
containers.removeLast()
|
|
130
|
+
val parent = containers.lastOrNull() ?: return container.value
|
|
131
|
+
parent.add(container.value)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private fun member(): String {
|
|
139
|
+
val key = string()
|
|
140
|
+
skipWhitespace()
|
|
141
|
+
index++ // ':'
|
|
142
|
+
return key
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private fun string(): String {
|
|
146
|
+
index++
|
|
147
|
+
val builder = StringBuilder()
|
|
148
|
+
while (true) {
|
|
149
|
+
val char = text[index++]
|
|
150
|
+
when (char) {
|
|
151
|
+
'"' -> {
|
|
152
|
+
return builder.toString()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
'\\' -> {
|
|
156
|
+
when (val escape = text[index++]) {
|
|
157
|
+
'b' -> {
|
|
158
|
+
builder.append('\b')
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
'f' -> {
|
|
162
|
+
builder.append('\u000C')
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
'n' -> {
|
|
166
|
+
builder.append('\n')
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
'r' -> {
|
|
170
|
+
builder.append('\r')
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
't' -> {
|
|
174
|
+
builder.append('\t')
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
'u' -> {
|
|
178
|
+
builder.append(text.substring(index, index + 4).toInt(16).toChar())
|
|
179
|
+
index += 4
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
else -> {
|
|
183
|
+
builder.append(escape)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
else -> {
|
|
189
|
+
builder.append(char)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
private fun number(): Any {
|
|
196
|
+
val start = index
|
|
197
|
+
while (index < text.length && text[index] in "+-0123456789.eE") index++
|
|
198
|
+
val value = text.substring(start, index).toDouble()
|
|
199
|
+
return if (value.isInfinite()) NonFiniteNumber else value
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
private fun literal(
|
|
203
|
+
word: String,
|
|
204
|
+
value: Any,
|
|
205
|
+
): Any {
|
|
206
|
+
index += word.length
|
|
207
|
+
return value
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private fun skipWhitespace() {
|
|
211
|
+
while (index < text.length && text[index].let { it == ' ' || it == '\t' || it == '\n' || it == '\r' }) {
|
|
212
|
+
index++
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private class Checker(
|
|
218
|
+
private val text: String,
|
|
219
|
+
) {
|
|
220
|
+
private var index = 0
|
|
221
|
+
private val containers = ArrayDeque<Char>()
|
|
222
|
+
|
|
223
|
+
fun check(): Boolean {
|
|
224
|
+
var expectValue = true
|
|
225
|
+
while (true) {
|
|
226
|
+
skipWhitespace()
|
|
227
|
+
if (expectValue) {
|
|
228
|
+
when (peek()) {
|
|
229
|
+
'{' -> {
|
|
230
|
+
index++
|
|
231
|
+
skipWhitespace()
|
|
232
|
+
if (peek() == '}') {
|
|
233
|
+
index++
|
|
234
|
+
expectValue = false
|
|
235
|
+
} else {
|
|
236
|
+
containers.addLast('{')
|
|
237
|
+
if (!member()) return false
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
'[' -> {
|
|
242
|
+
index++
|
|
243
|
+
skipWhitespace()
|
|
244
|
+
if (peek() == ']') {
|
|
245
|
+
index++
|
|
246
|
+
expectValue = false
|
|
247
|
+
} else {
|
|
248
|
+
containers.addLast('[')
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
'"' -> {
|
|
253
|
+
if (!string()) return false
|
|
254
|
+
expectValue = false
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
't' -> {
|
|
258
|
+
if (literal("true")) expectValue = false else return false
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
'f' -> {
|
|
262
|
+
if (literal("false")) expectValue = false else return false
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
'n' -> {
|
|
266
|
+
if (literal("null")) expectValue = false else return false
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
else -> {
|
|
270
|
+
if (number()) expectValue = false else return false
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
val container = containers.lastOrNull() ?: return index == text.length
|
|
275
|
+
when (peek()) {
|
|
276
|
+
',' -> {
|
|
277
|
+
index++
|
|
278
|
+
if (container == '{') {
|
|
279
|
+
skipWhitespace()
|
|
280
|
+
if (!member()) return false
|
|
281
|
+
}
|
|
282
|
+
expectValue = true
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
'}' -> {
|
|
286
|
+
if (container == '{') {
|
|
287
|
+
index++
|
|
288
|
+
containers.removeLast()
|
|
289
|
+
} else {
|
|
290
|
+
return false
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
']' -> {
|
|
295
|
+
if (container == '[') {
|
|
296
|
+
index++
|
|
297
|
+
containers.removeLast()
|
|
298
|
+
} else {
|
|
299
|
+
return false
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
else -> {
|
|
304
|
+
return false
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** An object member's key and colon; its value comes next. */
|
|
312
|
+
private fun member(): Boolean {
|
|
313
|
+
if (peek() != '"' || !string()) return false
|
|
314
|
+
skipWhitespace()
|
|
315
|
+
if (peek() != ':') return false
|
|
316
|
+
index++
|
|
317
|
+
return true
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private fun string(): Boolean {
|
|
321
|
+
index++
|
|
322
|
+
while (index < text.length) {
|
|
323
|
+
val char = text[index++]
|
|
324
|
+
when {
|
|
325
|
+
char == '"' -> {
|
|
326
|
+
return true
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
char < ' ' -> {
|
|
330
|
+
return false
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
char == '\\' -> {
|
|
334
|
+
if (index >= text.length) return false
|
|
335
|
+
when (text[index++]) {
|
|
336
|
+
'"', '\\', '/', 'b', 'f', 'n', 'r', 't' -> {
|
|
337
|
+
Unit
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
'u' -> {
|
|
341
|
+
if (index + 4 > text.length) return false
|
|
342
|
+
repeat(4) { if (!isHexDigit(text[index++])) return false }
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
else -> {
|
|
346
|
+
return false
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return false
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private fun number(): Boolean {
|
|
356
|
+
if (peek() == '-') index++
|
|
357
|
+
when {
|
|
358
|
+
peek() == '0' -> index++
|
|
359
|
+
peek() in '1'..'9' -> while (peek() in '0'..'9') index++
|
|
360
|
+
else -> return false
|
|
361
|
+
}
|
|
362
|
+
if (peek() == '.') {
|
|
363
|
+
index++
|
|
364
|
+
if (peek() !in '0'..'9') return false
|
|
365
|
+
while (peek() in '0'..'9') index++
|
|
366
|
+
}
|
|
367
|
+
if (peek() == 'e' || peek() == 'E') {
|
|
368
|
+
index++
|
|
369
|
+
if (peek() == '+' || peek() == '-') index++
|
|
370
|
+
if (peek() !in '0'..'9') return false
|
|
371
|
+
while (peek() in '0'..'9') index++
|
|
372
|
+
}
|
|
373
|
+
return true
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
private fun literal(word: String): Boolean {
|
|
377
|
+
if (!text.startsWith(word, index)) return false
|
|
378
|
+
index += word.length
|
|
379
|
+
return true
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
private fun skipWhitespace() {
|
|
383
|
+
while (index < text.length && text[index].let { it == ' ' || it == '\t' || it == '\n' || it == '\r' }) {
|
|
384
|
+
index++
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
private fun peek(): Char = if (index < text.length) text[index] else '\u0000'
|
|
389
|
+
|
|
390
|
+
private fun isHexDigit(char: Char): Boolean = char in '0'..'9' || char in 'a'..'f' || char in 'A'..'F'
|
|
391
|
+
}
|
|
392
|
+
}
|
|
@@ -33,19 +33,34 @@ interface PreferencesService {
|
|
|
33
33
|
class VersionedPreferencesService(
|
|
34
34
|
private val context: Context,
|
|
35
35
|
private val isolationKey: String,
|
|
36
|
+
// The family of isolation keys this service replaces: main's, or one named instance's.
|
|
37
|
+
private val familyPrefix: String = "HotUpdaterPrefs_",
|
|
36
38
|
) : PreferencesService {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
companion object {
|
|
40
|
+
/**
|
|
41
|
+
* Deletes the preferences of every other isolation key in [familyPrefix]'s
|
|
42
|
+
* family, the app version or channel this instance left behind. Files of
|
|
43
|
+
* other families belong to other instances and stay.
|
|
44
|
+
*/
|
|
45
|
+
internal fun deleteStalePreferences(
|
|
46
|
+
sharedPrefsDir: File,
|
|
47
|
+
familyPrefix: String,
|
|
48
|
+
isolationKey: String,
|
|
49
|
+
) {
|
|
50
|
+
if (sharedPrefsDir.exists() && sharedPrefsDir.isDirectory) {
|
|
51
|
+
sharedPrefsDir.listFiles()?.forEach { file ->
|
|
52
|
+
if (file.name.startsWith(familyPrefix) && file.name != "$isolationKey.xml") {
|
|
53
|
+
file.delete()
|
|
54
|
+
}
|
|
46
55
|
}
|
|
47
56
|
}
|
|
48
57
|
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
private val prefs: SharedPreferences
|
|
61
|
+
|
|
62
|
+
init {
|
|
63
|
+
deleteStalePreferences(File(context.applicationInfo.dataDir, "shared_prefs"), familyPrefix, isolationKey)
|
|
49
64
|
|
|
50
65
|
prefs = context.getSharedPreferences(isolationKey, Context.MODE_PRIVATE)
|
|
51
66
|
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
package com.hotupdater
|
|
2
|
+
|
|
3
|
+
import java.lang.ref.ReferenceQueue
|
|
4
|
+
import java.lang.ref.WeakReference
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A set of objects by identity that doesn't keep them alive. An identity
|
|
8
|
+
* hash code alone can't stand for an object: once it is collected, a new
|
|
9
|
+
* object may get the same code, and would be taken for the old one. Thread
|
|
10
|
+
* safe.
|
|
11
|
+
*/
|
|
12
|
+
internal class WeakIdentitySet<T : Any> {
|
|
13
|
+
private class Entry<T : Any>(
|
|
14
|
+
value: T,
|
|
15
|
+
queue: ReferenceQueue<T>?,
|
|
16
|
+
) : WeakReference<T>(value, queue) {
|
|
17
|
+
private val hash = System.identityHashCode(value)
|
|
18
|
+
|
|
19
|
+
override fun hashCode(): Int = hash
|
|
20
|
+
|
|
21
|
+
override fun equals(other: Any?): Boolean {
|
|
22
|
+
if (this === other) return true
|
|
23
|
+
if (other !is Entry<*>) return false
|
|
24
|
+
val value = get() ?: return false
|
|
25
|
+
return value === other.get()
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private val collected = ReferenceQueue<T>()
|
|
30
|
+
private val entries = HashSet<Entry<T>>()
|
|
31
|
+
|
|
32
|
+
/** True when [value] was not in the set. */
|
|
33
|
+
@Synchronized
|
|
34
|
+
fun add(value: T): Boolean {
|
|
35
|
+
dropCollected()
|
|
36
|
+
return entries.add(Entry(value, collected))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@Synchronized
|
|
40
|
+
fun remove(value: T): Boolean {
|
|
41
|
+
dropCollected()
|
|
42
|
+
return entries.remove(Entry(value, null))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@Synchronized
|
|
46
|
+
operator fun contains(value: T): Boolean {
|
|
47
|
+
dropCollected()
|
|
48
|
+
return Entry(value, null) in entries
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@Synchronized
|
|
52
|
+
fun isEmpty(): Boolean {
|
|
53
|
+
dropCollected()
|
|
54
|
+
return entries.isEmpty()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
val size: Int
|
|
58
|
+
@Synchronized get() {
|
|
59
|
+
dropCollected()
|
|
60
|
+
return entries.size
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Synchronized
|
|
64
|
+
fun clear() {
|
|
65
|
+
entries.clear()
|
|
66
|
+
while (collected.poll() != null) Unit
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// A collected entry equals only itself, which removes it.
|
|
70
|
+
private fun dropCollected() {
|
|
71
|
+
while (true) {
|
|
72
|
+
val entry = collected.poll() ?: return
|
|
73
|
+
entries.remove(entry)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|