@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.
Files changed (141) hide show
  1. package/HotUpdater.podspec +7 -11
  2. package/android/{generated/java/com/hotupdater → app/build/generated/source/codegen/java/com/facebook/fbreact/specs}/NativeHotUpdaterSpec.java +3 -2
  3. package/android/app/build/generated/source/codegen/jni/HotUpdater-generated.cpp +68 -0
  4. package/android/app/build/generated/source/codegen/jni/HotUpdater.h +31 -0
  5. package/android/{generated → app/build/generated/source/codegen}/jni/HotUpdaterSpec-generated.cpp +2 -2
  6. 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
  7. package/{ios/generated/HotUpdaterSpecJSI.h → android/app/build/generated/source/codegen/jni/react/renderer/components/HotUpdater/HotUpdaterJSI.h} +53 -6
  8. package/{ios/generated → android/app/build/generated/source/codegen/jni/react/renderer/components/HotUpdaterSpec}/HotUpdaterSpecJSI-generated.cpp +2 -3
  9. package/android/{generated → app/build/generated/source/codegen}/jni/react/renderer/components/HotUpdaterSpec/HotUpdaterSpecJSI.h +59 -8
  10. package/android/src/main/java/com/hotupdater/BundleFileStorageService.kt +200 -0
  11. package/android/src/main/java/com/hotupdater/FileManagerService.kt +104 -0
  12. package/android/src/main/java/com/hotupdater/HotUpdater.kt +62 -305
  13. package/android/src/main/java/com/hotupdater/HotUpdaterFactory.kt +49 -0
  14. package/android/src/main/java/com/hotupdater/HotUpdaterImpl.kt +176 -0
  15. package/android/src/main/java/com/hotupdater/HttpDownloadService.kt +98 -0
  16. package/android/src/main/java/com/hotupdater/VersionedPreferencesService.kt +69 -0
  17. package/android/src/main/java/com/hotupdater/ZipFileUnzipService.kt +52 -0
  18. package/android/src/newarch/HotUpdaterModule.kt +31 -34
  19. package/android/src/oldarch/HotUpdaterModule.kt +32 -34
  20. package/android/src/oldarch/HotUpdaterSpec.kt +2 -9
  21. package/ios/HotUpdater/Internal/BundleFileStorageService.swift +593 -0
  22. package/ios/HotUpdater/Internal/FileManagerService.swift +97 -0
  23. package/ios/HotUpdater/Internal/HotUpdater-Bridging-Header.h +8 -0
  24. package/ios/HotUpdater/Internal/HotUpdater.mm +241 -0
  25. package/ios/HotUpdater/Internal/HotUpdaterFactory.swift +24 -0
  26. package/ios/HotUpdater/Internal/HotUpdaterImpl.swift +143 -0
  27. package/ios/HotUpdater/Internal/NotificationExtension.swift +6 -0
  28. package/ios/HotUpdater/Internal/SSZipArchiveUnzipService.swift +25 -0
  29. package/ios/HotUpdater/Internal/URLSessionDownloadService.swift +101 -0
  30. package/ios/HotUpdater/Internal/VersionedPreferencesService.swift +82 -0
  31. package/ios/HotUpdater/Package.resolved +15 -0
  32. package/ios/HotUpdater/Public/HotUpdater.h +29 -0
  33. package/lib/commonjs/checkForUpdate.js +70 -0
  34. package/lib/commonjs/checkForUpdate.js.map +1 -0
  35. package/lib/commonjs/error.js +14 -0
  36. package/lib/commonjs/error.js.map +1 -0
  37. package/lib/commonjs/fetchUpdateInfo.js +74 -0
  38. package/lib/commonjs/fetchUpdateInfo.js.map +1 -0
  39. package/lib/commonjs/hooks/useEventCallback.js +17 -0
  40. package/lib/commonjs/hooks/useEventCallback.js.map +1 -0
  41. package/lib/commonjs/index.js +234 -0
  42. package/lib/commonjs/index.js.map +1 -0
  43. package/lib/commonjs/native.js +132 -0
  44. package/lib/commonjs/native.js.map +1 -0
  45. package/lib/commonjs/package.json +1 -0
  46. package/lib/commonjs/runUpdateProcess.js +69 -0
  47. package/lib/commonjs/runUpdateProcess.js.map +1 -0
  48. package/lib/commonjs/specs/NativeHotUpdater.js +9 -0
  49. package/lib/commonjs/specs/NativeHotUpdater.js.map +1 -0
  50. package/lib/commonjs/store.js +48 -0
  51. package/lib/commonjs/store.js.map +1 -0
  52. package/lib/commonjs/wrap.js +98 -0
  53. package/lib/commonjs/wrap.js.map +1 -0
  54. package/lib/module/checkForUpdate.js +64 -0
  55. package/lib/module/checkForUpdate.js.map +1 -0
  56. package/lib/module/error.js +9 -0
  57. package/lib/module/error.js.map +1 -0
  58. package/lib/module/fetchUpdateInfo.js +69 -0
  59. package/lib/module/fetchUpdateInfo.js.map +1 -0
  60. package/lib/module/hooks/useEventCallback.js +13 -0
  61. package/lib/module/hooks/useEventCallback.js.map +1 -0
  62. package/lib/module/index.js +211 -0
  63. package/lib/module/index.js.map +1 -0
  64. package/lib/module/native.js +119 -0
  65. package/lib/module/native.js.map +1 -0
  66. package/lib/module/package.json +1 -0
  67. package/lib/module/runUpdateProcess.js +64 -0
  68. package/lib/module/runUpdateProcess.js.map +1 -0
  69. package/lib/module/specs/NativeHotUpdater.js +5 -0
  70. package/lib/module/specs/NativeHotUpdater.js.map +1 -0
  71. package/lib/module/store.js +42 -0
  72. package/lib/module/store.js.map +1 -0
  73. package/lib/module/wrap.js +94 -0
  74. package/lib/module/wrap.js.map +1 -0
  75. package/lib/typescript/commonjs/checkForUpdate.d.ts +22 -0
  76. package/lib/typescript/commonjs/checkForUpdate.d.ts.map +1 -0
  77. package/{dist → lib/typescript/commonjs}/error.d.ts +1 -0
  78. package/lib/typescript/commonjs/error.d.ts.map +1 -0
  79. package/lib/typescript/commonjs/fetchUpdateInfo.d.ts +4 -0
  80. package/lib/typescript/commonjs/fetchUpdateInfo.d.ts.map +1 -0
  81. package/{dist → lib/typescript/commonjs}/hooks/useEventCallback.d.ts +1 -0
  82. package/lib/typescript/commonjs/hooks/useEventCallback.d.ts.map +1 -0
  83. package/{dist → lib/typescript/commonjs}/index.d.ts +38 -12
  84. package/lib/typescript/commonjs/index.d.ts.map +1 -0
  85. package/lib/typescript/commonjs/native.d.ts +64 -0
  86. package/lib/typescript/commonjs/native.d.ts.map +1 -0
  87. package/lib/typescript/commonjs/package.json +1 -0
  88. package/{dist → lib/typescript/commonjs}/runUpdateProcess.d.ts +1 -0
  89. package/lib/typescript/commonjs/runUpdateProcess.d.ts.map +1 -0
  90. package/{dist → lib/typescript/commonjs}/specs/NativeHotUpdater.d.ts +8 -9
  91. package/lib/typescript/commonjs/specs/NativeHotUpdater.d.ts.map +1 -0
  92. package/{dist → lib/typescript/commonjs}/store.d.ts +1 -0
  93. package/lib/typescript/commonjs/store.d.ts.map +1 -0
  94. package/{dist → lib/typescript/commonjs}/wrap.d.ts +3 -2
  95. package/lib/typescript/commonjs/wrap.d.ts.map +1 -0
  96. package/lib/typescript/module/checkForUpdate.d.ts +22 -0
  97. package/lib/typescript/module/checkForUpdate.d.ts.map +1 -0
  98. package/lib/typescript/module/error.d.ts +4 -0
  99. package/lib/typescript/module/error.d.ts.map +1 -0
  100. package/lib/typescript/module/fetchUpdateInfo.d.ts +4 -0
  101. package/lib/typescript/module/fetchUpdateInfo.d.ts.map +1 -0
  102. package/lib/typescript/module/hooks/useEventCallback.d.ts +5 -0
  103. package/lib/typescript/module/hooks/useEventCallback.d.ts.map +1 -0
  104. package/lib/typescript/module/index.d.ts +202 -0
  105. package/lib/typescript/module/index.d.ts.map +1 -0
  106. package/lib/typescript/module/native.d.ts +64 -0
  107. package/lib/typescript/module/native.d.ts.map +1 -0
  108. package/lib/typescript/module/package.json +1 -0
  109. package/lib/typescript/module/runUpdateProcess.d.ts +49 -0
  110. package/lib/typescript/module/runUpdateProcess.d.ts.map +1 -0
  111. package/lib/typescript/module/specs/NativeHotUpdater.d.ts +19 -0
  112. package/lib/typescript/module/specs/NativeHotUpdater.d.ts.map +1 -0
  113. package/lib/typescript/module/store.d.ts +11 -0
  114. package/lib/typescript/module/store.d.ts.map +1 -0
  115. package/lib/typescript/module/wrap.d.ts +51 -0
  116. package/lib/typescript/module/wrap.d.ts.map +1 -0
  117. package/package.json +59 -30
  118. package/src/checkForUpdate.ts +59 -9
  119. package/src/fetchUpdateInfo.ts +40 -12
  120. package/src/index.ts +37 -11
  121. package/src/native.ts +87 -41
  122. package/src/runUpdateProcess.ts +2 -2
  123. package/src/specs/NativeHotUpdater.ts +8 -10
  124. package/src/wrap.tsx +9 -13
  125. package/android/src/main/java/com/hotupdater/HotUpdaterPrefs.kt +0 -42
  126. package/dist/checkForUpdate.d.ts +0 -12
  127. package/dist/fetchUpdateInfo.d.ts +0 -3
  128. package/dist/index.js +0 -341
  129. package/dist/index.mjs +0 -301
  130. package/dist/native.d.ts +0 -41
  131. package/ios/HotUpdater/HotUpdater.h +0 -15
  132. package/ios/HotUpdater/HotUpdater.mm +0 -468
  133. package/ios/HotUpdater/HotUpdater.modulemap +0 -6
  134. package/ios/HotUpdater/HotUpdaterPrefs.h +0 -9
  135. package/ios/HotUpdater/HotUpdaterPrefs.mm +0 -45
  136. package/ios/generated/HotUpdaterSpec/HotUpdaterSpec-generated.mm +0 -81
  137. package/ios/generated/HotUpdaterSpec/HotUpdaterSpec.h +0 -112
  138. package/react-native.config.js +0 -12
  139. package/src/global.d.ts +0 -3
  140. /package/android/{generated → app/build/generated/source/codegen}/jni/CMakeLists.txt +0 -0
  141. /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
