From 61c539fa6a77dad785106da0f1933dfa7b29d2fd Mon Sep 17 00:00:00 2001 From: Alan Lee Date: Thu, 10 Apr 2025 19:52:23 -0700 Subject: [PATCH] Convert FileIoHandler.java to Kotlin (#50612) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/50612 Java to Kotlin conversion Changelog: [Internal] Reviewed By: cortinico Differential Revision: D72742937 fbshipit-source-id: b72b51460555226fa7fe99f4ca5290f46c478291 --- .../ReactAndroid/api/ReactAndroid.api | 4 +- .../packagerconnection/FileIoHandler.java | 191 ------------------ .../react/packagerconnection/FileIoHandler.kt | 161 +++++++++++++++ 3 files changed, 163 insertions(+), 193 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/FileIoHandler.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/FileIoHandler.kt diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index c37d9f0fa03..149ba7a51ea 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -3238,9 +3238,9 @@ public abstract interface class com/facebook/react/modules/websocket/WebSocketMo public abstract fun onMessage (Lokio/ByteString;Lcom/facebook/react/bridge/WritableMap;)V } -public class com/facebook/react/packagerconnection/FileIoHandler : java/lang/Runnable { +public final class com/facebook/react/packagerconnection/FileIoHandler : java/lang/Runnable { public fun ()V - public fun handlers ()Ljava/util/Map; + public final fun handlers ()Ljava/util/Map; public fun run ()V } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/FileIoHandler.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/FileIoHandler.java deleted file mode 100644 index 8d32a92684a..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/FileIoHandler.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.packagerconnection; - -import android.os.Handler; -import android.os.Looper; -import android.util.Base64; -import androidx.annotation.Nullable; -import com.facebook.common.logging.FLog; -import com.facebook.infer.annotation.Nullsafe; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import org.json.JSONObject; - -@Nullsafe(Nullsafe.Mode.LOCAL) -public class FileIoHandler implements Runnable { - private static final String TAG = JSPackagerClient.class.getSimpleName(); - private static final long FILE_TTL = 30 * 1000; - - private static class TtlFileInputStream { - private final FileInputStream mStream; - private long mTtl; - - public TtlFileInputStream(String path) throws FileNotFoundException { - mStream = new FileInputStream(path); - mTtl = System.currentTimeMillis() + FILE_TTL; - } - - private void extendTtl() { - mTtl = System.currentTimeMillis() + FILE_TTL; - } - - public boolean expiredTtl() { - return System.currentTimeMillis() >= mTtl; - } - - public String read(int size) throws IOException { - extendTtl(); - byte[] buffer = new byte[size]; - int bytesRead = mStream.read(buffer); - return Base64.encodeToString(buffer, 0, bytesRead, Base64.DEFAULT); - } - - public void close() throws IOException { - mStream.close(); - } - } - ; - - private int mNextHandle; - private final Handler mHandler; - private final Map mOpenFiles; - private final Map mRequestHandlers; - - public FileIoHandler() { - mNextHandle = 1; - mHandler = new Handler(Looper.getMainLooper()); - mOpenFiles = new HashMap<>(); - mRequestHandlers = new HashMap<>(); - mRequestHandlers.put( - "fopen", - new RequestOnlyHandler() { - @Override - public void onRequest(@Nullable Object params, Responder responder) { - synchronized (mOpenFiles) { - try { - JSONObject paramsObj = (JSONObject) params; - if (paramsObj == null) { - throw new Exception( - "params must be an object { mode: string, filename: string }"); - } - String mode = paramsObj.optString("mode"); - if (mode == null) { - throw new Exception("missing params.mode"); - } - String filename = paramsObj.optString("filename"); - if (filename == null) { - throw new Exception("missing params.filename"); - } - if (!mode.equals("r")) { - throw new IllegalArgumentException("unsupported mode: " + mode); - } - - responder.respond(addOpenFile(filename)); - } catch (Exception e) { - responder.error(e.toString()); - } - } - } - }); - mRequestHandlers.put( - "fclose", - new RequestOnlyHandler() { - @Override - public void onRequest(@Nullable Object params, Responder responder) { - synchronized (mOpenFiles) { - try { - if (!(params instanceof Number)) { - throw new Exception("params must be a file handle"); - } - TtlFileInputStream stream = mOpenFiles.get(params); - if (stream == null) { - throw new Exception("invalid file handle, it might have timed out"); - } - - mOpenFiles.remove(params); - stream.close(); - responder.respond(""); - } catch (Exception e) { - responder.error(e.toString()); - } - } - } - }); - mRequestHandlers.put( - "fread", - new RequestOnlyHandler() { - @Override - public void onRequest(@Nullable Object params, Responder responder) { - synchronized (mOpenFiles) { - try { - JSONObject paramsObj = (JSONObject) params; - if (paramsObj == null) { - throw new Exception("params must be an object { file: handle, size: number }"); - } - int file = paramsObj.optInt("file"); - if (file == 0) { - throw new Exception("invalid or missing file handle"); - } - int size = paramsObj.optInt("size"); - if (size == 0) { - throw new Exception("invalid or missing read size"); - } - TtlFileInputStream stream = mOpenFiles.get(file); - if (stream == null) { - throw new Exception("invalid file handle, it might have timed out"); - } - - responder.respond(stream.read(size)); - } catch (Exception e) { - responder.error(e.toString()); - } - } - } - }); - } - - public Map handlers() { - return mRequestHandlers; - } - - private int addOpenFile(String filename) throws FileNotFoundException { - int handle = mNextHandle++; - mOpenFiles.put(handle, new TtlFileInputStream(filename)); - if (mOpenFiles.size() == 1) { - mHandler.postDelayed(FileIoHandler.this, FILE_TTL); - } - return handle; - } - - @Override - public void run() { - // clean up files that are past their expiry date - synchronized (mOpenFiles) { - Iterator i = mOpenFiles.values().iterator(); - while (i.hasNext()) { - TtlFileInputStream stream = i.next(); - if (stream.expiredTtl()) { - i.remove(); - try { - stream.close(); - } catch (IOException e) { - FLog.e(TAG, "closing expired file failed: " + e.toString()); - } - } - } - if (!mOpenFiles.isEmpty()) { - mHandler.postDelayed(this, FILE_TTL); - } - } - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/FileIoHandler.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/FileIoHandler.kt new file mode 100644 index 00000000000..475ee0e09e2 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/FileIoHandler.kt @@ -0,0 +1,161 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.packagerconnection + +import android.os.Handler +import android.os.Looper +import android.util.Base64 +import com.facebook.common.logging.FLog +import java.io.FileInputStream +import java.io.FileNotFoundException +import java.io.IOException +import org.json.JSONObject + +public class FileIoHandler : Runnable { + + private class TtlFileInputStream(path: String?) { + private val stream = FileInputStream(path) + private var ttl: Long = System.currentTimeMillis() + FILE_TTL + + private fun extendTtl() { + ttl = System.currentTimeMillis() + FILE_TTL + } + + fun expiredTtl(): Boolean = System.currentTimeMillis() >= ttl + + @Throws(IOException::class) + fun read(size: Int): String { + extendTtl() + val buffer = ByteArray(size) + val bytesRead = stream.read(buffer) + return Base64.encodeToString(buffer, 0, bytesRead, Base64.DEFAULT) + } + + @Throws(IOException::class) + fun close() { + stream.close() + } + } + + private var nextHandle = 1 + private val handler = Handler(Looper.getMainLooper()) + private val openFiles: MutableMap = mutableMapOf() + private val requestHandlers: MutableMap = mutableMapOf() + + init { + requestHandlers["fopen"] = + object : RequestOnlyHandler() { + override fun onRequest(params: Any?, responder: Responder) { + synchronized(openFiles) { + try { + val paramsObj = + params as JSONObject? + ?: throw Exception( + "params must be an object { mode: string, filename: string }") + val mode = paramsObj.optString("mode") ?: throw Exception("missing params.mode") + val filename = + paramsObj.optString("filename") ?: throw Exception("missing params.filename") + require(mode == "r") { "unsupported mode: $mode" } + + responder.respond(addOpenFile(filename)) + } catch (e: Exception) { + responder.error(e.toString()) + } + } + } + } + requestHandlers["fclose"] = + object : RequestOnlyHandler() { + override fun onRequest(params: Any?, responder: Responder) { + synchronized(openFiles) { + try { + if (params !is Number) { + throw Exception("params must be a file handle") + } + val stream = + openFiles[params] + ?: throw Exception("invalid file handle, it might have timed out") + + openFiles.remove(params) + stream.close() + responder.respond("") + } catch (e: Exception) { + responder.error(e.toString()) + } + } + } + } + requestHandlers["fread"] = + object : RequestOnlyHandler() { + override fun onRequest(params: Any?, responder: Responder) { + synchronized(openFiles) { + try { + val paramsObj = + params as JSONObject? + ?: throw Exception( + "params must be an object { file: handle, size: number }") + val file = paramsObj.optInt("file") + if (file == 0) { + throw Exception("invalid or missing file handle") + } + val size = paramsObj.optInt("size") + if (size == 0) { + throw Exception("invalid or missing read size") + } + val stream = + openFiles[file] + ?: throw Exception("invalid file handle, it might have timed out") + + responder.respond(stream.read(size)) + } catch (e: Exception) { + responder.error(e.toString()) + } + } + } + } + } + + public fun handlers(): Map = requestHandlers + + @Throws(FileNotFoundException::class) + private fun addOpenFile(filename: String): Int { + val handle = nextHandle++ + openFiles[handle] = TtlFileInputStream(filename) + if (openFiles.size == 1) { + handler.postDelayed(this@FileIoHandler, FILE_TTL) + } + return handle + } + + override fun run() { + // clean up files that are past their expiry date + synchronized(openFiles) { + openFiles.entries.removeAll { (_, stream) -> + if (stream.expiredTtl()) { + try { + stream.close() + } catch (e: IOException) { + FLog.e(TAG, "Failed to close expired file", e) + } + true + } else { + false + } + } + + if (openFiles.isNotEmpty()) { + handler.postDelayed(this, FILE_TTL) + } + } + } + + private companion object { + private val TAG: String = JSPackagerClient::class.java.simpleName + private const val FILE_TTL = 30_000L + } +}