Skip to content

Commit 9a15521

Browse files
author
Fastace
committed
成功利用rustic 特定版本,通过标签终止文件的创建,实现了Stop操作,并应用到了备份取消流程中
1 parent 29fac2f commit 9a15521

8 files changed

Lines changed: 192 additions & 30 deletions

File tree

source/core/restic/src/main/kotlin/com/xayah/core/restic/ResticRepository.kt

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,12 +1102,20 @@ class ResticRepository @Inject constructor(
11021102
password: String,
11031103
filePath: String,
11041104
tags: List<String>,
1105+
additionalEnv: Map<String, String> = emptyMap(),
11051106
progressCallback: ResticProgressCallback? = null
11061107
): Pair<Int, String> {
11071108
return withContext(Dispatchers.IO) {
11081109
try {
1109-
// 使用 RUSTIC_PASSWORD 替代 RESTIC_PASSWORD
1110-
val env = mapOf("RUSTIC_PASSWORD" to password)
1110+
// 使用标签作为停止文件名
1111+
val tag = tags.firstOrNull() ?: "default"
1112+
val stopFilePath = File(context.cacheDir, tag).absolutePath
1113+
1114+
val env = mutableMapOf(
1115+
"RUSTIC_PASSWORD" to password,
1116+
"RUSTIC_STOP_FILE" to stopFilePath // 每个实例独立的停止文件
1117+
)
1118+
env.putAll(additionalEnv)
11111119

11121120
// Rustic 命令格式: backup <SOURCE> -r <REPO> --tag <TAGS>
11131121
val args = mutableListOf(
@@ -1248,22 +1256,26 @@ class ResticRepository @Inject constructor(
12481256
): Pair<Int, String> {
12491257
return withContext(Dispatchers.IO) {
12501258
try {
1251-
// 使用 OpenDAL 环境变量(与 restoreSnapshotFromS3 一致)
1259+
val tag = tags.firstOrNull() ?: "default"
1260+
val stopFilePath = File(context.cacheDir, tag).absolutePath
1261+
12521262
val env = mutableMapOf(
12531263
"OPENDAL_BUCKET" to extra.bucket,
12541264
"OPENDAL_ROOT" to formatOpenDALRoot(remotePath),
12551265
"OPENDAL_ENDPOINT" to buildOpenDALEndpoint(extra),
12561266
"OPENDAL_SECRET_ID" to extra.accessKeyId,
12571267
"OPENDAL_SECRET_KEY" to extra.secretAccessKey,
1258-
"RUSTIC_PASSWORD" to password
1268+
"RUSTIC_PASSWORD" to password,
1269+
"RUSTIC_INSTANCE_LABEL" to tag,
1270+
"RUSTIC_STOP_FILE" to stopFilePath
12591271
)
12601272

12611273
// Rustic 命令格式: backup <SOURCE> -r opendal:cos --tag <TAGS>
12621274
val args = mutableListOf(
12631275
"backup",
1264-
filePath, // 源文件作为位置参数
1265-
"-r", "opendal:cos", // 使用 OpenDAL 协议
1266-
"--progress-interval", "1s" // 输出用于进度解析
1276+
filePath,
1277+
"-r", "opendal:cos",
1278+
"--progress-interval", "1s"
12671279
)
12681280

12691281
// 添加标签
@@ -1274,14 +1286,6 @@ class ResticRepository @Inject constructor(
12741286

12751287
val result = executeRestic(*args.toTypedArray(), env = env, usePty = true)
12761288

1277-
// 注意: Rustic backup 可能也不支持 --json,需要移除进度解析
1278-
// 如果支持,保留以下代码:
1279-
// result.out.forEach { line ->
1280-
// parseBackupProgress(line)?.let { p ->
1281-
// progressCallback?.onBackupProgress(...)
1282-
// }
1283-
// }
1284-
12851289
Pair(result.code, result.out.joinToString("\n"))
12861290
} catch (e: Exception) {
12871291
Log.e(TAG, "Error during S3 Rustic backup", e)

source/core/service/src/main/kotlin/com/xayah/core/service/AbstractProcessingService.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,14 @@ internal abstract class AbstractProcessingService : Service() {
141141
mIsCanceled = true
142142
}
143143

144+
/**
145+
* 取消当前操作
146+
* 子类可以重写此方法来添加额外的取消逻辑(如写入 Rustic 停止文件)
147+
*/
148+
open fun cancel() {
149+
requestCancel()
150+
}
151+
144152
/**
145153
* 检查是否已请求取消
146154
*/

source/core/service/src/main/kotlin/com/xayah/core/service/AbstractProcessingServiceProxy.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,6 @@ abstract class AbstractProcessingServiceProxy {
105105

106106
suspend fun requestCancel() = getService().requestCancel()
107107

108+
suspend fun cancel() = getService().cancel()
109+
108110
}

source/core/service/src/main/kotlin/com/xayah/core/service/medium/backup/AbstractBackupService.kt

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,61 @@ internal abstract class AbstractBackupService : AbstractMediumService() {
106106
return mContext.readResticPassword() ?: "backup_${mBackupTimestamp}"
107107
}
108108

109+
// 添加成员变量
110+
protected var mCurrentProcessingTag: String? = null
111+
112+
// 实现 cancel 方法
113+
override fun cancel() {
114+
super.cancel()
115+
116+
try {
117+
// 获取当前正在处理的标签
118+
val tag = mCurrentProcessingTag ?: run {
119+
// 如果标签未设置,从当前处理索引获取
120+
val currentIndex = mTaskEntity.processingIndex
121+
if (currentIndex < mMediaEntities.size) {
122+
val media = mMediaEntities[currentIndex]
123+
val m = media.mediaEntity
124+
125+
// 媒体备份只有两种类型:PACKAGE_MEDIA 和 PACKAGE_CONFIG
126+
// 根据 processingIndex 判断当前阶段
127+
val dataType = when (media.mediaInfo.state) {
128+
OperationState.PROCESSING -> DataType.PACKAGE_MEDIA
129+
else -> DataType.PACKAGE_MEDIA // 默认为 MEDIA
130+
}
131+
132+
val tagSuffix = when (dataType) {
133+
DataType.PACKAGE_MEDIA -> "filesbackup"
134+
DataType.PACKAGE_CONFIG -> "filesconfig"
135+
else -> "filesbackup"
136+
}
137+
138+
"${m.name}-$mBackupTimestamp-$tagSuffix"
139+
} else {
140+
null
141+
}
142+
}
143+
144+
if (tag != null) {
145+
val stopFile = File(mContext.cacheDir, tag)
146+
stopFile.writeText(tag)
147+
Log.d("CancelDebug", "Stop file created for media tag: $tag")
148+
Log.d("CancelDebug", "Stop file path: ${stopFile.absolutePath}")
149+
Log.d("CancelDebug", "Stop file exists after write: ${stopFile.exists()}")
150+
} else {
151+
Log.d("CancelDebug", "No current tag available, creating wildcard stop file")
152+
val wildcardFile = File(mContext.cacheDir, ".rustic_stop_all")
153+
wildcardFile.writeText("*")
154+
Log.d("CancelDebug", "Wildcard stop file created at: ${wildcardFile.absolutePath}")
155+
}
156+
} catch (e: Exception) {
157+
log { "Failed to create stop file: ${e.message}" }
158+
Log.e("CancelDebug", "Exception creating stop file", e)
159+
}
160+
}
161+
109162
// Restic 无状态备份方法 - 支持DataType参数
163+
// 修改 backupWithRestic 方法
110164
protected suspend fun backupWithRestic(
111165
mediaName: String,
112166
compressedFile: File,
@@ -116,28 +170,32 @@ internal abstract class AbstractBackupService : AbstractMediumService() {
116170
val repoPath = getResticRepoPath()
117171
val password = getResticPassword()
118172

119-
// 动态检查仓库是否已初始化
120173
if (!resticRepo.checkRepository(repoPath, password)) {
121174
log { "Restic repository not initialized, skipping backup for $mediaName" }
122175
return false
123176
}
124177

125178
return try {
126179
val filePath = compressedFile.absolutePath
127-
// 根据文件类型确定标签后缀
128180
val tagSuffix = when (dataType) {
129181
DataType.PACKAGE_MEDIA -> "filesbackup"
130182
DataType.PACKAGE_CONFIG -> "filesconfig"
131183
else -> "filesbackup"
132184
}
133185

134-
// 新的文件备份标签格式:mediaName-timestamp-filesbackup/filesconfig
135186
val tag = "$mediaName-$mBackupTimestamp-$tagSuffix"
136187
val tags = listOf(tag)
137188

189+
// 【新增】设置当前处理标签
190+
Log.d("ResticTag", "Setting current media tag: $tag")
191+
mCurrentProcessingTag = tag
192+
138193
log { "Starting Restic backup for $mediaName with tag: $tag" }
139194
val result = resticRepo.backupWithResticToLocal(repoPath, password, filePath, tags)
140195

196+
// 【新增】清除标签
197+
mCurrentProcessingTag = null
198+
141199
if (result.first == 0) {
142200
log { "Restic backup completed successfully for $mediaName" }
143201
val snapshotId = extractSnapshotIdFromJson(result.second)
@@ -153,6 +211,9 @@ internal abstract class AbstractBackupService : AbstractMediumService() {
153211
false
154212
}
155213
} catch (e: Exception) {
214+
// 【新增】异常时也要清除标签
215+
mCurrentProcessingTag = null
216+
156217
val baseMessage = "Error during Restic backup"
157218
log { "$baseMessage for $mediaName" }
158219
log { "Exception type: ${e.javaClass.simpleName}" }

source/core/service/src/main/kotlin/com/xayah/core/service/medium/backup/BackupServiceCloudImpl.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,9 +267,14 @@ internal class BackupServiceCloudImpl @Inject constructor() : AbstractBackupServ
267267
else -> "filesbackup"
268268
}
269269

270-
// 新的文件备份标签格式mediaName-timestamp-filesbackup/filesconfig
270+
// 新的文件备份标签格式:mediaName-timestamp-filesbackup/filesconfig
271271
val tag = "$mediaName-$mBackupTimestamp-$tagSuffix"
272272
val tags = listOf(tag)
273+
274+
// 【新增】设置当前处理标签,用于取消机制
275+
Log.d("ResticTag", "Setting current tag: $tag")
276+
mCurrentProcessingTag = tag
277+
273278
val unifiedRepoPath = mContext.readS3ResticRepoPath() ?: remotePath
274279

275280
val result = resticRepo.backupFileToS3(
@@ -296,6 +301,9 @@ internal class BackupServiceCloudImpl @Inject constructor() : AbstractBackupServ
296301
}
297302
)
298303

304+
// 【新增】清除当前处理标签(正常完成)
305+
mCurrentProcessingTag = null
306+
299307
if (result.first == 0) {
300308
val snapshotId = extractSnapshotIdFromJson(result.second)
301309
if (snapshotId != null) {
@@ -307,6 +315,9 @@ internal class BackupServiceCloudImpl @Inject constructor() : AbstractBackupServ
307315
false
308316
}
309317
} catch (e: Exception) {
318+
// 【新增】异常时也要清除标签
319+
mCurrentProcessingTag = null
320+
310321
Log.e(mTAG, "Error during S3 file Restic backup", e)
311322
false
312323
}

source/core/service/src/main/kotlin/com/xayah/core/service/packages/backup/AbstractBackupService.kt

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,51 @@ internal abstract class AbstractBackupService : AbstractPackagesService() {
328328
}
329329
}
330330

331+
// 添加成员变量来跟踪当前正在备份的标签
332+
protected var mCurrentProcessingTag: String? = null
333+
334+
// 重写 cancel 方法
335+
override fun cancel() {
336+
super.cancel()
337+
338+
try {
339+
val tag = mCurrentProcessingTag ?: run {
340+
val currentIndex = mTaskEntity.processingIndex
341+
if (currentIndex < mPkgEntities.size) {
342+
val pkg = mPkgEntities[currentIndex]
343+
val p = pkg.packageEntity
344+
val userId = "user_${p.userId}"
345+
346+
// 根据当前包的处理进度确定数据类型
347+
val dataType = when (pkg.processingIndex) {
348+
0 -> "apk"
349+
1 -> "user"
350+
2 -> "user_de"
351+
3 -> "data"
352+
4 -> "obb"
353+
5 -> "media"
354+
6 -> "config"
355+
else -> "apk"
356+
}
357+
358+
"$userId-${p.packageName}-$mBackupTimestamp-$dataType"
359+
} else {
360+
null
361+
}
362+
}
363+
364+
if (tag != null) {
365+
val stopFile = File(mContext.cacheDir, tag)
366+
stopFile.writeText(tag)
367+
log { "Stop file created for tag: $tag at ${stopFile.absolutePath}" }
368+
} else {
369+
log { "No valid tag available, cannot create stop file" }
370+
}
371+
} catch (e: Exception) {
372+
log { "Failed to create stop file: ${e.message}" }
373+
}
374+
}
375+
331376
// Restic 无状态备份方法
332377
protected suspend fun backupWithRestic(
333378
packageName: String,
@@ -338,25 +383,38 @@ internal abstract class AbstractBackupService : AbstractPackagesService() {
338383
val repoPath = getResticRepoPath()
339384
val password = getResticPassword()
340385

341-
// 动态检查仓库是否已初始化
342386
if (!resticRepo.checkRepository(repoPath, password)) {
343387
log { "Restic repository not initialized, skipping backup for $packageName" }
344388
return false
345389
}
346390

347391
return try {
348392
val filePath = compressedFile.absolutePath
349-
// 从路径中提取用户信息,例如 "user_0"
350393
val userId = extractUserIdFromPath(filePath)
351-
// 从 DataType 获取备份类型,例如 "apk", "data" 等
352394
val backupType = dataType.type
353395

354-
// 构建单一标签: 用户-包名-备份类型-时间戳
355396
val tag = "$userId-$packageName-$mBackupTimestamp-$backupType"
356397
val tags = listOf(tag)
357398

399+
// 【新增】设置当前处理的标签
400+
Log.d("ResticTag", "Setting current tag: $tag")
401+
mCurrentProcessingTag = tag
402+
403+
val additionalEnv = mapOf(
404+
"RUSTIC_INSTANCE_LABEL" to tag
405+
)
406+
Log.d("ResticTag", "Additional env: $additionalEnv")
358407
log { "Starting Restic backup for $packageName with tag: $tag" }
359-
val result = resticRepo.backupWithResticToLocal(repoPath, password, filePath, tags)
408+
val result = resticRepo.backupWithResticToLocal(
409+
repoPath = repoPath,
410+
password = password,
411+
filePath = filePath,
412+
tags = tags,
413+
additionalEnv = additionalEnv
414+
)
415+
416+
// 【新增】清除当前标签
417+
mCurrentProcessingTag = null
360418

361419
if (result.first == 0) {
362420
log { "Restic backup completed successfully for $packageName" }
@@ -373,6 +431,9 @@ internal abstract class AbstractBackupService : AbstractPackagesService() {
373431
false
374432
}
375433
} catch (e: Exception) {
434+
// 【新增】异常时也要清除标签
435+
mCurrentProcessingTag = null
436+
376437
val baseMessage = "Error during Restic backup"
377438
log { "$baseMessage for $packageName" }
378439
log { "Exception type: ${e.javaClass.simpleName}" }

source/core/service/src/main/kotlin/com/xayah/core/service/packages/backup/BackupServiceCloudImpl.kt

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,20 @@ internal class BackupServiceCloudImpl @Inject constructor() : AbstractBackupServ
253253
remotePath: String
254254
): Boolean {
255255
return try {
256+
// 从路径中提取用户信息
256257
val userId = extractUserIdFromPath(compressedFile.absolutePath)
257258
val backupType = dataType.type
259+
260+
// 构建标签: userId-packageName-timestamp-dataType
258261
val tag = "$userId-$packageName-$mBackupTimestamp-$backupType"
259262
val tags = listOf(tag)
263+
264+
// 【关键】设置当前处理标签,用于取消机制
265+
Log.d("ResticTag", "Setting current tag: $tag")
266+
mCurrentProcessingTag = tag
267+
260268
val unifiedRepoPath = mContext.readS3ResticRepoPath() ?: remotePath
269+
261270
val result = resticRepo.backupFileToS3(
262271
extra = s3Extra,
263272
remotePath = unifiedRepoPath,
@@ -270,7 +279,7 @@ internal class BackupServiceCloudImpl @Inject constructor() : AbstractBackupServ
270279
bytesWritten: Long, bytesTotal: Long,
271280
filesSkipped: Long, bytesSkipped: Long
272281
) {
273-
// 恢复进度备份时不使用
282+
// 恢复进度,备份时不使用
274283
}
275284

276285
override fun onBackupProgress(
@@ -283,18 +292,24 @@ internal class BackupServiceCloudImpl @Inject constructor() : AbstractBackupServ
283292
}
284293
)
285294

295+
// 【关键】备份完成后清除标签
296+
mCurrentProcessingTag = null
297+
286298
if (result.first == 0) {
287299
val snapshotId = extractSnapshotIdFromJson(result.second)
288300
if (snapshotId != null) {
289-
// 仅记录日志不更新数据库
301+
// 仅记录日志,不更新数据库
290302
Log.d(mTAG, "Restic S3 backup successful for $packageName, snapshotId: $snapshotId")
291-
updateCloudResticInfo(packageName, snapshotId, remotePath) // 仅记录日志
303+
updateCloudResticInfo(packageName, snapshotId, remotePath)
292304
}
293305
true
294306
} else {
295307
false
296308
}
297309
} catch (e: Exception) {
310+
// 【关键】异常时也要清除标签
311+
mCurrentProcessingTag = null
312+
298313
Log.e(mTAG, "Error during S3 Restic backup", e)
299314
false
300315
}

0 commit comments

Comments
 (0)