From 59688cf79f3eabde7b600bfe0b47651675a1ce5a Mon Sep 17 00:00:00 2001 From: bannedbook Date: Sat, 30 May 2020 21:56:14 +0800 Subject: [PATCH] update --- build.gradle | 6 +- .../com/github/shadowsocks/bg/BaseService.kt | 2 +- .../github/shadowsocks/bg/ProxyInstance.kt | 22 ++--- .../github/shadowsocks/bg/TrafficMonitor.kt | 4 +- .../github/shadowsocks/bg/V2ProxyInstance.kt | 30 ++++++ .../github/shadowsocks/bg/V2RayVpnService.kt | 14 ++- .../github/shadowsocks/bg/V2TrafficMonitor.kt | 35 +++++++ .../github/shadowsocks/database/Profile.kt | 90 +++++++++++++++-- .../shadowsocks/database/ProfileManager.kt | 96 ++++++++++++++++++- .../shadowsocks/preference/DataStore.kt | 3 +- .../subscription/SubscriptionService.kt | 5 +- core/src/main/res/values/strings.xml | 1 + gitupdate.bat | 2 +- .../github/shadowsocks/ProfilesFragment.kt | 38 ++++++-- 14 files changed, 301 insertions(+), 47 deletions(-) create mode 100644 core/src/main/java/com/github/shadowsocks/bg/V2ProxyInstance.kt create mode 100644 core/src/main/java/com/github/shadowsocks/bg/V2TrafficMonitor.kt diff --git a/build.gradle b/build.gradle index 61d117f..c57bd04 100644 --- a/build.gradle +++ b/build.gradle @@ -14,8 +14,8 @@ buildscript { junitVersion = '4.13' androidTestVersion = '1.2.0' androidEspressoVersion = '3.2.0' - versionCode = 5000788 - versionName = '5.1.3-nightly' + versionCode = 5000808 + versionName = '5.1.4-nightly' resConfigs = ['ar', 'es', 'fa', 'fr', 'ja', 'ko', 'ru', 'tr', 'zh-rCN', 'zh-rTW'] } @@ -25,7 +25,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.0.0-rc01' + classpath 'com.android.tools.build:gradle:4.0.0' classpath 'com.github.ben-manes:gradle-versions-plugin:0.27.0' classpath 'com.google.android.gms:oss-licenses-plugin:0.9.5' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.0.0-beta04' diff --git a/core/src/main/java/com/github/shadowsocks/bg/BaseService.kt b/core/src/main/java/com/github/shadowsocks/bg/BaseService.kt index c92a2c4..b53c7dd 100644 --- a/core/src/main/java/com/github/shadowsocks/bg/BaseService.kt +++ b/core/src/main/java/com/github/shadowsocks/bg/BaseService.kt @@ -341,7 +341,7 @@ object BaseService { } data.notification = createNotification(profile.formattedName) - Core.analytics.logEvent("start", bundleOf(Pair(FirebaseAnalytics.Param.METHOD, tag))) + //Core.analytics.logEvent("start", bundleOf(Pair(FirebaseAnalytics.Param.METHOD, tag))) data.changeState(State.Connecting) data.connectingJob = GlobalScope.launch(Dispatchers.Main) { diff --git a/core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt b/core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt index 39c021a..40f1cd2 100644 --- a/core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt +++ b/core/src/main/java/com/github/shadowsocks/bg/ProxyInstance.kt @@ -21,8 +21,7 @@ package com.github.shadowsocks.bg import android.content.Context -import android.util.Base64 -import com.github.shadowsocks.Core +import android.util.Log import com.github.shadowsocks.acl.Acl import com.github.shadowsocks.acl.AclSyncer import com.github.shadowsocks.database.Profile @@ -31,26 +30,23 @@ import com.github.shadowsocks.plugin.PluginConfiguration import com.github.shadowsocks.plugin.PluginManager import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.utils.parseNumericAddress -import com.github.shadowsocks.utils.signaturesCompat -import com.github.shadowsocks.utils.useCancellable import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext import java.io.File import java.io.IOException -import java.net.* -import java.security.MessageDigest +import java.net.Inet4Address +import java.net.Inet6Address +import java.net.UnknownHostException /** * This class sets up environment for ss-local. */ -class ProxyInstance(val profile: Profile, private val route: String = profile.route) { +open class ProxyInstance(open val profile: Profile, private val route: String = profile.route) { private var configFile: File? = null var trafficMonitor: TrafficMonitor? = null val plugin by lazy { PluginManager.init(PluginConfiguration(profile.plugin ?: "")) } private var scheduleConfigUpdate = false - suspend fun init(service: BaseService.Interface, hosts: HostsFile) { + open suspend fun init(service: BaseService.Interface, hosts: HostsFile) { // it's hard to resolve DNS on a specific interface so we'll do it here if (profile.host.parseNumericAddress() == null) { @@ -79,7 +75,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro * Sensitive shadowsocks configuration file requires extra protection. It may be stored in encrypted storage or * device storage, depending on which is currently available. */ - fun start(service: BaseService.Interface, stat: File, configFile: File, extraFlag: String? = null) { + open fun start(service: BaseService.Interface, stat: File, configFile: File, extraFlag: String? = null) { trafficMonitor = TrafficMonitor(stat) this.configFile = configFile @@ -109,12 +105,12 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro service.data.processes!!.start(cmd) } - fun scheduleUpdate() { + open fun scheduleUpdate() { if (route !in arrayOf(Acl.ALL, Acl.CUSTOM_RULES)) AclSyncer.schedule(route) if (scheduleConfigUpdate) RemoteConfig.fetchAsync() } - fun shutdown(scope: CoroutineScope) { + open fun shutdown(scope: CoroutineScope) { trafficMonitor?.apply { thread.shutdown(scope) persistStats(profile.id) // Make sure update total traffic when stopping the runner diff --git a/core/src/main/java/com/github/shadowsocks/bg/TrafficMonitor.kt b/core/src/main/java/com/github/shadowsocks/bg/TrafficMonitor.kt index 621ee8f..112312c 100644 --- a/core/src/main/java/com/github/shadowsocks/bg/TrafficMonitor.kt +++ b/core/src/main/java/com/github/shadowsocks/bg/TrafficMonitor.kt @@ -32,7 +32,7 @@ import java.io.IOException import java.nio.ByteBuffer import java.nio.ByteOrder -class TrafficMonitor(statFile: File) { +open class TrafficMonitor(statFile: File) { val thread = object : LocalSocketListener("TrafficMonitor-" + statFile.name, statFile) { private val buffer = ByteArray(16) private val stat = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN) @@ -57,7 +57,7 @@ class TrafficMonitor(statFile: File) { private var dirty = false private var persisted: TrafficStats? = null - fun requestUpdate(): Pair { + open fun requestUpdate(): Pair { val now = SystemClock.elapsedRealtime() val delta = now - timestampLast timestampLast = now diff --git a/core/src/main/java/com/github/shadowsocks/bg/V2ProxyInstance.kt b/core/src/main/java/com/github/shadowsocks/bg/V2ProxyInstance.kt new file mode 100644 index 0000000..37b1513 --- /dev/null +++ b/core/src/main/java/com/github/shadowsocks/bg/V2ProxyInstance.kt @@ -0,0 +1,30 @@ +package com.github.shadowsocks.bg + +import com.github.shadowsocks.Core +import com.github.shadowsocks.database.Profile +import com.github.shadowsocks.net.HostsFile +import kotlinx.coroutines.CoroutineScope +import libv2ray.V2RayPoint +import java.io.File + +class V2ProxyInstance(override val profile: Profile, private val route: String = profile.route) : ProxyInstance(profile,route) { + private var v2rayPoint: V2RayPoint? =null + constructor(v2Point: V2RayPoint,profile: Profile, route: String) : this(profile,route) { + this.v2rayPoint=v2Point + trafficMonitor = V2TrafficMonitor(File(Core.deviceStorage.noBackupFilesDir, "stat_main"),v2rayPoint!!) + } + + override suspend fun init(service: BaseService.Interface, hosts: HostsFile) { + } + override fun start(service: BaseService.Interface, stat: File, configFile: File, extraFlag: String?) { + } + override fun scheduleUpdate() { + } + override fun shutdown(scope: CoroutineScope) { + trafficMonitor?.apply { + thread.shutdown(scope) + persistStats(profile.id) // Make sure update total traffic when stopping the runner + } + trafficMonitor = null + } +} \ No newline at end of file diff --git a/core/src/main/java/com/github/shadowsocks/bg/V2RayVpnService.kt b/core/src/main/java/com/github/shadowsocks/bg/V2RayVpnService.kt index 921f5c9..91ac0f5 100644 --- a/core/src/main/java/com/github/shadowsocks/bg/V2RayVpnService.kt +++ b/core/src/main/java/com/github/shadowsocks/bg/V2RayVpnService.kt @@ -132,6 +132,8 @@ class V2RayVpnService : VpnService() , BaseService.Interface{ data.connectingJob = GlobalScope.launch(Dispatchers.Main) { try { activeProfile = ProfileManager.getProfile(DataStore.profileId)!! + val proxy = V2ProxyInstance(v2rayPoint,activeProfile,activeProfile.route) + data.proxy = proxy genStoreV2rayConfig() startV2ray() } catch (_: CancellationException) { @@ -324,12 +326,15 @@ class V2RayVpnService : VpnService() , BaseService.Interface{ try { v2rayPoint.runLoop() } catch (e: Exception) { - Log.d(packageName, e.toString()) + Log.e(packageName, e.toString()) + e.printStackTrace() } if (v2rayPoint.isRunning) { + Log.e(packageName, "v2rayPoint isRunning") data.changeState(BaseService.State.Connected) } else { + Log.e(packageName, "v2rayPoint is not Running") //MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_START_FAILURE, "") //cancelNotification() } @@ -351,6 +356,13 @@ class V2RayVpnService : VpnService() , BaseService.Interface{ } data.notification?.destroy() data.notification = null + val ids = listOfNotNull(data.proxy, data.udpFallback).map { + it.shutdown(this) + it.profile.id + } + data.binder.trafficPersisted(ids) + data.proxy = null + data.udpFallback = null // change the state data.changeState(BaseService.State.Stopped, msg) diff --git a/core/src/main/java/com/github/shadowsocks/bg/V2TrafficMonitor.kt b/core/src/main/java/com/github/shadowsocks/bg/V2TrafficMonitor.kt new file mode 100644 index 0000000..2090ded --- /dev/null +++ b/core/src/main/java/com/github/shadowsocks/bg/V2TrafficMonitor.kt @@ -0,0 +1,35 @@ +package com.github.shadowsocks.bg + +import com.github.shadowsocks.aidl.TrafficStats +import libv2ray.V2RayPoint +import rx.Observable +import java.io.File + +class V2TrafficMonitor(statFile: File): TrafficMonitor(statFile) { + private var v2rayPoint: V2RayPoint? =null + + constructor(statFile: File,v2Point: V2RayPoint) : this(statFile) { + this.v2rayPoint=v2Point + } + + override fun requestUpdate(): Pair { + var updated = false + if (v2rayPoint!!.isRunning ) { + Observable.interval(3, java.util.concurrent.TimeUnit.SECONDS) + .subscribe { + val uplink = v2rayPoint!!.queryStats("socks", "uplink") + val downlink = v2rayPoint!!.queryStats("socks", "downlink") + val zero_speed = (uplink == 0L && downlink == 0L) + if (!zero_speed ) { + current.txTotal+=uplink + current.txRate = uplink / 3 + current.rxTotal+=downlink + current.rxRate = downlink / 3 + } + } + } + if (current.txRate>0 || current.rxRate>0) updated = true + return Pair(current, updated) + } + +} \ No newline at end of file diff --git a/core/src/main/java/com/github/shadowsocks/database/Profile.kt b/core/src/main/java/com/github/shadowsocks/database/Profile.kt index 6b5ab05..0aa83e7 100644 --- a/core/src/main/java/com/github/shadowsocks/database/Profile.kt +++ b/core/src/main/java/com/github/shadowsocks/database/Profile.kt @@ -128,10 +128,80 @@ data class Profile( private val decodedPattern_ssr_protocolparam = "(?i)(.*)[?&]protoparam=([A-Za-z0-9_=-]*)(.*)".toRegex() private val decodedPattern_ssr_groupparam = "(?i)(.*)[?&]group=([A-Za-z0-9_=-]*)(.*)".toRegex() - private val pattern_vmess = "(?i)vmess://([A-Za-z0-9_=-]+)".toRegex() + private val pattern_vmess = "(?i)vmess://(.*)".toRegex() private fun base64Decode(data: String) = String(Base64.decode(data.replace("=", ""), Base64.URL_SAFE), Charsets.UTF_8) + /** + * base64 decode + */ + fun decodeForVmess(text: String): String { + try { + return Base64.decode(text, Base64.NO_WRAP).toString(charset("UTF-8")) + } catch (e: Exception) { + e.printStackTrace() + return "" + } + } + + fun profileFromVmessBean(vmess: VmessBean, profile:Profile): Profile { + profile.profileType = "vmess" + //profile.id=vmess.guid.toLong() + profile.remoteDns=vmess.remoteDns + profile.host=vmess.address + profile.alterId=vmess.alterId + profile.headerType=vmess.headerType + profile.password=vmess.id + profile.network=vmess.network + profile.path=vmess.path + profile.remotePort=vmess.port + profile.name=vmess.remarks + profile.requestHost=vmess.requestHost + profile.method=vmess.security + profile.streamSecurity=vmess.streamSecurity + profile.url_group=vmess.subid + + if(vmess.route=="0")profile.route="all" + else if(vmess.route=="1")profile.route="bypass-lan" + else if(vmess.route=="2")profile.route="bypass-china" + else if(vmess.route=="3")profile.route="bypass-lan-china" + else profile.route="all" + + return profile + } + + + private fun ResolveVmess4Kitsunebi(server: String): VmessBean { + val vmess = VmessBean() + var result = server.replace(VMESS_PROTOCOL, "") + val indexSplit = result.indexOf("?") + if (indexSplit > 0) { + result = result.substring(0, indexSplit) + } + result = decodeForVmess(result) + val arr1 = result.split('@') + if (arr1.count() != 2) { + return vmess + } + val arr21 = arr1[0].split(':') + val arr22 = arr1[1].split(':') + if (arr21.count() != 2 || arr21.count() != 2) { + return vmess + } + + vmess.address = arr22[0] + vmess.port = parseInt(arr22[1]) + vmess.security = arr21[0] + vmess.id = arr21[1] + + vmess.security = "chacha20-poly1305" + vmess.network = "tcp" + vmess.headerType = "none" + vmess.remarks = "Alien" + vmess.alterId = 0 + + return vmess + } fun findAllVmessUrls(data: CharSequence?, feature: Profile? = null) = pattern_vmess.findAll(data ?: "").map { val server = it.groupValues[1] @@ -140,11 +210,10 @@ data class Profile( try { val indexSplit = server.indexOf("?") if (indexSplit > 0) { - null - //profile = ResolveVmess4Kitsunebi(server,subid) + profileFromVmessBean(ResolveVmess4Kitsunebi(server),profile) } else { var result = server.replace(VMESS_PROTOCOL, "") - result = base64Decode(result) + result = decodeForVmess(result) if (TextUtils.isEmpty(result)) { null } @@ -176,8 +245,9 @@ data class Profile( profile.streamSecurity = vmessQRCode.tls profile } - } catch (e: IllegalArgumentException) { - Log.e(TAG, "Invalid SSR URI: ${it.value}") + } catch (e: Exception) { + printLog(e) + Log.e(TAG, "Invalid Vmess URI: ${it.value}") null } }.filterNotNull().toMutableSet() @@ -232,7 +302,7 @@ data class Profile( profile.name = uri.fragment profile } else { - Log.e(TAG, "Unrecognized URI: ${it.value}") + Log.e(TAG, "Unrecognized URI") null } } else { @@ -363,6 +433,9 @@ data class Profile( @Query("SELECT * FROM `Profile` WHERE `id` = :id") operator fun get(id: Long): Profile? + @Query("SELECT * FROM `Profile` WHERE `host` = :host LIMIT 1") + fun getByHost(host: String): Profile? + @Query("SELECT * FROM `Profile` WHERE `Subscription` != 2 ORDER BY `userOrder`") fun listActive(): List @@ -425,8 +498,7 @@ data class Profile( return builder.build() } - fun isSameAs(other: Profile): Boolean = other.host == host && other.remotePort == remotePort && - other.password == password && other.method == method + fun isSameAs(other: Profile): Boolean = other.host == host override fun toString() = toUri().toString() diff --git a/core/src/main/java/com/github/shadowsocks/database/ProfileManager.kt b/core/src/main/java/com/github/shadowsocks/database/ProfileManager.kt index 36ea048..c5f1da0 100644 --- a/core/src/main/java/com/github/shadowsocks/database/ProfileManager.kt +++ b/core/src/main/java/com/github/shadowsocks/database/ProfileManager.kt @@ -22,6 +22,7 @@ package com.github.shadowsocks.database import SpeedUpVPN.VpnEncrypt import android.database.sqlite.SQLiteCantOpenDatabaseException +import android.util.Base64 import android.util.Log import android.util.LongSparseArray import com.github.shadowsocks.Core @@ -31,6 +32,7 @@ import com.github.shadowsocks.utils.forEachTry import com.github.shadowsocks.utils.printLog import com.google.gson.JsonStreamParser import org.json.JSONArray +import java.io.BufferedReader import java.io.IOException import java.io.InputStream import java.sql.SQLException @@ -50,10 +52,20 @@ object ProfileManager { @Throws(SQLException::class) fun createProfile(profile: Profile = Profile()): Profile { - profile.id = 0 - profile.userOrder = PrivateDatabase.profileDao.nextOrder() ?: 0 - profile.id = PrivateDatabase.profileDao.create(profile) - listener?.onAdd(profile) + var existOne=PrivateDatabase.profileDao.getByHost(profile.host) + if (existOne==null) { + profile.id = 0 + profile.userOrder = PrivateDatabase.profileDao.nextOrder() ?: 0 + profile.id = PrivateDatabase.profileDao.create(profile) + listener?.onAdd(profile) + } + else { + existOne.copyFeatureSettingsTo(profile) + profile.id=existOne.id + profile.tx=existOne.tx + profile.rx=existOne.rx + updateProfile(profile) + } return profile } @@ -77,6 +89,82 @@ object ProfileManager { deletProfiles(old) } + fun importProfiles(text: CharSequence, replace: Boolean = false):Int { + val profiles = if (replace) getAllProfiles()?.associateBy { it.formattedAddress } else null + val feature = if (replace) { + profiles?.values?.singleOrNull { it.id == DataStore.profileId } + } else Core.currentProfile?.first + + val lazyClear = lazy { clear() } + + var profilesSet:MutableSet = LinkedHashSet() + val ssPofiles = Profile.findAllSSUrls(text, feature) + val v2Profiles= Profile.findAllVmessUrls(text, feature) + profilesSet.addAll(ssPofiles) + profilesSet.addAll(v2Profiles) + var newProfiles:List = profilesSet.toList() + if (newProfiles.isNotEmpty()){ + newProfiles.asIterable().forEachTry { + if (replace) { + lazyClear.value + // if two profiles has the same address, treat them as the same profile and copy stats over + profiles?.get(it.formattedAddress)?.apply { + it.tx = tx + it.rx = rx + } + } + createProfile(it) + } + + return newProfiles.size + } + else return 0 + } + + fun importProfilesFromBase64FileSequence(files: Sequence, replace: Boolean = false):Boolean { + var i=0 + files.asIterable().forEachTry { + if(importProfilesFromBase64File(it,replace))i++ + } + return (i>0) + } + + fun importProfilesFromBase64File(file: InputStream, replace: Boolean = false):Boolean { + var content: String="" + var reader:BufferedReader?=null + try { + reader = BufferedReader(file.reader()) + content = reader.readText() + content=String(Base64.decode(content, Base64.DEFAULT)) + } + catch (e:Exception){ + printLog(e) + } + finally { + reader?.close() + } + return importProfiles(content,replace) > 0 + } + + fun importProfilesFromFile(file: InputStream, replace: Boolean = false):Boolean { + val reader = BufferedReader(file.reader()) + var content: String + try { + content = reader.readText() + } finally { + reader.close() + } + return importProfiles(content,replace) >0 + } + + fun importProfilesFromFileSequence(files: Sequence, replace: Boolean = false):Boolean { + var i=0 + files.asIterable().forEachTry { + if(importProfilesFromFile(it,replace))i++ + } + return (i>0) + } + fun createProfilesFromJson(jsons: Sequence, replace: Boolean = false) { val profiles = if (replace) getAllProfiles()?.associateBy { it.formattedAddress } else null val feature = if (replace) { diff --git a/core/src/main/java/com/github/shadowsocks/preference/DataStore.kt b/core/src/main/java/com/github/shadowsocks/preference/DataStore.kt index b8f6e11..0eb114d 100644 --- a/core/src/main/java/com/github/shadowsocks/preference/DataStore.kt +++ b/core/src/main/java/com/github/shadowsocks/preference/DataStore.kt @@ -78,7 +78,8 @@ object DataStore : OnPreferenceDataStoreChangeListener { } } val serviceMode :String get() { - if (ProfileManager.getProfile(profileId)?.profileType=="vmess")return Key.v2rayVpn + val activeProfile=ProfileManager.getProfile(profileId) + if (activeProfile==null || activeProfile.profileType=="vmess")return Key.v2rayVpn return publicStore.getString(Key.serviceMode) ?: Key.modeVpn } val listenAddress get() = if (publicStore.getBoolean(Key.shareOverLan, false)) "0.0.0.0" else "127.0.0.1" diff --git a/core/src/main/java/com/github/shadowsocks/subscription/SubscriptionService.kt b/core/src/main/java/com/github/shadowsocks/subscription/SubscriptionService.kt index 5778a37..52e7da8 100644 --- a/core/src/main/java/com/github/shadowsocks/subscription/SubscriptionService.kt +++ b/core/src/main/java/com/github/shadowsocks/subscription/SubscriptionService.kt @@ -24,8 +24,6 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.app.Service -import android.content.BroadcastReceiver -import android.content.Context import android.content.Intent import android.content.IntentFilter import android.os.IBinder @@ -35,7 +33,6 @@ import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat import androidx.lifecycle.MutableLiveData -import com.crashlytics.android.Crashlytics import com.github.shadowsocks.Core import com.github.shadowsocks.Core.app import com.github.shadowsocks.core.R @@ -174,6 +171,8 @@ class SubscriptionService : Service(), CoroutineScope { } for (json in jsons.asIterable()) try { + if (ProfileManager.importProfilesFromBase64File(json)) + else Profile.parseJson(JsonStreamParser(json.bufferedReader()).asSequence().single(), feature) { subscriptions.compute(it.name to it.formattedAddress) { _, oldProfile -> when (oldProfile?.subscription) { diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 9d6a09d..0f41616 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -109,6 +109,7 @@ Successfully export! Failed to export. Successfully import! + Successfully import %d profiles. Failed to import. diff --git a/gitupdate.bat b/gitupdate.bat index e109320..d7f456f 100644 --- a/gitupdate.bat +++ b/gitupdate.bat @@ -3,6 +3,6 @@ git pull origin master git add -A git commit -m "update" git push origin master -git tag -a v5.1.3 -m "release v5.1.3" +git tag -a v5.1.4 -m "release v5.1.4" git push origin --tags pause \ No newline at end of file diff --git a/mobile/src/main/java/com/github/shadowsocks/ProfilesFragment.kt b/mobile/src/main/java/com/github/shadowsocks/ProfilesFragment.kt index caa01d4..e8d9eea 100644 --- a/mobile/src/main/java/com/github/shadowsocks/ProfilesFragment.kt +++ b/mobile/src/main/java/com/github/shadowsocks/ProfilesFragment.kt @@ -79,6 +79,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import java.io.FileNotFoundException import java.io.IOException import java.net.* import java.nio.charset.StandardCharsets @@ -587,15 +588,9 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { } R.id.action_import_clipboard -> { try { - var profilesSet:MutableSet = LinkedHashSet() - val ssPofiles = Profile.findAllSSUrls(clipboard.primaryClip!!.getItemAt(0).text, Core.currentProfile?.first) - val v2Profiles= Profile.findAllVmessUrls(clipboard.primaryClip!!.getItemAt(0).text, Core.currentProfile?.first) - profilesSet.addAll(ssPofiles) - profilesSet.addAll(v2Profiles) - var profiles:List = profilesSet.toList() - if (profiles.isNotEmpty()) { - profiles.forEach { ProfileManager.createProfile(it) } - (activity as MainActivity).snackbar().setText(R.string.action_import_msg).show() + var i=ProfileManager.importProfiles(clipboard.primaryClip!!.getItemAt(0).text) + if (i>0) { + try {(activity as MainActivity).snackbar().setText(getString(com.github.shadowsocks.core.R.string.action_import_msg2, i)).show()}catch (t:Throwable){} return true } } catch (exc: Exception) { @@ -839,6 +834,19 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { else when (requestCode) { REQUEST_IMPORT_PROFILES -> { val activity = activity as MainActivity + + try { + if(ProfileManager.importProfilesFromFileSequence(data!!.datas.asSequence().map { + activity.contentResolver.openInputStream(it) + }.filterNotNull())) return + } + catch (e: FileNotFoundException) { + activity.snackbar(e.readableMessage).show() + } + catch (e: Exception) { + printLog(e) + } + try { ProfileManager.createProfilesFromJson(data!!.datas.asSequence().map { activity.contentResolver.openInputStream(it) @@ -849,6 +857,18 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { } REQUEST_REPLACE_PROFILES -> { val activity = activity as MainActivity + try { + if(ProfileManager.importProfilesFromFileSequence(data!!.datas.asSequence().map { + activity.contentResolver.openInputStream(it) + }.filterNotNull(), true)) return + } + catch (e: FileNotFoundException) { + activity.snackbar(e.readableMessage).show() + } + catch (e: Exception) { + printLog(e) + } + try { ProfileManager.createProfilesFromJson(data!!.datas.asSequence().map { activity.contentResolver.openInputStream(it)