Commit 08b2b87c authored by Mygod's avatar Mygod

Initial changes to subscriptions

parent 5b018c97
......@@ -53,10 +53,12 @@ data class Profile(
// user configurable fields
var name: String? = "",
var host: String = sponsored,
var remotePort: Int = 8388,
var password: String = "u1rRWTssNv0p",
var method: String = "aes-256-cfb",
var route: String = "all",
var remoteDns: String = "dns.google",
var proxyApps: Boolean = false,
......@@ -252,8 +254,11 @@ data class Profile(
@Query("SELECT * FROM `Profile` WHERE `id` = :id")
operator fun get(id: Long): Profile?
@Query("SELECT * FROM `Profile` ORDER BY `userOrder`")
fun list(): List<Profile>
@Query("SELECT * FROM `Profile` WHERE `Subscription` != 2 ORDER BY `userOrder`")
fun listActive(): List<Profile>
@Query("SELECT * FROM `Profile`")
fun listAll(): List<Profile>
@Query("SELECT MAX(`userOrder`) + 1 FROM `Profile`")
fun nextOrder(): Long?
......
......@@ -24,9 +24,7 @@ import android.database.sqlite.SQLiteCantOpenDatabaseException
import android.util.LongSparseArray
import com.github.shadowsocks.Core
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.forEachTry
import com.github.shadowsocks.utils.printLog
import com.github.shadowsocks.utils.*
import com.google.gson.JsonStreamParser
import org.json.JSONArray
import java.io.IOException
......@@ -54,36 +52,43 @@ object ProfileManager {
return profile
}
fun createProfilesFromSubscription(jsons: Sequence<InputStream>, replace: Boolean,
oldProfiles: List<Profile>?) {
val profiles = oldProfiles?.associateBy { it.formattedAddress }
val feature = profiles?.values?.singleOrNull { it.id == DataStore.profileId }
val lazyClear = lazy { clear() }
fun createProfilesFromSubscription(jsons: Sequence<InputStream>) {
val currentId = DataStore.profileId
val profiles = getAllProfiles()
val subscriptions = mutableMapOf<String, Profile>()
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 ->
Profile.parseJson(JsonStreamParser(json.bufferedReader()).asSequence().single(), feature) {
if (replace) {
lazyClear.value
}
// if two profiles has the same address, treat them as the same profile and copy settings over
profiles?.get(it.formattedAddress)?.apply {
it.tx = tx
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
val oldProfile = subscriptions[it.formattedAddress]
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
}
else -> createProfile(it.apply {
subscription = Profile.SubscriptionStatus.Active
subscriptions[it.formattedAddress] = it
})
}
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) {
......@@ -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
val lookup = LongSparseArray<Profile>(profiles.size).apply { profiles.forEach { put(it.id, it) } }
return JSONArray(profiles.map { it.toJson(lookup) }.toTypedArray())
......@@ -159,9 +164,19 @@ object ProfileManager {
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)
fun getAllProfiles(): List<Profile>? = try {
PrivateDatabase.profileDao.list()
PrivateDatabase.profileDao.listAll()
} catch (ex: SQLiteCantOpenDatabaseException) {
throw IOException(ex)
} catch (ex: SQLException) {
......
......@@ -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
}
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) }
fun <K, V> MutableMap<K, V>.computeIfAbsentCompat(key: K, value: () -> V) = if (Build.VERSION.SDK_INT < 24) {
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 {
return suspendCancellableCoroutine { cont ->
......
......@@ -344,7 +344,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
}
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>()
init {
......@@ -543,7 +543,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
true
}
R.id.action_export_clipboard -> {
val profiles = ProfileManager.getAllProfiles()
val profiles = ProfileManager.getActiveProfiles()
(activity as MainActivity).snackbar().setText(if (profiles != null) {
clipboard.setPrimaryClip(ClipData.newPlainText(null, profiles.joinToString("\n")))
R.string.action_export_msg
......
......@@ -64,7 +64,7 @@ class UdpFallbackProfileActivity : AppCompatActivity() {
}
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() }
override fun onBindViewHolder(holder: ProfileViewHolder, position: Int) =
......
......@@ -41,7 +41,6 @@ import com.github.shadowsocks.MainActivity
import com.github.shadowsocks.R
import com.github.shadowsocks.ToolbarFragment
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.plugin.AlertDialogFragment
import com.github.shadowsocks.utils.asIterable
......@@ -231,29 +230,16 @@ class SubscriptionFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener
fetchJob = GlobalScope.launch {
val subscription = Subscription.instance
val oldProfiles = ProfileManager.getAllProfiles()
var replace = true
for (url in subscription.urls.asIterable()) {
try {
val connection = url.openConnection() as HttpURLConnection
connection.useCancellable {
ProfileManager.createProfilesFromSubscription(sequenceOf(connection.inputStream),
replace, oldProfiles)
ProfileManager.createProfilesFromSubscription(sequenceOf(connection.inputStream))
}
} catch (e: Exception) {
e.printStackTrace()
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() {
}
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) =
if (position == 0) holder.bindDefault() else holder.bind(profiles[position - 1])
......
......@@ -209,7 +209,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
private fun populateProfiles() {
ProfileManager.ensureNotEmpty()
val profiles = ProfileManager.getAllProfiles()!!
val profiles = ProfileManager.getActiveProfiles()!!
fab.value = null
fab.entries = profiles.map { it.formattedName }.toTypedArray()
fab.entryValues = profiles.map { it.id.toString() }.toTypedArray()
......
......@@ -52,7 +52,7 @@ class ProfilesDialogFragment : LeanbackListPreferenceDialogFragmentCompat() {
}
}
private inner class ProfilesAdapter : RecyclerView.Adapter<ProfileViewHolder>() {
val profiles = ProfileManager.getAllProfiles()!!
val profiles = ProfileManager.getActiveProfiles()!!
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ProfileViewHolder(
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