@capgo/capacitor-updater 4.42.0 → 4.43.5

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 (67) hide show
  1. package/CapgoCapacitorUpdater.podspec +7 -5
  2. package/Package.swift +40 -0
  3. package/README.md +1913 -303
  4. package/android/build.gradle +41 -8
  5. package/android/proguard-rules.pro +45 -0
  6. package/android/src/main/AndroidManifest.xml +1 -3
  7. package/android/src/main/java/ee/forgr/capacitor_updater/AppLifecycleObserver.java +88 -0
  8. package/android/src/main/java/ee/forgr/capacitor_updater/BundleInfo.java +223 -195
  9. package/android/src/main/java/ee/forgr/capacitor_updater/BundleStatus.java +23 -23
  10. package/android/src/main/java/ee/forgr/capacitor_updater/Callback.java +13 -0
  11. package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +2720 -1242
  12. package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +1854 -0
  13. package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipher.java +359 -121
  14. package/android/src/main/java/ee/forgr/capacitor_updater/DataManager.java +28 -0
  15. package/android/src/main/java/ee/forgr/capacitor_updater/DelayCondition.java +44 -49
  16. package/android/src/main/java/ee/forgr/capacitor_updater/DelayUntilNext.java +4 -4
  17. package/android/src/main/java/ee/forgr/capacitor_updater/DelayUpdateUtils.java +296 -0
  18. package/android/src/main/java/ee/forgr/capacitor_updater/DeviceIdHelper.java +215 -0
  19. package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +858 -117
  20. package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +156 -0
  21. package/android/src/main/java/ee/forgr/capacitor_updater/InternalUtils.java +45 -0
  22. package/android/src/main/java/ee/forgr/capacitor_updater/Logger.java +360 -0
  23. package/android/src/main/java/ee/forgr/capacitor_updater/ShakeDetector.java +72 -0
  24. package/android/src/main/java/ee/forgr/capacitor_updater/ShakeMenu.java +603 -0
  25. package/dist/docs.json +3022 -765
  26. package/dist/esm/definitions.d.ts +1717 -198
  27. package/dist/esm/definitions.js +103 -1
  28. package/dist/esm/definitions.js.map +1 -1
  29. package/dist/esm/history.d.ts +1 -0
  30. package/dist/esm/history.js +283 -0
  31. package/dist/esm/history.js.map +1 -0
  32. package/dist/esm/index.d.ts +3 -2
  33. package/dist/esm/index.js +5 -4
  34. package/dist/esm/index.js.map +1 -1
  35. package/dist/esm/web.d.ts +43 -42
  36. package/dist/esm/web.js +122 -37
  37. package/dist/esm/web.js.map +1 -1
  38. package/dist/plugin.cjs.js +512 -37
  39. package/dist/plugin.cjs.js.map +1 -1
  40. package/dist/plugin.js +512 -37
  41. package/dist/plugin.js.map +1 -1
  42. package/ios/Sources/CapacitorUpdaterPlugin/AES.swift +87 -0
  43. package/ios/Sources/CapacitorUpdaterPlugin/BigInt.swift +55 -0
  44. package/ios/Sources/CapacitorUpdaterPlugin/BundleInfo.swift +177 -0
  45. package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/BundleStatus.swift +12 -12
  46. package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +2020 -0
  47. package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +1959 -0
  48. package/ios/Sources/CapacitorUpdaterPlugin/CryptoCipher.swift +313 -0
  49. package/ios/Sources/CapacitorUpdaterPlugin/DelayUpdateUtils.swift +257 -0
  50. package/ios/Sources/CapacitorUpdaterPlugin/DeviceIdHelper.swift +120 -0
  51. package/ios/Sources/CapacitorUpdaterPlugin/InternalUtils.swift +392 -0
  52. package/ios/Sources/CapacitorUpdaterPlugin/Logger.swift +310 -0
  53. package/ios/Sources/CapacitorUpdaterPlugin/RSA.swift +274 -0
  54. package/ios/Sources/CapacitorUpdaterPlugin/ShakeMenu.swift +441 -0
  55. package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/UserDefaultsExtension.swift +1 -2
  56. package/package.json +49 -41
  57. package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdater.java +0 -1131
  58. package/ios/Plugin/BundleInfo.swift +0 -113
  59. package/ios/Plugin/CapacitorUpdater.swift +0 -850
  60. package/ios/Plugin/CapacitorUpdaterPlugin.h +0 -10
  61. package/ios/Plugin/CapacitorUpdaterPlugin.m +0 -27
  62. package/ios/Plugin/CapacitorUpdaterPlugin.swift +0 -678
  63. package/ios/Plugin/CryptoCipher.swift +0 -240
  64. /package/{LICENCE → LICENSE} +0 -0
  65. /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/DelayCondition.swift +0 -0
  66. /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/DelayUntilNext.swift +0 -0
  67. /package/ios/{Plugin → Sources/CapacitorUpdaterPlugin}/Info.plist +0 -0
