@@ -4,6 +4,7 @@ import android.app.Notification
44import android.app.PendingIntent
55import android.content.Intent
66import android.net.VpnService
7+ import android.net.Uri
78import android.os.Build
89import android.os.ParcelFileDescriptor
910import android.os.PowerManager
@@ -20,6 +21,7 @@ import com.masterdns.vpn.util.GlobalSettingsStore
2021import com.masterdns.vpn.util.VpnManager
2122import kotlinx.coroutines.*
2223import java.io.File
24+ import java.io.FileInputStream
2325import java.io.RandomAccessFile
2426import java.net.InetAddress
2527import java.net.InetSocketAddress
@@ -46,6 +48,8 @@ class MasterDnsVpnService : VpnService() {
4648 private var goClientJob: Job ? = null
4749 private var logTailJob: Job ? = null
4850 private var wakeLock: PowerManager .WakeLock ? = null
51+ private var mtuExportTargetUri: String? = null
52+ private var mtuTempOutputDir: File ? = null
4953 @Volatile
5054 private var isStopping = false
5155 @Volatile
@@ -102,24 +106,31 @@ class MasterDnsVpnService : VpnService() {
102106
103107 val configFile = File (configDir, " client_config.toml" )
104108 val resolversFile = File (configDir, " client_resolvers.txt" )
105- val advanced = parseAdvanced(profile.advancedJson).toMutableMap()
109+ mtuExportTargetUri = null
110+ mtuTempOutputDir = null
111+ val advanced = parseAdvanced(profile.advancedJson)
106112 val saveMtuToFile = advanced[" SAVE_MTU_SERVERS_TO_FILE" ].equals(" true" , ignoreCase = true )
107113 var runtimeProfile = profile
108114 if (saveMtuToFile) {
109- val mtuDir = File (getExternalFilesDir(null ), " masterdnsvpn_mtu" )
110- if (! mtuDir.exists()) {
111- mtuDir.mkdirs()
112- }
113- val fileName = advanced[" MTU_SERVERS_FILE_NAME" ]
115+ val configuredPath = advanced[" MTU_SERVERS_FILE_NAME" ]
114116 ?.trim()
115117 ?.ifBlank { " masterdnsvpn_success_test_{time}.log" }
116118 ? : " masterdnsvpn_success_test_{time}.log"
117- val cleanName = File (fileName).name
118- val outputPath = File (mtuDir, cleanName).absolutePath
119- advanced[" MTU_SERVERS_FILE_NAME" ] = outputPath
120- runtimeProfile = profile.copy(advancedJson = Gson ().toJson(advanced))
121- VpnManager .appendLog(" MTU results folder: ${mtuDir.absolutePath} " )
122- VpnManager .appendLog(" MTU results file pattern: $outputPath " )
119+ val exportUri = advanced[" MTU_EXPORT_URI" ]?.trim().orEmpty()
120+ if (exportUri.isNotBlank()) {
121+ val advancedMutable = advanced.toMutableMap()
122+ val safeName = File (configuredPath).name.ifBlank { " masterdnsvpn_success_test_{time}.log" }
123+ val tmpDir = File (filesDir, " mtu_exports" ).apply { mkdirs() }
124+ val tmpPath = File (tmpDir, safeName).absolutePath
125+ advancedMutable[" MTU_SERVERS_FILE_NAME" ] = tmpPath
126+ runtimeProfile = profile.copy(advancedJson = Gson ().toJson(advancedMutable))
127+ mtuExportTargetUri = exportUri
128+ mtuTempOutputDir = tmpDir
129+ VpnManager .appendLog(" MTU results temp path: $tmpPath " )
130+ VpnManager .appendLog(" MTU export destination selected via file manager" )
131+ } else {
132+ VpnManager .appendLog(" MTU results target: $configuredPath " )
133+ }
123134 }
124135
125136 configFile.writeText(
@@ -131,10 +142,10 @@ class MasterDnsVpnService : VpnService() {
131142 localDnsEnabledOverride = if (proxyMode) false else null
132143 )
133144 )
134- if (profile .resolvers.isNotBlank()) {
135- resolversFile.writeText(ConfigGenerator .generateResolvers(profile ))
145+ if (runtimeProfile .resolvers.isNotBlank()) {
146+ resolversFile.writeText(ConfigGenerator .generateResolvers(runtimeProfile ))
136147 } else if (! resolversFile.exists() || resolversFile.readText().isBlank()) {
137- resolversFile.writeText(ConfigGenerator .generateResolvers(profile ))
148+ resolversFile.writeText(ConfigGenerator .generateResolvers(runtimeProfile ))
138149 } else {
139150 VpnManager .appendLog(" Using existing client_resolvers.txt from app storage" )
140151 }
@@ -280,6 +291,7 @@ class MasterDnsVpnService : VpnService() {
280291 VpnManager .updateState(VpnManager .VpnState .DISCONNECTED )
281292 VpnManager .stopTrafficMonitor()
282293 VpnManager .appendLog(" VPN disconnected" )
294+ exportMtuResultsIfNeeded()
283295 releaseWakeLock()
284296
285297 runCatching {
@@ -377,6 +389,25 @@ class MasterDnsVpnService : VpnService() {
377389 }
378390 }
379391
392+ private fun exportMtuResultsIfNeeded () {
393+ val target = mtuExportTargetUri?.takeIf { it.isNotBlank() } ? : return
394+ val dir = mtuTempOutputDir ? : return
395+ val latest = dir.listFiles()
396+ ?.filter { it.isFile }
397+ ?.maxByOrNull { it.lastModified() }
398+ ? : return
399+ runCatching {
400+ val uri = Uri .parse(target)
401+ contentResolver.openOutputStream(uri, " wt" )?.use { out ->
402+ FileInputStream (latest).use { input -> input.copyTo(out ) }
403+ } ? : error(" Cannot open selected destination" )
404+ }.onSuccess {
405+ VpnManager .appendLog(" MTU results exported to selected destination" )
406+ }.onFailure {
407+ VpnManager .appendLog(" MTU export failed: ${it.message} " )
408+ }
409+ }
410+
380411 private suspend fun ensureSocksPortAvailable (port : Int ) {
381412 if (! isLocalPortInUse(port)) return
382413 VpnManager .appendLog(" SOCKS5 port $port is busy, attempting to free it..." )
0 commit comments