Skip to content

Commit e1521bf

Browse files
author
Fastace
committed
Fix:修复Restic备份快照之后,触发APK恢复逻辑,目前引导恢复页已经能够正确读取,BUG:第二次恢复同一个APP,会累积显示2次待修复,之后点击继续按钮,依然会恢复失败,待修复
1 parent 4388187 commit e1521bf

10 files changed

Lines changed: 197 additions & 18 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
kotlin version: 2.0.21
2+
error message: The daemon has terminated unexpectedly on startup attempt #1 with error code: 0. The daemon process output:
3+
1. Kotlin compile daemon is ready
4+

source/app/src/main/kotlin/com/xayah/databackup/MainActivity.kt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,19 @@ class MainActivity : AppCompatActivity() {
128128
composable(MainRoutes.PackagesBackupProcessingGraph.route) {
129129
PackagesBackupProcessingGraph()
130130
}
131-
composable(MainRoutes.PackagesRestoreProcessingGraph.route) {
132-
PackagesRestoreProcessingGraph()
131+
composable(
132+
route = MainRoutes.PackagesRestoreProcessingGraph.route,
133+
arguments = listOf(
134+
navArgument(MainRoutes.ARG_ACCOUNT_NAME) { type = NavType.StringType },
135+
navArgument(MainRoutes.ARG_ACCOUNT_REMOTE) { type = NavType.StringType },
136+
navArgument(MainRoutes.ARG_PACKAGE_NAME_FILTER) { type = NavType.StringType }
137+
)
138+
) { backStackEntry ->
139+
val cloudName = backStackEntry.arguments?.getString(MainRoutes.ARG_ACCOUNT_NAME) ?: ""
140+
val backupDir = backStackEntry.arguments?.getString(MainRoutes.ARG_ACCOUNT_REMOTE) ?: ""
141+
val packageName = backStackEntry.arguments?.getString(MainRoutes.ARG_PACKAGE_NAME_FILTER) ?: ""
142+
Log.d("Navigation", "PackagesRestoreProcessingGraph: cloudName=$cloudName, backupDir=$backupDir, packageName=$packageName")
143+
PackagesRestoreProcessingGraph(packageNameFilter = packageName)
133144
}
134145
composable(MainRoutes.MediumBackupProcessingGraph.route) {
135146
MediumBackupProcessingGraph()

source/core/ui/src/main/kotlin/com/xayah/core/ui/route/Routes.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ sealed class MainRoutes(val route: String) {
1616
const val ARG_OP_TYPE = "opType"
1717
const val ARG_ID = "id"
1818
const val ARG_GROUP = "group"
19+
const val ARG_PACKAGE_NAME_FILTER = "packageName"
1920
}
2021

2122
data object Dashboard : MainRoutes(route = "main_dashboard")
@@ -84,8 +85,8 @@ sealed class MainRoutes(val route: String) {
8485

8586
data object PackagesRestoreProcessing : MainRoutes(route = "main_packages_restore_processing")
8687
data object PackagesRestoreProcessingSetup : MainRoutes(route = "main_packages_restore_processing_setup")
87-
data object PackagesRestoreProcessingGraph : MainRoutes(route = "main_packages_restore_processing_graph/{$ARG_ACCOUNT_NAME}/{$ARG_ACCOUNT_REMOTE}") {
88-
fun getRoute(cloudName: String = encodedURLWithSpace, backupDir: String = encodedURLWithSpace) = "main_packages_restore_processing_graph/${cloudName}/${backupDir}"
88+
data object PackagesRestoreProcessingGraph : MainRoutes(route = "main_packages_restore_processing_graph/{$ARG_ACCOUNT_NAME}/{$ARG_ACCOUNT_REMOTE}/{$ARG_PACKAGE_NAME_FILTER}") {
89+
fun getRoute(cloudName: String = encodedURLWithSpace, backupDir: String = encodedURLWithSpace, packageName: String = encodedURLWithSpace) = "main_packages_restore_processing_graph/${cloudName}/${backupDir}/${packageName}"
8990
}
9091

9192
data object MediumBackupProcessing : MainRoutes(route = "main_medium_backup_processing")

source/feature/main/processing/src/main/kotlin/com/xayah/feature/main/processing/AbstractPackagesProcessingViewModel.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import kotlinx.coroutines.flow.flatMapLatest
1515
import kotlinx.coroutines.flow.map
1616

1717
data object UpdateApps : ProcessingUiIntent()
18+
data class UpdateAppsWithFilter(val packageNameFilter: String) : ProcessingUiIntent()
1819
data class SetCloudEntity(val name: String) : ProcessingUiIntent()
1920
data class FinishSetup(val navController: NavController) : ProcessingUiIntent()
2021
data object GetUsers : ProcessingUiIntent()

source/feature/main/processing/src/main/kotlin/com/xayah/feature/main/processing/packages/restore/NavHost.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
2222
@ExperimentalFoundationApi
2323
@ExperimentalMaterial3Api
2424
@Composable
25-
fun PackagesRestoreProcessingGraph() {
25+
fun PackagesRestoreProcessingGraph(packageNameFilter: String = "") {
2626
val localNavController = rememberNavController()
2727
val viewModel = hiltViewModel<RestoreViewModelImpl>()
2828

@@ -47,7 +47,11 @@ fun PackagesRestoreProcessingGraph() {
4747
)
4848
}
4949
composable(MainRoutes.PackagesRestoreProcessingSetup.route) {
50-
PagePackagesRestoreProcessingSetup(localNavController = localNavController, viewModel = viewModel)
50+
PagePackagesRestoreProcessingSetup(
51+
localNavController = localNavController,
52+
viewModel = viewModel,
53+
packageNameFilter = packageNameFilter // 添加这行
54+
)
5155
}
5256
}
5357
}

source/feature/main/processing/src/main/kotlin/com/xayah/feature/main/processing/packages/restore/RestoreViewModelImpl.kt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package com.xayah.feature.main.processing.packages.restore
22

3+
import android.util.Log
34
import android.content.Context
45
import androidx.compose.material3.ExperimentalMaterial3Api
56
import androidx.lifecycle.SavedStateHandle
67
import com.xayah.core.data.repository.CloudRepository
78
import com.xayah.core.data.repository.PackageRepository
89
import com.xayah.core.data.repository.TaskRepository
10+
import com.xayah.core.data.repository.AppsRepo
911
import com.xayah.core.model.OpType
1012
import com.xayah.core.model.StorageMode
1113
import com.xayah.core.model.database.PackageEntity
@@ -23,6 +25,7 @@ import com.xayah.core.util.LogUtil
2325
import com.xayah.core.util.decodeURL
2426
import com.xayah.core.util.localBackupSaveDir
2527
import com.xayah.core.util.navigateSingle
28+
import com.xayah.feature.main.processing.UpdateAppsWithFilter
2629
import com.xayah.feature.main.processing.AbstractPackagesProcessingViewModel
2730
import com.xayah.feature.main.processing.FinishSetup
2831
import com.xayah.feature.main.processing.GetUsers
@@ -49,6 +52,7 @@ class RestoreViewModelImpl @Inject constructor(
4952
mTaskRepo: TaskRepository,
5053
private val mPkgRepo: PackageRepository,
5154
private val mCloudRepo: CloudRepository,
55+
private val appsRepo: AppsRepo,
5256
mLocalService: ProcessingServiceProxyLocalImpl,
5357
mCloudService: ProcessingServiceProxyCloudImpl,
5458
private val args: SavedStateHandle,
@@ -76,6 +80,40 @@ class RestoreViewModelImpl @Inject constructor(
7680
_packagesSize.value = bytes.formatSize()
7781
}
7882

83+
is UpdateAppsWithFilter -> {
84+
Log.d("RestoreViewModelImpl", "处理 UpdateAppsWithFilter: ${intent.packageNameFilter}")
85+
86+
val cloud: String
87+
val backupSaveDir: String
88+
if (uiState.value.cloudEntity == null) {
89+
cloud = ""
90+
backupSaveDir = "${mContext.localBackupSaveDir()}/restore/"
91+
} else {
92+
cloud = uiState.value.cloudEntity!!.name
93+
backupSaveDir = "${uiState.value.cloudEntity!!.remote}/restore/"
94+
}
95+
96+
Log.d("RestoreViewModelImpl", "查询参数: cloud=$cloud, backupDir=$backupSaveDir")
97+
98+
// 使用 mPkgRepo 而不是 appsRepo,并传入正确的参数
99+
val allActivatedApps = mPkgRepo.queryActivated(OpType.RESTORE, cloud, backupSaveDir)
100+
Log.d("RestoreViewModelImpl", "所有激活应用数: ${allActivatedApps.size}")
101+
allActivatedApps.forEach { app ->
102+
Log.d("RestoreViewModelImpl", "激活应用: ${app.packageName}, cloud=${app.indexInfo.cloud}, backupDir=${app.indexInfo.backupDir}")
103+
}
104+
105+
val packages = allActivatedApps.filter { it.packageName == intent.packageNameFilter }
106+
Log.d("RestoreViewModelImpl", "筛选结果: 找到 ${packages.size} 个应用")
107+
108+
var bytes = 0.0
109+
packages.forEach {
110+
bytes += it.displayStatsBytes
111+
}
112+
_packages.value = packages
113+
_packagesSize.value = bytes.formatSize()
114+
Log.d("RestoreViewModelImpl", "更新完成: packages=${packages.size}, size=${bytes.formatSize()}")
115+
}
116+
79117
is SetCloudEntity -> {
80118
val name = args.get<String>(MainRoutes.ARG_ACCOUNT_NAME)?.decodeURL()?.trim() ?: ""
81119
if (name.isNotEmpty()) {

source/feature/main/processing/src/main/kotlin/com/xayah/feature/main/processing/packages/restore/Setup.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.xayah.feature.main.processing.packages.restore
22

3+
import android.util.Log
34
import androidx.compose.animation.ExperimentalAnimationApi
45
import androidx.compose.foundation.ExperimentalFoundationApi
56
import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -50,6 +51,7 @@ import com.xayah.feature.main.processing.ProcessingSetupScaffold
5051
import com.xayah.feature.main.processing.R
5152
import com.xayah.feature.main.processing.SetCloudEntity
5253
import com.xayah.feature.main.processing.UpdateApps
54+
import com.xayah.feature.main.processing.UpdateAppsWithFilter
5355
import kotlinx.coroutines.ExperimentalCoroutinesApi
5456

5557
@ExperimentalCoroutinesApi
@@ -58,17 +60,24 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
5860
@ExperimentalAnimationApi
5961
@ExperimentalMaterial3Api
6062
@Composable
61-
fun PagePackagesRestoreProcessingSetup(localNavController: NavHostController, viewModel: RestoreViewModelImpl) {
63+
fun PagePackagesRestoreProcessingSetup(localNavController: NavHostController, viewModel: RestoreViewModelImpl,packageNameFilter: String = "") {
6264
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())
6365
val packages by viewModel.packages.collectAsStateWithLifecycle()
6466
val packagesSize by viewModel.packagesSize.collectAsStateWithLifecycle()
6567
val restoreUsers by viewModel.restoreUsers.collectAsStateWithLifecycle()
6668

6769
LaunchedEffect(null) {
70+
Log.d("Navigation", "PagePackagesRestoreProcessingSetup 启动,packageNameFilter: $packageNameFilter")
6871
viewModel.launchOnIO {
6972
viewModel.emitIntent(SetCloudEntity(""))
7073
viewModel.emitIntent(GetUsers)
71-
viewModel.emitIntent(UpdateApps)
74+
if (packageNameFilter.isNotEmpty()) {
75+
Log.d("Navigation", "使用包名筛选: $packageNameFilter")
76+
viewModel.emitIntent(UpdateAppsWithFilter(packageNameFilter))
77+
} else {
78+
Log.d("Navigation", "加载所有应用")
79+
viewModel.emitIntent(UpdateApps)
80+
}
7281
}
7382
}
7483

source/feature/main/restore/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ dependencies {
1818
implementation(project(":core:data"))
1919
implementation(project(":core:util"))
2020
implementation(project(":core:restic"))
21+
implementation(project(":core:rootservice"))
22+
implementation(project(":core:database"))
2123

2224
implementation(libs.kotlinx.serialization.json)
2325

source/feature/main/restore/src/main/kotlin/com/xayah/feature/main/restore/ResticBackupDetailPage.kt

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import com.xayah.core.util.DateUtil
4545
import com.xayah.core.model.DataType
4646
import com.xayah.feature.main.restore.ResticBackupGroup
4747
import kotlinx.coroutines.launch
48+
import java.net.URLEncoder
4849

4950
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class, ExperimentalAnimationApi::class)
5051
@Composable
@@ -131,16 +132,16 @@ fun ResticBackupDetailPage(
131132
Log.d("ResticRestore", "恢复成功,准备读取备份目录")
132133
val backupDir = "${viewModel.readBackupDirectory()}/restore/"
133134
Log.d("ResticRestore", "导航到恢复页面,备份目录: $backupDir")
134-
135-
navController.navigateSingle(
136-
MainRoutes.List.getRoute(
137-
target = Target.Apps,
138-
opType = OpType.RESTORE,
139-
cloudName = "",
140-
backupDir = backupDir
141-
)
135+
viewModel.refreshLocalDatabase(backupDir)
136+
// 修改为传递备份目录参数
137+
val route = MainRoutes.PackagesRestoreProcessingGraph.getRoute(
138+
cloudName = URLEncoder.encode("", "UTF-8"),
139+
backupDir = URLEncoder.encode(backupDir, "UTF-8"),
140+
packageName = group.packageName
142141
)
143-
Log.d("ResticRestore", "导航完成")
142+
Log.d("Navigation", "构建路由: $route")
143+
navController.navigateSingle(route)
144+
Log.d("Navigation", "导航完成: ResticBackupDetailPage → PackagesRestoreProcessingGraph")
144145
} else {
145146
Log.e("ResticRestore", "恢复失败")
146147
}

source/feature/main/restore/src/main/kotlin/com/xayah/feature/main/restore/ResticRestoreViewModel.kt

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ import com.xayah.core.util.DateUtil
1515
import com.xayah.feature.main.restore.ResticBackupGroup
1616
import com.xayah.core.datastore.readBackupDirectory
1717
import com.xayah.core.util.localBackupSaveDir
18+
import com.xayah.core.data.repository.AppsRepo
19+
import com.xayah.core.model.OpType
20+
import com.xayah.core.model.database.PackageEntity
21+
import com.xayah.core.database.dao.PackageDao
22+
import com.xayah.core.rootservice.service.RemoteRootService
1823
import dagger.hilt.android.lifecycle.HiltViewModel
1924
import dagger.hilt.android.qualifiers.ApplicationContext
2025
import kotlinx.coroutines.flow.MutableStateFlow
@@ -23,11 +28,15 @@ import kotlinx.coroutines.flow.asStateFlow
2328
import kotlinx.coroutines.launch
2429
import kotlinx.serialization.Serializable
2530
import javax.inject.Inject
31+
import java.io.File
2632

2733
@HiltViewModel
2834
class ResticRestoreViewModel @Inject constructor(
2935
@ApplicationContext private val context: Context,
30-
private val resticRepo: ResticRepository
36+
private val resticRepo: ResticRepository,
37+
private val appsRepo: AppsRepo,
38+
private val rootService: RemoteRootService, // 添加
39+
private val appsDao: PackageDao
3140
) : ViewModel() {
3241

3342
// 速度跟踪变量 - 添加到这里
@@ -92,6 +101,105 @@ class ResticRestoreViewModel @Inject constructor(
92101
}
93102
}
94103

104+
suspend fun refreshLocalDatabase(backupDir: String) {
105+
Log.d("ResticRestore", "刷新本地数据库: $backupDir")
106+
try {
107+
val restoreDir = File(backupDir)
108+
if (restoreDir.exists()) {
109+
Log.d("ResticRestore", "开始扫描恢复目录: $backupDir")
110+
111+
// 扫描 apps 子目录
112+
val appsDir = File(restoreDir, "apps")
113+
if (appsDir.exists()) {
114+
scanAppsDirectory(appsDir)
115+
} else {
116+
Log.w("ResticRestore", "apps 目录不存在: ${appsDir.path}")
117+
}
118+
} else {
119+
Log.w("ResticRestore", "恢复目录不存在: $backupDir")
120+
}
121+
Log.d("ResticRestore", "数据库刷新完成")
122+
} catch (e: Exception) {
123+
Log.e("ResticRestore", "数据库刷新失败: ${e.message}", e)
124+
}
125+
}
126+
127+
private suspend fun scanAppsDirectory(appsDir: File) {
128+
val packageDirs = appsDir.listFiles { file -> file.isDirectory }
129+
packageDirs?.forEach { packageDir ->
130+
val userDirs = packageDir.listFiles { file -> file.isDirectory }
131+
userDirs?.forEach { userDir ->
132+
val configFile = File(userDir, "package_restore_config.json")
133+
if (configFile.exists()) {
134+
try {
135+
// 读取配置文件并更新数据库
136+
val packageEntity = readPackageConfig(configFile, packageDir.name, userDir.name)
137+
if (packageEntity != null) {
138+
appsDao.upsert(packageEntity)
139+
Log.d("ResticRestore", "应用已插入数据库: ${packageDir.name}")
140+
updateDatabase(packageDir.name)
141+
Log.d("ResticRestore", "发现恢复的应用: ${packageDir.name}")
142+
}
143+
} catch (e: Exception) {
144+
Log.e("ResticRestore", "处理应用配置失败: ${configFile.path}", e)
145+
}
146+
}
147+
}
148+
}
149+
}
150+
151+
private suspend fun readPackageConfig(configFile: File, packageName: String, userDirName: String): PackageEntity? {
152+
return try {
153+
val entity = rootService.readJson<PackageEntity>(configFile.path)
154+
entity?.copy(
155+
id = 0,
156+
indexInfo = entity.indexInfo.copy(
157+
opType = OpType.RESTORE,
158+
packageName = packageName,
159+
userId = userDirName.split("_").lastOrNull()?.toIntOrNull() ?: 0,
160+
cloud = "",
161+
backupDir = "${context.localBackupSaveDir()}/restore/"
162+
),
163+
extraInfo = entity.extraInfo.copy(activated = false)
164+
)
165+
} catch (e: Exception) {
166+
Log.e("ResticRestore", "读取配置文件失败: ${configFile.path}", e)
167+
null
168+
}
169+
}
170+
171+
private suspend fun updateDatabase(packageName: String) {
172+
Log.d("ResticRestore", "激活应用: $packageName")
173+
try {
174+
// 查询该包名的所有应用记录
175+
val existingApps = appsDao.queryPackages(OpType.RESTORE, "", "${context.localBackupSaveDir()}/restore/")
176+
.filter { it.packageName == packageName }
177+
178+
Log.d("ResticRestore", "查询到的应用记录数: ${existingApps.size}")
179+
existingApps.forEach { app ->
180+
Log.d("ResticRestore", "应用记录: ${app.packageName}, backupDir: ${app.indexInfo.backupDir}, activated: ${app.extraInfo.activated}")
181+
}
182+
183+
if (existingApps.isNotEmpty()) {
184+
Log.d("ResticRestore", "激活应用: $packageName, 找到 ${existingApps.size} 个记录")
185+
existingApps.forEach { app ->
186+
appsDao.activateById(app.id, true)
187+
Log.d("ResticRestore", "应用已激活: $packageName (ID: ${app.id})")
188+
}
189+
} else {
190+
Log.w("ResticRestore", "未找到应用记录: $packageName")
191+
192+
// 添加调试查询
193+
val allApps = appsDao.queryPackages(OpType.RESTORE, "", "${context.localBackupSaveDir()}/restore/")
194+
Log.d("ResticRestore", "数据库中所有相关应用:")
195+
allApps.forEach { app ->
196+
Log.d("ResticRestore", "- ${app.packageName}, backupDir: ${app.indexInfo.backupDir}")
197+
}
198+
}
199+
} catch (e: Exception) {
200+
Log.e("ResticRestore", "激活应用失败: ${e.message}", e)
201+
}
202+
}
95203
// 添加恢复方法
96204
suspend fun restoreFromResticSnapshots(group: ResticBackupGroup): Boolean {
97205
Log.d("ResticRestore", "开始快照恢复流程,包名: ${group.packageName}")

0 commit comments

Comments
 (0)