7 Commits

Author SHA1 Message Date
CherretGit
c387cd13b8 get bins and move downloaded files to files dir 2026-03-14 22:10:46 +07:00
CherretGit
a06989033e add version to StorageData 2026-03-14 21:07:37 +07:00
CherretGit
da2b53ace7 add author and description to hosts, strategies and ipsets 2026-03-14 20:47:09 +07:00
CherretGit
a145ae4349 fix config path 2026-03-14 20:19:08 +07:00
CherretGit
1f2df5bdfc fix npe with invalid manifest 2026-03-14 20:14:27 +07:00
egor-white
47c3b24733 totally remade viewmodels and zaprettmanager to manifests 2026-03-14 14:29:36 +03:00
CherretGit
eb082a6072 refactor code 2026-03-14 16:53:29 +07:00
20 changed files with 414 additions and 395 deletions

View File

@@ -199,8 +199,8 @@ class ByeDpiVpnService : VpnService() {
socksIp,
socksPort,
getActiveByeDPIStrategyContent(sharedPreferences),
prepareList(listSet),
prepareIpset(ipsetSet),
prepareList(listSet.map { it.file }.toTypedArray()),
prepareIpset(ipsetSet.map { it.file }.toTypedArray()),
sharedPreferences)
val result = NativeBridge().jniStartProxy(args)
if (result < 0) {

View File

@@ -1,14 +1,20 @@
package com.cherret.zaprett.data
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
@Serializable
data class StorageData(
val schema: Int,
val id: String,
val name: String,
val version: String,
val author: String,
val description: String,
val dependencies: List<String> = emptyList(),
val file: String
)
val file: String,
){
@Transient
var manifestPath: String = ""
}

View File

@@ -0,0 +1,16 @@
package com.cherret.zaprett.data
import kotlinx.serialization.Serializable
@Serializable
data class UpdateInfo(
val version: String,
val versionCode: Int,
val downloadUrl: String,
val changelogUrl: String
)
data class UpdateData(
val updateInfo: UpdateInfo,
val changelog: String
)

View File

