From faea4375569b04b74da6e9313e8891553ef51c1f Mon Sep 17 00:00:00 2001 From: Dark_Avery Date: Mon, 23 Mar 2026 02:42:08 +0300 Subject: [PATCH] feat(android): add advanced upstream tuning settings --- .../org/flowseal/tgwsproxy/MainActivity.kt | 6 +++ .../org/flowseal/tgwsproxy/ProxyConfig.kt | 50 ++++++++++++++++++ .../flowseal/tgwsproxy/ProxySettingsStore.kt | 20 ++++++++ .../flowseal/tgwsproxy/PythonProxyBridge.kt | 3 ++ .../src/main/python/android_proxy_bridge.py | 9 +++- .../app/src/main/res/layout/activity_main.xml | 42 +++++++++++++++ android/app/src/main/res/values/strings.xml | 3 ++ tests/test_android_proxy_bridge.py | 51 +++++++++++++++++++ 8 files changed, 182 insertions(+), 2 deletions(-) diff --git a/android/app/src/main/java/org/flowseal/tgwsproxy/MainActivity.kt b/android/app/src/main/java/org/flowseal/tgwsproxy/MainActivity.kt index 2a178b3..727be4f 100644 --- a/android/app/src/main/java/org/flowseal/tgwsproxy/MainActivity.kt +++ b/android/app/src/main/java/org/flowseal/tgwsproxy/MainActivity.kt @@ -108,6 +108,9 @@ class MainActivity : AppCompatActivity() { binding.hostInput.setText(config.host) binding.portInput.setText(config.portText) binding.dcIpInput.setText(config.dcIpText) + binding.logMaxMbInput.setText(config.logMaxMbText) + binding.bufferKbInput.setText(config.bufferKbText) + binding.poolSizeInput.setText(config.poolSizeText) binding.verboseSwitch.isChecked = config.verbose } @@ -116,6 +119,9 @@ class MainActivity : AppCompatActivity() { host = binding.hostInput.text?.toString().orEmpty(), portText = binding.portInput.text?.toString().orEmpty(), dcIpText = binding.dcIpInput.text?.toString().orEmpty(), + logMaxMbText = binding.logMaxMbInput.text?.toString().orEmpty(), + bufferKbText = binding.bufferKbInput.text?.toString().orEmpty(), + poolSizeText = binding.poolSizeInput.text?.toString().orEmpty(), verbose = binding.verboseSwitch.isChecked, ) } diff --git a/android/app/src/main/java/org/flowseal/tgwsproxy/ProxyConfig.kt b/android/app/src/main/java/org/flowseal/tgwsproxy/ProxyConfig.kt index a8ffcde..39cf065 100644 --- a/android/app/src/main/java/org/flowseal/tgwsproxy/ProxyConfig.kt +++ b/android/app/src/main/java/org/flowseal/tgwsproxy/ProxyConfig.kt @@ -4,6 +4,9 @@ data class ProxyConfig( val host: String = DEFAULT_HOST, val portText: String = DEFAULT_PORT.toString(), val dcIpText: String = DEFAULT_DC_IP_LINES.joinToString("\n"), + val logMaxMbText: String = formatDecimal(DEFAULT_LOG_MAX_MB), + val bufferKbText: String = DEFAULT_BUFFER_KB.toString(), + val poolSizeText: String = DEFAULT_POOL_SIZE.toString(), val verbose: Boolean = false, ) { fun validate(): ValidationResult { @@ -37,11 +40,44 @@ data class ProxyConfig( } } + val logMaxMbValue = logMaxMbText.trim().toDoubleOrNull() + ?: return ValidationResult( + errorMessage = "Размер лог-файла должен быть числом." + ) + if (logMaxMbValue <= 0.0) { + return ValidationResult( + errorMessage = "Размер лог-файла должен быть больше нуля." + ) + } + + val bufferKbValue = bufferKbText.trim().toIntOrNull() + ?: return ValidationResult( + errorMessage = "Буфер сокета должен быть целым числом." + ) + if (bufferKbValue < 4) { + return ValidationResult( + errorMessage = "Буфер сокета должен быть не меньше 4 KB." + ) + } + + val poolSizeValue = poolSizeText.trim().toIntOrNull() + ?: return ValidationResult( + errorMessage = "Размер WS pool должен быть целым числом." + ) + if (poolSizeValue < 0) { + return ValidationResult( + errorMessage = "Размер WS pool не может быть отрицательным." + ) + } + return ValidationResult( normalized = NormalizedProxyConfig( host = hostValue, port = portValue, dcIpList = lines, + logMaxMb = logMaxMbValue, + bufferKb = bufferKbValue, + poolSize = poolSizeValue, verbose = verbose, ) ) @@ -50,11 +86,22 @@ data class ProxyConfig( companion object { const val DEFAULT_HOST = "127.0.0.1" const val DEFAULT_PORT = 1080 + const val DEFAULT_LOG_MAX_MB = 5.0 + const val DEFAULT_BUFFER_KB = 256 + const val DEFAULT_POOL_SIZE = 4 val DEFAULT_DC_IP_LINES = listOf( "2:149.154.167.220", "4:149.154.167.220", ) + fun formatDecimal(value: Double): String { + return if (value % 1.0 == 0.0) { + value.toInt().toString() + } else { + value.toString() + } + } + private fun isIpv4Address(value: String): Boolean { val octets = value.split(".") if (octets.size != 4) { @@ -80,5 +127,8 @@ data class NormalizedProxyConfig( val host: String, val port: Int, val dcIpList: List, + val logMaxMb: Double, + val bufferKb: Int, + val poolSize: Int, val verbose: Boolean, ) diff --git a/android/app/src/main/java/org/flowseal/tgwsproxy/ProxySettingsStore.kt b/android/app/src/main/java/org/flowseal/tgwsproxy/ProxySettingsStore.kt index e93249d..9a08cc9 100644 --- a/android/app/src/main/java/org/flowseal/tgwsproxy/ProxySettingsStore.kt +++ b/android/app/src/main/java/org/flowseal/tgwsproxy/ProxySettingsStore.kt @@ -13,6 +13,20 @@ class ProxySettingsStore(context: Context) { KEY_DC_IP_TEXT, ProxyConfig.DEFAULT_DC_IP_LINES.joinToString("\n"), ).orEmpty(), + logMaxMbText = ProxyConfig.formatDecimal( + preferences.getFloat( + KEY_LOG_MAX_MB, + ProxyConfig.DEFAULT_LOG_MAX_MB.toFloat(), + ).toDouble() + ), + bufferKbText = preferences.getInt( + KEY_BUFFER_KB, + ProxyConfig.DEFAULT_BUFFER_KB, + ).toString(), + poolSizeText = preferences.getInt( + KEY_POOL_SIZE, + ProxyConfig.DEFAULT_POOL_SIZE, + ).toString(), verbose = preferences.getBoolean(KEY_VERBOSE, false), ) } @@ -22,6 +36,9 @@ class ProxySettingsStore(context: Context) { .putString(KEY_HOST, config.host) .putInt(KEY_PORT, config.port) .putString(KEY_DC_IP_TEXT, config.dcIpList.joinToString("\n")) + .putFloat(KEY_LOG_MAX_MB, config.logMaxMb.toFloat()) + .putInt(KEY_BUFFER_KB, config.bufferKb) + .putInt(KEY_POOL_SIZE, config.poolSize) .putBoolean(KEY_VERBOSE, config.verbose) .apply() } @@ -31,6 +48,9 @@ class ProxySettingsStore(context: Context) { private const val KEY_HOST = "host" private const val KEY_PORT = "port" private const val KEY_DC_IP_TEXT = "dc_ip_text" + private const val KEY_LOG_MAX_MB = "log_max_mb" + private const val KEY_BUFFER_KB = "buf_kb" + private const val KEY_POOL_SIZE = "pool_size" private const val KEY_VERBOSE = "verbose" } } diff --git a/android/app/src/main/java/org/flowseal/tgwsproxy/PythonProxyBridge.kt b/android/app/src/main/java/org/flowseal/tgwsproxy/PythonProxyBridge.kt index 55ff549..e6e4ce9 100644 --- a/android/app/src/main/java/org/flowseal/tgwsproxy/PythonProxyBridge.kt +++ b/android/app/src/main/java/org/flowseal/tgwsproxy/PythonProxyBridge.kt @@ -17,6 +17,9 @@ object PythonProxyBridge { config.host, config.port, config.dcIpList, + config.logMaxMb, + config.bufferKb, + config.poolSize, config.verbose, ).toString() } diff --git a/android/app/src/main/python/android_proxy_bridge.py b/android/app/src/main/python/android_proxy_bridge.py index 910d1fb..b83201e 100644 --- a/android/app/src/main/python/android_proxy_bridge.py +++ b/android/app/src/main/python/android_proxy_bridge.py @@ -41,7 +41,9 @@ def _normalize_dc_ip_list(dc_ip_list: Iterable[object]) -> list[str]: def start_proxy(app_dir: str, host: str, port: int, - dc_ip_list: Iterable[object], verbose: bool = False) -> str: + dc_ip_list: Iterable[object], log_max_mb: float = 5.0, + buf_kb: int = 256, pool_size: int = 4, + verbose: bool = False) -> str: global _RUNTIME, _LAST_ERROR with _RUNTIME_LOCK: @@ -59,12 +61,15 @@ def start_proxy(app_dir: str, host: str, port: int, on_error=_remember_error, ) runtime.reset_log_file() - runtime.setup_logging(verbose=verbose) + runtime.setup_logging(verbose=verbose, log_max_mb=float(log_max_mb)) config = { "host": host, "port": int(port), "dc_ip": _normalize_dc_ip_list(dc_ip_list), + "log_max_mb": float(log_max_mb), + "buf_kb": int(buf_kb), + "pool_size": int(pool_size), "verbose": bool(verbose), } runtime.save_config(config) diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml index ead3c2a..db5aed8 100644 --- a/android/app/src/main/res/layout/activity_main.xml +++ b/android/app/src/main/res/layout/activity_main.xml @@ -197,6 +197,48 @@ android:layout_marginTop="16dp" android:text="@string/verbose_label" /> + + + + + + + + + + + + + + + Proxy host Proxy port DC to IP mappings (one DC:IP per line) + Max log size before rotation (MB) + Socket buffer size (KB) + WS pool size per DC Verbose logging Save Settings Start Service diff --git a/tests/test_android_proxy_bridge.py b/tests/test_android_proxy_bridge.py index d7f159b..75cdb2b 100644 --- a/tests/test_android_proxy_bridge.py +++ b/tests/test_android_proxy_bridge.py @@ -73,6 +73,57 @@ class AndroidProxyBridgeTests(unittest.TestCase): "4:149.154.167.220", ]) + def test_start_proxy_saves_advanced_runtime_config(self): + captured = {} + + class FakeRuntime: + def __init__(self, *args, **kwargs): + captured["runtime_init"] = kwargs + self.log_file = Path("/tmp/proxy.log") + + def reset_log_file(self): + captured["reset_log_file"] = True + + def setup_logging(self, verbose=False, log_max_mb=5): + captured["verbose"] = verbose + captured["log_max_mb"] = log_max_mb + + def save_config(self, config): + captured["config"] = dict(config) + + def start_proxy(self, config): + captured["start_proxy"] = dict(config) + return True + + def is_proxy_running(self): + return True + + def stop_proxy(self): + captured["stop_proxy"] = True + + original_runtime = android_proxy_bridge.ProxyAppRuntime + try: + android_proxy_bridge.ProxyAppRuntime = FakeRuntime + log_path = android_proxy_bridge.start_proxy( + "/tmp/app", + "127.0.0.1", + 1080, + ["2:149.154.167.220"], + 7.0, + 512, + 6, + True, + ) + finally: + android_proxy_bridge.ProxyAppRuntime = original_runtime + + self.assertEqual(log_path, "/tmp/proxy.log") + self.assertEqual(captured["config"]["log_max_mb"], 7.0) + self.assertEqual(captured["config"]["buf_kb"], 512) + self.assertEqual(captured["config"]["pool_size"], 6) + self.assertEqual(captured["log_max_mb"], 7.0) + self.assertTrue(captured["verbose"]) + if __name__ == "__main__": unittest.main()