@@ -1,6 +1,6 @@
1
1
  ext {
2
2
  junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
3
- androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.4.1'
3
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.4.2'
4
4
  androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.1.3'
5
5
  androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.4.0'
6
6
  }
@@ -11,18 +11,29 @@ buildscript {
11
11
  mavenCentral()
12
12
  }
13
13
  dependencies {
14
- classpath 'com.android.tools.build:gradle:7.3.1'
14
+ classpath 'com.android.tools.build:gradle:8.13.0'
15
15
  }
16
16
  }
17
17
 
18
18
  apply plugin: 'com.android.library'
19
19
 
20
+ // Capacitor Android v4 does not declare `namespace`, but AGP 8 requires it.
21
+ project(':capacitor-android') {
22
+ pluginManager.withPlugin('com.android.library') {
23
+ android {
24
+ if (namespace == null) {
25
+ namespace "com.getcapacitor.android"
26
+ }
27
+ }
28
+ }
29
+ }
30
+
20
31
  android {
21
32
  namespace "ee.forgr.capacitor_updater"
22
- compileSdkVersion project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 32
33
+ compileSdkVersion project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 36
23
34
  defaultConfig {
24
35
  minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 22
25
- targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 32
36
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 36
26
37
  versionCode 1
27
38
  versionName "1.0"
28
39
  testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -37,8 +48,8 @@ android {
37
48
  abortOnError false
38
49
  }
39
50
  compileOptions {
40
- sourceCompatibility JavaVersion.VERSION_1_8
41
- targetCompatibility JavaVersion.VERSION_1_8
51
+ sourceCompatibility JavaVersion.VERSION_11
52
+ targetCompatibility JavaVersion.VERSION_11
42
53
  }
43
54
  }
44
55
 
@@ -49,13 +60,35 @@ repositories {
49
60
 
50
61
 
51
62
  dependencies {
63
+ def work_version = "2.10.5"
64
+ def lifecycle_version = "2.10.0"
65
+ implementation "androidx.work:work-runtime:$work_version"
66
+ implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
67
+ implementation "com.google.android.gms:play-services-tasks:18.4.1"
68
+ implementation "com.google.guava:guava:33.5.0-android"
52
69
  implementation fileTree(dir: 'libs', include: ['*.jar'])
53
70
  implementation project(':capacitor-android')
54
71
  implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
55
- implementation 'com.android.volley:volley:1.2.1'
56
72
  implementation 'io.github.g00fy2:versioncompare:1.5.0'
57
- implementation 'com.google.code.gson:gson:2.10.1'
73
+ // Play Core library for in-app updates
74
+ implementation 'com.google.android.play:app-update:2.1.0'
75
+ implementation 'com.google.android.play:app-update-ktx:2.1.0'
58
76
  testImplementation "junit:junit:$junitVersion"
77
+ testImplementation 'org.mockito:mockito-core:5.21.0'
78
+ testImplementation 'org.json:json:20250517'
79
+ testImplementation 'org.robolectric:robolectric:4.16.1'
59
80
  androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
60
81
  androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
82
+ implementation 'org.brotli:dec:0.1.2'
83
+ implementation 'com.squareup.okhttp3:okhttp:4.12.0'
84
+ }
85
+
86
+ tasks.withType(Test) {
87
+ testLogging {
88
+ events "passed", "skipped", "failed", "standardOut", "standardError"
89
+ exceptionFormat "full"
90
+ showStandardStreams = true
91
+ showCauses = true
92
+ showStackTraces = true
93
+ }
61
94
  }
@@ -0,0 +1,45 @@
1
+ # Add project specific ProGuard rules here.
2
+ # You can control the set of applied configuration files using the
3
+ # proguardFiles setting in build.gradle.
4
+ #
5
+ # For more details, see
6
+ # http://developer.android.com/guide/developing/tools/proguard.html
7
+
8
+ # If your project uses WebView with JS, uncomment the following
9
+ # and specify the fully qualified class name to the JavaScript interface
10
+ # class:
11
+ #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12
+ # public *;
13
+ #}
14
+
15
+ # Uncomment this to preserve the line number information for
16
+ # debugging stack traces.
17
+ #-keepattributes SourceFile,LineNumberTable
18
+
19
+ # If you keep the line number information, uncomment this to
20
+ # hide the original source file name.
21
+ #-renamesourcefileattribute SourceFile
22
+
23
+ # Preserve annotation and signature metadata used by runtime checks
24
+ -keepattributes *Annotation*, Signature
25
+
26
+ # Preserve the entire Capgo plugin package
27
+ -keep class ee.forgr.capacitor_updater.** { *; }
28
+
29
+ # Preserve Capacitor classes and members accessed via reflection for autoSplashscreen
30
+ # These rules are safe even if SplashScreen plugin is not present - they only reference core Capacitor classes
31
+ -keep class com.getcapacitor.Bridge {
32
+ com.getcapacitor.MessageHandler msgHandler;
33
+ }
34
+
35
+ -keep class com.getcapacitor.MessageHandler { *; }
36
+
37
+ -keep class com.getcapacitor.PluginCall {
38
+ <init>(com.getcapacitor.MessageHandler, java.lang.String, java.lang.String, java.lang.String, com.getcapacitor.JSObject);
39
+ }
40
+
41
+ # Keep SplashScreen plugin methods that are called via reflection
42
+ # This applies to any plugin, not just SplashScreen
43
+ -keep class * implements com.getcapacitor.PluginHandle {
44
+ public void invoke(java.lang.String, com.getcapacitor.PluginCall);
45
+ }
@@ -1,6 +1,4 @@
1
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
- package="ee.forgr.capacitor_updater">
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
3
2
  <application>
4
- <service android:name="ee.forgr.capacitor_updater.DownloadService" />
5
3
  </application>
6
4
  </manifest>
@@ -0,0 +1,88 @@
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+
7
+ package ee.forgr.capacitor_updater;
8
+
9
+ import androidx.annotation.NonNull;
10
+ import androidx.lifecycle.DefaultLifecycleObserver;
11
+ import androidx.lifecycle.LifecycleOwner;
12
+ import androidx.lifecycle.ProcessLifecycleOwner;
13
+
14
+ /**
15
+ * Observes app-level lifecycle events using ProcessLifecycleOwner.
16
+ * This provides reliable detection of when the entire app (not just an activity)
17
+ * moves to foreground or background.
18
+ *
19
+ * Unlike activity lifecycle callbacks, this won't trigger false positives when:
20
+ * - Opening a native camera view
21
+ * - Opening share sheets
22
+ * - Any other native activity overlays within the app
23
+ *
24
+ * The ON_STOP event is dispatched with a ~700ms delay after the last activity
25
+ * passes through onStop(), which helps avoid false triggers during configuration
26
+ * changes or quick activity transitions.
27
+ */
28
+ public class AppLifecycleObserver implements DefaultLifecycleObserver {
29
+
30
+ public interface AppLifecycleListener {
31
+ void onAppMovedToForeground();
32
+ void onAppMovedToBackground();
33
+ }
34
+
35
+ private final AppLifecycleListener listener;
36
+ private final Logger logger;
37
+ private boolean isRegistered = false;
38
+
39
+ public AppLifecycleObserver(AppLifecycleListener listener, Logger logger) {
40
+ this.listener = listener;
41
+ this.logger = logger;
42
+ }
43
+
44
+ public void register() {
45
+ if (isRegistered) {
46
+ return;
47
+ }
48
+ try {
49
+ ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
50
+ isRegistered = true;
51
+ logger.info("AppLifecycleObserver registered with ProcessLifecycleOwner");
52
+ } catch (Exception e) {
53
+ logger.error("Failed to register AppLifecycleObserver: " + e.getMessage());
54
+ }
55
+ }
56
+
57
+ public void unregister() {
58
+ if (!isRegistered) {
59
+ return;
60
+ }
61
+ try {
62
+ ProcessLifecycleOwner.get().getLifecycle().removeObserver(this);
63
+ isRegistered = false;
64
+ logger.info("AppLifecycleObserver unregistered from ProcessLifecycleOwner");
65
+ } catch (Exception e) {
66
+ logger.error("Failed to unregister AppLifecycleObserver: " + e.getMessage());
67
+ }
68
+ }
69
+
70
+ @Override
71
+ public void onStart(@NonNull LifecycleOwner owner) {
72
+ // App moved to foreground (at least one activity is visible)
73
+ logger.info("ProcessLifecycleOwner: App moved to foreground");
74
+ if (listener != null) {
75
+ listener.onAppMovedToForeground();
76
+ }
77
+ }
78
+
79
+ @Override
80
+ public void onStop(@NonNull LifecycleOwner owner) {
81
+ // App moved to background (no activities are visible)
82
+ // Note: This is called with a ~700ms delay to avoid false triggers
83
+ logger.info("ProcessLifecycleOwner: App moved to background");
84
+ if (listener != null) {
85
+ listener.onAppMovedToBackground();
86
+ }
87
+ }
88
+ }
@@ -6,9 +6,10 @@
6
6
 
7
7
  package ee.forgr.capacitor_updater;
8
8
 
9
- import com.getcapacitor.JSObject;
10
9
  import java.text.SimpleDateFormat;
11
10
  import java.util.Date;
11
+ import java.util.HashMap;
12
+ import java.util.Map;
12
13
  import java.util.Objects;
13
14
  import java.util.TimeZone;
14
15
  import org.json.JSONException;
@@ -17,198 +18,225 @@ import org.json.JSONTokener;
17
18
 
18
19
  public class BundleInfo {
19
20
 
20
- private static final SimpleDateFormat sdf = new SimpleDateFormat(
21
- "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
22
- );
23
-
24
- public static final String ID_BUILTIN = "builtin";
25
- public static final String VERSION_UNKNOWN = "unknown";
26
- public static final String DOWNLOADED_BUILTIN = "1970-01-01T00:00:00.000Z";
27
-
28
- private final String downloaded;
29
- private final String id;
30
- private final String version;
31
- private final String checksum;
32
- private final BundleStatus status;
33
-
34
- static {
35
- sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
36
- }
37
-
38
- public BundleInfo(final BundleInfo source) {
39
- this(
40
- source.id,
41
- source.version,
42
- source.status,
43
- source.downloaded,
44
- source.checksum
45
- );
46
- }
47
-
48
- public BundleInfo(
49
- final String id,
50
- final String version,
51
- final BundleStatus status,
52
- final Date downloaded,
53
- final String checksum
54
- ) {
55
- this(id, version, status, sdf.format(downloaded), checksum);
56
- }
57
-
58
- public BundleInfo(
59
- final String id,
60
- final String version,
61
- final BundleStatus status,
62
- final String downloaded,
63
- final String checksum
64
- ) {
65
- this.downloaded = downloaded.trim();
66
- this.id = id;
67
- this.version = version;
68
- this.checksum = checksum;
69
- this.status = status;
70
- }
71
-
72
- public Boolean isBuiltin() {
73
- return ID_BUILTIN.equals(this.id);
74
- }
75
-
76
- public Boolean isUnknown() {
77
- return VERSION_UNKNOWN.equals(this.id);
78
- }
79
-
80
- public Boolean isErrorStatus() {
81
- return BundleStatus.ERROR == this.status;
82
- }
83
-
84
- public Boolean isDeleted() {
85
- return BundleStatus.DELETED == this.status;
86
- }
87
-
88
- public boolean isDownloaded() {
89
- return (
90
- !this.isBuiltin() &&
91
- this.downloaded != null &&
92
- !this.downloaded.isEmpty() &&
93
- !this.isDeleted()
94
- );
95
- }
96
-
97
- public String getDownloaded() {
98
- return this.isBuiltin() ? DOWNLOADED_BUILTIN : this.downloaded;
99
- }
100
-
101
- public BundleInfo setDownloaded(Date downloaded) {
102
- return new BundleInfo(
103
- this.id,
104
- this.version,
105
- this.status,
106
- downloaded,
107
- this.checksum
108
- );
109
- }
110
-
111
- public String getChecksum() {
112
- return this.isBuiltin() ? "" : this.checksum;
113
- }
114
-
115
- public BundleInfo setChecksum(String checksum) {
116
- return new BundleInfo(
117
- this.id,
118
- this.version,
119
- this.status,
120
- this.downloaded,
121
- checksum
122
- );
123
- }
124
-
125
- public String getId() {
126
- return this.isBuiltin() ? ID_BUILTIN : this.id;
127
- }
128
-
129
- public BundleInfo setId(String id) {
130
- return new BundleInfo(
131
- id,
132
- this.version,
133
- this.status,
134
- this.downloaded,
135
- this.checksum
136
- );
137
- }
138
-
139
- public String getVersionName() {
140
- return this.version == null ? ID_BUILTIN : this.version;
141
- }
142
-
143
- public BundleInfo setVersionName(String version) {
144
- return new BundleInfo(
145
- this.id,
146
- version,
147
- this.status,
148
- this.downloaded,
149
- this.checksum
150
- );
151
- }
152
-
153
- public BundleStatus getStatus() {
154
- return this.isBuiltin() ? BundleStatus.SUCCESS : this.status;
155
- }
156
-
157
- public BundleInfo setStatus(BundleStatus status) {
158
- return new BundleInfo(
159
- this.id,
160
- this.version,
161
- status,
162
- this.downloaded,
163
- this.checksum
164
- );
165
- }
166
-
167
- public static BundleInfo fromJSON(final JSObject json) throws JSONException {
168
- return BundleInfo.fromJSON(json.toString());
169
- }
170
-
171
- public static BundleInfo fromJSON(final String jsonString)
172
- throws JSONException {
173
- JSONObject json = new JSONObject(new JSONTokener(jsonString));
174
- return new BundleInfo(
175
- json.has("id") ? json.getString("id") : "",
176
- json.has("version")
177
- ? json.getString("version")
178
- : BundleInfo.VERSION_UNKNOWN,
179
- json.has("status")
180
- ? BundleStatus.fromString(json.getString("status"))
181
- : BundleStatus.PENDING,
182
- json.has("downloaded") ? json.getString("downloaded") : "",
183
- json.has("checksum") ? json.getString("checksum") : ""
184
- );
185
- }
186
-
187
- public JSObject toJSON() {
188
- final JSObject result = new JSObject();
189
- result.put("id", this.getId());
190
- result.put("version", this.getVersionName());
191
- result.put("downloaded", this.getDownloaded());
192
- result.put("checksum", this.getChecksum());
193
- result.put("status", this.getStatus());
194
- return result;
195
- }
196
-
197
- @Override
198
- public boolean equals(final Object o) {
199
- if (this == o) return true;
200
- if (!(o instanceof BundleInfo)) return false;
201
- final BundleInfo that = (BundleInfo) o;
202
- return this.getId().equals(that.getId());
203
- }
204
-
205
- @Override
206
- public int hashCode() {
207
- return Objects.hash(this.version);
208
- }
209
-
210
- @Override
211
- public String toString() {
212
- return this.toJSON().toString();
213
- }
21
+ private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
22
+
23
+ public static final String ID_BUILTIN = "builtin";
24
+ public static final String VERSION_UNKNOWN = "unknown";
25
+ public static final String DOWNLOADED_BUILTIN = "1970-01-01T00:00:00.000Z";
26
+
27
+ private final String downloaded;
28
+ private final String id;
29
+ private final String version;
30
+ private final String checksum;
31
+ private final BundleStatus status;
32
+ private final String link;
33
+ private final String comment;
34
+
35
+ static {
36
+ sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
37
+ }
38
+
39
+ public BundleInfo(final BundleInfo source) {
40
+ this(source.id, source.version, source.status, source.downloaded, source.checksum, source.link, source.comment);
41
+ }
42
+
43
+ public BundleInfo(final String id, final String version, final BundleStatus status, final Date downloaded, final String checksum) {
44
+ this(id, version, status, sdf.format(downloaded), checksum, null, null);
45
+ }
46
+
47
+ public BundleInfo(
48
+ final String id,
49
+ final String version,
50
+ final BundleStatus status,
51
+ final Date downloaded,
52
+ final String checksum,
53
+ final String link,
54
+ final String comment
55
+ ) {
56
+ this(id, version, status, sdf.format(downloaded), checksum, link, comment);
57
+ }
58
+
59
+ public BundleInfo(final String id, final String version, final BundleStatus status, final String downloaded, final String checksum) {
60
+ this(id, version, status, downloaded, checksum, null, null);
61
+ }
62
+
63
+ public BundleInfo(
64
+ final String id,
65
+ final String version,
66
+ final BundleStatus status,
67
+ final String downloaded,
68
+ final String checksum,
69
+ final String link,
70
+ final String comment
71
+ ) {
72
+ this.downloaded = downloaded != null ? downloaded.trim() : "";
73
+ this.id = id != null ? id : "";
74
+ this.version = version;
75
+ this.checksum = checksum != null ? checksum : "";
76
+ this.status = status != null ? status : BundleStatus.ERROR;
77
+ this.link = link;
78
+ this.comment = comment;
79
+ }
80
+
81
+ public Boolean isBuiltin() {
82
+ return ID_BUILTIN.equals(this.id);
83
+ }
84
+
85
+ public Boolean isUnknown() {
86
+ return VERSION_UNKNOWN.equals(this.id);
87
+ }
88
+
89
+ public Boolean isErrorStatus() {
90
+ return BundleStatus.ERROR == this.status;
91
+ }
92
+
93
+ public Boolean isDeleted() {
94
+ return BundleStatus.DELETED == this.status;
95
+ }
96
+
97
+ public boolean isDownloaded() {
98
+ return (!this.isBuiltin() && this.downloaded != null && !this.downloaded.isEmpty() && !this.isDeleted());
99
+ }
100
+
101
+ public String getDownloaded() {
102
+ return this.isBuiltin() ? DOWNLOADED_BUILTIN : (this.downloaded != null ? this.downloaded : "");
103
+ }
104
+
105
+ public BundleInfo setDownloaded(Date downloaded) {
106
+ return new BundleInfo(this.id, this.version, this.status, downloaded, this.checksum, this.link, this.comment);
107
+ }
108
+
109
+ public String getChecksum() {
110
+ return this.isBuiltin() ? "" : (this.checksum != null ? this.checksum : "");
111
+ }
112
+
113
+ public BundleInfo setChecksum(String checksum) {
114
+ return new BundleInfo(this.id, this.version, this.status, this.downloaded, checksum, this.link, this.comment);
115
+ }
116
+
117
+ public String getId() {
118
+ return this.isBuiltin() ? ID_BUILTIN : this.id;
119
+ }
120
+
121
+ public BundleInfo setId(String id) {
122
+ return new BundleInfo(id, this.version, this.status, this.downloaded, this.checksum, this.link, this.comment);
123
+ }
124
+
125
+ public String getVersionName() {
126
+ return this.version == null ? ID_BUILTIN : this.version;
127
+ }
128
+
129
+ public BundleInfo setVersionName(String version) {
130
+ return new BundleInfo(this.id, version, this.status, this.downloaded, this.checksum, this.link, this.comment);
131
+ }
132
+
133
+ public BundleStatus getStatus() {
134
+ if (this.isBuiltin()) {
135
+ return BundleStatus.SUCCESS;
136
+ }
137
+ return this.status != null ? this.status : BundleStatus.ERROR;
138
+ }
139
+
140
+ public BundleInfo setStatus(BundleStatus status) {
141
+ return new BundleInfo(this.id, this.version, status, this.downloaded, this.checksum, this.link, this.comment);
142
+ }
143
+
144
+ public String getLink() {
145
+ return this.link;
146
+ }
147
+
148
+ public BundleInfo setLink(String link) {
149
+ return new BundleInfo(this.id, this.version, this.status, this.downloaded, this.checksum, link, this.comment);
150
+ }
151
+
152
+ public String getComment() {
153
+ return this.comment;
154
+ }
155
+
156
+ public BundleInfo setComment(String comment) {
157
+ return new BundleInfo(this.id, this.version, this.status, this.downloaded, this.checksum, this.link, comment);
158
+ }
159
+
160
+ public static BundleInfo fromJSON(final String jsonString) throws JSONException {
161
+ JSONObject json = new JSONObject(new JSONTokener(jsonString));
162
+ return new BundleInfo(
163
+ json.has("id") ? json.getString("id") : "",
164
+ json.has("version") ? json.getString("version") : BundleInfo.VERSION_UNKNOWN,
165
+ json.has("status") ? BundleStatus.fromString(json.getString("status")) : BundleStatus.PENDING,
166
+ json.has("downloaded") ? json.getString("downloaded") : "",
167
+ json.has("checksum") ? json.getString("checksum") : "",
168
+ json.has("link") ? json.getString("link") : null,
169
+ json.has("comment") ? json.getString("comment") : null
170
+ );
171
+ }
172
+
173
+ public Map<String, Object> toJSONMap() {
174
+ final Map<String, Object> result = new HashMap<>();
175
+ result.put("id", this.getId());
176
+ result.put("version", this.getVersionName());
177
+ result.put("downloaded", this.getDownloaded());
178
+ result.put("checksum", this.getChecksum());
179
+ result.put("status", this.getStatus().toString());
180
+ if (this.link != null && !this.link.isEmpty()) {
181
+ result.put("link", this.link);
182
+ }
183
+ if (this.comment != null && !this.comment.isEmpty()) {
184
+ result.put("comment", this.comment);
185
+ }
186
+ return result;
187
+ }
188
+
189
+ @Override
190
+ public boolean equals(final Object o) {
191
+ if (this == o) return true;
192
+ if (!(o instanceof BundleInfo)) return false;
193
+ final BundleInfo that = (BundleInfo) o;
194
+ return this.getId().equals(that.getId());
195
+ }
196
+
197
+ @Override
198
+ public int hashCode() {
199
+ return Objects.hash(this.version);
200
+ }
201
+
202
+ @Override
203
+ public String toString() {
204
+ try {
205
+ // Build JSON manually with extra safety checks
206
+ StringBuilder json = new StringBuilder();
207
+ json.append("{");
208
+
209
+ // Safe ID access
210
+ String safeId = this.id != null ? this.id : "";
211
+ if (this.isBuiltin()) safeId = ID_BUILTIN;
212
+ json.append("\"id\":\"").append(safeId).append("\",");
213
+
214
+ // Safe version access
215
+ String safeVersion = this.version != null ? this.version : ID_BUILTIN;
216
+ json.append("\"version\":\"").append(safeVersion).append("\",");
217
+
218
+ // Safe downloaded access
219
+ String safeDownloaded = this.downloaded != null ? this.downloaded : "";
220
+ if (this.isBuiltin()) safeDownloaded = DOWNLOADED_BUILTIN;
221
+ json.append("\"downloaded\":\"").append(safeDownloaded).append("\",");
222
+
223
+ // Safe checksum access
224
+ String safeChecksum = this.checksum != null ? this.checksum : "";
225
+ json.append("\"checksum\":\"").append(safeChecksum).append("\",");
226
+
227
+ // Safe status access
228
+ BundleStatus safeStatus = this.status != null ? this.status : BundleStatus.ERROR;
229
+ if (this.isBuiltin()) safeStatus = BundleStatus.SUCCESS;
230
+ json.append("\"status\":\"").append(safeStatus.toString()).append("\"");
231
+
232
+ json.append("}");
233
+ return json.toString();
234
+ } catch (Exception e) {
235
+ // Log the error for debugging but still return valid JSON
236
+ System.err.println("BundleInfo toString() error: " + e.getMessage());
237
+ e.printStackTrace();
238
+ // Return absolute minimal JSON
239
+ return "{\"id\":\"" + (this.id != null ? this.id : "unknown") + "\",\"status\":\"error\"}";
240
+ }
241
+ }
214
242
  }