@@ -51,6 +51,7 @@ import androidx.compose.ui.unit.sp
import com.cherret.zaprett.R
import com.cherret.zaprett.data.DependencyUI
import com.cherret.zaprett.data.RepoItemUI
import com.cherret.zaprett.data.StorageData
import com.cherret.zaprett.data.StrategyCheckResult
import com.cherret.zaprett.data.StrategyTestingStatus
import com.cherret.zaprett.ui.viewmodel.BaseRepoViewModel
@@ -60,7 +61,7 @@ import com.cherret.zaprett.utils.getActiveStrategy
import kotlinx.coroutines.launch
@Composable
fun ListSwitchItem(item: String, isChecked: Boolean, onCheckedChange: (Boolean) -> Unit, onDeleteClick: () -> Unit) {
fun ListSwitchItem(item: StorageData, isChecked: Boolean, onCheckedChange: (Boolean) -> Unit, onDeleteClick: () -> Unit) {
ElevatedCard(
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceContainer),
@@ -74,7 +75,14 @@ fun ListSwitchItem(item: String, isChecked: Boolean, onCheckedChange: (Boolean)
.fillMaxWidth()
.padding(16.dp)
) {
Text(text = item, modifier = Modifier.weight(1f))
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(text = item.name)
Text(text = stringResource(R.string.title_author, item.author))
Text(text = item.description)
}
Switch(
checked = isChecked,
onCheckedChange = onCheckedChange,
@@ -255,7 +263,9 @@ fun StrategySelectionItem(strategy : StrategyCheckResult, prefs : SharedPreferen
)
FilledTonalIconButton(
onClick = {
if (getActiveStrategy(prefs).isNotEmpty()) disableStrategy(getActiveStrategy(prefs)[0], prefs)
getActiveStrategy(prefs).getOrNull()?.file
?.takeIf { it.isNotEmpty() }
?.let { disableStrategy(it, prefs) }
enableStrategy(strategy.path, prefs)
scope.launch {
snackbarHostState.showSnackbar(

View File

@@ -16,6 +16,7 @@ import androidx.compose.runtime.setValue
import androidx.core.content.ContextCompat
import androidx.lifecycle.AndroidViewModel
import com.cherret.zaprett.R
import com.cherret.zaprett.data.StorageData
import com.cherret.zaprett.utils.checkStoragePermission
import com.cherret.zaprett.utils.getZaprettPath
import com.cherret.zaprett.utils.restartService
@@ -30,11 +31,11 @@ import java.io.IOException
abstract class BaseListsViewModel(application: Application) : AndroidViewModel(application) {
val context = application
var allItems by mutableStateOf<List<String>>(emptyList())
var allItems by mutableStateOf<List<StorageData>>(emptyList())
private set
var activeItems by mutableStateOf<List<String>>(emptyList())
var activeItems by mutableStateOf<List<StorageData>>(emptyList())
private set
val checked = mutableStateMapOf<String, Boolean>()
val checked = mutableStateMapOf<StorageData, Boolean>()
var isRefreshing by mutableStateOf(false)
private set
@@ -44,10 +45,10 @@ abstract class BaseListsViewModel(application: Application) : AndroidViewModel(a
private var _showNoPermissionDialog = MutableStateFlow(false)
val showNoPermissionDialog: StateFlow<Boolean> = _showNoPermissionDialog
abstract fun loadAllItems(): Array<String>
abstract fun loadActiveItems(): Array<String>
abstract fun onCheckedChange(item: String, isChecked: Boolean, snackbarHostState: SnackbarHostState, scope: CoroutineScope)
abstract fun deleteItem(item: String, snackbarHostState: SnackbarHostState, scope: CoroutineScope)
abstract fun loadAllItems(): Array<StorageData>
abstract fun loadActiveItems(): Array<StorageData>
abstract fun onCheckedChange(item: StorageData, isChecked: Boolean, snackbarHostState: SnackbarHostState, scope: CoroutineScope)
abstract fun deleteItem(item: StorageData, snackbarHostState: SnackbarHostState, scope: CoroutineScope)
fun refresh() {
when (checkStoragePermission(context)) {

View File

@@ -9,7 +9,6 @@ import androidx.compose.material3.SnackbarResult
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.cherret.zaprett.R
@@ -20,18 +19,18 @@ import com.cherret.zaprett.data.ListType
import com.cherret.zaprett.data.RepoItemFull
import com.cherret.zaprett.data.RepoItemUI
import com.cherret.zaprett.data.RepoManifest
import com.cherret.zaprett.data.ServiceType
import com.cherret.zaprett.data.RepoTab
import com.cherret.zaprett.data.ServiceType
import com.cherret.zaprett.data.StorageData
import com.cherret.zaprett.utils.DownloadUtils.download
import com.cherret.zaprett.utils.DownloadUtils.getFileSha256
import com.cherret.zaprett.utils.DownloadUtils.registerDownloadListener
import com.cherret.zaprett.utils.NetworkUtils.getRepo
import com.cherret.zaprett.utils.NetworkUtils.resolveDependencies
import com.cherret.zaprett.utils.checkStoragePermission
import com.cherret.zaprett.utils.download
import com.cherret.zaprett.utils.getFileSha256
import com.cherret.zaprett.utils.getHostListMode
import com.cherret.zaprett.utils.getRepo
import com.cherret.zaprett.utils.getServiceType
import com.cherret.zaprett.utils.getZaprettPath
import com.cherret.zaprett.utils.registerDownloadListener
import com.cherret.zaprett.utils.resolveDependencies
import com.cherret.zaprett.utils.restartService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -78,7 +77,7 @@ abstract class BaseRepoViewModel(application: Application) : AndroidViewModel(ap
val isInstalling = mutableStateMapOf<String, Boolean>()
val isUpdateInstalling = mutableStateMapOf<String, Boolean>()
abstract fun getInstalledLists(): Array<String>
abstract fun getInstalledLists(): Array<StorageData>
abstract val repoTab: RepoTab
val repoUrl = sharedPreferences.getString("repo_url", "https://raw.githubusercontent.com/CherretGit/zaprett-repo/refs/heads/main/index.json") ?: "https://raw.githubusercontent.com/CherretGit/zaprett-repo/refs/heads/main/index.json"
private val Json = Json {
@@ -156,7 +155,7 @@ abstract class BaseRepoViewModel(application: Application) : AndroidViewModel(ap
}
fun isItemInstalled(item: RepoItemUI): Boolean {
return getInstalledLists().any { File(it).name == item.name }
return getInstalledLists().any { it.id == item.id }
}
fun install(item: RepoItemUI) {
@@ -204,6 +203,7 @@ abstract class BaseRepoViewModel(application: Application) : AndroidViewModel(ap
}
val targetFile = baseDir
.resolve("files")
.resolve(targetDirSuffix)
.resolve(uri.lastPathSegment!!
.replace(Regex("""-\d+(?=\.|$)"""), ""))
@@ -220,6 +220,7 @@ abstract class BaseRepoViewModel(application: Application) : AndroidViewModel(ap
item.schema,
item.id,
item.name,
item.version,
item.author,
item.description,
item.dependencies,

View File

@@ -2,8 +2,10 @@ package com.cherret.zaprett.ui.viewmodel
import android.app.Application
import com.cherret.zaprett.data.RepoTab
import com.cherret.zaprett.data.StorageData
import com.cherret.zaprett.utils.getAllBin
class BinRepoViewModel(application: Application): BaseRepoViewModel(application) {
override fun getInstalledLists(): Array<String> = emptyArray()
override fun getInstalledLists(): Array<StorageData> = getAllBin()
override val repoTab = RepoTab.bins
}

View File

@@ -20,17 +20,16 @@ import com.cherret.zaprett.byedpi.ByeDpiVpnService
import com.cherret.zaprett.data.ServiceStatus
import com.cherret.zaprett.data.ServiceStatusUI
import com.cherret.zaprett.data.ServiceType
import com.cherret.zaprett.utils.download
import com.cherret.zaprett.utils.DownloadUtils.download
import com.cherret.zaprett.utils.DownloadUtils.installApk
import com.cherret.zaprett.utils.DownloadUtils.registerDownloadListener
import com.cherret.zaprett.utils.NetworkUtils.getUpdate
import com.cherret.zaprett.utils.getActiveStrategy
import com.cherret.zaprett.utils.getChangelog
import com.cherret.zaprett.utils.getModuleVersion
import com.cherret.zaprett.utils.getNfqws2Version
import com.cherret.zaprett.utils.getNfqwsVersion
import com.cherret.zaprett.utils.getServiceType
import com.cherret.zaprett.utils.getStatus
import com.cherret.zaprett.utils.getUpdate
import com.cherret.zaprett.utils.installApk
import com.cherret.zaprett.utils.registerDownloadListener
import com.cherret.zaprett.utils.restartService
import com.cherret.zaprett.utils.startService
import com.cherret.zaprett.utils.stopService
@@ -79,16 +78,18 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
var showUpdateDialog = mutableStateOf(false)
fun checkForUpdate() {
suspend fun checkForUpdate() {
if (prefs.getBoolean("auto_update", BuildConfig.auto_update)) {
getUpdate(prefs) {
if (it != null) {
downloadUrl.value = it.downloadUrl.toString()
getChangelog(it.changelogUrl.toString()) { log -> changeLog.value = log }
newVersion.value = it.version
getUpdate(prefs)
.onSuccess { updateData ->
downloadUrl.value = updateData.updateInfo.downloadUrl
changeLog.value = updateData.changelog
newVersion.value = updateData.updateInfo.version
updateAvailable.value = true
}
}
.onFailure { exception ->
_errorFlow.value = exception.toString()
}
}
}
@@ -144,7 +145,7 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
}
} else {
if (ByeDpiVpnService.status == ServiceStatus.Disconnected || ByeDpiVpnService.status == ServiceStatus.Failed) {
if (getActiveStrategy(prefs).isNotEmpty()) {
if (getActiveStrategy(prefs).isSuccess) {
scope.launch {
snackbarHostState.showSnackbar(context.getString(R.string.snack_starting_service))
}
@@ -255,7 +256,7 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) {
installApk(context, uri)
},
onError = {
_errorFlow.value = it
})
}
else {

View File

@@ -3,12 +3,13 @@ package com.cherret.zaprett.ui.viewmodel
import android.app.Application
import com.cherret.zaprett.data.ListType
import com.cherret.zaprett.data.RepoTab
import com.cherret.zaprett.data.StorageData
import com.cherret.zaprett.utils.getAllExcludeLists
import com.cherret.zaprett.utils.getAllLists
import com.cherret.zaprett.utils.getHostListMode
class HostRepoViewModel(application: Application): BaseRepoViewModel(application) {
override fun getInstalledLists(): Array<String> =
override fun getInstalledLists(): Array<StorageData> =
if (getHostListMode(sharedPreferences) == ListType.whitelist) getAllLists() else getAllExcludeLists()
override val repoTab = RepoTab.lists
}

View File

@@ -5,6 +5,7 @@ import android.content.Context
import androidx.compose.material3.SnackbarHostState
import com.cherret.zaprett.data.ListType
import com.cherret.zaprett.data.ServiceType
import com.cherret.zaprett.data.StorageData
import com.cherret.zaprett.utils.disableList
import com.cherret.zaprett.utils.enableList
import com.cherret.zaprett.utils.getActiveExcludeLists
@@ -20,18 +21,19 @@ import java.io.File
class HostsViewModel(application: Application): BaseListsViewModel(application) {
private val sharedPreferences = application.getSharedPreferences("settings", Context.MODE_PRIVATE)
override fun loadAllItems(): Array<String> =
override fun loadAllItems(): Array<StorageData> =
if (getHostListMode(sharedPreferences) == ListType.whitelist) getAllLists()
else getAllExcludeLists()
override fun loadActiveItems(): Array<String> =
override fun loadActiveItems(): Array<StorageData> =
if (getHostListMode(sharedPreferences) == ListType.whitelist) getActiveLists(sharedPreferences)
else getActiveExcludeLists(sharedPreferences)
override fun deleteItem(item: String, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
override fun deleteItem(item: StorageData, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
val wasChecked = checked[item] == true
disableList(item, sharedPreferences)
val success = File(item).delete()
if (success) refresh()
disableList(item.manifestPath, sharedPreferences)
val successArtifact = File(item.file).delete()
val successManifest = File(item.manifestPath).delete()
if (successArtifact && successManifest) refresh()
if (getServiceType(sharedPreferences) != ServiceType.byedpi) {
getStatus { isEnabled ->
if (isEnabled && wasChecked) {
@@ -42,9 +44,9 @@ class HostsViewModel(application: Application): BaseListsViewModel(application)
}
}
override fun onCheckedChange(item: String, isChecked: Boolean, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
override fun onCheckedChange(item: StorageData, isChecked: Boolean, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
checked[item] = isChecked
if (isChecked) enableList(item, sharedPreferences) else disableList(item, sharedPreferences)
if (isChecked) enableList(item.manifestPath, sharedPreferences) else disableList(item.manifestPath, sharedPreferences)
if (getServiceType(sharedPreferences) != ServiceType.byedpi) {
getStatus { isEnabled ->
if (isEnabled) {

View File

@@ -3,12 +3,13 @@ package com.cherret.zaprett.ui.viewmodel
import android.app.Application
import com.cherret.zaprett.data.ListType
import com.cherret.zaprett.data.RepoTab
import com.cherret.zaprett.data.StorageData
import com.cherret.zaprett.utils.getAllExcludeIpsets
import com.cherret.zaprett.utils.getAllIpsets
import com.cherret.zaprett.utils.getHostListMode
class IpsetRepoViewModel(application: Application): BaseRepoViewModel(application) {
override fun getInstalledLists(): Array<String> =
override fun getInstalledLists(): Array<StorageData> =
if (getHostListMode(sharedPreferences) == ListType.whitelist) getAllIpsets() else getAllExcludeIpsets()
override val repoTab = RepoTab.ipsets
}

View File

@@ -5,6 +5,7 @@ import android.content.Context
import androidx.compose.material3.SnackbarHostState
import com.cherret.zaprett.data.ListType
import com.cherret.zaprett.data.ServiceType
import com.cherret.zaprett.data.StorageData
import com.cherret.zaprett.utils.disableIpset
import com.cherret.zaprett.utils.enableIpset
import com.cherret.zaprett.utils.getActiveExcludeIpsets
@@ -20,18 +21,19 @@ import java.io.File
class IpsetViewModel(application: Application): BaseListsViewModel(application) {
private val sharedPreferences = application.getSharedPreferences("settings", Context.MODE_PRIVATE)
override fun loadAllItems(): Array<String> =
override fun loadAllItems(): Array<StorageData> =
if (getHostListMode(sharedPreferences) == ListType.whitelist) getAllIpsets()
else getAllExcludeIpsets()
override fun loadActiveItems(): Array<String> =
override fun loadActiveItems(): Array<StorageData> =
if (getHostListMode(sharedPreferences) == ListType.whitelist) getActiveIpsets(sharedPreferences)
else getActiveExcludeIpsets(sharedPreferences)
override fun deleteItem(item: String, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
override fun deleteItem(item: StorageData, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
val wasChecked = checked[item] == true
disableIpset(item, sharedPreferences)
val success = File(item).delete()
if (success) refresh()
disableIpset(item.manifestPath, sharedPreferences)
val successArtifact = File(item.file).delete()
val successManifest = File(item.manifestPath).delete()
if (successArtifact && successManifest) refresh()
if (getServiceType(sharedPreferences) != ServiceType.byedpi) {
getStatus { isEnabled ->
if (isEnabled && wasChecked) {
@@ -42,9 +44,9 @@ class IpsetViewModel(application: Application): BaseListsViewModel(application)
}
}
override fun onCheckedChange(item: String, isChecked: Boolean, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
override fun onCheckedChange(item: StorageData, isChecked: Boolean, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
checked[item] = isChecked
if (isChecked) enableIpset(item, sharedPreferences) else disableIpset(item, sharedPreferences)
if (isChecked) enableIpset(item.manifestPath, sharedPreferences) else disableIpset(item.manifestPath, sharedPreferences)
if (getServiceType(sharedPreferences) != ServiceType.byedpi) {
getStatus { isEnabled ->
if (isEnabled) {

View File

@@ -3,12 +3,13 @@ package com.cherret.zaprett.ui.viewmodel
import android.app.Application
import com.cherret.zaprett.data.RepoTab
import com.cherret.zaprett.data.ServiceType
import com.cherret.zaprett.data.StorageData
import com.cherret.zaprett.utils.getAllByeDPIStrategies
import com.cherret.zaprett.utils.getAllNfqwsStrategies
import com.cherret.zaprett.utils.getServiceType
class StrategyRepoViewModel(application: Application): BaseRepoViewModel(application) {
override fun getInstalledLists(): Array<String> =
override fun getInstalledLists(): Array<StorageData> =
if (getServiceType(sharedPreferences) != ServiceType.byedpi) {
getAllNfqwsStrategies()
} else {

View File

@@ -70,9 +70,9 @@ class StrategySelectionViewModel(application: Application) : AndroidViewModel(ap
fun loadStrategies() {
val strategyList = getAllStrategies(prefs)
strategyStates.clear()
strategyList.forEach { name ->
strategyList.forEach { manifest ->
strategyStates += StrategyCheckResult(
path = name,
path = manifest.name,
status = StrategyTestingStatus.Waiting,
progress = 0f,
domains = emptyList()
@@ -108,7 +108,7 @@ class StrategySelectionViewModel(application: Application) : AndroidViewModel(ap
val result = mutableListOf<String>()
getActiveLists(prefs).forEach { path ->
runCatching {
File(path).useLines { lines ->
File(path.file).useLines { lines ->
lines.forEach { line ->
result += line
}

View File

@@ -7,6 +7,7 @@ import androidx.compose.material3.SnackbarHostState
import com.cherret.zaprett.byedpi.ByeDpiVpnService
import com.cherret.zaprett.data.ServiceStatus
import com.cherret.zaprett.data.ServiceType
import com.cherret.zaprett.data.StorageData
import com.cherret.zaprett.utils.disableStrategy
import com.cherret.zaprett.utils.enableStrategy
import com.cherret.zaprett.utils.getActiveByeDPIStrategy
@@ -19,6 +20,7 @@ import com.cherret.zaprett.utils.getServiceType
import com.cherret.zaprett.utils.getStatus
import kotlinx.coroutines.CoroutineScope
import java.io.File
import kotlin.emptyArray
class StrategyViewModel(application: Application): BaseListsViewModel(application) {
private val sharedPreferences = application.getSharedPreferences("settings", Context.MODE_PRIVATE)
@@ -28,14 +30,20 @@ class StrategyViewModel(application: Application): BaseListsViewModel(applicatio
ServiceType.nfqws2 -> Nfqws2StrategyProvider()
ServiceType.byedpi -> ByeDPIStrategyProvider(sharedPreferences)
}
override fun loadAllItems(): Array<String> = strategyProvider.getAll()
override fun loadActiveItems(): Array<String> = strategyProvider.getActive()
override fun loadAllItems(): Array<StorageData> = strategyProvider.getAll()
// костыль, желательно переделать
override fun loadActiveItems(): Array<StorageData> {
val activeItem = strategyProvider.getActive().getOrNull()
return if (activeItem != null) arrayOf(activeItem)
else emptyArray()
}
override fun deleteItem(item: String, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
override fun deleteItem(item: StorageData, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
val wasChecked = checked[item] == true
disableStrategy(item, sharedPreferences)
val success = File(item).delete()
if (success) refresh()
disableStrategy(item.manifestPath, sharedPreferences)
val successArtifact = File(item.file).delete()
val successManifest = File(item.manifestPath).delete()
if (successArtifact && successManifest) refresh()
if (getServiceType(sharedPreferences) != ServiceType.byedpi) {
getStatus { isEnabled ->
if (isEnabled && wasChecked) {
@@ -51,19 +59,19 @@ class StrategyViewModel(application: Application): BaseListsViewModel(applicatio
}
}
override fun onCheckedChange(item: String, isChecked: Boolean, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
override fun onCheckedChange(item: StorageData, isChecked: Boolean, snackbarHostState: SnackbarHostState, scope: CoroutineScope) {
checked[item] = isChecked
if (isChecked) {
checked.keys.forEach { key ->
checked[key] = false
disableStrategy(key, sharedPreferences)
disableStrategy(key.manifestPath, sharedPreferences)
}
checked[item] = true
enableStrategy(item, sharedPreferences)
enableStrategy(item.manifestPath, sharedPreferences)
}
else {
checked[item] = false
disableStrategy(item, sharedPreferences)
disableStrategy(item.manifestPath, sharedPreferences)
}
if (getServiceType(sharedPreferences) != ServiceType.byedpi) {
getStatus { isEnabled ->
@@ -81,8 +89,8 @@ class StrategyViewModel(application: Application): BaseListsViewModel(applicatio
}
interface StrategyProvider {
fun getAll(): Array<String>
fun getActive(): Array<String>
fun getAll(): Array<StorageData>
fun getActive(): Result<StorageData>
}
class NfqwsStrategyProvider : StrategyProvider {

View File

@@ -0,0 +1,106 @@
package com.cherret.zaprett.utils
import android.annotation.SuppressLint
import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.Build
import android.os.Environment
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.core.net.toUri
import java.io.File
import java.security.MessageDigest
object DownloadUtils {
fun registerDownloadListener(context: Context, downloadId: Long, onDownloaded: (Uri) -> Unit, onError: (String) -> Unit) {// AI Generated
val receiver = object : BroadcastReceiver() {
@SuppressLint("Range")
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action != DownloadManager.ACTION_DOWNLOAD_COMPLETE) return
if (intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) != downloadId) return
val dm = context?.getSystemService(Context.DOWNLOAD_SERVICE) as? DownloadManager ?: return
val query = DownloadManager.Query().setFilterById(downloadId)
dm.query(query)?.use { cursor ->
if (cursor.moveToFirst()) {
val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
val reason = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON))
when (status) {
DownloadManager.STATUS_SUCCESSFUL -> {
val uriString = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
if (uriString != null) {
val uri = uriString.toUri()
context.unregisterReceiver(this)
onDownloaded(uri)
}
}
DownloadManager.STATUS_FAILED -> {
context.unregisterReceiver(this)
val errorMessage = when (reason) {
DownloadManager.ERROR_CANNOT_RESUME -> "Cannot resume download"
DownloadManager.ERROR_DEVICE_NOT_FOUND -> "Device not found"
DownloadManager.ERROR_FILE_ALREADY_EXISTS -> "File already exists"
DownloadManager.ERROR_FILE_ERROR -> "File error"
DownloadManager.ERROR_HTTP_DATA_ERROR -> "HTTP data error"
DownloadManager.ERROR_INSUFFICIENT_SPACE -> "Insufficient space"
DownloadManager.ERROR_TOO_MANY_REDIRECTS -> "Too many redirects"
DownloadManager.ERROR_UNHANDLED_HTTP_CODE -> "Unhandled HTTP code"
DownloadManager.ERROR_UNKNOWN -> "Unknown error"
else -> "Download failed: reason=$reason"
}
onError(errorMessage)
}
}
}
}
}
}
val intentFilter = IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED)
} else {
ContextCompat.registerReceiver(context, receiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), ContextCompat.RECEIVER_EXPORTED)
}
}
fun getFileSha256(file: File): String {
val digest = MessageDigest.getInstance("SHA-256")
file.inputStream().use { input ->
val buffer = ByteArray(1024)
var bytesRead: Int
while (input.read(buffer).also { bytesRead = it } != -1) {
digest.update(buffer, 0, bytesRead)
}
}
return digest.digest().joinToString("") { "%02x".format(it) }
}
fun download(context: Context, url: String): Long {
val fileName = url.substringAfterLast("/")
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val request = DownloadManager.Request(url.toUri()).apply {
setTitle(fileName)
setDescription(fileName)
setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE)
}
return downloadManager.enqueue(request)
}
fun installApk(context: Context, uri: Uri) {
val file = File(uri.path!!)
val apkUri = FileProvider.getUriForFile(context, "${context.packageName}.provider", file)
if (context.packageManager.canRequestPackageInstalls()) {
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(apkUri, "application/vnd.android.package-archive")
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
}
context.startActivity(intent)
}
}
}

View File

@@ -0,0 +1,93 @@
package com.cherret.zaprett.utils
import android.content.SharedPreferences
import com.cherret.zaprett.data.DependencyEntry
import com.cherret.zaprett.data.RepoIndex
import com.cherret.zaprett.data.RepoIndexItem
import com.cherret.zaprett.data.RepoItemFull
import com.cherret.zaprett.data.RepoManifest
import com.cherret.zaprett.data.ResolveResult
import com.cherret.zaprett.data.UpdateData
import com.cherret.zaprett.data.UpdateInfo
import io.ktor.client.HttpClient
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import kotlinx.serialization.json.Json
import kotlin.collections.forEach
object NetworkUtils {
private val client = HttpClient(OkHttp)
private val json = Json { ignoreUnknownKeys = true }
fun getRepo(url: String, filter: (RepoIndexItem) -> Boolean): Flow<List<RepoItemFull>> = flow {
val index = client.get(url).bodyAsText()
val indexJson = json.decodeFromString<RepoIndex>(index)
val filtered = indexJson.items.filter(filter)
val semaphore = Semaphore(15)
val manifest = coroutineScope {
filtered.map { item ->
async {
semaphore.withPermit {
val manifest =
json.decodeFromString<RepoManifest>(client.get(item.manifest).bodyAsText())
RepoItemFull(item, manifest)
}
}
}.awaitAll()
}
emit(manifest)
}
fun resolveDependencies(items: List<RepoItemFull>): Flow<ResolveResult> = flow {
val resolved = mutableSetOf<String>()
val depsMap = mutableMapOf<String, DependencyEntry>()
val manifestCache = mutableMapOf<String, RepoManifest>()
suspend fun collect(manifest: RepoManifest, rootName: String) {
manifest.dependencies.forEach { depUrl ->
val dep = manifestCache.getOrPut(depUrl) {
json.decodeFromString<RepoManifest>(
client.get(depUrl).bodyAsText()
)
}
val entry = depsMap.getOrPut(dep.name) {
DependencyEntry(dep)
}
entry.dependencies += rootName
if (resolved.add(dep.name)) {
collect(dep, dep.name)
}
}
}
items.forEach { item ->
collect(item.manifest, item.index.id)
}
emit(
ResolveResult(
roots = items,
dependencies = depsMap.values.toList()
)
)
}
suspend fun getUpdate(sharedPreferences: SharedPreferences): Result<UpdateData> {
val url = sharedPreferences.getString("update_repo_url", "https://raw.githubusercontent.com/CherretGit/zaprett-app/refs/heads/main/update.json")?: "https://raw.githubusercontent.com/CherretGit/zaprett-app/refs/heads/main/update.json"
return runCatching {
val update = client.get(url).bodyAsText()
val updateInfo = json.decodeFromString<UpdateInfo>(update)
val changeLog = client.get(updateInfo.changelogUrl).bodyAsText()
UpdateData(
updateInfo,
changeLog
)
}
}
}

View File

@@ -1,149 +0,0 @@
package com.cherret.zaprett.utils
import android.annotation.SuppressLint
import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.Build
import androidx.compose.runtime.mutableStateOf
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import com.cherret.zaprett.data.DependencyEntry
import com.cherret.zaprett.data.RepoIndex
import com.cherret.zaprett.data.RepoIndexItem
import com.cherret.zaprett.data.RepoItemFull
import com.cherret.zaprett.data.RepoManifest
import com.cherret.zaprett.data.ResolveResult
import io.ktor.client.HttpClient
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import kotlinx.serialization.json.Json
import java.io.File
import java.security.MessageDigest
private val client = HttpClient(OkHttp)
private val json = Json { ignoreUnknownKeys = true }
fun getRepo(url: String, filter: (RepoIndexItem) -> Boolean): Flow<List<RepoItemFull>> = flow {
val index = client.get(url).bodyAsText()
val indexJson = json.decodeFromString<RepoIndex>(index)
val filtered = indexJson.items.filter(filter)
val manifest = coroutineScope {
filtered.map { item ->
async {
Semaphore(15).withPermit {
val manifest =
json.decodeFromString<RepoManifest>(client.get(item.manifest).bodyAsText())
RepoItemFull(item, manifest)
}
}
}.awaitAll()
}
emit(manifest)
}
fun resolveDependencies(items: List<RepoItemFull>): Flow<ResolveResult> = flow {
val resolved = mutableSetOf<String>()
val depsMap = mutableMapOf<String, DependencyEntry>()
val manifestCache = mutableMapOf<String, RepoManifest>()
suspend fun collect(manifest: RepoManifest, rootName: String) {
manifest.dependencies.forEach { depUrl ->
val dep = manifestCache.getOrPut(depUrl) {
json.decodeFromString<RepoManifest>(
client.get(depUrl).bodyAsText()
)
}
val entry = depsMap.getOrPut(dep.name) {
DependencyEntry(dep)
}
entry.dependencies += rootName
if (resolved.add(dep.name)) {
collect(dep, dep.name)
}
}
}
items.forEach { item ->
collect(item.manifest, item.index.id)
}
emit(
ResolveResult(
roots = items,
dependencies = depsMap.values.toList()
)
)
}
fun registerDownloadListener(context: Context, downloadId: Long, onDownloaded: (Uri) -> Unit, onError: (String) -> Unit) {// AI Generated
val receiver = object : BroadcastReceiver() {
@SuppressLint("Range")
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action != DownloadManager.ACTION_DOWNLOAD_COMPLETE) return
if (intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) != downloadId) return
val dm = context?.getSystemService(Context.DOWNLOAD_SERVICE) as? DownloadManager ?: return
val query = DownloadManager.Query().setFilterById(downloadId)
dm.query(query)?.use { cursor ->
if (cursor.moveToFirst()) {
val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
val reason = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON))
when (status) {
DownloadManager.STATUS_SUCCESSFUL -> {
val uriString = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
if (uriString != null) {
val uri = uriString.toUri()
context.unregisterReceiver(this)
onDownloaded(uri)
}
}
DownloadManager.STATUS_FAILED -> {
context.unregisterReceiver(this)
val errorMessage = when (reason) {
DownloadManager.ERROR_CANNOT_RESUME -> "Cannot resume download"
DownloadManager.ERROR_DEVICE_NOT_FOUND -> "Device not found"
DownloadManager.ERROR_FILE_ALREADY_EXISTS -> "File already exists"
DownloadManager.ERROR_FILE_ERROR -> "File error"
DownloadManager.ERROR_HTTP_DATA_ERROR -> "HTTP data error"
DownloadManager.ERROR_INSUFFICIENT_SPACE -> "Insufficient space"
DownloadManager.ERROR_TOO_MANY_REDIRECTS -> "Too many redirects"
DownloadManager.ERROR_UNHANDLED_HTTP_CODE -> "Unhandled HTTP code"
DownloadManager.ERROR_UNKNOWN -> "Unknown error"
else -> "Download failed: reason=$reason"
}
onError(errorMessage)
}
}
}
}
}
}
val intentFilter = IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED)
} else {
ContextCompat.registerReceiver(context, receiver, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE), ContextCompat.RECEIVER_EXPORTED)
}
}
fun getFileSha256(file: File): String {
val digest = MessageDigest.getInstance("SHA-256")
file.inputStream().use { input ->
val buffer = ByteArray(1024)
var bytesRead: Int
while (input.read(buffer).also { bytesRead = it } != -1) {
digest.update(buffer, 0, bytesRead)
}
}
return digest.digest().joinToString("") { "%02x".format(it) }
}

View File

@@ -1,99 +0,0 @@
package com.cherret.zaprett.utils
import android.app.DownloadManager
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Environment
import androidx.core.content.FileProvider
import androidx.core.net.toUri
import com.cherret.zaprett.BuildConfig
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import okhttp3.Call
import okhttp3.Callback
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okio.IOException
import java.io.File
private val client = OkHttpClient()
private val json = Json { ignoreUnknownKeys = true }
// PLS EGOR-WHITE REFACTOR THIS
fun getUpdate(sharedPreferences: SharedPreferences, callback: (UpdateInfo?) -> Unit) {
val request = Request.Builder().url(sharedPreferences.getString("update_repo_url", "https://raw.githubusercontent.com/CherretGit/zaprett-app/refs/heads/main/update.json")?: "https://raw.githubusercontent.com/CherretGit/zaprett-app/refs/heads/main/update.json").build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
callback(null)
}
override fun onResponse(call: Call, response: Response) {
response.use {
if (!response.isSuccessful) {
throw IOException()
callback(null)
}
val jsonString = response.body.string()
val updateInfo = json.decodeFromString<UpdateInfo>(jsonString)
updateInfo?.versionCode?.let { versionCode ->
if (versionCode > BuildConfig.VERSION_CODE)
callback(updateInfo)
}
}
}
})
}
fun getChangelog(changelogUrl: String, callback: (String?) -> Unit) {
val request = Request.Builder().url(changelogUrl).build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
callback(null)
}
override fun onResponse(call: Call, response: Response) {
response.use {
if (!response.isSuccessful) {
callback(null)
return
}
val changelogText = response.body.string()
callback(changelogText)
}
}
})
}
fun download(context: Context, url: String): Long {
val fileName = url.substringAfterLast("/")
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val request = DownloadManager.Request(url.toUri()).apply {
setTitle(fileName)
setDescription(fileName)
setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE)
}
return downloadManager.enqueue(request)
}
fun installApk(context: Context, uri: Uri) {
val file = File(uri.path!!)
val apkUri = FileProvider.getUriForFile(context, "${context.packageName}.provider", file)
if (context.packageManager.canRequestPackageInstalls()) {
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(apkUri, "application/vnd.android.package-archive")
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
}
context.startActivity(intent)
}
}
@Serializable
data class UpdateInfo(
val version: String?,
val versionCode: Int?,
val downloadUrl: String?,
val changelogUrl: String?
)

View File

@@ -12,9 +12,13 @@ import androidx.core.content.ContextCompat
import androidx.core.content.edit
import com.cherret.zaprett.data.AppListType
import com.cherret.zaprett.data.ListType
import com.cherret.zaprett.data.RepoManifest
import com.cherret.zaprett.data.ServiceType
import com.cherret.zaprett.data.StorageData
import com.cherret.zaprett.data.ZaprettConfig
import com.topjohnwu.superuser.Shell
import io.ktor.client.plugins.cache.storage.FileStorage
import kotlinx.io.files.FileNotFoundException
import kotlinx.serialization.json.Json
import java.io.File
@@ -124,7 +128,7 @@ fun getNfqws2Version(callback: (String) -> Unit) {
}
fun getConfigFile(): File {
return getZaprettPath().resolve("/config.json")
return getZaprettPath().resolve("config.json")
}
fun setStartOnBoot(prefs: SharedPreferences, callback: (Boolean) -> Unit) {
@@ -149,63 +153,57 @@ fun getZaprettPath(): File {
return Environment.getExternalStorageDirectory().resolve("zaprett")
}
fun getAllLists(): Array<String> {
val listsDir = getZaprettPath().resolve("lists/include")
return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
?.map { it.absolutePath }
fun getManifestsPath(): File {
return getZaprettPath().resolve("manifests")
}
fun getValidManifests(listsDir: File): Array<StorageData> {
return (listsDir.listFiles()
?.mapNotNull { file ->
if (!file.isFile || file.extension.lowercase() != "json") return@mapNotNull null
parseManifestFromFile(file).getOrNull()?.takeIf {
File(it.file).exists()
}
}
?.toTypedArray()
?: emptyArray()
?: emptyArray())
}
fun getAllIpsets(): Array<String> {
val listsDir = getZaprettPath().resolve("ipset/include")
return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
?.map { it.absolutePath }
?.toTypedArray()
?: emptyArray()
fun getAllLists(): Array<StorageData> {
val listsDir = getManifestsPath().resolve("lists/include")
return getValidManifests(listsDir)
}
fun getAllExcludeLists(): Array<String> {
val listsDir = getZaprettPath().resolve("lists/include")
return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
?.map { it.absolutePath }
?.toTypedArray()
?: emptyArray()
fun getAllIpsets(): Array<StorageData> {
val listsDir = getManifestsPath().resolve("ipset/include")
return getValidManifests(listsDir)
}
fun getAllExcludeIpsets(): Array<String> {
val listsDir = getZaprettPath().resolve("ipset/exclude/")
return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
?.map { it.absolutePath }
?.toTypedArray()
?: emptyArray()
fun getAllExcludeLists(): Array<StorageData> {
val listsDir = getManifestsPath().resolve("lists/exclude")
return getValidManifests(listsDir)
}
fun getAllNfqwsStrategies(): Array<String> {
val listsDir = getZaprettPath().resolve("strategies/nfqws")
return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
?.map { it.absolutePath }
?.toTypedArray()
?: emptyArray()
fun getAllExcludeIpsets(): Array<StorageData> {
val listsDir = getManifestsPath().resolve("ipset/exclude")
return getValidManifests(listsDir)
}
fun getAllNfqws2Strategies(): Array<String> {
val listsDir = getZaprettPath().resolve("strategies/nfqws2")
return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
?.map { it.absolutePath }
?.toTypedArray()
?: emptyArray()
fun getAllNfqwsStrategies(): Array<StorageData> {
val listsDir = getManifestsPath().resolve("strategies/nfqws")
return getValidManifests(listsDir)
}
fun getAllByeDPIStrategies(): Array<String> {
val listsDir = getZaprettPath().resolve("strategies/byedpi")
return listsDir.listFiles { file -> file.isFile && file.extension.lowercase() == "txt" }
?.map { it.absolutePath }
?.toTypedArray()
?: emptyArray()
fun getAllNfqws2Strategies(): Array<StorageData> {
val listsDir = getManifestsPath().resolve("strategies/nfqws2")
return getValidManifests(listsDir)
}
fun getAllStrategies(sharedPreferences: SharedPreferences): Array<String> {
fun getAllByeDPIStrategies(): Array<StorageData> {
val listsDir = getManifestsPath().resolve("strategies/byedpi")
return getValidManifests(listsDir)
}
fun getAllStrategies(sharedPreferences: SharedPreferences): Array<StorageData> {
return when (getServiceType(sharedPreferences)) {
ServiceType.nfqws -> getAllNfqwsStrategies()
ServiceType.nfqws2 -> getAllNfqws2Strategies()
@@ -213,11 +211,16 @@ fun getAllStrategies(sharedPreferences: SharedPreferences): Array<String> {
}
}
fun getActiveLists(sharedPreferences: SharedPreferences): Array<String> {
fun getAllBin(): Array<StorageData> {
val listsDir = getManifestsPath().resolve("bin")
return getValidManifests(listsDir)
}
fun getActiveLists(sharedPreferences: SharedPreferences): Array<StorageData> {
if (getServiceType(sharedPreferences) != ServiceType.byedpi) {
return readConfig().activeLists.toTypedArray()
return readConfig().activeLists.mapNotNull { parseManifestFromFile(File(it)).getOrNull() }.toTypedArray()
} else {
return sharedPreferences.getStringSet("lists", emptySet())?.toTypedArray() ?: emptyArray()
return sharedPreferences.getStringSet("lists", emptySet())!!.mapNotNull { parseManifestFromFile(File(it)).getOrNull() }.toTypedArray()
}
}
@@ -242,55 +245,59 @@ fun setServiceType(sharedPreferences: SharedPreferences, serviceType: ServiceTyp
}
}
fun getActiveIpsets(sharedPreferences: SharedPreferences): Array<String> {
fun getActiveIpsets(sharedPreferences: SharedPreferences): Array<StorageData> {
if (getServiceType(sharedPreferences) != ServiceType.byedpi) {
return readConfig().activeIpsets.toTypedArray()
} else return sharedPreferences.getStringSet("ipsets", emptySet())?.toTypedArray() ?: emptyArray()
return readConfig().activeIpsets.mapNotNull { parseManifestFromFile(File(it)).getOrNull() }.toTypedArray()
} else return sharedPreferences.getStringSet("ipsets", emptySet())!!.mapNotNull { parseManifestFromFile(File(it)).getOrNull() }.toTypedArray()
}
fun getActiveExcludeLists(sharedPreferences: SharedPreferences): Array<String> {
fun getActiveExcludeLists(sharedPreferences: SharedPreferences): Array<StorageData> {
if (getServiceType(sharedPreferences) != ServiceType.byedpi) {
return readConfig().activeExcludeLists.toTypedArray()
return readConfig().activeExcludeLists.mapNotNull { parseManifestFromFile(File(it)).getOrNull() }.toTypedArray()
} else {
return sharedPreferences.getStringSet("exclude_lists", emptySet())?.toTypedArray() ?: emptyArray()
return sharedPreferences.getStringSet("exclude_lists", emptySet())!!.mapNotNull { parseManifestFromFile(File(it)).getOrNull() }.toTypedArray()
}
}
fun getActiveExcludeIpsets(sharedPreferences: SharedPreferences): Array<String> {
fun getActiveExcludeIpsets(sharedPreferences: SharedPreferences): Array<StorageData> {
if (getServiceType(sharedPreferences) != ServiceType.byedpi) {
return readConfig().activeExcludeIpsets.toTypedArray()
} else return sharedPreferences.getStringSet("exclude_ipsets", emptySet())?.toTypedArray() ?: emptyArray()
return readConfig().activeExcludeIpsets.mapNotNull { parseManifestFromFile(File(it)).getOrNull() }.toTypedArray()
} else return sharedPreferences.getStringSet("exclude_ipsets", emptySet())!!.mapNotNull { parseManifestFromFile(File(it)).getOrNull() }.toTypedArray()
}
fun getActiveNfqwsStrategy(): Array<String> {
val strategy = readConfig().strategy
return if (strategy.isNotBlank()) arrayOf(strategy) else emptyArray()
fun getActiveNfqwsStrategy(): Result<StorageData> {
return parseManifestFromFile(File(readConfig().strategy))
}
fun getActiveNfqws2Strategy(): Array<String> {
val strategy = readConfig().strategyNfqws2
return if (strategy.isNotBlank()) arrayOf(strategy) else emptyArray()
fun getActiveNfqws2Strategy(): Result<StorageData> {
return parseManifestFromFile(File(readConfig().strategyNfqws2))
}
fun getActiveByeDPIStrategy(sharedPreferences: SharedPreferences): Array<String> {
val path = sharedPreferences.getString("active_strategy", "")
if (!path.isNullOrBlank() && File(path).exists()) {
return arrayOf(path)
}
return emptyArray()
fun getActiveByeDPIStrategy(sharedPreferences: SharedPreferences): Result<StorageData> {
val path = sharedPreferences.getString("active_strategy", "")?: ""
// if (!path.isNullOrBlank() && File(path).exists()) {
// return arrayOf(path)
// }
// return emptyArray()
return parseManifestFromFile(File(path))
}
fun getActiveByeDPIStrategyContent(sharedPreferences: SharedPreferences): List<String> {
val path = sharedPreferences.getString("active_strategy", "")
if (!path.isNullOrBlank() && File(path).exists()) {
return File(path).readLines()
}
return emptyList()
val path = sharedPreferences.getString("active_strategy", "")?: ""
if (path.isBlank()) return emptyList()
val manifest = parseManifestFromFile(File(path)).getOrNull()
val file = manifest?.file?.let { File(it) }
return if (file?.exists() == true) file.readLines()
else emptyList()
}
fun getActiveStrategy(sharedPreferences: SharedPreferences): Array<String> {
return if (getServiceType(sharedPreferences) != ServiceType.byedpi) getActiveNfqwsStrategy()
else getActiveByeDPIStrategy(sharedPreferences)
fun getActiveStrategy(sharedPreferences: SharedPreferences): Result<StorageData> {
return when(getServiceType(sharedPreferences)) {
ServiceType.byedpi -> getActiveByeDPIStrategy(sharedPreferences)
ServiceType.nfqws -> getActiveNfqwsStrategy()
ServiceType.nfqws2 -> getActiveNfqws2Strategy()
}
}
fun enableList(path: String, sharedPreferences: SharedPreferences) {
@@ -563,4 +570,13 @@ fun getHostListMode(prefs: SharedPreferences): ListType {
} else {
return runCatching { enumValueOf<ListType>(prefs.getString("list_type", ListType.whitelist.name) ?: ListType.whitelist.name) }.getOrDefault(ListType.whitelist)
}
}
fun parseManifestFromFile(file: File): Result<StorageData> {
return runCatching {
if (!file.isFile || !file.canRead()) throw IllegalArgumentException("can't find manifest file")
val manifest = Json.decodeFromString<StorageData>(file.readText())
manifest.manifestPath = file.path
manifest
}
}