@amazon-devices/expo-file-system 2.0.9000000000 → 3.0.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.
@@ -9,18 +9,26 @@
9
9
  #include "ExponentFileSystem.h"
10
10
 
11
11
  #include <JsonContainer/JsonContainer.h>
12
- #include <KeplerScript/logger/KPLog.h>
13
- #include <KeplerScript/utils/KeplerPath.h>
12
+ #include <Kepler/turbomodule/TMLog.h>
14
13
  #include <openssl/evp.h>
15
14
  #include <openssl/md5.h>
16
15
  #include <sys/stat.h>
17
16
 
17
+ #include <algorithm>
18
18
  #include <filesystem>
19
19
  #include <fstream>
20
20
  #include <iostream>
21
21
  #include <memory>
22
22
  #include <sstream>
23
23
  #include <string>
24
+ #include <thread>
25
+
26
+ #include "CurlWrapper.h"
27
+
28
+ using com::amazon::kepler::turbomodule::ArrayBuffer;
29
+ using com::amazon::kepler::turbomodule::NativeJSArray;
30
+ using com::amazon::kepler::turbomodule::NativeJSObject;
31
+ using com::amazon::kepler::turbomodule::Promise;
24
32
 
25
33
  namespace {
26
34
  std::string md5Hash(const std::string& content) {
@@ -54,7 +62,7 @@ std::string base64Encode(const std::string& input) {
54
62
  }
55
63
 
56
64
  template<typename T>
57
- T getOrDefault(volta::react::NativeJSObject map, std::string key, const T& defaultValue) {
65
+ T getOrDefault(NativeJSObject map, std::string key, const T& defaultValue) {
58
66
  if (map.count(key)) {
59
67
  return std::get<T>(map[key]);
60
68
  }
@@ -62,8 +70,9 @@ T getOrDefault(volta::react::NativeJSObject map, std::string key, const T& defau
62
70
  }
63
71
  } // namespace
64
72
 
65
- ExponentFileSystem::ExponentFileSystem() : volta::react::KeplerTurboModule("ExponentFileSystem") {
66
- volta::react::KPLog.debug("ExponentFileSystem constructor {}", name());
73
+ ExponentFileSystem::ExponentFileSystem() : com::amazon::kepler::turbomodule::KeplerTurboModule("ExponentFileSystem") {
74
+ TMDEBUG(std::string("ExponentFileSystem constructor ") + name());
75
+ curlWrapper_ = std::make_unique<CurlWrapper>();
67
76
  };
68
77
 
69
78
  /**
@@ -79,21 +88,19 @@ std::string ExponentFileSystem::cacheDirectory() {
79
88
  };
80
89
 
81
90
  std::string ExponentFileSystem::bundleDirectory() {
82
- return volta::react::KeplerPath::getPath(volta::react::KeplerPathKind::JavaScriptBundle);
91
+ const std::string bundleName = "index.hermes.bundle";
92
+ return std::filesystem::absolute("pkg/bundle/" + bundleName);
83
93
  };
84
94
 
85
- volta::react::Promise ExponentFileSystem::getInfoAsync(
86
- const std::string fileUri,
87
- volta::react::NativeJSObject options) {
95
+ Promise ExponentFileSystem::getInfoAsync(const std::string fileUri, NativeJSObject options) {
88
96
  bool shouldIncludeFileSize = getOrDefault(options, "size", false);
89
97
  bool shouldIncludeMD5Hash = getOrDefault(options, "md5", false);
90
98
 
91
- return volta::react::Promise([this, fileUri, shouldIncludeFileSize, shouldIncludeMD5Hash](
92
- std::shared_ptr<volta::react::Promise> promise) {
99
+ return Promise([this, fileUri, shouldIncludeFileSize, shouldIncludeMD5Hash](std::shared_ptr<Promise> promise) {
93
100
  bool exists = std::filesystem::exists(fileUri);
94
101
 
95
102
  if (!exists) {
96
- promise->resolve(volta::react::NativeJSObject{{"exists", false}, {"isDirectory", false}});
103
+ promise->resolve(NativeJSObject{{"exists", false}, {"isDirectory", false}});
97
104
  return;
98
105
  }
99
106
 
@@ -102,15 +109,18 @@ volta::react::Promise ExponentFileSystem::getInfoAsync(
102
109
  struct stat fileStat;
103
110
  int status = stat(fileUri.c_str(), &fileStat);
104
111
  if (status != 0) {
105
- volta::react::KPLog.error("[ExponentFileSystem] error while obtaining information about the file!");
106
- promise->reject("Error while obtaining information about the file!");
112
+ std::string errorMessage = std::strerror(errno);
113
+ TMERROR(std::string("[ExponentFileSystem] error in `stat()` call: ") + errorMessage + " while obtaining information about the file: " + fileUri + "!");
114
+ promise->reject(
115
+ "Error while obtaining information about the file! `stat()` call exited with error: " +
116
+ errorMessage);
107
117
  return;
108
118
  }
109
119
 
110
120
  std::uintmax_t modificationTime = fileStat.st_mtime;
111
121
  std::uintmax_t fileSize = getSize(fileUri);
112
122
 
113
- auto fileInfo = volta::react::NativeJSObject{
123
+ auto fileInfo = NativeJSObject{
114
124
  {"exists", true},
115
125
  {"isDirectory", isDirectory},
116
126
  {"modification", std::to_string(modificationTime)}};
@@ -121,19 +131,20 @@ volta::react::Promise ExponentFileSystem::getInfoAsync(
121
131
 
122
132
  if (shouldIncludeMD5Hash) {
123
133
  std::ifstream stream(fileUri);
124
- volta::react::KPLog.debug("[MD5] trying to open the file: {} for read", fileUri);
134
+ TMDEBUG(std::string("[MD5] trying to open the file: ") + fileUri + " for read");
125
135
  if (stream.fail()) {
126
- volta::react::KPLog.error("[MD5] unable to open file at {} for read", fileUri);
127
- promise->reject("Error while trying to open the file");
136
+ std::string errorMessage = std::strerror(errno);
137
+ TMERROR(std::string("[MD5] unable to open file at ") + fileUri + " for read: " + errorMessage);
138
+ promise->reject("Error while trying to open the file: " + errorMessage);
128
139
  return;
129
140
  } else {
130
141
  std::stringstream buffer;
131
142
  buffer << stream.rdbuf();
132
143
  std::string content = buffer.str();
133
- volta::react::KPLog.debug("[MD5] content: {}", content);
134
- volta::react::KPLog.debug("[MD5] calculating the file content hash...");
144
+ TMDEBUG(std::string("[MD5] content: ") + content);
145
+ TMDEBUG("[MD5] calculating the file content hash...");
135
146
  std::string hash = md5Hash(content);
136
- volta::react::KPLog.debug("[MD5] file hash: {}", hash);
147
+ TMDEBUG(std::string("[MD5] file hash: ") + hash);
137
148
  fileInfo.insert({"md5", hash});
138
149
  }
139
150
  }
@@ -141,140 +152,140 @@ volta::react::Promise ExponentFileSystem::getInfoAsync(
141
152
  });
142
153
  };
143
154
 
144
- volta::react::Promise ExponentFileSystem::readAsStringAsync(
145
- const std::string fileUri,
146
- volta::react::NativeJSObject options) {
155
+ Promise ExponentFileSystem::readAsStringAsync(const std::string fileUri, NativeJSObject options) {
147
156
  std::string desiredEncoding = getOrDefault(options, "encoding", std::string("utf8"));
148
157
  int bytesToSkip = int(getOrDefault(options, "position", static_cast<double>(0)));
149
158
  int length = getOrDefault(options, "length", static_cast<double>(-1));
150
159
 
151
- return volta::react::Promise(
152
- [this, fileUri, desiredEncoding, bytesToSkip, length](std::shared_ptr<volta::react::Promise> promise) {
153
- try {
154
- volta::react::KPLog.debug("[ExponentFileSystem] path: {}", fileUri);
155
- std::int32_t fileSize = std::filesystem::file_size(fileUri);
156
-
157
- std::ifstream stream(fileUri);
158
- volta::react::KPLog.debug("[ExponentFileSystem] trying to open the file: {} for read", fileUri);
159
- if (stream.fail()) {
160
- volta::react::KPLog.error("[ExponentFileSystem] unable to open file at {} for read", fileUri);
161
- promise->reject("Error while trying to open the file");
162
- return;
163
- }
160
+ return Promise([this, fileUri, desiredEncoding, bytesToSkip, length](std::shared_ptr<Promise> promise) {
161
+ try {
162
+ TMDEBUG(std::string("[ExponentFileSystem] path: ") + fileUri);
163
+ std::int32_t fileSize = std::filesystem::file_size(fileUri);
164
164
 
165
- if (bytesToSkip >= fileSize) {
166
- volta::react::KPLog.error("[ExponentFileSystem] cannot skip beyond end of file!");
167
- promise->reject("Cannot skip beyond end of file");
168
- return;
169
- }
165
+ std::ifstream stream(fileUri);
166
+ TMDEBUG(std::string("[ExponentFileSystem] trying to open the file: ") + fileUri + " for read");
167
+ if (stream.fail()) {
168
+ std::string errorMessage = std::strerror(errno);
169
+ TMERROR(std::string("[ExponentFileSystem] unable to open file at ") + fileUri + " for read: " + errorMessage);
170
+ promise->reject("Error while trying to open the file: " + errorMessage);
171
+ return;
172
+ }
170
173
 
171
- std::int32_t bytesToRead = length < 0 ? std::min(fileSize, fileSize - bytesToSkip)
172
- : std::min(length, fileSize - bytesToSkip);
174
+ if (bytesToSkip >= fileSize) {
175
+ TMERROR("[ExponentFileSystem] cannot skip beyond end of file!");
176
+ promise->reject("Cannot skip beyond end of file");
177
+ return;
178
+ }
173
179
 
174
- stream.seekg(bytesToSkip, std::ios::beg);
175
- std::string content;
176
- content.resize(bytesToRead);
177
- stream.read(&content[0], bytesToRead);
178
- volta::react::KPLog.debug("[ExponentFileSystem] content: {}", content);
180
+ std::int32_t bytesToRead =
181
+ length < 0 ? std::min(fileSize, fileSize - bytesToSkip) : std::min(length, fileSize - bytesToSkip);
179
182
 
180
- if (desiredEncoding == "base64") {
181
- volta::react::KPLog.debug("[base64] base64 encoding...");
182
- promise->resolve(base64Encode(content));
183
- } else {
184
- promise->resolve(content);
185
- }
186
- } catch (std::exception& e) {
187
- volta::react::KPLog.error("[ExponentFileSystem] reading file exception reason: {}", e.what());
188
- promise->reject(e.what());
189
- }
190
- });
183
+ stream.seekg(bytesToSkip, std::ios::beg);
184
+ std::string content;
185
+ content.resize(bytesToRead);
186
+ stream.read(&content[0], bytesToRead);
187
+ TMDEBUG(std::string("[ExponentFileSystem] content: ") + content);
188
+
189
+ if (desiredEncoding == "base64") {
190
+ TMDEBUG("[base64] base64 encoding...");
191
+ promise->resolve(base64Encode(content));
192
+ } else {
193
+ promise->resolve(content);
194
+ }
195
+ } catch (std::exception& e) {
196
+ TMERROR(std::string("[ExponentFileSystem] reading file exception reason: ") + e.what());
197
+ promise->reject(e.what());
198
+ }
199
+ });
191
200
  };
192
201
 
193
- volta::react::Promise ExponentFileSystem::getContentUriAsync(const std::string fileUri) {
194
- return volta::react::Promise([this, &fileUri](std::shared_ptr<volta::react::Promise> promise) {
202
+ Promise ExponentFileSystem::getContentUriAsync(const std::string fileUri) {
203
+ return Promise([this, &fileUri](std::shared_ptr<Promise> promise) {
195
204
  std::string contentUri = std::filesystem::absolute(fileUri);
196
205
 
197
206
  promise->resolve(contentUri);
198
207
  });
199
208
  };
200
209
 
201
- volta::react::Promise ExponentFileSystem::writeAsStringAsync(
210
+ Promise ExponentFileSystem::writeAsStringAsync(
202
211
  const std::string fileUri,
203
212
  const std::string contents,
204
- volta::react::NativeJSObject options) {
213
+ NativeJSObject options) {
205
214
  std::string desiredEncoding = getOrDefault(options, "encoding", std::string("utf8"));
206
215
 
207
- return volta::react::Promise(
208
- [this, fileUri, contents, desiredEncoding](std::shared_ptr<volta::react::Promise> promise) {
209
- try {
210
- std::ofstream stream(fileUri);
211
- volta::react::KPLog.debug("[ExponentFileSystem] trying to open the file: {} for write", fileUri);
212
- if (stream.fail()) {
213
- volta::react::KPLog.error("[ExponentFileSystem] unable to open file at {} for write", fileUri);
214
- promise->reject("Error while trying to open the file");
215
- return;
216
- } else {
217
- if (desiredEncoding == "base64") {
218
- stream << base64Encode(contents);
219
- } else {
220
- stream << contents;
221
- }
216
+ return Promise([this, fileUri, contents, desiredEncoding](std::shared_ptr<Promise> promise) {
217
+ try {
218
+ std::ofstream stream(fileUri);
219
+ TMDEBUG(std::string("[ExponentFileSystem] trying to open the file: ") + fileUri + " for write");
220
+ if (stream.fail()) {
221
+ std::string errorMessage = std::strerror(errno);
222
+ TMERROR(std::string("[ExponentFileSystem] unable to open file at ") + fileUri + " for write: " + errorMessage);
223
+ promise->reject("Error while trying to open the file: " + errorMessage);
224
+ return;
225
+ } else {
226
+ if (desiredEncoding == "base64") {
227
+ stream << base64Encode(contents);
228
+ } else {
229
+ stream << contents;
230
+ }
222
231
 
223
- if (stream.fail()) {
224
- volta::react::KPLog.error("[ExponentFileSystem] error while writing to file");
225
- promise->reject("Error while trying to write to the file");
226
- return;
227
- }
228
- stream.close();
229
- }
230
- promise->resolve();
231
- } catch (std::exception& e) {
232
- volta::react::KPLog.error("[ExponentFileSystem] writing file exception reason: {}", e.what());
233
- promise->reject(e.what());
232
+ if (stream.fail()) {
233
+ std::string errorMessage = std::strerror(errno);
234
+ TMERROR(std::string("[ExponentFileSystem] error while writing to file: ") + errorMessage);
235
+ promise->reject("Error while trying to write to the file: " + errorMessage);
236
+ return;
234
237
  }
235
- });
238
+ stream.close();
239
+ }
240
+ promise->resolve(NULL);
241
+ } catch (std::exception& e) {
242
+ TMERROR(std::string("[ExponentFileSystem] writing file exception reason: ") + e.what());
243
+ promise->reject(e.what());
244
+ }
245
+ });
236
246
  };
237
247
 
238
- volta::react::Promise ExponentFileSystem::writeArrayBufferAsync(
239
- const std::string fileUri,
240
- const volta::react::ArrayBuffer arrayBuffer) {
241
- return volta::react::Promise([this, fileUri, arrayBuffer](std::shared_ptr<volta::react::Promise> promise) {
248
+ Promise ExponentFileSystem::writeArrayBufferAsync(const std::string fileUri, const ArrayBuffer arrayBuffer) {
249
+ return Promise([this, fileUri, arrayBuffer](std::shared_ptr<Promise> promise) {
242
250
  try {
243
251
  std::ofstream file(fileUri, std::ios::binary);
244
- std::shared_ptr<std::vector<uint8_t>> dataPtr = arrayBuffer.dataVector();
252
+ auto dataPtr =
253
+ std::make_shared<std::vector<uint8_t>>(arrayBuffer.data(), arrayBuffer.data() + arrayBuffer.size());
245
254
 
246
255
  if (file.fail()) {
247
- volta::react::KPLog.error("[ExponentFileSystem] unable to open file at {} for response write", fileUri);
248
- promise->reject("File while tring to open the file");
256
+ std::string errorMessage = std::strerror(errno);
257
+ TMERROR(std::string("[ExponentFileSystem] unable to open file at ") + fileUri + " for response write: " + errorMessage);
258
+ promise->reject("File while tring to open the file: " + errorMessage);
249
259
  return;
250
260
  } else {
251
261
  if (dataPtr && !dataPtr->empty()) {
252
262
  file.write(reinterpret_cast<const char*>(dataPtr->data()), dataPtr->size());
253
263
  if (file.fail()) {
254
- volta::react::KPLog.error("[ExponentFileSystem] error while writing response to file");
255
- promise->reject("Error while trying to write response to file");
264
+ std::string errorMessage = std::strerror(errno);
265
+ TMERROR(std::string("[ExponentFileSystem] error while writing response to file: ") + errorMessage);
266
+ promise->reject("Error while trying to write response to file: " + errorMessage);
256
267
  return;
257
268
  }
258
269
  }
259
270
  }
260
- promise->resolve();
271
+ promise->resolve(NULL);
261
272
  } catch (std::exception& e) {
262
- volta::react::KPLog.error("[ExponentFileSystem] writing file exception reason: {}", e.what());
273
+ TMERROR(std::string("[ExponentFileSystem] writing file exception reason: ") + e.what());
263
274
  promise->reject(e.what());
264
275
  }
265
276
  });
266
277
  }
267
278
 
268
- volta::react::Promise ExponentFileSystem::deleteAsync(const std::string fileUri, volta::react::NativeJSObject options) {
279
+ Promise ExponentFileSystem::deleteAsync(const std::string fileUri, NativeJSObject options) {
269
280
  bool idempotent = getOrDefault(options, "idempotent", false);
270
281
 
271
- return volta::react::Promise([this, fileUri, idempotent](std::shared_ptr<volta::react::Promise> promise) {
282
+ return Promise([this, fileUri, idempotent](std::shared_ptr<Promise> promise) {
272
283
  if (!std::filesystem::exists(fileUri)) {
273
284
  if (idempotent) {
274
- promise->resolve();
285
+ promise->resolve(NULL);
275
286
  return;
276
287
  } else {
277
- volta::react::KPLog.error("[ExponentFileSystem] file does not exist at the specified URI!");
288
+ TMERROR("[ExponentFileSystem] file does not exist at the specified URI!");
278
289
  promise->reject("File does not exist at the specified URI");
279
290
  return;
280
291
  }
@@ -283,77 +294,73 @@ volta::react::Promise ExponentFileSystem::deleteAsync(const std::string fileUri,
283
294
  if (std::filesystem::is_regular_file(fileUri)) {
284
295
  try {
285
296
  std::filesystem::remove(fileUri);
286
- promise->resolve();
297
+ promise->resolve(NULL);
287
298
  } catch (std::filesystem::filesystem_error& e) {
288
- volta::react::KPLog.error("[ExponentFileSystem] file deletion exception, reason: {}", e.what());
299
+ TMERROR(std::string("[ExponentFileSystem] file deletion exception, reason: ") + e.what());
289
300
  promise->reject(e.what());
290
301
  }
291
302
  } else {
292
- volta::react::KPLog.error("[ExponentFileSystem] resource is not a regular file!");
303
+ TMERROR("[ExponentFileSystem] resource is not a regular file!");
293
304
  promise->reject("Resource is not a regular file");
294
305
  }
295
306
  });
296
307
  };
297
308
 
298
- volta::react::Promise ExponentFileSystem::moveAsync(volta::react::NativeJSObject options) {
309
+ Promise ExponentFileSystem::moveAsync(NativeJSObject options) {
299
310
  std::string from = getOrDefault(options, "from", std::string(""));
300
311
  std::string to = getOrDefault(options, "to", std::string(""));
301
312
 
302
- return volta::react::Promise([this, from, to](std::shared_ptr<volta::react::Promise> promise) {
313
+ return Promise([this, from, to](std::shared_ptr<Promise> promise) {
303
314
  if (from.empty() || to.empty()) {
304
- volta::react::KPLog.error(
305
- "[ExponentFileSystem]::moveAsync 'from' or 'to' not provided but they are both required");
315
+ TMERROR("[ExponentFileSystem]::moveAsync 'from' or 'to' not provided but they are both required");
306
316
  promise->reject("'from' or 'to' not provided but they are both required");
307
317
  return;
308
318
  }
309
319
 
310
320
  try {
311
321
  std::filesystem::rename(from, to);
312
- promise->resolve();
322
+ promise->resolve(NULL);
313
323
  } catch (std::filesystem::filesystem_error& e) {
314
- volta::react::KPLog.error("[ExponentFileSystem] file transfer exception, reason: {}", e.what());
324
+ TMERROR(std::string("[ExponentFileSystem] file transfer exception, reason: ") + e.what());
315
325
  promise->reject(e.what());
316
326
  }
317
327
  });
318
328
  };
319
329
 
320
- volta::react::Promise ExponentFileSystem::copyAsync(volta::react::NativeJSObject options) {
330
+ Promise ExponentFileSystem::copyAsync(NativeJSObject options) {
321
331
  std::string from = getOrDefault(options, "from", std::string(""));
322
332
  std::string to = getOrDefault(options, "to", std::string(""));
323
333
 
324
- return volta::react::Promise([this, from, to](std::shared_ptr<volta::react::Promise> promise) {
334
+ return Promise([this, from, to](std::shared_ptr<Promise> promise) {
325
335
  if (from.empty() || to.empty()) {
326
- volta::react::KPLog.error(
327
- "[ExponentFileSystem]::copyAsync 'from' or 'to' not provided but they are both required");
336
+ TMERROR("[ExponentFileSystem]::copyAsync 'from' or 'to' not provided but they are both required");
328
337
  promise->reject("'from' or 'to' not provided but they are both required");
329
338
  return;
330
339
  }
331
340
 
332
341
  try {
333
342
  std::filesystem::copy(from, to, std::filesystem::copy_options::recursive);
334
- promise->resolve();
343
+ promise->resolve(NULL);
335
344
  } catch (std::filesystem::filesystem_error& e) {
336
- volta::react::KPLog.error("[ExponentFileSystem] file transfer exception, reason: {}", e.what());
345
+ TMERROR(std::string("[ExponentFileSystem] file transfer exception, reason: ") + e.what());
337
346
  promise->reject(e.what());
338
347
  }
339
348
  });
340
349
  };
341
350
 
342
- volta::react::Promise ExponentFileSystem::makeDirectoryAsync(
343
- const std::string fileUri,
344
- volta::react::NativeJSObject options) {
351
+ Promise ExponentFileSystem::makeDirectoryAsync(const std::string fileUri, NativeJSObject options) {
345
352
  bool idempotent = getOrDefault(options, "intermediates", false);
346
353
 
347
- return volta::react::Promise([this, fileUri, idempotent](std::shared_ptr<volta::react::Promise> promise) {
354
+ return Promise([this, fileUri, idempotent](std::shared_ptr<Promise> promise) {
348
355
  try {
349
356
  bool exists = std::filesystem::exists(fileUri);
350
357
 
351
358
  if (exists) {
352
359
  if (idempotent) {
353
- promise->resolve();
360
+ promise->resolve(NULL);
354
361
  return;
355
362
  }
356
- volta::react::KPLog.error("[ExponentFileSystem] directory already exists!");
363
+ TMERROR("[ExponentFileSystem] directory already exists!");
357
364
  promise->reject("Directory already exists");
358
365
  return;
359
366
  }
@@ -361,36 +368,34 @@ volta::react::Promise ExponentFileSystem::makeDirectoryAsync(
361
368
  std::error_code errorCode;
362
369
 
363
370
  if (std::filesystem::create_directories(fileUri, errorCode)) {
364
- volta::react::KPLog.debug("[ExponentFileSystem] directory created successfully!");
371
+ TMDEBUG("[ExponentFileSystem] directory created successfully!");
365
372
  } else {
366
- volta::react::KPLog.error(
367
- "[ExponentFileSystem] directory creation failed, message: {}",
368
- errorCode.message());
373
+ TMERROR(std::string("[ExponentFileSystem] directory creation failed, message: ") + errorCode.message());
369
374
  promise->reject(errorCode.message());
370
375
  return;
371
376
  }
372
- promise->resolve();
377
+ promise->resolve(NULL);
373
378
  } catch (std::exception& e) {
374
- volta::react::KPLog.error("[ExponentFileSystem] directory creation exception, reason: {}", e.what());
379
+ TMERROR(std::string("[ExponentFileSystem] directory creation exception, reason: ") + e.what());
375
380
  promise->reject(e.what());
376
381
  }
377
382
  });
378
383
  };
379
384
 
380
- volta::react::Promise ExponentFileSystem::readDirectoryAsync(const std::string fileUri) {
381
- return volta::react::Promise([this, fileUri](std::shared_ptr<volta::react::Promise> promise) {
385
+ Promise ExponentFileSystem::readDirectoryAsync(const std::string fileUri) {
386
+ return Promise([this, fileUri](std::shared_ptr<Promise> promise) {
382
387
  try {
383
388
  if (!std::filesystem::exists(fileUri)) {
384
- volta::react::KPLog.error("[ExponentFileSystem] directory does not exist at the specified URI!");
389
+ TMERROR("[ExponentFileSystem] directory does not exist at the specified URI!");
385
390
  promise->reject("Directory does not exist at the specified URI");
386
391
  return;
387
392
  } else if (!std::filesystem::is_directory(fileUri)) {
388
- volta::react::KPLog.error("[ExponentFileSystem] resource at the specified URI is not a directory!");
393
+ TMERROR("[ExponentFileSystem] resource at the specified URI is not a directory!");
389
394
  promise->reject("Resource at the specified URI is not a directory");
390
395
  return;
391
396
  }
392
397
 
393
- auto childrenNames = volta::react::NativeJSArray{};
398
+ auto childrenNames = NativeJSArray{};
394
399
 
395
400
  // Symbolic links of the directory entries are not followed
396
401
  // during this enumeration.
@@ -400,60 +405,173 @@ volta::react::Promise ExponentFileSystem::readDirectoryAsync(const std::string f
400
405
 
401
406
  promise->resolve(childrenNames);
402
407
  } catch (std::exception& e) {
403
- volta::react::KPLog.error("[ExponentFileSystem] Reading directory exception: {}", e.what());
408
+ TMERROR(std::string("[ExponentFileSystem] Reading directory exception: ") + e.what());
404
409
  promise->reject(e.what());
405
410
  }
406
411
  });
407
412
  };
408
413
 
409
- volta::react::Promise ExponentFileSystem::getFreeDiskStorageAsync() {
410
- return volta::react::Promise([](std::shared_ptr<volta::react::Promise> promise) {
414
+ Promise ExponentFileSystem::getFreeDiskStorageAsync() {
415
+ return Promise([](std::shared_ptr<Promise> promise) {
411
416
  try {
412
417
  std::string path = std::filesystem::absolute("data");
413
418
 
414
419
  std::error_code errorCode;
415
420
  std::filesystem::space_info spaceInfo = std::filesystem::space(path, errorCode);
416
421
  if (errorCode) {
417
- volta::react::KPLog.error(
418
- "[ExponentFileSystem] Error when getting free disk space: {}",
419
- errorCode.message());
422
+ TMERROR(std::string("[ExponentFileSystem] Error when getting free disk space: ") + errorCode.message());
420
423
  promise->reject(errorCode.message());
421
424
  return;
422
425
  }
423
- volta::react::KPLog.debug("[ExponentFileSystem] Free space: {}", std::to_string(spaceInfo.available));
426
+ TMDEBUG(std::string("[ExponentFileSystem] Free space: ") + std::to_string(spaceInfo.available));
424
427
  promise->resolve(std::to_string(spaceInfo.available));
425
428
  } catch (std::exception& e) {
426
- volta::react::KPLog.error("[ExponentFileSystem] Getting free disk storage exception: {}", e.what());
429
+ TMERROR(std::string("[ExponentFileSystem] Getting free disk storage exception: ") + e.what());
427
430
  promise->reject(e.what());
428
431
  }
429
432
  });
430
433
  };
431
434
 
432
- volta::react::Promise ExponentFileSystem::getTotalDiskCapacityAsync() {
433
- return volta::react::Promise([](std::shared_ptr<volta::react::Promise> promise) { promise->resolve(); });
435
+ Promise ExponentFileSystem::getTotalDiskCapacityAsync() {
436
+ return Promise([](std::shared_ptr<Promise> promise) { promise->resolve(NULL); });
434
437
  };
435
438
 
436
- volta::react::Promise ExponentFileSystem::downloadAsync(
437
- const std::string,
438
- const std::string,
439
- volta::react::NativeJSObject,
440
- double,
441
- std::vector<utils::json::JsonContainer>) {
442
- return volta::react::Promise([](std::shared_ptr<volta::react::Promise> promise) {
443
- promise->reject("downloadAsync method has been deprecated, Update expo-file-system npm package");
444
- });
445
- };
439
+ Promise ExponentFileSystem::downloadAsync(
440
+ const std::string url,
441
+ const std::string fileUri,
442
+ NativeJSObject options,
443
+ double /* requestId */,
444
+ std::vector<utils::json::JsonContainer> headers) {
445
+ bool shouldComputeMd5 = getOrDefault(options, "md5", false);
446
+
447
+ // Convert headers vector to std::map
448
+ std::map<std::string, std::string> requestHeaders;
449
+ for (auto header : headers) {
450
+ try {
451
+ auto key = header["key"].getValue<std::string>();
452
+ auto value = header["value"].getValue<std::string>();
453
+ requestHeaders[key] = value;
454
+ } catch (...) {
455
+ // Skip malformed header entries
456
+ }
457
+ }
446
458
 
447
- volta::react::Promise ExponentFileSystem::uploadAsync(
448
- const std::string,
449
- const std::string,
450
- volta::react::NativeJSObject,
451
- double,
452
- std::vector<utils::json::JsonContainer>) {
453
- return volta::react::Promise([](std::shared_ptr<volta::react::Promise> promise) {
454
- promise->reject("uploadAsync method has been deprecated, Update expo-file-system npm package");
455
- });
456
- };
459
+ return Promise(
460
+ [this, url, fileUri, shouldComputeMd5, requestHeaders](std::shared_ptr<Promise> promise) {
461
+ TMDEBUG(std::string("[ExponentFileSystem] downloadAsync: ") + url + " -> " + fileUri);
462
+
463
+ // Run curl on a background thread to avoid blocking the JS thread.
464
+ // curl_easy_perform is synchronous and will block for the entire download
465
+ // duration, which triggers the KeplerScript thread watchdog on large files.
466
+ auto curlWrapper = curlWrapper_.get();
467
+ std::thread([promise, curlWrapper, url, fileUri, shouldComputeMd5, requestHeaders]() {
468
+ auto result = curlWrapper->downloadToFile(url, fileUri, requestHeaders);
469
+
470
+ if (!result.success) {
471
+ TMERROR(std::string("[ExponentFileSystem] downloadAsync failed: ") + result.errorMessage);
472
+ promise->reject(result.errorMessage);
473
+ return;
474
+ }
475
+
476
+ // Build response headers as NativeJSObject
477
+ auto responseHeaders = NativeJSObject{};
478
+ std::string mimeType;
479
+ for (const auto& [key, value] : result.headers) {
480
+ if (key == "__response_body__") continue;
481
+ responseHeaders.insert({key, value});
482
+ std::string lowerKey = key;
483
+ std::transform(lowerKey.begin(), lowerKey.end(), lowerKey.begin(), ::tolower);
484
+ if (lowerKey == "content-type") {
485
+ mimeType = value.substr(0, value.find(';'));
486
+ }
487
+ }
488
+
489
+ auto downloadResult = NativeJSObject{
490
+ {"uri", fileUri},
491
+ {"status", static_cast<double>(result.statusCode)},
492
+ {"headers", responseHeaders},
493
+ {"mimeType", mimeType.empty() ? std::string("") : mimeType}};
494
+
495
+ if (shouldComputeMd5) {
496
+ try {
497
+ std::ifstream stream(fileUri, std::ios::binary);
498
+ if (stream.good()) {
499
+ std::stringstream buffer;
500
+ buffer << stream.rdbuf();
501
+ downloadResult.insert({"md5", md5Hash(buffer.str())});
502
+ }
503
+ } catch (std::exception& e) {
504
+ TMERROR(std::string("[ExponentFileSystem] MD5 computation failed: ") + e.what());
505
+ }
506
+ }
507
+
508
+ promise->resolve(downloadResult);
509
+ }).detach();
510
+ });
511
+ }
512
+
513
+ Promise ExponentFileSystem::uploadAsync(
514
+ const std::string url,
515
+ const std::string fileUri,
516
+ NativeJSObject options,
517
+ double /* requestId */,
518
+ std::vector<utils::json::JsonContainer> headers) {
519
+ std::string httpMethod = getOrDefault(options, "httpMethod", std::string("POST"));
520
+
521
+ // Convert headers vector to std::map
522
+ std::map<std::string, std::string> requestHeaders;
523
+ for (auto header : headers) {
524
+ try {
525
+ auto key = header["key"].getValue<std::string>();
526
+ auto value = header["value"].getValue<std::string>();
527
+ requestHeaders[key] = value;
528
+ } catch (...) {
529
+ // Skip malformed header entries
530
+ }
531
+ }
532
+
533
+ return Promise(
534
+ [this, url, fileUri, httpMethod, requestHeaders](std::shared_ptr<Promise> promise) {
535
+ TMDEBUG(std::string("[ExponentFileSystem] uploadAsync: ") + fileUri + " -> " + url + " (" + httpMethod + ")");
536
+
537
+ // Run curl on a background thread to avoid blocking the JS thread.
538
+ auto curlWrapper = curlWrapper_.get();
539
+ std::thread([promise, curlWrapper, url, fileUri, httpMethod, requestHeaders]() {
540
+ auto result = curlWrapper->uploadFromFile(url, fileUri, httpMethod, requestHeaders);
541
+
542
+ if (!result.success) {
543
+ TMERROR(std::string("[ExponentFileSystem] uploadAsync failed: ") + result.errorMessage);
544
+ promise->reject(result.errorMessage);
545
+ return;
546
+ }
547
+
548
+ // Build response headers as NativeJSObject
549
+ auto responseHeaders = NativeJSObject{};
550
+ std::string mimeType;
551
+ std::string responseBody;
552
+ for (const auto& [key, value] : result.headers) {
553
+ if (key == "__response_body__") {
554
+ responseBody = value;
555
+ continue;
556
+ }
557
+ responseHeaders.insert({key, value});
558
+ std::string lowerKey = key;
559
+ std::transform(lowerKey.begin(), lowerKey.end(), lowerKey.begin(), ::tolower);
560
+ if (lowerKey == "content-type") {
561
+ mimeType = value.substr(0, value.find(';'));
562
+ }
563
+ }
564
+
565
+ auto uploadResult = NativeJSObject{
566
+ {"body", responseBody},
567
+ {"status", static_cast<double>(result.statusCode)},
568
+ {"headers", responseHeaders},
569
+ {"mimeType", mimeType.empty() ? std::string("") : mimeType}};
570
+
571
+ promise->resolve(uploadResult);
572
+ }).detach();
573
+ });
574
+ }
457
575
 
458
576
  std::uintmax_t ExponentFileSystem::getSize(const std::string& path) {
459
577
  if (std::filesystem::is_regular_file(path)) {
@@ -475,8 +593,9 @@ std::uintmax_t ExponentFileSystem::getSize(const std::string& path) {
475
593
  };
476
594
 
477
595
  void ExponentFileSystem::aggregateMethods(
478
- volta::react::MethodAggregator<volta::react::KeplerTurboModule>& methodAggregator) const noexcept {
479
- volta::react::KPLog.debug("methodAggregator");
596
+ com::amazon::kepler::turbomodule::MethodAggregator<com::amazon::kepler::turbomodule::KeplerTurboModule>&
597
+ methodAggregator) const noexcept {
598
+ TMDEBUG("methodAggregator");
480
599
 
481
600
  methodAggregator.addMethod("documentDirectory", 0, &ExponentFileSystem::documentDirectory);
482
601
  methodAggregator.addMethod("cacheDirectory", 0, &ExponentFileSystem::cacheDirectory);
@@ -494,5 +613,5 @@ void ExponentFileSystem::aggregateMethods(
494
613
  methodAggregator.addMethod("getFreeDiskStorageAsync", 0, &ExponentFileSystem::getFreeDiskStorageAsync);
495
614
  methodAggregator.addMethod("getTotalDiskCapacityAsync", 0, &ExponentFileSystem::getTotalDiskCapacityAsync);
496
615
  methodAggregator.addMethod("downloadAsync", 5, &ExponentFileSystem::downloadAsync);
497
- methodAggregator.addMethod("uploadAsync", 4, &ExponentFileSystem::uploadAsync);
616
+ methodAggregator.addMethod("uploadAsync", 5, &ExponentFileSystem::uploadAsync);
498
617
  }