Skip to content

Commit 303b642

Browse files
committed
Refactor subscription import & matching logic
1 parent 80eec03 commit 303b642

File tree

1 file changed

+73
-31
lines changed

1 file changed

+73
-31
lines changed

V2rayNG/app/src/main/java/com/v2ray/ang/handler/AngConfigManager.kt

Lines changed: 73 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -221,21 +221,14 @@ object AngConfigManager {
221221
if (servers == null) {
222222
return 0
223223
}
224-
val removedSelectedServer =
225-
if (!TextUtils.isEmpty(subid) && !append) {
226-
MmkvManager.decodeServerConfig(
227-
MmkvManager.getSelectServer().orEmpty()
228-
)?.let {
229-
if (it.subscriptionId == subid) {
230-
return@let it
231-
}
232-
return@let null
233-
}
234-
} else {
235-
null
236-
}
237-
if (!append) {
238-
MmkvManager.removeServerViaSubid(subid)
224+
// Find the currently selected server that matches the subscription ID
225+
val removedSelected = if (subid.isNotBlank() && !append) {
226+
MmkvManager.getSelectServer()
227+
.takeIf { it?.isNotBlank() == true }
228+
?.let { MmkvManager.decodeServerConfig(it) }
229+
?.takeIf { it.subscriptionId == subid }
230+
} else {
231+
null
239232
}
240233

241234
val subItem = MmkvManager.decodeSubscription(subid)
@@ -254,18 +247,12 @@ object AngConfigManager {
254247

255248
// Batch save all parsed configs (only one serverList read/write)
256249
if (configs.isNotEmpty()) {
257-
val keys = batchSaveConfigs(configs, subid)
258-
259-
// Handle removed selected server
260-
removedSelectedServer?.let { removed ->
261-
val matchKey = keys.find { key ->
262-
val savedConfig = MmkvManager.decodeServerConfig(key)
263-
savedConfig != null &&
264-
savedConfig.server == removed.server &&
265-
savedConfig.serverPort == removed.serverPort
266-
}
267-
matchKey?.let { MmkvManager.setSelectServer(it) }
250+
if (!append) {
251+
MmkvManager.removeServerViaSubid(subid)
268252
}
253+
val keyToProfile = batchSaveConfigs(configs, subid)
254+
val matchKey = findMatchedProfileKey(keyToProfile, removedSelected)
255+
matchKey?.let { MmkvManager.setSelectServer(it) }
269256
}
270257

271258
return configs.size
@@ -281,10 +268,10 @@ object AngConfigManager {
281268
*
282269
* @param configs The list of ProfileItem to save.
283270
* @param subid The subscription ID.
284-
* @return The list of generated keys.
271+
* @return Map of generated keys to their corresponding ProfileItem.
285272
*/
286-
private fun batchSaveConfigs(configs: List<ProfileItem>, subid: String): List<String> {
287-
val keys = mutableListOf<String>()
273+
private fun batchSaveConfigs(configs: List<ProfileItem>, subid: String): Map<String, ProfileItem> {
274+
val keyToProfile = mutableMapOf<String, ProfileItem>()
288275

289276
// Read serverList once
290277
val serverList = MmkvManager.decodeServerList(subid)
@@ -302,12 +289,67 @@ object AngConfigManager {
302289
needSetSelected = false
303290
}
304291
}
305-
keys.add(key)
292+
keyToProfile[key] = config
306293
}
307294

308295
// Write serverList once
309296
MmkvManager.encodeServerList(serverList, subid)
310-
return keys
297+
return keyToProfile
298+
}
299+
300+
/**
301+
* Finds a matched profile key from the given key-profile map using multi-level matching.
302+
* Matching priority (from highest to lowest):
303+
* 1. Exact match: server + port + password
304+
* 2. Match by remarks (exact match)
305+
* 3. Match by server + port
306+
* 4. Match by server only
307+
*
308+
* @param keyToProfile Map of server keys to their ProfileItem
309+
* @param target Target profile to match
310+
* @return Matched key or null
311+
*/
312+
private fun findMatchedProfileKey(keyToProfile: Map<String, ProfileItem>, target: ProfileItem?): String? {
313+
if (keyToProfile.isEmpty() || target == null) return null
314+
315+
// Level 1: Match by remarks
316+
if (target.remarks.isNotBlank()) {
317+
keyToProfile.entries.firstOrNull { (_, saved) ->
318+
isSameText(saved.remarks, target.remarks)
319+
}?.key?.let { return it }
320+
}
321+
322+
// Level 2: Exact match (server + port + password)
323+
keyToProfile.entries.firstOrNull { (_, saved) ->
324+
isSameText(saved.server, target.server) &&
325+
isSameText(saved.serverPort, target.serverPort) &&
326+
isSameText(saved.password, target.password)
327+
}?.key?.let { return it }
328+
329+
// Level 3: Match by server + port
330+
keyToProfile.entries.firstOrNull { (_, saved) ->
331+
isSameText(saved.server, target.server) &&
332+
isSameText(saved.serverPort, target.serverPort)
333+
}?.key?.let { return it }
334+
335+
// Level 4: Match by server only
336+
keyToProfile.entries.firstOrNull { (_, saved) ->
337+
isSameText(saved.server, target.server)
338+
}?.key?.let { return it }
339+
340+
return null
341+
}
342+
343+
/**
344+
* Case-insensitive trimmed string comparison.
345+
*
346+
* @param left First string
347+
* @param right Second string
348+
* @return True if both are non-empty and equal (case-insensitive, trimmed)
349+
*/
350+
private fun isSameText(left: String?, right: String?): Boolean {
351+
if (left.isNullOrBlank() || right.isNullOrBlank()) return false
352+
return left.trim().equals(right.trim(), ignoreCase = true)
311353
}
312354

313355
/**

0 commit comments

Comments
 (0)