Commit 08b2b87c authored by Mygod's avatar Mygod

Initial changes to subscriptions

parent 5b018c97
...@@ -53,10 +53,12 @@ data class Profile( ...@@ -53,10 +53,12 @@ data class Profile(
// user configurable fields // user configurable fields
var name: String? = "", var name: String? = "",
var host: String = sponsored, var host: String = sponsored,
var remotePort: Int = 8388, var remotePort: Int = 8388,
var password: String = "u1rRWTssNv0p", var password: String = "u1rRWTssNv0p",
var method: String = "aes-256-cfb", var method: String = "aes-256-cfb",
var route: String = "all", var route: String = "all",
var remoteDns: String = "dns.google", var remoteDns: String = "dns.google",
var proxyApps: Boolean = false, var proxyApps: Boolean = false,
...@@ -252,8 +254,11 @@ data class Profile( ...@@ -252,8 +254,11 @@ data class Profile(
@Query("SELECT * FROM `Profile` WHERE `id` = :id") @Query("SELECT * FROM `Profile` WHERE `id` = :id")
operator fun get(id: Long): Profile? operator fun get(id: Long): Profile?
@Query("SELECT * FROM `Profile` ORDER BY `userOrder`") @Query("SELECT * FROM `Profile` WHERE `Subscription` != 2 ORDER BY `userOrder`")
fun list(): List<Profile> fun listActive(): List<Profile>
@Query("SELECT * FROM `Profile`")
fun listAll(): List<Profile>
@Query("SELECT MAX(`userOrder`) + 1 FROM `Profile`") @Query("SELECT MAX(`userOrder`) + 1 FROM `Profile`")
fun nextOrder(): Long? fun nextOrder(): Long?
......
...@@ -24,9 +24,7 @@ import android.database.sqlite.SQLiteCantOpenDatabaseException ...@@ -24,9 +24,7 @@ import android.database.sqlite.SQLiteCantOpenDatabaseException
import android.util.LongSparseArray import android.util.LongSparseArray
import com.github.shadowsocks.Core import com.github.shadowsocks.Core
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.DirectBoot import com.github.shadowsocks.utils.*
import com.github.shadowsocks.utils.forEachTry
import com.github.shadowsocks.utils.printLog
import com.google.gson.JsonStreamParser import com.google.gson.JsonStreamParser
import org.json.JSONArray import org.json.JSONArray
import java.io.IOException import java.io.IOException
...@@ -54,38 +52,45 @@ object ProfileManager { ...@@ -54,38 +52,45 @@ object ProfileManager {
return profile return profile
} }
fun createProfilesFromSubscription(jsons: Sequence<InputStream>, replace: Boolean, fun createProfilesFromSubscription(jsons: Sequence<InputStream>) {
oldProfiles: List<Profile>?) { val currentId = DataStore.profileId
val profiles = oldProfiles?.associateBy { it.formattedAddress } val profiles = getAllProfiles()
val feature = profiles?.values?.singleOrNull { it.id == DataStore.profileId } val subscriptions = mutableMapOf<String, Profile>()
val lazyClear = lazy { clear() } val toUpdate = mutableSetOf<Long>()
var feature: Profile? = null
profiles?.forEach { profile -> // preprocessing phase
if (currentId == profile.id) feature = profile
if (profile.subscription == Profile.SubscriptionStatus.UserConfigured) return@forEach
if (subscriptions.putIfAbsentCompat(profile.formattedAddress, profile) != null) {
delProfile(profile.id)
if (currentId == profile.id) DataStore.profileId = 0
} else if (profile.subscription == Profile.SubscriptionStatus.Active) {
toUpdate.add(profile.id)
profile.subscription = Profile.SubscriptionStatus.Obsolete
}
}
jsons.asIterable().forEachTry { json -> jsons.asIterable().forEachTry { json ->
Profile.parseJson(JsonStreamParser(json.bufferedReader()).asSequence().single(), feature) { Profile.parseJson(JsonStreamParser(json.bufferedReader()).asSequence().single(), feature) {
if (replace) { val oldProfile = subscriptions[it.formattedAddress]
lazyClear.value when (oldProfile?.subscription) {
Profile.SubscriptionStatus.Active -> { } // skip dup subscription
Profile.SubscriptionStatus.Obsolete -> {
oldProfile.password = it.password
oldProfile.method = it.method
oldProfile.subscription = Profile.SubscriptionStatus.Active
} }
// if two profiles has the same address, treat them as the same profile and copy settings over else -> createProfile(it.apply {
profiles?.get(it.formattedAddress)?.apply { subscription = Profile.SubscriptionStatus.Active
it.tx = tx subscriptions[it.formattedAddress] = it
it.rx = rx })
it.individual = individual
it.route = route
it.bypass = bypass
it.ipv6 = ipv6
it.metered = metered
it.proxyApps = proxyApps
it.remoteDns = remoteDns
it.udpdns = udpdns
it.udpFallback = udpFallback
}
it.subscription = Profile.SubscriptionStatus.Active
createProfile(it)
} }
} }
} }
profiles?.forEach { profile -> if (toUpdate.contains(profile.id)) updateProfile(profile) }
}
fun createProfilesFromJson(jsons: Sequence<InputStream>, replace: Boolean = false) { fun createProfilesFromJson(jsons: Sequence<InputStream>, replace: Boolean = false) {
val profiles = if (replace) getAllProfiles()?.associateBy { it.formattedAddress } else null val profiles = if (replace) getAllProfiles()?.associateBy { it.formattedAddress } else null
val feature = if (replace) { val feature = if (replace) {
...@@ -107,7 +112,7 @@ object ProfileManager { ...@@ -107,7 +112,7 @@ object ProfileManager {
} }
} }
fun serializeToJson(profiles: List<Profile>? = getAllProfiles()): JSONArray? { fun serializeToJson(profiles: List<Profile>? = getActiveProfiles()): JSONArray? {
if (profiles == null) return null if (profiles == null) return null
val lookup = LongSparseArray<Profile>(profiles.size).apply { profiles.forEach { put(it.id, it) } } val lookup = LongSparseArray<Profile>(profiles.size).apply { profiles.forEach { put(it.id, it) } }
return JSONArray(profiles.map { it.toJson(lookup) }.toTypedArray()) return JSONArray(profiles.map { it.toJson(lookup) }.toTypedArray())
...@@ -159,9 +164,19 @@ object ProfileManager { ...@@ -159,9 +164,19 @@ object ProfileManager {
if (!nonEmpty) DataStore.profileId = createProfile().id if (!nonEmpty) DataStore.profileId = createProfile().id
} }
@Throws(IOException::class)
fun getActiveProfiles(): List<Profile>? = try {
PrivateDatabase.profileDao.listActive()
} catch (ex: SQLiteCantOpenDatabaseException) {
throw IOException(ex)
} catch (ex: SQLException) {
printLog(ex)
null
}
@Throws(IOException::class) @Throws(IOException::class)
fun getAllProfiles(): List<Profile>? = try { fun getAllProfiles(): List<Profile>? = try {
PrivateDatabase.profileDao.list() PrivateDatabase.profileDao.listAll()
} catch (ex: SQLiteCantOpenDatabaseException) { } catch (ex: SQLiteCantOpenDatabaseException) {
throw IOException(ex) throw IOException(ex)
} catch (ex: SQLException) { } catch (ex: SQLException) {
......
...@@ -89,8 +89,12 @@ fun String?.parseNumericAddress(): InetAddress? = Os.inet_pton(OsConstants.AF_IN ...@@ -89,8 +89,12 @@ fun String?.parseNumericAddress(): InetAddress? = Os.inet_pton(OsConstants.AF_IN
if (Build.VERSION.SDK_INT >= 29) it else parseNumericAddress.invoke(null, this) as InetAddress if (Build.VERSION.SDK_INT >= 29) it else parseNumericAddress.invoke(null, this) as InetAddress
} }
fun <K, V> MutableMap<K, V>.computeIfAbsentCompat(key: K, value: () -> V) = if (Build.VERSION.SDK_INT >= 24) fun <K, V> MutableMap<K, V>.computeIfAbsentCompat(key: K, value: () -> V) = if (Build.VERSION.SDK_INT < 24) {
computeIfAbsent(key) { value() } else this[key] ?: value().also { put(key, it) } this[key] ?: value().also { put(key, it) }
} else computeIfAbsent(key) { value() }
fun <K, V> MutableMap<K, V>.putIfAbsentCompat(key: K, value: V) = if (Build.VERSION.SDK_INT < 24) {
this[key] ?: put(key, value)
} else putIfAbsent(key, value)
suspend fun <T> HttpURLConnection.useCancellable(block: suspend HttpURLConnection.() -> T): T { suspend fun <T> HttpURLConnection.useCancellable(block: suspend HttpURLConnection.() -> T): T {
return suspendCancellableCoroutine { cont -> return suspendCancellableCoroutine { cont ->
......
...@@ -344,7 +344,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -344,7 +344,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
} }
inner class ProfilesAdapter : RecyclerView.Adapter<ProfileViewHolder>(), ProfileManager.Listener { inner class ProfilesAdapter : RecyclerView.Adapter<ProfileViewHolder>(), ProfileManager.Listener {
internal val profiles = ProfileManager.getAllProfiles()?.toMutableList() ?: mutableListOf() internal val profiles = ProfileManager.getActiveProfiles()?.toMutableList() ?: mutableListOf()
private val updated = HashSet<Profile>() private val updated = HashSet<Profile>()
init { init {
...@@ -543,7 +543,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -543,7 +543,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
true true
} }
R.id.action_export_clipboard -> { R.id.action_export_clipboard -> {
val profiles = ProfileManager.getAllProfiles() val profiles = ProfileManager.getActiveProfiles()
(activity as MainActivity).snackbar().setText(if (profiles != null) { (activity as MainActivity).snackbar().setText(if (profiles != null) {
clipboard.setPrimaryClip(ClipData.newPlainText(null, profiles.joinToString("\n"))) clipboard.setPrimaryClip(ClipData.newPlainText(null, profiles.joinToString("\n")))
R.string.action_export_msg R.string.action_export_msg
......
...@@ -64,7 +64,7 @@ class UdpFallbackProfileActivity : AppCompatActivity() { ...@@ -64,7 +64,7 @@ class UdpFallbackProfileActivity : AppCompatActivity() {
} }
inner class ProfilesAdapter : RecyclerView.Adapter<ProfileViewHolder>() { inner class ProfilesAdapter : RecyclerView.Adapter<ProfileViewHolder>() {
internal val profiles = (ProfileManager.getAllProfiles()?.toMutableList() ?: mutableListOf()) internal val profiles = (ProfileManager.getActiveProfiles()?.toMutableList() ?: mutableListOf())
.filter { it.id != editingId && PluginConfiguration(it.plugin ?: "").selected.isEmpty() } .filter { it.id != editingId && PluginConfiguration(it.plugin ?: "").selected.isEmpty() }
override fun onBindViewHolder(holder: ProfileViewHolder, position: Int) = override fun onBindViewHolder(holder: ProfileViewHolder, position: Int) =
......
...@@ -41,7 +41,6 @@ import com.github.shadowsocks.MainActivity ...@@ -41,7 +41,6 @@ import com.github.shadowsocks.MainActivity
import com.github.shadowsocks.R import com.github.shadowsocks.R
import com.github.shadowsocks.ToolbarFragment import com.github.shadowsocks.ToolbarFragment
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.plugin.AlertDialogFragment import com.github.shadowsocks.plugin.AlertDialogFragment
import com.github.shadowsocks.utils.asIterable import com.github.shadowsocks.utils.asIterable
...@@ -231,29 +230,16 @@ class SubscriptionFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener ...@@ -231,29 +230,16 @@ class SubscriptionFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener
fetchJob = GlobalScope.launch { fetchJob = GlobalScope.launch {
val subscription = Subscription.instance val subscription = Subscription.instance
val oldProfiles = ProfileManager.getAllProfiles()
var replace = true
for (url in subscription.urls.asIterable()) { for (url in subscription.urls.asIterable()) {
try { try {
val connection = url.openConnection() as HttpURLConnection val connection = url.openConnection() as HttpURLConnection
connection.useCancellable { connection.useCancellable {
ProfileManager.createProfilesFromSubscription(sequenceOf(connection.inputStream), ProfileManager.createProfilesFromSubscription(sequenceOf(connection.inputStream))
replace, oldProfiles)
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
activity.snackbar(e.readableMessage).show() activity.snackbar(e.readableMessage).show()
} finally {
replace = false
}
}
val userProfiles = oldProfiles?.filter { it.subscription == Profile.SubscriptionStatus.UserConfigured }
if (userProfiles != null) {
for (profile in userProfiles.asIterable()) {
ProfileManager.createProfile(profile)
} }
} }
......
...@@ -71,7 +71,7 @@ class ConfigActivity : AppCompatActivity() { ...@@ -71,7 +71,7 @@ class ConfigActivity : AppCompatActivity() {
} }
inner class ProfilesAdapter : RecyclerView.Adapter<ProfileViewHolder>() { inner class ProfilesAdapter : RecyclerView.Adapter<ProfileViewHolder>() {
internal val profiles = ProfileManager.getAllProfiles()?.toMutableList() ?: mutableListOf() internal val profiles = ProfileManager.getActiveProfiles()?.toMutableList() ?: mutableListOf()
override fun onBindViewHolder(holder: ProfileViewHolder, position: Int) = override fun onBindViewHolder(holder: ProfileViewHolder, position: Int) =
if (position == 0) holder.bindDefault() else holder.bind(profiles[position - 1]) if (position == 0) holder.bindDefault() else holder.bind(profiles[position - 1])
......
...@@ -209,7 +209,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo ...@@ -209,7 +209,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
private fun populateProfiles() { private fun populateProfiles() {
ProfileManager.ensureNotEmpty() ProfileManager.ensureNotEmpty()
val profiles = ProfileManager.getAllProfiles()!! val profiles = ProfileManager.getActiveProfiles()!!
fab.value = null fab.value = null
fab.entries = profiles.map { it.formattedName }.toTypedArray() fab.entries = profiles.map { it.formattedName }.toTypedArray()
fab.entryValues = profiles.map { it.id.toString() }.toTypedArray() fab.entryValues = profiles.map { it.id.toString() }.toTypedArray()
......
...@@ -52,7 +52,7 @@ class ProfilesDialogFragment : LeanbackListPreferenceDialogFragmentCompat() { ...@@ -52,7 +52,7 @@ class ProfilesDialogFragment : LeanbackListPreferenceDialogFragmentCompat() {
} }
} }
private inner class ProfilesAdapter : RecyclerView.Adapter<ProfileViewHolder>() { private inner class ProfilesAdapter : RecyclerView.Adapter<ProfileViewHolder>() {
val profiles = ProfileManager.getAllProfiles()!! val profiles = ProfileManager.getActiveProfiles()!!
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ProfileViewHolder( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ProfileViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.leanback_list_preference_item_single_2, LayoutInflater.from(parent.context).inflate(R.layout.leanback_list_preference_item_single_2,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment