react-native-stallion 2.1.0-alpha.2 → 2.1.0-alpha.3

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.
@@ -3,6 +3,7 @@ package com.stallion.events;
3
3
  public class StallionEventConstants {
4
4
  public enum NativeProdEventTypes {
5
5
  DOWNLOAD_STARTED_PROD,
6
+ DOWNLOAD_RESUME_PROD,
6
7
  DOWNLOAD_ERROR_PROD,
7
8
  DOWNLOAD_COMPLETE_PROD,
8
9
  SYNC_ERROR_PROD,
@@ -20,6 +21,7 @@ public class StallionEventConstants {
20
21
  DOWNLOAD_COMPLETE_STAGE,
21
22
  EXCEPTION_STAGE,
22
23
  DOWNLOAD_STARTED_STAGE,
24
+ DOWNLOAD_RESUME_STAGE,
23
25
  DOWNLOAD_ERROR_STAGE,
24
26
  INSTALLED_STAGE,
25
27
  }
@@ -0,0 +1,48 @@
1
+ package com.stallion.networkmanager;
2
+
3
+ import com.stallion.storage.StallionConfig;
4
+ import com.stallion.utils.StallionFileManager;
5
+
6
+ import java.io.File;
7
+ import java.io.FileInputStream;
8
+ import java.io.FileOutputStream;
9
+ import java.util.Objects;
10
+
11
+ public class StallionDownloadCacheManager {
12
+ private static final String metaFilePath = "/download-cache.meta";
13
+
14
+ public static long getDownloadCache(StallionConfig config, String downloadUrl, String downloadPath) {
15
+ String lastDownloadingUrl = config.getLastDownloadingUrl();
16
+ long alreadyDownloaded = readMetaFile(downloadPath);
17
+ if(!Objects.equals(lastDownloadingUrl, downloadUrl) || alreadyDownloaded <= 0) {
18
+ config.setLastDownloadingUrl(downloadUrl);
19
+ StallionFileManager.deleteFileOrFolderSilently(new File(downloadPath));
20
+ return 0;
21
+ } else {
22
+ return alreadyDownloaded;
23
+ }
24
+ }
25
+
26
+ public static void saveDownloadCache(String path, long bytes) {
27
+ try (FileOutputStream fos = new FileOutputStream(path + metaFilePath)) {
28
+ fos.write(Long.toString(bytes).getBytes());
29
+ } catch (Exception ignored) {}
30
+ }
31
+
32
+ private static long readMetaFile(String path) {
33
+ File meta = new File(path + metaFilePath);
34
+ if (!meta.exists()) return 0;
35
+ try (FileInputStream fis = new FileInputStream(meta)) {
36
+ byte[] data = new byte[(int) meta.length()];
37
+ fis.read(data);
38
+ return Long.parseLong(new String(data));
39
+ } catch (Exception e) {
40
+ return 0;
41
+ }
42
+ }
43
+
44
+ public static void deleteDownloadCache(String path) {
45
+ File meta = new File(path + metaFilePath);
46
+ if (meta.exists()) meta.delete();
47
+ }
48
+ }
@@ -8,6 +8,7 @@ import java.io.File;
8
8
  import java.io.FileInputStream;
9
9
  import java.io.FileOutputStream;
10
10
  import java.io.IOException;
11
+ import java.io.RandomAccessFile;
11
12
  import java.net.HttpURLConnection;
12
13
  import java.net.URL;
13
14
  import java.nio.ByteBuffer;
@@ -28,6 +29,7 @@ public class StallionFileDownloader {
28
29
  public static void downloadBundle(
29
30
  String downloadUrl,
30
31
  String downloadDirectory,
32
+ long alreadyDownloaded,
31
33
  StallionDownloadCallback stallionDownloadCallback
32
34
  ) {
33
35
  executor.execute(() -> {
@@ -61,7 +63,7 @@ public class StallionFileDownloader {
61
63
  }
62
64
 
63
65
  // Download file
64
- downloadFile(downloadUrl, downloadedZip, appToken, sdkToken, stallionDownloadCallback);
66
+ downloadFile(downloadUrl, downloadedZip, appToken, sdkToken, stallionDownloadCallback, alreadyDownloaded, downloadDirectory);
65
67
 
66
68
  // Validate and unzip the downloaded file
67
69
  validateAndUnzip(downloadedZip, downloadDirectory, stallionDownloadCallback);
@@ -75,7 +77,7 @@ public class StallionFileDownloader {
75
77
  private static long getFileSize(String downloadUrl, String appToken, String apiKey) throws IOException {
76
78
  HttpURLConnection connection = null;
77
79
  try {
78
- connection = setupConnection(downloadUrl, appToken, apiKey);
80
+ connection = setupConnection(downloadUrl, appToken, apiKey, 0);
79
81
  return connection.getContentLength();
80
82
  } finally {
81
83
  if (connection != null) {
@@ -96,11 +98,10 @@ public class StallionFileDownloader {
96
98
 
97
99
  private static File prepareForDownload(String downloadDirectory) throws IOException {
98
100
  File downloadFolder = new File(downloadDirectory);
99
- if (downloadFolder.exists()) {
100
- StallionFileManager.deleteFileOrFolderSilently(downloadFolder);
101
- }
102
- if (!downloadFolder.mkdirs()) {
103
- throw new IOException("Failed to create download directory: " + downloadDirectory);
101
+ if (!downloadFolder.exists()) {
102
+ if (!downloadFolder.mkdirs()) {
103
+ throw new IOException("Failed to create download directory: " + downloadDirectory);
104
+ }
104
105
  }
105
106
  return new File(downloadFolder, StallionApiConstants.ZIP_FILE_NAME);
106
107
  }
@@ -110,21 +111,21 @@ public class StallionFileDownloader {
110
111
  File destinationFile,
111
112
  String appToken,
112
113
  String sdkToken,
113
- StallionDownloadCallback callback
114
+ StallionDownloadCallback callback,
115
+ long alreadyDownloaded,
116
+ String downloadDirectory
114
117
  ) throws IOException {
115
- HttpURLConnection connection = null;
118
+ HttpURLConnection connection = setupConnection(downloadUrl, appToken, sdkToken, alreadyDownloaded);
116
119
  try (
117
- BufferedInputStream inputStream = new BufferedInputStream(setupConnection(downloadUrl, appToken, sdkToken).getInputStream());
118
- FileOutputStream fout = new FileOutputStream(destinationFile);
119
- BufferedOutputStream bout = new BufferedOutputStream(fout, StallionApiConstants.DOWNLOAD_BUFFER_SIZE)
120
+ BufferedInputStream inputStream = new BufferedInputStream(connection.getInputStream());
121
+ RandomAccessFile raf = new RandomAccessFile(destinationFile, "rw")
120
122
  ) {
121
- connection = setupConnection(downloadUrl, appToken, sdkToken);
122
-
123
+ raf.seek(alreadyDownloaded);
123
124
  byte[] buffer = new byte[StallionApiConstants.DOWNLOAD_BUFFER_SIZE];
124
- long totalBytes = connection.getContentLength();
125
- long receivedBytes = 0;
125
+ long totalBytes = connection.getContentLength() + alreadyDownloaded;
126
+ long receivedBytes = alreadyDownloaded;
126
127
  int bytesRead;
127
- double lastProgress = 0;
128
+ double lastProgress = (double) receivedBytes / totalBytes;
128
129
 
129
130
  // Ensure totalBytes is valid
130
131
  if (totalBytes <= 0) {
@@ -133,9 +134,11 @@ public class StallionFileDownloader {
133
134
  }
134
135
 
135
136
  while ((bytesRead = inputStream.read(buffer)) != -1) {
136
- bout.write(buffer, 0, bytesRead);
137
+ raf.write(buffer, 0, bytesRead);
137
138
  receivedBytes += bytesRead;
138
139
 
140
+ StallionDownloadCacheManager.saveDownloadCache(downloadDirectory, receivedBytes);
141
+
139
142
  double progress = (double) receivedBytes / totalBytes;
140
143
  if (Double.isNaN(progress) || Double.isInfinite(progress)) {
141
144
  callback.onReject(StallionApiConstants.DOWNLOAD_ERROR_PREFIX, "Invalid progress calculation");
@@ -148,8 +151,7 @@ public class StallionFileDownloader {
148
151
  }
149
152
  }
150
153
 
151
- bout.close();
152
- fout.close();
154
+ raf.close();
153
155
  inputStream.close();
154
156
 
155
157
  // Check for incomplete download
@@ -161,18 +163,25 @@ public class StallionFileDownloader {
161
163
  callback.onReject(StallionApiConstants.DOWNLOAD_ERROR_PREFIX, "IOException occurred: ");
162
164
  throw e;
163
165
  } finally {
164
- if (connection != null) {
165
- connection.disconnect();
166
- }
166
+ connection.disconnect();
167
167
  }
168
168
  }
169
169
 
170
170
 
171
- private static HttpURLConnection setupConnection(String downloadUrl, String appToken, String sdkToken) throws IOException {
171
+ private static HttpURLConnection setupConnection(
172
+ String downloadUrl,
173
+ String appToken,
174
+ String sdkToken,
175
+ long offset
176
+ ) throws IOException {
172
177
  URL url = new URL(downloadUrl);
173
178
  HttpURLConnection connection = (HttpURLConnection) url.openConnection();
174
179
  connection.setRequestMethod("GET");
175
180
 
181
+ if (offset > 0) {
182
+ connection.setRequestProperty("Range", "bytes=" + offset + "-");
183
+ }
184
+
176
185
  if(!appToken.isEmpty()) {
177
186
  connection.setRequestProperty(StallionApiConstants.STALLION_APP_TOKEN_KEY, appToken);
178
187
  }
@@ -4,6 +4,7 @@ import com.facebook.react.bridge.Promise;
4
4
  import com.facebook.react.bridge.ReadableMap;
5
5
  import com.stallion.events.StallionEventConstants;
6
6
  import com.stallion.events.StallionEventManager;
7
+ import com.stallion.storage.StallionConfig;
7
8
  import com.stallion.storage.StallionConfigConstants;
8
9
  import com.stallion.storage.StallionMetaConstants;
9
10
  import com.stallion.storage.StallionStateManager;
@@ -21,14 +22,18 @@ public class StallionStageManager {
21
22
  && receivedHash != null
22
23
  && !receivedHash.isEmpty()
23
24
  ) {
24
- String downloadPath = stallionStateManager.getStallionConfig().getFilesDirectory()
25
+ StallionConfig config = stallionStateManager.getStallionConfig();
26
+ String downloadPath = config.getFilesDirectory()
25
27
  + StallionConfigConstants.STAGE_DIRECTORY
26
28
  + StallionConfigConstants.TEMP_FOLDER_SLOT;
29
+ long alreadyDownloaded = StallionDownloadCacheManager.getDownloadCache(config, receivedDownloadUrl, downloadPath);
30
+
31
+ emitDownloadStartedStage(receivedHash, alreadyDownloaded > 0);
27
32
 
28
- emitDownloadStartedStage(receivedHash);
29
33
  StallionFileDownloader.downloadBundle(
30
34
  receivedDownloadUrl,
31
35
  downloadPath,
36
+ alreadyDownloaded,
32
37
  new StallionDownloadCallback() {
33
38
  @Override
34
39
  public void onReject(String prefix, String error) {
@@ -42,6 +47,7 @@ public class StallionStageManager {
42
47
  stallionStateManager.stallionMeta.setStageTempHash(receivedHash);
43
48
  stallionStateManager.syncStallionMeta();
44
49
  emitDownloadSuccessStage(receivedHash);
50
+ StallionDownloadCacheManager.deleteDownloadCache(downloadPath);
45
51
  promise.resolve(successPayload);
46
52
  }
47
53
 
@@ -77,13 +83,15 @@ public class StallionStageManager {
77
83
  );
78
84
  }
79
85
 
80
- private static void emitDownloadStartedStage(String releaseHash) {
86
+ private static void emitDownloadStartedStage(String releaseHash, Boolean isResume) {
81
87
  JSONObject startedPayload = new JSONObject();
82
88
  try {
83
89
  startedPayload.put("releaseHash", releaseHash);
84
90
  } catch (Exception ignored) { }
85
91
  StallionEventManager.getInstance().sendEvent(
86
- StallionEventConstants.NativeStageEventTypes.DOWNLOAD_STARTED_STAGE.toString(),
92
+ isResume ?
93
+ StallionEventConstants.NativeStageEventTypes.DOWNLOAD_RESUME_STAGE.toString()
94
+ : StallionEventConstants.NativeStageEventTypes.DOWNLOAD_STARTED_STAGE.toString(),
87
95
  startedPayload
88
96
  );
89
97
  }
@@ -106,16 +106,21 @@ public class StallionSyncHandler {
106
106
  }
107
107
  try {
108
108
  StallionStateManager stateManager = StallionStateManager.getInstance();
109
- String downloadPath = stateManager.getStallionConfig().getFilesDirectory()
109
+ StallionConfig config = stateManager.getStallionConfig();
110
+ String downloadPath = config.getFilesDirectory()
110
111
  + StallionConfigConstants.PROD_DIRECTORY
111
112
  + StallionConfigConstants.TEMP_FOLDER_SLOT;
112
- String projectId = stateManager.getStallionConfig().getProjectId();
113
+ String projectId = config.getProjectId();
114
+ String downloadUrl = newReleaseUrl + "?projectId=" + projectId;
113
115
 
114
- emitDownloadStarted(newReleaseHash);
116
+ long alreadyDownloaded = StallionDownloadCacheManager.getDownloadCache(config, downloadUrl, downloadPath);
117
+
118
+ emitDownloadStarted(newReleaseHash, alreadyDownloaded > 0);
115
119
 
116
120
  StallionFileDownloader.downloadBundle(
117
- newReleaseUrl + "?projectId=" + projectId,
121
+ downloadUrl,
118
122
  downloadPath,
123
+ alreadyDownloaded,
119
124
  new StallionDownloadCallback() {
120
125
  @Override
121
126
  public void onReject(String prefix, String error) {
@@ -133,6 +138,7 @@ public class StallionSyncHandler {
133
138
  StallionSlotManager.stabilizeProd();
134
139
  }
135
140
  stateManager.syncStallionMeta();
141
+ StallionDownloadCacheManager.deleteDownloadCache(downloadPath);
136
142
  emitDownloadSuccess(newReleaseHash);
137
143
  }
138
144
 
@@ -182,13 +188,13 @@ public class StallionSyncHandler {
182
188
  );
183
189
  }
184
190
 
185
- private static void emitDownloadStarted(String releaseHash) {
191
+ private static void emitDownloadStarted(String releaseHash, Boolean isResume) {
186
192
  JSONObject successPayload = new JSONObject();
187
193
  try {
188
194
  successPayload.put("releaseHash", releaseHash);
189
195
  } catch (Exception ignored) { }
190
196
  StallionEventManager.getInstance().sendEvent(
191
- NativeProdEventTypes.DOWNLOAD_STARTED_PROD.toString(),
197
+ isResume ? NativeProdEventTypes.DOWNLOAD_RESUME_PROD.toString(): NativeProdEventTypes.DOWNLOAD_STARTED_PROD.toString(),
192
198
  successPayload
193
199
  );
194
200
  }
@@ -19,6 +19,7 @@ public class StallionConfig {
19
19
  private final String appVersion;
20
20
  private final SharedPreferences sharedPreferences;
21
21
  private final String filesDirectory;
22
+ private String lastDownloadingUrl;
22
23
 
23
24
  public StallionConfig(Context context, SharedPreferences sharedPreferences) {
24
25
  this.sharedPreferences = sharedPreferences;
@@ -63,6 +64,18 @@ public class StallionConfig {
63
64
 
64
65
  this.appVersion = fetchAppVersion(context);
65
66
  this.filesDirectory = context.getFilesDir().getAbsolutePath();
67
+ this.lastDownloadingUrl = sharedPreferences.getString(StallionConfigConstants.LAST_DOWNLOADING_URL_IDENTIFIER, "");
68
+ }
69
+
70
+ public String getLastDownloadingUrl() {
71
+ return this.lastDownloadingUrl;
72
+ }
73
+
74
+ public void setLastDownloadingUrl(String newUrl) {
75
+ this.lastDownloadingUrl = newUrl;
76
+ SharedPreferences.Editor editor = sharedPreferences.edit();
77
+ editor.putString(StallionConfigConstants.LAST_DOWNLOADING_URL_IDENTIFIER, newUrl);
78
+ editor.apply();
66
79
  }
67
80
 
68
81
  private String fetchAppVersion(Context context) {
@@ -9,6 +9,7 @@ public class StallionConfigConstants {
9
9
  public static final String STALLION_APP_TOKEN_IDENTIFIER = "StallionAppToken";
10
10
  public static final String UNIQUE_ID_IDENTIFIER = "stallionDeviceId";
11
11
  public static final String API_KEY_IDENTIFIER = "x-sdk-access-token";
12
+ public static final String LAST_DOWNLOADING_URL_IDENTIFIER = "StallionLastDownloadingUrl";
12
13
 
13
14
  public static final String PROD_DIRECTORY = "/StallionProd";
14
15
  public static final String STAGE_DIRECTORY = "/StallionStage";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-stallion",
3
- "version": "2.1.0-alpha.2",
3
+ "version": "2.1.0-alpha.3",
4
4
  "description": "Offical React Native SDK for Stallion",
5
5
  "main": "index",
6
6
  "types": "types/index.d.ts",