Skip to content

Commit 5b782cc

Browse files
author
Fastace
committed
BUG:目前疑似云端备份REST API衔接有问题,服务启动有问题,计划将Rclone的API端口服务控制配置到设置页面
1 parent 3654094 commit 5b782cc

15 files changed

Lines changed: 416 additions & 89 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/core/data/src/main/kotlin/com/xayah/core/data/repository/CloudRepository.kt

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,20 @@ import com.xayah.core.util.LogUtil
1212
import com.xayah.core.util.PathUtil
1313
import com.xayah.core.util.model.ShellResult
1414
import com.xayah.core.database.dao.UploadIdDao
15+
import com.xayah.core.model.CloudType
16+
import com.xayah.core.util.GsonUtil
17+
import com.xayah.core.model.database.S3Extra
18+
import com.xayah.core.model.database.FTPExtra
19+
import com.xayah.core.model.database.SMBExtra
20+
import com.xayah.core.model.database.SFTPExtra
21+
import com.xayah.core.model.database.WebDAVExtra
1522
import dagger.hilt.android.qualifiers.ApplicationContext
1623
import kotlinx.coroutines.flow.distinctUntilChanged
1724
import kotlinx.coroutines.flow.first
1825
import java.io.PrintWriter
1926
import java.io.StringWriter
2027
import javax.inject.Inject
28+
import java.io.File
2129

