feat(android): add advanced upstream tuning settings

This commit is contained in:
Dark_Avery 2026-03-23 02:42:08 +03:00
parent b5b6a8021e
commit faea437556
8 changed files with 182 additions and 2 deletions

View File

@ -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,
)
}

View File

@ -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<String>,
val logMaxMb: Double,
val bufferKb: Int,
val poolSize: Int,
val verbose: Boolean,
)

View File

@ -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"
}
}

View File

@ -17,6 +17,9 @@ object PythonProxyBridge {
config.host,
config.port,
config.dcIpList,
config.logMaxMb,
config.bufferKb,
config.poolSize,
config.verbose,
).toString()
}

View File

@ -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)

View File

@ -197,6 +197,48 @@
android:layout_marginTop="16dp"
android:text="@string/verbose_label" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="@string/log_max_mb_hint">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/logMaxMbInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="@string/buffer_kb_hint">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/bufferKbInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="@string/pool_size_hint">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/poolSizeInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
android:id="@+id/errorText"
android:layout_width="match_parent"

View File

@ -20,6 +20,9 @@
<string name="host_hint">Proxy host</string>
<string name="port_hint">Proxy port</string>
<string name="dc_ip_hint">DC to IP mappings (one DC:IP per line)</string>
<string name="log_max_mb_hint">Max log size before rotation (MB)</string>
<string name="buffer_kb_hint">Socket buffer size (KB)</string>
<string name="pool_size_hint">WS pool size per DC</string>
<string name="verbose_label">Verbose logging</string>
<string name="save_button">Save Settings</string>
<string name="start_button">Start Service</string>

View File

@ -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()