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
This commit is contained in:
Alan Lee
2025-04-10 19:52:23 -07:00
committed by Facebook GitHub Bot
parent c51c4b1922
commit 61c539fa6a
3 changed files with 163 additions and 193 deletions
@@ -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 <init> ()V
public fun handlers ()Ljava/util/Map;
public final fun handlers ()Ljava/util/Map;
public fun run ()V
}
@@ -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<Integer, TtlFileInputStream> mOpenFiles;
private final Map<String, RequestHandler> 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<String, RequestHandler> 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<TtlFileInputStream> 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);
}
}
}
}
@@ -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<Int, TtlFileInputStream> = mutableMapOf()
private val requestHandlers: MutableMap<String, RequestHandler> = 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<String, RequestHandler> = 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
}
}