Skip to content

Commit

Permalink
fix: Scheme of reloading
Browse files Browse the repository at this point in the history
Change-Id: I9522e49ffaaf69c2e1af06e984f867c8131429c0
  • Loading branch information
XayahSuSuSu committed Sep 29, 2023
1 parent 7111b8c commit 5a80688
Show file tree
Hide file tree
Showing 12 changed files with 381 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,7 @@ interface PackageRestoreEntireDao {

@Delete(entity = PackageRestoreEntire::class)
suspend fun delete(items: List<PackageRestoreEntire>)

@Query("DELETE FROM PackageRestoreEntire")
suspend fun clearTable()
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.xayah.databackup.ui.activity.main.page.restore

import android.content.Context
import android.content.Intent
import android.content.pm.PackageInfo
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
Expand All @@ -21,14 +21,19 @@ import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.hilt.navigation.compose.hiltViewModel
import com.xayah.databackup.R
import com.xayah.databackup.data.OperationMask
import com.xayah.databackup.data.PackageRestoreEntire
import com.xayah.databackup.data.PackageRestoreEntireDao
import com.xayah.databackup.ui.activity.main.page.backup.StorageItem
import com.xayah.databackup.ui.activity.main.page.backup.StorageItemType
import com.xayah.databackup.ui.activity.main.router.MainRoutes
Expand All @@ -46,24 +51,27 @@ import com.xayah.databackup.ui.component.paddingHorizontal
import com.xayah.databackup.ui.component.paddingTop
import com.xayah.databackup.ui.token.CommonTokens
import com.xayah.databackup.ui.token.RadioTokens
import com.xayah.databackup.util.CompressionType
import com.xayah.databackup.util.ConstantUtil
import com.xayah.databackup.util.DataType
import com.xayah.databackup.util.IntentUtil
import com.xayah.databackup.util.LogUtil
import com.xayah.databackup.util.PathUtil
import com.xayah.databackup.util.command.EnvUtil
import com.xayah.databackup.util.command.InstallationUtil
import com.xayah.databackup.util.command.PreparationUtil
import com.xayah.databackup.util.command.toLineString
import com.xayah.databackup.util.databasePath
import com.xayah.databackup.util.iconPath
import com.xayah.databackup.util.readExternalRestoreSaveChild
import com.xayah.databackup.util.readInternalRestoreSaveChild
import com.xayah.databackup.util.readRestoreSavePath
import com.xayah.databackup.util.saveExternalRestoreSaveChild
import com.xayah.databackup.util.saveInternalRestoreSaveChild
import com.xayah.databackup.util.saveRestoreSavePath
import com.xayah.librootservice.parcelables.PathParcelable
import com.xayah.librootservice.service.RemoteRootService
import com.xayah.librootservice.util.ExceptionUtil.tryOn
import com.xayah.librootservice.util.ExceptionUtil.tryOnScope
import com.xayah.librootservice.util.ExceptionUtil.tryService
import com.xayah.librootservice.util.withIOContext
import kotlinx.coroutines.launch
import kotlin.system.exitProcess

@ExperimentalMaterial3Api
private suspend fun DialogState.openDirectoryDialog(context: Context) {
Expand Down Expand Up @@ -192,42 +200,153 @@ private suspend fun DialogState.openDirectoryDialog(context: Context) {
}
}

private data class TypedTimestamp(
val timestamp: Long,
val archivePathList: MutableList<PathParcelable>,
)

private data class TypedPath(
val packageName: String,
val typedTimestampList: MutableList<TypedTimestamp>,
)

@ExperimentalMaterial3Api
private suspend fun DialogState.openReloadDialog(context: Context) {
val textList = mutableListOf<String>()
open(
private suspend fun DialogState.openReloadDialog(context: Context, logUtil: LogUtil, packageRestoreEntireDao: PackageRestoreEntireDao) {
openLoading(
title = context.getString(R.string.prompt),
icon = ImageVector.vectorResource(context.theme, context.resources, R.drawable.ic_rounded_folder_open),
onLoading = {
// Copy the databases and icons from restore save path.
var isSuccess = true
PreparationUtil.copyRecursivelyAndPreserve(path = PathUtil.getDatabaseSavePath(), targetPath = PathUtil.getParentPath(context.databasePath()))
.also { (succeed, out) ->
if (succeed.not()) {
isSuccess = false
textList.add("${context.getString(R.string.databases_reload_failed)}: ${out}.")
}
}
PreparationUtil.copyRecursivelyAndPreserve(path = PathUtil.getIconSavePath(), targetPath = PathUtil.getParentPath(context.iconPath()))
.also { (succeed, out) ->
if (succeed.not()) {
isSuccess = false
textList.add("${context.getString(R.string.icon_reload_failed)}: ${out}.")
}
withIOContext {
val logTag = "Reload"
val logId = logUtil.log(logTag, "Start reloading...")
val remoteRootService = RemoteRootService(context)
val packageManager = context.packageManager
val installationUtil = InstallationUtil(logId, logUtil)
val pathList = remoteRootService.walkFileTree(PathUtil.getRestorePackagesSavePath())
val typedPathList = mutableListOf<TypedPath>()

logUtil.log(logTag, "Clear the table and try to create icon dir")
// Clear table first
packageRestoreEntireDao.clearTable()
// Create icon dir if it doesn't exist
EnvUtil.createIconDirectory(context)

logUtil.log(logTag, "Classify the paths")
// Classify the paths
pathList.forEach { path ->
tryOnScope(
block = {
val pathListSize = path.pathList.size
val packageName = path.pathList[pathListSize - 3]
val timestamp = path.pathList[pathListSize - 2].toLong()
val typedPathListIndex = typedPathList.indexOfLast { it.packageName == packageName }
if (typedPathListIndex == -1) {
val typedTimestamp = TypedTimestamp(timestamp = timestamp, archivePathList = mutableListOf(path))
typedPathList.add(TypedPath(packageName = packageName, typedTimestampList = mutableListOf(typedTimestamp)))
} else {
val typedTimestampList = typedPathList[typedPathListIndex].typedTimestampList
val typedTimestampIndex = typedTimestampList.indexOfLast { it.timestamp == timestamp }
if (typedTimestampIndex == -1) {
val typedTimestamp = TypedTimestamp(timestamp = timestamp, archivePathList = mutableListOf(path))
typedPathList[typedPathListIndex].typedTimestampList.add(typedTimestamp)
} else {
typedPathList[typedPathListIndex].typedTimestampList[typedTimestampIndex].archivePathList.add(path)
}
}
},
onException = {
logUtil.log(logTag, "Failed: ${it.message}")
}
)
}

// Try to restart application.
if (isSuccess) tryOn(block = {
context.packageManager.getLaunchIntentForPackage(context.packageName).also { intent: Intent? ->
intent!!.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
context.applicationContext.startActivity(intent)
exitProcess(0)
logUtil.log(logTag, "Reload the archives")
typedPathList.forEach { typedPath ->
// For each package
val packageName = typedPath.packageName
logUtil.log(logTag, "Package: $packageName")

typedPath.typedTimestampList.forEach { typedTimestamp ->
// For each timestamp
var packageInfo: PackageInfo? = null
val timestamp = typedTimestamp.timestamp
val archivePathList = typedTimestamp.archivePathList
var compressionType: CompressionType = CompressionType.ZSTD
var operationCode = OperationMask.None

logUtil.log(logTag, "Timestamp: $timestamp")
val tmpApkPath = PathUtil.getTmpApkPath(context = context, packageName = packageName)
remoteRootService.deleteRecursively(tmpApkPath)
remoteRootService.mkdirs(tmpApkPath)

archivePathList.forEach { archivePath ->
// For each archive
logUtil.log(logTag, "Archive: ${archivePath.pathString}")
tryOnScope(
block = {
when (archivePath.nameWithoutExtension) {
DataType.PACKAGE_APK.type -> {
val type = CompressionType.suffixOf(archivePath.extension)
if (type != null) {
compressionType = type
installationUtil.decompress(
archivePath = archivePath.pathString,
tmpApkPath = tmpApkPath,
compressionType = type
)
remoteRootService.listFilePaths(tmpApkPath).also { pathList ->
if (pathList.isNotEmpty()) {
packageInfo = remoteRootService.getPackageArchiveInfo(pathList.first())
operationCode = operationCode or OperationMask.Apk
}
}
} else {
logUtil.log(logTag, "Failed to parse compression type: ${archivePath.extension}")
}
}

DataType.PACKAGE_USER.type -> {
operationCode = operationCode or OperationMask.Data
}
}
},
onException = {
logUtil.log(logTag, "Failed: ${it.message}")
}
)
}

val packageRestoreEntire = PackageRestoreEntire(
packageName = packageName,
label = "",
backupOpCode = operationCode,
operationCode = OperationMask.None,
timestamp = timestamp,
versionName = "",
versionCode = 0,
flags = 0,
compressionType = compressionType,
active = false
)
packageInfo?.apply {
packageRestoreEntire.also { entity ->
entity.label = applicationInfo.loadLabel(packageManager).toString()
entity.versionName = versionName ?: ""
entity.versionCode = longVersionCode
entity.flags = applicationInfo.flags
}
val icon = applicationInfo.loadIcon(packageManager)
EnvUtil.saveIcon(context, packageName, icon)
logUtil.log(logTag, "Icon saved")
}
packageRestoreEntireDao.upsert(packageRestoreEntire)

remoteRootService.deleteRecursively(tmpApkPath)
}
}
}, onException = {
textList.add(context.getString(R.string.restart_failed))
})
remoteRootService.destroyService()
}
},
block = { Text(text = textList.toLineString().trim()) }
)
}

Expand All @@ -237,8 +356,11 @@ private suspend fun DialogState.openReloadDialog(context: Context) {
fun PageRestore() {
val scope = rememberCoroutineScope()
val context = LocalContext.current
val viewModel = hiltViewModel<RestoreViewModel>()
val dialogSlot = LocalSlotScope.current!!.dialogSlot
val navController = LocalSlotScope.current!!.navController
val uiState by viewModel.uiState

LazyColumn(
modifier = Modifier.paddingHorizontal(CommonTokens.PaddingMedium),
verticalArrangement = Arrangement.spacedBy(CommonTokens.PaddingLarge)
Expand All @@ -265,7 +387,7 @@ fun PageRestore() {
val onClicks = listOf<suspend () -> Unit>(
{
dialogSlot.openConfirmDialog(context, context.getString(R.string.confirm_reload)).also { (confirmed, _) ->
if (confirmed) dialogSlot.openReloadDialog(context)
if (confirmed) dialogSlot.openReloadDialog(context, uiState.logUtil, uiState.packageRestoreEntireDao)
}
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.xayah.databackup.ui.activity.main.page.restore

import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import com.xayah.databackup.data.PackageRestoreEntireDao
import com.xayah.databackup.util.LogUtil
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

data class RestoreUiState(
val logUtil: LogUtil,
val packageRestoreEntireDao: PackageRestoreEntireDao,
)

@HiltViewModel
class RestoreViewModel @Inject constructor(logUtil: LogUtil, packageRestoreEntireDao: PackageRestoreEntireDao) : ViewModel() {
private val _uiState = mutableStateOf(RestoreUiState(logUtil = logUtil, packageRestoreEntireDao = packageRestoreEntireDao))
val uiState: State<RestoreUiState>
get() = _uiState
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import com.xayah.librootservice.util.ExceptionUtil
import com.xayah.librootservice.util.withIOContext
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

/**
* Creates a [DialogState] and acts as a slot with [DialogState.Insert].
Expand Down Expand Up @@ -92,30 +91,6 @@ class DialogState {
}
}

/**
* Suspend forever for special usages.
*/
suspend fun open(
title: String,
icon: ImageVector,
onLoading: suspend () -> Unit,
block: @Composable () -> Unit,
) {
return suspendCoroutine {
content = {
AlertDialog(
onDismissRequest = {},
confirmButton = {},
dismissButton = null,
title = { Text(text = title) },
icon = { Icon(imageVector = icon, contentDescription = null) },
text = { Loader(modifier = Modifier.fillMaxWidth(), onLoading = onLoading, content = block) },
properties = DialogProperties(dismissOnBackPress = false, dismissOnClickOutside = false)
)
}
}
}

/**
* Loading dialog
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,29 @@ import com.xayah.databackup.util.SymbolUtil.QUOTE
import com.xayah.databackup.util.command.EnvUtil.getCurrentAppVersionName
import com.xayah.librootservice.util.ExceptionUtil.tryOn

private const val TAR_SUFFIX = "tar"
private const val ZSTD_SUFFIX = "tar.zst"
private const val LZ4_SUFFIX = "tar.lz4"

enum class CompressionType(val type: String, val suffix: String, val compressPara: String, val decompressPara: String) {
TAR("tar", "tar", "", ""),
ZSTD("zstd", "tar.zst", "zstd -r -T0 --ultra -1 -q --priority=rt", "-I ${QUOTE}zstd${QUOTE}"),
LZ4("lz4", "tar.lz4", "zstd -r -T0 --ultra -1 -q --priority=rt --format=lz4", "-I ${QUOTE}zstd${QUOTE}");
TAR("tar", TAR_SUFFIX, "", ""),
ZSTD("zstd", ZSTD_SUFFIX, "zstd -r -T0 --ultra -1 -q --priority=rt", "-I ${QUOTE}zstd${QUOTE}"),
LZ4("lz4", LZ4_SUFFIX, "zstd -r -T0 --ultra -1 -q --priority=rt --format=lz4", "-I ${QUOTE}zstd${QUOTE}");

companion object {
fun of(name: String?): CompressionType {
return tryOn(
block = {
CompressionType.valueOf(name!!.uppercase())
},
onException = {
ZSTD
})
fun of(name: String?): CompressionType = tryOn(
block = {
CompressionType.valueOf(name!!.uppercase())
},
onException = {
ZSTD
})

fun suffixOf(suffix: String): CompressionType? = when (suffix) {
TAR_SUFFIX -> TAR
ZSTD_SUFFIX -> ZSTD
LZ4_SUFFIX -> LZ4
else -> null
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class OperationBackupUtil(

val logTag = "APK"
val logId = logUtil.log(logTag, "Start backing up...")
val archivePath = "${getPackageItemSavePath(packageName)}/apk.${compressionType.suffix}"
val archivePath = "${getPackageItemSavePath(packageName)}/${DataType.PACKAGE_APK.type}.${compressionType.suffix}"
val cmd = if (compatibleMode)
"- ./*.apk ${if (compressionType == CompressionType.TAR) "" else "| ${compressionType.compressPara}"} > $archivePath"
else
Expand Down Expand Up @@ -216,7 +216,7 @@ class OperationRestoreUtil(

val logTag = "APK"
val logId = logUtil.log(logTag, "Start restoring...")
val archivePath = "${getPackageItemSavePath(packageName = packageName, timestamp = timestamp)}/apk.${compressionType.suffix}"
val archivePath = "${getPackageItemSavePath(packageName = packageName, timestamp = timestamp)}/${DataType.PACKAGE_APK.type}.${compressionType.suffix}"

// Return if the archive doesn't exist.
remoteRootService.exists(archivePath).also { exists ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ interface IRemoteRootService {
UserHandle getUserHandle(int userId);
StorageStats queryStatsForPackage(in PackageInfo packageInfo, in UserHandle user);
List<UserInfo> getUsers();
ParcelFileDescriptor walkFileTree(String path);
PackageInfo getPackageArchiveInfo(String path);
}
Loading

0 comments on commit 5a80688

Please sign in to comment.