@hot-updater/react-native 0.17.0 → 0.18.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 +7 -11
- package/android/{generated/java/com/hotupdater → app/build/generated/source/codegen/java/com/facebook/fbreact/specs}/NativeHotUpdaterSpec.java +3 -2
- package/android/app/build/generated/source/codegen/jni/HotUpdater-generated.cpp +68 -0
- package/android/app/build/generated/source/codegen/jni/HotUpdater.h +31 -0
- package/android/{generated → app/build/generated/source/codegen}/jni/HotUpdaterSpec-generated.cpp +2 -2
- package/android/{generated/jni/react/renderer/components/HotUpdaterSpec/HotUpdaterSpecJSI-generated.cpp → app/build/generated/source/codegen/jni/react/renderer/components/HotUpdater/HotUpdaterJSI-generated.cpp} +3 -4
- package/{ios/generated/HotUpdaterSpecJSI.h → android/app/build/generated/source/codegen/jni/react/renderer/components/HotUpdater/HotUpdaterJSI.h} +53 -6
- package/{ios/generated → android/app/build/generated/source/codegen/jni/react/renderer/components/HotUpdaterSpec}/HotUpdaterSpecJSI-generated.cpp +2 -3
- package/android/{generated → app/build/generated/source/codegen}/jni/react/renderer/components/HotUpdaterSpec/HotUpdaterSpecJSI.h +59 -8
- package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +200 -0
- package/android/src/main/java/com/hotupdater/FileManagerService.kt +104 -0
- package/android/src/main/java/com/hotupdater/HotUpdater.kt +62 -305
- package/android/src/main/java/com/hotupdater/HotUpdaterFactory.kt +49 -0
- package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +176 -0
- package/android/src/main/java/com/hotupdater/HttpDownloadService.kt +98 -0
- package/android/src/main/java/com/hotupdater/VersionedPreferencesService.kt +69 -0
- package/android/src/main/java/com/hotupdater/ZipFileUnzipService.kt +52 -0
- package/android/src/newarch/HotUpdaterModule.kt +31 -34
- package/android/src/oldarch/HotUpdaterModule.kt +32 -34
- package/android/src/oldarch/HotUpdaterSpec.kt +2 -9
- package/ios/HotUpdater/Internal/BundleFileStorageService.swift +593 -0
- package/ios/HotUpdater/Internal/FileManagerService.swift +97 -0
- package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +8 -0
- package/ios/HotUpdater/Internal/HotUpdater.mm +241 -0
- package/ios/HotUpdater/Internal/HotUpdaterFactory.swift +24 -0
- package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +143 -0
- package/ios/HotUpdater/Internal/NotificationExtension.swift +6 -0
- package/ios/HotUpdater/Internal/SSZipArchiveUnzipService.swift +25 -0
- package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +101 -0
- package/ios/HotUpdater/Internal/VersionedPreferencesService.swift +82 -0
- package/ios/HotUpdater/Package.resolved +15 -0
- package/ios/HotUpdater/Public/HotUpdater.h +29 -0
- package/lib/commonjs/checkForUpdate.js +70 -0
- package/lib/commonjs/checkForUpdate.js.map +1 -0
- package/lib/commonjs/error.js +14 -0
- package/lib/commonjs/error.js.map +1 -0
- package/lib/commonjs/fetchUpdateInfo.js +74 -0
- package/lib/commonjs/fetchUpdateInfo.js.map +1 -0
- package/lib/commonjs/hooks/useEventCallback.js +17 -0
- package/lib/commonjs/hooks/useEventCallback.js.map +1 -0
- package/lib/commonjs/index.js +234 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/native.js +132 -0
- package/lib/commonjs/native.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/runUpdateProcess.js +69 -0
- package/lib/commonjs/runUpdateProcess.js.map +1 -0
- package/lib/commonjs/specs/NativeHotUpdater.js +9 -0
- package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -0
- package/lib/commonjs/store.js +48 -0
- package/lib/commonjs/store.js.map +1 -0
- package/lib/commonjs/wrap.js +98 -0
- package/lib/commonjs/wrap.js.map +1 -0
- package/lib/module/checkForUpdate.js +64 -0
- package/lib/module/checkForUpdate.js.map +1 -0
- package/lib/module/error.js +9 -0
- package/lib/module/error.js.map +1 -0
- package/lib/module/fetchUpdateInfo.js +69 -0
- package/lib/module/fetchUpdateInfo.js.map +1 -0
- package/lib/module/hooks/useEventCallback.js +13 -0
- package/lib/module/hooks/useEventCallback.js.map +1 -0
- package/lib/module/index.js +211 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/native.js +119 -0
- package/lib/module/native.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/runUpdateProcess.js +64 -0
- package/lib/module/runUpdateProcess.js.map +1 -0
- package/lib/module/specs/NativeHotUpdater.js +5 -0
- package/lib/module/specs/NativeHotUpdater.js.map +1 -0
- package/lib/module/store.js +42 -0
- package/lib/module/store.js.map +1 -0
- package/lib/module/wrap.js +94 -0
- package/lib/module/wrap.js.map +1 -0
- package/lib/typescript/commonjs/checkForUpdate.d.ts +22 -0
- package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -0
- package/{dist → lib/typescript/commonjs}/error.d.ts +1 -0
- package/lib/typescript/commonjs/error.d.ts.map +1 -0
- package/lib/typescript/commonjs/fetchUpdateInfo.d.ts +4 -0
- package/lib/typescript/commonjs/fetchUpdateInfo.d.ts.map +1 -0
- package/{dist → lib/typescript/commonjs}/hooks/useEventCallback.d.ts +1 -0
- package/lib/typescript/commonjs/hooks/useEventCallback.d.ts.map +1 -0
- package/{dist → lib/typescript/commonjs}/index.d.ts +38 -12
- package/lib/typescript/commonjs/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/native.d.ts +64 -0
- package/lib/typescript/commonjs/native.d.ts.map +1 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/{dist → lib/typescript/commonjs}/runUpdateProcess.d.ts +1 -0
- package/lib/typescript/commonjs/runUpdateProcess.d.ts.map +1 -0
- package/{dist → lib/typescript/commonjs}/specs/NativeHotUpdater.d.ts +8 -9
- package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -0
- package/{dist → lib/typescript/commonjs}/store.d.ts +1 -0
- package/lib/typescript/commonjs/store.d.ts.map +1 -0
- package/{dist → lib/typescript/commonjs}/wrap.d.ts +3 -2
- package/lib/typescript/commonjs/wrap.d.ts.map +1 -0
- package/lib/typescript/module/checkForUpdate.d.ts +22 -0
- package/lib/typescript/module/checkForUpdate.d.ts.map +1 -0
- package/lib/typescript/module/error.d.ts +4 -0
- package/lib/typescript/module/error.d.ts.map +1 -0
- package/lib/typescript/module/fetchUpdateInfo.d.ts +4 -0
- package/lib/typescript/module/fetchUpdateInfo.d.ts.map +1 -0
- package/lib/typescript/module/hooks/useEventCallback.d.ts +5 -0
- package/lib/typescript/module/hooks/useEventCallback.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +202 -0
- package/lib/typescript/module/index.d.ts.map +1 -0
- package/lib/typescript/module/native.d.ts +64 -0
- package/lib/typescript/module/native.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/runUpdateProcess.d.ts +49 -0
- package/lib/typescript/module/runUpdateProcess.d.ts.map +1 -0
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts +19 -0
- package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -0
- package/lib/typescript/module/store.d.ts +11 -0
- package/lib/typescript/module/store.d.ts.map +1 -0
- package/lib/typescript/module/wrap.d.ts +51 -0
- package/lib/typescript/module/wrap.d.ts.map +1 -0
- package/package.json +59 -30
- package/src/checkForUpdate.ts +59 -9
- package/src/fetchUpdateInfo.ts +40 -12
- package/src/index.ts +37 -11
- package/src/native.ts +87 -41
- package/src/runUpdateProcess.ts +2 -2
- package/src/specs/NativeHotUpdater.ts +8 -10
- package/src/wrap.tsx +9 -13
- package/android/src/main/java/com/hotupdater/HotUpdaterPrefs.kt +0 -42
- package/dist/checkForUpdate.d.ts +0 -12
- package/dist/fetchUpdateInfo.d.ts +0 -3
- package/dist/index.js +0 -341
- package/dist/index.mjs +0 -301
- package/dist/native.d.ts +0 -41
- package/ios/HotUpdater/HotUpdater.h +0 -15
- package/ios/HotUpdater/HotUpdater.mm +0 -468
- package/ios/HotUpdater/HotUpdater.modulemap +0 -6
- package/ios/HotUpdater/HotUpdaterPrefs.h +0 -9
- package/ios/HotUpdater/HotUpdaterPrefs.mm +0 -45
- package/ios/generated/HotUpdaterSpec/HotUpdaterSpec-generated.mm +0 -81
- package/ios/generated/HotUpdaterSpec/HotUpdaterSpec.h +0 -112
- package/react-native.config.js +0 -12
- package/src/global.d.ts +0 -3
- /package/android/{generated → app/build/generated/source/codegen}/jni/CMakeLists.txt +0 -0
- /package/android/{generated → app/build/generated/source/codegen}/jni/HotUpdaterSpec.h +0 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
package com.hotupdater
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import java.io.File
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Interface for file system operations
|
|
8
|
+
*/
|
|
9
|
+
interface FileSystemService {
|
|
10
|
+
/**
|
|
11
|
+
* Checks if a file exists at the given path
|
|
12
|
+
*/
|
|
13
|
+
fun fileExists(path: String): Boolean
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates directory at the given path, including any necessary parent directories
|
|
17
|
+
*/
|
|
18
|
+
fun createDirectory(path: String): Boolean
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Removes a file or directory at the given path
|
|
22
|
+
*/
|
|
23
|
+
fun removeItem(path: String): Boolean
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Moves a file or directory from source path to destination path
|
|
27
|
+
*/
|
|
28
|
+
fun moveItem(
|
|
29
|
+
sourcePath: String,
|
|
30
|
+
destinationPath: String,
|
|
31
|
+
): Boolean
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Copies a file or directory from source path to destination path
|
|
35
|
+
*/
|
|
36
|
+
fun copyItem(
|
|
37
|
+
sourcePath: String,
|
|
38
|
+
destinationPath: String,
|
|
39
|
+
): Boolean
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Lists the contents of a directory
|
|
43
|
+
*/
|
|
44
|
+
fun contentsOfDirectory(path: String): List<String>
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Gets the external files directory for the application
|
|
48
|
+
*/
|
|
49
|
+
fun getExternalFilesDir(): File?
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Implementation of FileSystemService using standard File API
|
|
54
|
+
*/
|
|
55
|
+
class FileManagerService(
|
|
56
|
+
private val context: Context,
|
|
57
|
+
) : FileSystemService {
|
|
58
|
+
override fun fileExists(path: String): Boolean = File(path).exists()
|
|
59
|
+
|
|
60
|
+
override fun createDirectory(path: String): Boolean = File(path).mkdirs()
|
|
61
|
+
|
|
62
|
+
override fun removeItem(path: String): Boolean = File(path).deleteRecursively()
|
|
63
|
+
|
|
64
|
+
override fun moveItem(
|
|
65
|
+
sourcePath: String,
|
|
66
|
+
destinationPath: String,
|
|
67
|
+
): Boolean {
|
|
68
|
+
val source = File(sourcePath)
|
|
69
|
+
val destination = File(destinationPath)
|
|
70
|
+
|
|
71
|
+
return try {
|
|
72
|
+
if (destination.exists()) {
|
|
73
|
+
destination.deleteRecursively()
|
|
74
|
+
}
|
|
75
|
+
source.renameTo(destination)
|
|
76
|
+
} catch (e: Exception) {
|
|
77
|
+
false
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
override fun copyItem(
|
|
82
|
+
sourcePath: String,
|
|
83
|
+
destinationPath: String,
|
|
84
|
+
): Boolean {
|
|
85
|
+
val source = File(sourcePath)
|
|
86
|
+
val destination = File(destinationPath)
|
|
87
|
+
|
|
88
|
+
return try {
|
|
89
|
+
if (destination.exists()) {
|
|
90
|
+
destination.deleteRecursively()
|
|
91
|
+
}
|
|
92
|
+
source.copyRecursively(target = destination, overwrite = true)
|
|
93
|
+
} catch (e: Exception) {
|
|
94
|
+
false
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
override fun contentsOfDirectory(path: String): List<String> {
|
|
99
|
+
val directory = File(path)
|
|
100
|
+
return directory.listFiles()?.map { it.name } ?: listOf()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
override fun getExternalFilesDir(): File? = context.getExternalFilesDir(null)
|
|
104
|
+
}
|
|
@@ -2,23 +2,16 @@ package com.hotupdater
|
|
|
2
2
|
|
|
3
3
|
import android.app.Activity
|
|
4
4
|
import android.content.Context
|
|
5
|
-
import android.os.Handler
|
|
6
|
-
import android.os.Looper
|
|
7
|
-
import android.util.Log
|
|
8
5
|
import android.view.View
|
|
9
|
-
import com.facebook.react.ReactApplication
|
|
10
6
|
import com.facebook.react.ReactPackage
|
|
11
7
|
import com.facebook.react.bridge.NativeModule
|
|
12
8
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
13
9
|
import com.facebook.react.uimanager.ReactShadowNode
|
|
14
10
|
import com.facebook.react.uimanager.ViewManager
|
|
15
|
-
import kotlinx.coroutines.Dispatchers
|
|
16
|
-
import kotlinx.coroutines.withContext
|
|
17
|
-
import java.io.File
|
|
18
|
-
import java.net.HttpURLConnection
|
|
19
|
-
import java.net.URL
|
|
20
|
-
import java.util.zip.ZipFile
|
|
21
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Main React Native package for HotUpdater
|
|
14
|
+
*/
|
|
22
15
|
class HotUpdater : ReactPackage {
|
|
23
16
|
override fun createViewManagers(context: ReactApplicationContext): MutableList<ViewManager<View, ReactShadowNode<*>>> = mutableListOf()
|
|
24
17
|
|
|
@@ -26,309 +19,73 @@ class HotUpdater : ReactPackage {
|
|
|
26
19
|
listOf(HotUpdaterModule(context)).toMutableList()
|
|
27
20
|
|
|
28
21
|
companion object {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Gets the app version
|
|
24
|
+
* @param context Application context
|
|
25
|
+
* @return App version name or null if not available
|
|
26
|
+
*/
|
|
27
|
+
fun getAppVersion(context: Context): String? = HotUpdaterFactory.getInstance(context).getAppVersion()
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Generates a bundle ID based on build timestamp
|
|
31
|
+
* @param context Application context
|
|
32
|
+
* @return The minimum bundle ID string
|
|
33
|
+
*/
|
|
34
|
+
fun getMinBundleId(context: Context): String = HotUpdaterFactory.getInstance(context).getMinBundleId()
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Gets the current update channel
|
|
38
|
+
* @param context Application context
|
|
39
|
+
* @return The channel name or null if not set
|
|
40
|
+
*/
|
|
41
|
+
fun getChannel(context: Context): String? = HotUpdaterFactory.getInstance(context).getChannel()
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Gets the path to the bundle file
|
|
45
|
+
* @param context Application context
|
|
46
|
+
* @return The path to the bundle file
|
|
47
|
+
*/
|
|
48
|
+
fun getJSBundleFile(context: Context): String = HotUpdaterFactory.getInstance(context).getJSBundleFile()
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Updates the bundle from the specified URL
|
|
52
|
+
* @param context Application context
|
|
53
|
+
* @param bundleId ID of the bundle to update
|
|
54
|
+
* @param fileUrl URL of the bundle file to download (or null to reset)
|
|
55
|
+
* @param progressCallback Callback for download progress updates
|
|
56
|
+
* @return true if the update was successful
|
|
57
|
+
*/
|
|
58
|
+
suspend fun updateBundle(
|
|
53
59
|
context: Context,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
updaterPrefs.setItem("HotUpdaterBundleURL", bundleURL)
|
|
58
|
-
|
|
59
|
-
if (bundleURL == null) {
|
|
60
|
-
return
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
val reactIntegrationManager = ReactIntegrationManager(context)
|
|
64
|
-
val activity: Activity? = getCurrentActivity(context)
|
|
65
|
-
val reactApplication: ReactApplication =
|
|
66
|
-
reactIntegrationManager.getReactApplication(activity?.application)
|
|
67
|
-
val newBundleURL = getJSBundleFile(context)
|
|
68
|
-
reactIntegrationManager.setJSBundle(reactApplication, newBundleURL)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
private fun extractZipFileAtPath(
|
|
72
|
-
filePath: String,
|
|
73
|
-
destinationPath: String,
|
|
60
|
+
bundleId: String,
|
|
61
|
+
fileUrl: String?,
|
|
62
|
+
progressCallback: (Double) -> Unit,
|
|
74
63
|
): Boolean =
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
true
|
|
90
|
-
} catch (e: Exception) {
|
|
91
|
-
Log.d("HotUpdater", "Failed to unzip file: ${e.message}")
|
|
92
|
-
false
|
|
93
|
-
}
|
|
64
|
+
HotUpdaterFactory.getInstance(context).updateBundle(
|
|
65
|
+
bundleId,
|
|
66
|
+
fileUrl,
|
|
67
|
+
progressCallback,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Reloads the React Native application
|
|
72
|
+
* @param context Application context
|
|
73
|
+
*/
|
|
74
|
+
fun reload(context: Context) {
|
|
75
|
+
val currentActivity = getCurrentActivity(context)
|
|
76
|
+
HotUpdaterFactory.getInstance(context).reload(currentActivity)
|
|
77
|
+
}
|
|
94
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Gets the current activity from ReactApplicationContext
|
|
81
|
+
* @param context Context that might be a ReactApplicationContext
|
|
82
|
+
* @return The current activity or null
|
|
83
|
+
*/
|
|
95
84
|
private fun getCurrentActivity(context: Context): Activity? =
|
|
96
85
|
if (context is ReactApplicationContext) {
|
|
97
86
|
context.currentActivity
|
|
98
87
|
} else {
|
|
99
88
|
null
|
|
100
89
|
}
|
|
101
|
-
|
|
102
|
-
fun reload(context: Context) {
|
|
103
|
-
val reactIntegrationManager = ReactIntegrationManager(context)
|
|
104
|
-
val activity: Activity? = getCurrentActivity(context)
|
|
105
|
-
val reactApplication: ReactApplication =
|
|
106
|
-
reactIntegrationManager.getReactApplication(activity?.application)
|
|
107
|
-
val bundleURL = getJSBundleFile(context)
|
|
108
|
-
reactIntegrationManager.setJSBundle(reactApplication, bundleURL)
|
|
109
|
-
Handler(Looper.getMainLooper()).post {
|
|
110
|
-
reactIntegrationManager.reload(reactApplication)
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
fun getJSBundleFile(context: Context): String {
|
|
115
|
-
val updaterPrefs = getPrefs(context)
|
|
116
|
-
val urlString = updaterPrefs.getItem("HotUpdaterBundleURL")
|
|
117
|
-
if (urlString.isNullOrEmpty()) {
|
|
118
|
-
return "assets://index.android.bundle"
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
val file = File(urlString)
|
|
122
|
-
if (!file.exists()) {
|
|
123
|
-
updaterPrefs.setItem("HotUpdaterBundleURL", null)
|
|
124
|
-
return "assets://index.android.bundle"
|
|
125
|
-
}
|
|
126
|
-
return urlString
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
fun setChannel(
|
|
130
|
-
context: Context,
|
|
131
|
-
channel: String,
|
|
132
|
-
) {
|
|
133
|
-
val updaterPrefs = getPrefs(context)
|
|
134
|
-
updaterPrefs.setItem("HotUpdaterChannel", channel)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
fun getChannel(context: Context): String? {
|
|
138
|
-
val updaterPrefs = getPrefs(context)
|
|
139
|
-
return updaterPrefs.getItem("HotUpdaterChannel")
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
suspend fun updateBundle(
|
|
143
|
-
context: Context,
|
|
144
|
-
bundleId: String,
|
|
145
|
-
zipUrl: String?,
|
|
146
|
-
progressCallback: ((Double) -> Unit),
|
|
147
|
-
): Boolean {
|
|
148
|
-
Log.d("HotUpdater", "updateBundle bundleId $bundleId zipUrl $zipUrl")
|
|
149
|
-
if (zipUrl.isNullOrEmpty()) {
|
|
150
|
-
setBundleURL(context, null)
|
|
151
|
-
return true
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
val baseDir = context.getExternalFilesDir(null)
|
|
155
|
-
val bundleStoreDir = File(baseDir, "bundle-store")
|
|
156
|
-
if (!bundleStoreDir.exists()) {
|
|
157
|
-
bundleStoreDir.mkdirs()
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
val finalBundleDir = File(bundleStoreDir, bundleId)
|
|
161
|
-
if (finalBundleDir.exists()) {
|
|
162
|
-
Log.d("HotUpdater", "Bundle for bundleId $bundleId already exists. Using cached bundle.")
|
|
163
|
-
val existingIndexFile = finalBundleDir.walk().find { it.name == "index.android.bundle" }
|
|
164
|
-
if (existingIndexFile != null) {
|
|
165
|
-
finalBundleDir.setLastModified(System.currentTimeMillis())
|
|
166
|
-
setBundleURL(context, existingIndexFile.absolutePath)
|
|
167
|
-
cleanupOldBundles(bundleStoreDir)
|
|
168
|
-
return true
|
|
169
|
-
} else {
|
|
170
|
-
finalBundleDir.deleteRecursively()
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
val tempDir = File(baseDir, "bundle-temp")
|
|
175
|
-
if (tempDir.exists()) {
|
|
176
|
-
tempDir.deleteRecursively()
|
|
177
|
-
}
|
|
178
|
-
tempDir.mkdirs()
|
|
179
|
-
|
|
180
|
-
val tempZipFile = File(tempDir, "bundle.zip")
|
|
181
|
-
val extractedDir = File(tempDir, "extracted")
|
|
182
|
-
extractedDir.mkdirs()
|
|
183
|
-
|
|
184
|
-
val isSuccess =
|
|
185
|
-
withContext(Dispatchers.IO) {
|
|
186
|
-
val downloadUrl = URL(zipUrl)
|
|
187
|
-
val conn =
|
|
188
|
-
try {
|
|
189
|
-
downloadUrl.openConnection() as HttpURLConnection
|
|
190
|
-
} catch (e: Exception) {
|
|
191
|
-
Log.d("HotUpdater", "Failed to open connection: ${e.message}")
|
|
192
|
-
tempDir.deleteRecursively()
|
|
193
|
-
return@withContext false
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
try {
|
|
197
|
-
conn.connect()
|
|
198
|
-
val totalSize = conn.contentLength
|
|
199
|
-
if (totalSize <= 0) {
|
|
200
|
-
Log.d("HotUpdater", "Invalid content length: $totalSize")
|
|
201
|
-
tempDir.deleteRecursively()
|
|
202
|
-
return@withContext false
|
|
203
|
-
}
|
|
204
|
-
conn.inputStream.use { input ->
|
|
205
|
-
tempZipFile.outputStream().use { output ->
|
|
206
|
-
val buffer = ByteArray(8 * 1024)
|
|
207
|
-
var bytesRead: Int
|
|
208
|
-
var totalRead = 0L
|
|
209
|
-
var lastProgressTime = System.currentTimeMillis()
|
|
210
|
-
|
|
211
|
-
while (input.read(buffer).also { bytesRead = it } != -1) {
|
|
212
|
-
output.write(buffer, 0, bytesRead)
|
|
213
|
-
totalRead += bytesRead
|
|
214
|
-
val currentTime = System.currentTimeMillis()
|
|
215
|
-
if (currentTime - lastProgressTime >= 100) {
|
|
216
|
-
val progress = totalRead.toDouble() / totalSize
|
|
217
|
-
progressCallback.invoke(progress)
|
|
218
|
-
lastProgressTime = currentTime
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
progressCallback.invoke(1.0)
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
} catch (e: Exception) {
|
|
225
|
-
Log.d("HotUpdater", "Failed to download data from URL: $zipUrl, Error: ${e.message}")
|
|
226
|
-
tempDir.deleteRecursively()
|
|
227
|
-
return@withContext false
|
|
228
|
-
} finally {
|
|
229
|
-
conn.disconnect()
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (!extractZipFileAtPath(tempZipFile.absolutePath, extractedDir.absolutePath)) {
|
|
233
|
-
Log.d("HotUpdater", "Failed to extract zip file.")
|
|
234
|
-
tempDir.deleteRecursively()
|
|
235
|
-
return@withContext false
|
|
236
|
-
}
|
|
237
|
-
true
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (!isSuccess) {
|
|
241
|
-
tempDir.deleteRecursively()
|
|
242
|
-
return false
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
val indexFileExtracted = extractedDir.walk().find { it.name == "index.android.bundle" }
|
|
246
|
-
if (indexFileExtracted == null) {
|
|
247
|
-
Log.d("HotUpdater", "index.android.bundle not found in extracted files.")
|
|
248
|
-
tempDir.deleteRecursively()
|
|
249
|
-
return false
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (finalBundleDir.exists()) {
|
|
253
|
-
finalBundleDir.deleteRecursively()
|
|
254
|
-
}
|
|
255
|
-
if (!extractedDir.renameTo(finalBundleDir)) {
|
|
256
|
-
extractedDir.copyRecursively(finalBundleDir, overwrite = true)
|
|
257
|
-
extractedDir.deleteRecursively()
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
val finalIndexFile = finalBundleDir.walk().find { it.name == "index.android.bundle" }
|
|
261
|
-
if (finalIndexFile == null) {
|
|
262
|
-
Log.d("HotUpdater", "index.android.bundle not found in final directory.")
|
|
263
|
-
tempDir.deleteRecursively()
|
|
264
|
-
return false
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
finalBundleDir.setLastModified(System.currentTimeMillis())
|
|
268
|
-
val bundlePath = finalIndexFile.absolutePath
|
|
269
|
-
Log.d("HotUpdater", "Setting bundle URL: $bundlePath")
|
|
270
|
-
setBundleURL(context, bundlePath)
|
|
271
|
-
cleanupOldBundles(bundleStoreDir)
|
|
272
|
-
tempDir.deleteRecursively()
|
|
273
|
-
|
|
274
|
-
Log.d("HotUpdater", "Downloaded and extracted file successfully.")
|
|
275
|
-
return true
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
private fun cleanupOldBundles(bundleStoreDir: File) {
|
|
279
|
-
val bundles = bundleStoreDir.listFiles { file -> file.isDirectory }?.toList() ?: return
|
|
280
|
-
val sortedBundles = bundles.sortedByDescending { it.lastModified() }
|
|
281
|
-
if (sortedBundles.size > 1) {
|
|
282
|
-
sortedBundles.drop(1).forEach { oldBundle ->
|
|
283
|
-
Log.d("HotUpdater", "Removing old bundle: ${oldBundle.name}")
|
|
284
|
-
oldBundle.deleteRecursively()
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
fun getMinBundleId(): String =
|
|
290
|
-
try {
|
|
291
|
-
val buildTimestampMs = BuildConfig.BUILD_TIMESTAMP
|
|
292
|
-
val bytes =
|
|
293
|
-
ByteArray(16).apply {
|
|
294
|
-
this[0] = ((buildTimestampMs shr 40) and 0xFF).toByte()
|
|
295
|
-
this[1] = ((buildTimestampMs shr 32) and 0xFF).toByte()
|
|
296
|
-
this[2] = ((buildTimestampMs shr 24) and 0xFF).toByte()
|
|
297
|
-
this[3] = ((buildTimestampMs shr 16) and 0xFF).toByte()
|
|
298
|
-
this[4] = ((buildTimestampMs shr 8) and 0xFF).toByte()
|
|
299
|
-
this[5] = (buildTimestampMs and 0xFF).toByte()
|
|
300
|
-
this[6] = 0x70.toByte()
|
|
301
|
-
this[7] = 0x00.toByte()
|
|
302
|
-
this[8] = 0x80.toByte()
|
|
303
|
-
this[9] = 0x00.toByte()
|
|
304
|
-
this[10] = 0x00.toByte()
|
|
305
|
-
this[11] = 0x00.toByte()
|
|
306
|
-
this[12] = 0x00.toByte()
|
|
307
|
-
this[13] = 0x00.toByte()
|
|
308
|
-
this[14] = 0x00.toByte()
|
|
309
|
-
this[15] = 0x00.toByte()
|
|
310
|
-
}
|
|
311
|
-
String.format(
|
|
312
|
-
"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
|
|
313
|
-
bytes[0].toInt() and 0xFF,
|
|
314
|
-
bytes[1].toInt() and 0xFF,
|
|
315
|
-
bytes[2].toInt() and 0xFF,
|
|
316
|
-
bytes[3].toInt() and 0xFF,
|
|
317
|
-
bytes[4].toInt() and 0xFF,
|
|
318
|
-
bytes[5].toInt() and 0xFF,
|
|
319
|
-
bytes[6].toInt() and 0xFF,
|
|
320
|
-
bytes[7].toInt() and 0xFF,
|
|
321
|
-
bytes[8].toInt() and 0xFF,
|
|
322
|
-
bytes[9].toInt() and 0xFF,
|
|
323
|
-
bytes[10].toInt() and 0xFF,
|
|
324
|
-
bytes[11].toInt() and 0xFF,
|
|
325
|
-
bytes[12].toInt() and 0xFF,
|
|
326
|
-
bytes[13].toInt() and 0xFF,
|
|
327
|
-
bytes[14].toInt() and 0xFF,
|
|
328
|
-
bytes[15].toInt() and 0xFF,
|
|
329
|
-
)
|
|
330
|
-
} catch (e: Exception) {
|
|
331
|
-
"00000000-0000-0000-0000-000000000000"
|
|
332
|
-
}
|
|
333
90
|
}
|
|
334
91
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
package com.hotupdater
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Factory for creating HotUpdaterImpl instances with proper dependency injection
|
|
7
|
+
*/
|
|
8
|
+
object HotUpdaterFactory {
|
|
9
|
+
@Volatile
|
|
10
|
+
private var instance: HotUpdaterImpl? = null
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Gets the singleton instance of HotUpdaterImpl
|
|
14
|
+
* @param context Application context
|
|
15
|
+
* @return HotUpdaterImpl instance
|
|
16
|
+
*/
|
|
17
|
+
fun getInstance(context: Context): HotUpdaterImpl =
|
|
18
|
+
instance ?: synchronized(this) {
|
|
19
|
+
instance ?: createHotUpdaterImpl(context).also { instance = it }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Creates a new HotUpdaterImpl instance with all dependencies
|
|
24
|
+
* @param context Application context
|
|
25
|
+
* @return New HotUpdaterImpl instance
|
|
26
|
+
*/
|
|
27
|
+
private fun createHotUpdaterImpl(context: Context): HotUpdaterImpl {
|
|
28
|
+
val appContext = context.applicationContext
|
|
29
|
+
val appVersion = HotUpdaterImpl.getAppVersion(appContext) ?: "unknown"
|
|
30
|
+
|
|
31
|
+
// Create services
|
|
32
|
+
val fileSystem = FileManagerService(appContext)
|
|
33
|
+
val preferences = VersionedPreferencesService(appContext, appVersion)
|
|
34
|
+
val downloadService = HttpDownloadService()
|
|
35
|
+
val unzipService = ZipFileUnzipService()
|
|
36
|
+
|
|
37
|
+
// Create bundle storage with dependencies
|
|
38
|
+
val bundleStorage =
|
|
39
|
+
BundleFileStorageService(
|
|
40
|
+
fileSystem,
|
|
41
|
+
downloadService,
|
|
42
|
+
unzipService,
|
|
43
|
+
preferences,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
// Create and return the implementation
|
|
47
|
+
return HotUpdaterImpl(appContext, bundleStorage, preferences)
|
|
48
|
+
}
|
|
49
|
+
}
|