- fun getAppVersion(context: Context): String? {
30
- val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0)
31
- return packageInfo.versionName
32
- }
33
-
34
- @Volatile
35
- private var prefsInstance: HotUpdaterPrefs? = null
36
-
37
- @Volatile
38
- private var cachedAppVersion: String? = null
39
-
40
- private fun getPrefs(context: Context): HotUpdaterPrefs {
41
- val appContext = context.applicationContext
42
- val currentAppVersion = getAppVersion(appContext) ?: "unknown"
43
- synchronized(this) {
44
- if (prefsInstance == null || cachedAppVersion != currentAppVersion) {
45
- prefsInstance = HotUpdaterPrefs(appContext, currentAppVersion)
46
- cachedAppVersion = currentAppVersion
47
- }
48
- return prefsInstance!!
49
- }
50
- }
51
-
52
- private fun setBundleURL(
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
- bundleURL: String?,
55
- ) {
56
- val updaterPrefs = getPrefs(context)
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
- try {
76
- ZipFile(filePath).use { zip ->
77
- zip.entries().asSequence().forEach { entry ->
78
- val file = File(destinationPath, entry.name)
79
- if (entry.isDirectory) {
80
- file.mkdirs()
81
- } else {
82
- file.parentFile?.mkdirs()
83
- zip.getInputStream(entry).use { input ->
84
- file.outputStream().use { output -> input.copyTo(output) }
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
+ }