ReviewOS

also looking at this

craft-native/craft

feat(android): downloadFile and saveFile in Zig — ratchet 69 → 67

#176
Merged glennmichael123 wants to merge feat/android-files into main
6 files +477 -3
packages/zig/src/android_dispatch.zigmodified+72-1
Changes to packages/zig/src/android_dispatch.zig
@@ -53,6 +53,7 @@ const contacts = ;
5353const widgets = ;
5454const shortcuts = ;
5555const screen = ;
56const files = ;
5657const main_thread = ;
5758
5859const Jni = jni.Jni;
@@ -347,6 +348,16 @@ const natives = [_]jni.JNINativeMethod{
347348 .signature = "(Landroid/app/Activity;Z)Z",
348349 .fnPtr = @ptrCast(&nativeSetKeepAwake),
349350 },
351 .{
352 .name = "nativeDownloadFile",
353 .signature = "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;)Z",
354 .fnPtr = @ptrCast(&nativeDownloadFile),
355 },
356 .{
357 .name = "nativeSaveFile",
358 .signature = "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;)Z",
359 .fnPtr = @ptrCast(&nativeSaveFile),
360 },
350361};
351362
352363/// How binding went. A value rather than a log line, so every path is
@@ -1401,6 +1412,66 @@ fn nativeSetKeepAwake(
14011412 return jni.JNI_TRUE;
14021413}
14031414
1415/// `nativeDownloadFile(activity, url, filename)`.
1416///
1417/// Resolves with the download id as a JSON *string*, because the shim
1418/// interpolated its `Long` into a quoted JavaScript string and the page has
1419/// always received text.
1420fn nativeDownloadFile(
1421 env: jni.JNIEnv,
1422 _: jni.jobject,
1423 activity: jni.jobject,
1424 url: jni.jstring,
1425 filename: jni.jstring,
1426) callconv(.c) jni.jboolean {
1427 const j = Jni.init(env);
1428 var arena = std.heap.ArenaAllocator.init(backing);
1429 defer arena.deinit();
1430 const allocator = arena.allocator();
1431
1432 const url_text = j.stringToUtf8(allocator, url) catch return jni.JNI_FALSE;
1433 const name_text = j.stringToUtf8(allocator, filename) catch return jni.JNI_FALSE;
1434
1435 const id = files.download(j, allocator, activity, url_text, name_text) catch |err| {
1436 calendar.rejectOn(allocator, files.download_reject_global, @errorName(err)) catch {};
1437 return jni.JNI_TRUE;
1438 };
1439
1440 var buf: [24]u8 = undefined;
1441 const digits = std.fmt.bufPrint(&buf, "{d}", .{id}) catch return jni.JNI_FALSE;
1442 const payload = files.jsonString(allocator, digits) catch return jni.JNI_FALSE;
1443 events.settle(allocator, files.download_resolve_global, payload) catch return jni.JNI_FALSE;
1444 return jni.JNI_TRUE;
1445}
1446
1447/// `nativeSaveFile(activity, data, filename)`.
1448fn nativeSaveFile(
1449 env: jni.JNIEnv,
1450 _: jni.jobject,
1451 activity: jni.jobject,
1452 data: jni.jstring,
1453 filename: jni.jstring,
1454) callconv(.c) jni.jboolean {
1455 const j = Jni.init(env);
1456 var arena = std.heap.ArenaAllocator.init(backing);
1457 defer arena.deinit();
1458 const allocator = arena.allocator();
1459
1460 const data_text = j.stringToUtf8(allocator, data) catch return jni.JNI_FALSE;
1461 const name_text = j.stringToUtf8(allocator, filename) catch return jni.JNI_FALSE;
1462
1463 // The plan can be `nothing`, and the shim still resolves with the path.
1464 // Reproduced rather than corrected — see #175.
1465 const path = files.save(j, allocator, activity, name_text, files.planFor(data_text)) catch |err| {
1466 calendar.rejectOn(allocator, files.save_reject_global, @errorName(err)) catch {};
1467 return jni.JNI_TRUE;
1468 };
1469
1470 const payload = files.jsonString(allocator, path) catch return jni.JNI_FALSE;
1471 events.settle(allocator, files.save_resolve_global, payload) catch return jni.JNI_FALSE;
1472 return jni.JNI_TRUE;
1473}
1474
14041475// =============================================================================
14051476// Tests
14061477// =============================================================================
@@ -1512,7 +1583,7 @@ test "the registered natives name methods the Kotlin actually declares" {
15121583 // A descriptor is checked by the JVM at registration, so a wrong one fails
15131584 // at load rather than at call — but only if the *name* matches something.
15141585 // These two strings are the contract with CraftBridge.kt.
1515 try testing.expectEqual(@as(usize, 35), natives.len);
1586 try testing.expectEqual(@as(usize, 37), natives.len);
15161587 try testing.expectEqualStrings("nativeGetDeviceInfo", std.mem.span(natives[0].name));
15171588
15181589 // And the class they bind to is the fixed one, not the templated bridge.