2230
class CloudRepository @Inject constructor(
2331
@ApplicationContext private val context: Context,
@@ -170,6 +178,102 @@ class CloudRepository @Inject constructor(
170178
log { "withClient: Client disconnected" }
171179
}
172180

181+
suspend fun generateRcloneConfig(cloudEntity: CloudEntity): String {
182+
return when (cloudEntity.type) {
183+
CloudType.S3 -> generateS3Config(cloudEntity)
184+
CloudType.FTP -> generateFTPConfig(cloudEntity)
185+
CloudType.SMB -> generateSMBConfig(cloudEntity)
186+
CloudType.SFTP -> generateSFTPConfig(cloudEntity)
187+
CloudType.WEBDAV -> generateWebDAVConfig(cloudEntity)
188+
}
189+
}
190+
191+
private fun generateS3Config(cloudEntity: CloudEntity): String {
192+
val extra: S3Extra = GsonUtil().fromJson(cloudEntity.extra, S3Extra::class.java) ?: return ""
193+
return buildString {
194+
appendLine("[${cloudEntity.name}]")
195+
appendLine("type = s3")
196+
appendLine("provider = AWS")
197+
appendLine("access_key_id = ${cloudEntity.user}")
198+
appendLine("secret_access_key = ${cloudEntity.pass}")
199+
appendLine("region = ${extra.region}")
200+
if (extra.endpoint.isNotEmpty()) {
201+
appendLine("endpoint = ${extra.endpoint}")
202+
}
203+
appendLine("acl = private")
204+
}
205+
}
206+
207+
private fun generateFTPConfig(cloudEntity: CloudEntity): String {
208+
val extra: FTPExtra = GsonUtil().fromJson(cloudEntity.extra, FTPExtra::class.java) ?: return ""
209+
return buildString {
210+
appendLine("[${cloudEntity.name}]")
211+
appendLine("type = ftp")
212+
appendLine("host = ${cloudEntity.host}")
213+
appendLine("user = ${cloudEntity.user}")
214+
appendLine("pass = ${cloudEntity.pass}")
215+
appendLine("port = ${extra.port}")
216+
}
217+
}
218+
219+
private fun generateSMBConfig(cloudEntity: CloudEntity): String {
220+
val extra: SMBExtra = GsonUtil().fromJson(cloudEntity.extra, SMBExtra::class.java) ?: return ""
221+
return buildString {
222+
appendLine("[${cloudEntity.name}]")
223+
appendLine("type = smb")
224+
appendLine("host = ${cloudEntity.host}")
225+
appendLine("user = ${cloudEntity.user}")
226+
appendLine("pass = ${cloudEntity.pass}")
227+
appendLine("port = ${extra.port}")
228+
appendLine("domain = ${extra.domain}")
229+
appendLine("share = ${extra.share}")
230+
}
231+
}
232+
233+
private fun generateSFTPConfig(cloudEntity: CloudEntity): String {
234+
val extra: SFTPExtra = GsonUtil().fromJson(cloudEntity.extra, SFTPExtra::class.java) ?: return ""
235+
return buildString {
236+
appendLine("[${cloudEntity.name}]")
237+
appendLine("type = sftp")
238+
appendLine("host = ${cloudEntity.host}")
239+
appendLine("user = ${cloudEntity.user}")
240+
appendLine("pass = ${cloudEntity.pass}")
241+
appendLine("port = ${extra.port}")
242+
if (extra.privateKey.isNotEmpty()) {
243+
appendLine("key_file = ${extra.privateKey}")
244+
}
245+
}
246+
}
247+
248+
private fun generateWebDAVConfig(cloudEntity: CloudEntity): String {
249+
val extra: WebDAVExtra = GsonUtil().fromJson(cloudEntity.extra, WebDAVExtra::class.java) ?: return ""
250+
return buildString {
251+
appendLine("[${cloudEntity.name}]")
252+
appendLine("type = webdav")
253+
appendLine("url = ${cloudEntity.host}")
254+
appendLine("user = ${cloudEntity.user}")
255+
appendLine("pass = ${cloudEntity.pass}")
256+
appendLine("vendor = other")
257+
if (extra.insecure) {
258+
appendLine("insecure_tls = true")
259+
}
260+
}
261+
}
262+
263+
suspend fun writeRcloneConfig(cloudEntity: CloudEntity): Boolean {
264+
return runCatching {
265+
val configContent = generateRcloneConfig(cloudEntity)
266+
val configFile = File(context.filesDir, "rclone/rclone.conf")
267+
268+
// 确保目录存在
269+
configFile.parentFile?.mkdirs()
270+
271+
// 写入配置文件
272+
configFile.writeText(configContent)
273+
true
274+
}.getOrElse { false }
275+
}
276+
173277
// 3. 保留原有方法:处理多个激活的客户端
174278
suspend fun withActivatedClients(block: suspend (clients: List<Pair<CloudClient, CloudEntity>>) -> Unit) = run {
175279
val clients: MutableList<Pair<CloudClient, CloudEntity>> = mutableListOf()

source/core/rclone/src/main/kotlin/com/xayah/core/rclone/RcloneNative.kt

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,16 @@ class RcloneNative @Inject constructor(
2222
* 适配 libsu:确保路径对 Root 可见,并强制刷新权限
2323
*/
2424
fun getRcloneBinaryPath(context: Context): String {
25-
val privateBinaryFile = File(context.filesDir, "rclone")
25+
val rcloneDir = File(context.filesDir, "rclone")
26+
val privateBinaryFile = File(rcloneDir, "rclone")
2627
val privateBinaryPath = privateBinaryFile.absolutePath
2728

28-
// 1. 如果私有目录已存在二进制文件
29+
// 1. 确保目录存在
30+
if (!rcloneDir.exists()) {
31+
rcloneDir.mkdirs()
32+
}
33+
34+
// 2. 如果私有目录已存在二进制文件
2935
if (privateBinaryFile.exists()) {
3036
logger.logBinaryPathFound(privateBinaryPath)
3137
ensureExecutable(privateBinaryFile)
@@ -83,8 +89,7 @@ class RcloneNative @Inject constructor(
8389

8490
fun isDownloadNeeded(context: Context): Boolean {
8591
val prefs = context.getSharedPreferences(DOWNLOAD_PREFS_NAME, Context.MODE_PRIVATE)
86-
// 如果物理文件丢失,也视为需要下载
87-
val binaryMissing = !File(context.filesDir, "rclone").exists()
92+
val binaryMissing = !File(File(context.filesDir, "rclone"), "rclone").exists()
8893
return prefs.getBoolean(NEED_DOWNLOAD_KEY, false) || binaryMissing
8994
}
9095

@@ -99,7 +104,7 @@ class RcloneNative @Inject constructor(
99104
* 只要物理存在即视为有效,执行权限由调用方 (libsu) 尝试修复
100105
*/
101106
fun isPrivateBinaryValid(context: Context): Boolean {
102-
val binaryFile = File(context.filesDir, "rclone")
107+
val binaryFile = File(File(context.filesDir, "rclone"), "rclone")
103108
return binaryFile.exists() && binaryFile.length() > 0
104109
}
105110
}

source/core/rclone/src/main/kotlin/com/xayah/core/rclone/RcloneRepository.kt

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,26 @@ class RcloneRepository @Inject constructor(
3535
val defaultEnv = mutableMapOf(
3636
"HOME" to context.filesDir.absolutePath,
3737
"XDG_CONFIG_HOME" to File(context.filesDir, "rclone").absolutePath,
38-
"RCLONE_CONFIG" to File(File(context.filesDir, "rclone"), "rclone.conf").absolutePath
38+
"RCLONE_CONFIG" to File(File(context.filesDir, "rclone"), "rclone.conf").absolutePath,
39+
"GODEBUG" to "netdns=cgo"
3940
)
4041
defaultEnv.putAll(env)
4142

42-
// 构建带环境变量的命令
43-
val envExports = defaultEnv.map { "export ${it.key}=\"${it.value}\"" }
44-
val command = envExports.joinToString(" && ") + " && $rclonePath ${args.joinToString(" ")}"
43+
val envPrefix = defaultEnv.map { "${it.key}=\"${it.value}\"" }.joinToString(" ")
4544

46-
Log.d(TAG, "Executing Root Command: $command")
45+
// Join args properly.
46+
// Note: If args contain spaces (like the remote path), ensure they are quoted before passing here.
47+
val fullCommand = "$envPrefix $rclonePath ${args.joinToString(" ")}"
48+
49+
Log.d(TAG, "Executing Root Command: $fullCommand")
50+
val result = Shell.cmd(fullCommand).exec()
51+
52+
// 添加详细错误日志
53+
if (!result.isSuccess) {
54+
Log.e(TAG, "Command failed with exit code: ${result.code}")
55+
Log.e(TAG, "Standard output: ${result.out.joinToString("\n")}")
56+
}
4757

48-
val result = Shell.cmd(command).exec()
4958
logger.logCommandResult(result.code, result.out.joinToString("\n"))
5059
result
5160
}
@@ -60,9 +69,6 @@ class RcloneRepository @Inject constructor(
6069
} else null
6170
}
6271

63-
/**
64-
* 启动 Restic 服务器
65-
*/
6672
/**
6773
* 启动 Rclone 服务器
6874
*/
@@ -74,13 +80,15 @@ class RcloneRepository @Inject constructor(
7480
): Shell.Result {
7581
logger.logResticServerStart(remote, addr)
7682

77-
val args = mutableListOf("serve", "restic")
78-
79-
if (verbose) args.add("-v")
80-
args.addAll(listOf("--addr", addr))
81-
args.add("$remote:$path")
82-
83-
val result = executeRclone(*args.toTypedArray())
83+
// DO NOT include rclonePath here, executeRclone adds it automatically
84+
val result = executeRclone(
85+
"serve",
86+
"restic",
87+
"$remote:$path",
88+
"--addr", addr,
89+
"--bind", "0.0.0.0",
90+
"-vv"
91+
)
8492

8593
if (result.isSuccess) {
8694
logger.logResticServerStarted(addr)

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ class ResticRepository @Inject constructor(
177177
val output = if (result.isSuccess) {
178178
result.out.joinToString("\n")
179179
} else {
180-
result.err.joinToString("\n")
180+
result.out.joinToString("\n")
181181
}
182182

183183
if (result.isSuccess) {
@@ -195,7 +195,7 @@ class ResticRepository @Inject constructor(
195195
* 使用 REST 后端初始化仓库
196196
*/
197197
suspend fun initRepositoryWithResticBackend(
198-
resticServerUrl: String = "http://localhost:38080/",
198+
resticServerUrl: String = "http://127.0.0.1:38080/",
199199
password: String
200200
): Result<String> {
201201
val repoPath = "rest:$resticServerUrl"
@@ -220,7 +220,7 @@ class ResticRepository @Inject constructor(
220220
* 使用 REST 后端备份文件
221221
*/
222222
suspend fun backupFileWithResticBackend(
223-
resticServerUrl: String = "http://localhost:38080/",
223+
resticServerUrl: String = "http://127.0.0.1:38080/",
224224
password: String,
225225
filePath: String,
226226
tags: List<String>

source/core/service/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ dependencies {
2020
implementation(project(":core:data"))
2121
implementation(project(":core:network"))
2222
implementation(project(":core:restic"))
23+
implementation(project(":core:rclone"))
2324
compileOnly(project(":core:hiddenapi"))
2425

2526

@@ -28,4 +29,5 @@ dependencies {
2829

2930
// Preferences DataStore
3031
implementation(libs.androidx.datastore.preferences)
32+
implementation(libs.libsu.core)
3133
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ internal abstract class AbstractBackupService : AbstractPackagesService() {
388388
}
389389

390390
// 添加辅助方法:从路径中提取用户ID
391-
private fun extractUserIdFromPath(path: String): String {
391+
protected fun extractUserIdFromPath(path: String): String {
392392
val regex = Regex("/(user_\\d+)/")
393393
return regex.find(path)?.groupValues?.get(1) ?: "user_0"
394394
}

0 commit comments

Comments
 (0)