Skip to content

Commit 0ed1d82

Browse files
committed
feat: migrate to toml from json
1 parent 325ae1a commit 0ed1d82

File tree

4 files changed

+151
-101
lines changed

4 files changed

+151
-101
lines changed

src/main/kotlin/net/azisaba/automaticbackupscript/Application.kt

Lines changed: 78 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,16 @@ class Application {
3131
private fun ensureNotLocked(repo: File) = File(repo, "locks").listFiles().isNullOrEmpty()
3232

3333
suspend fun backup() {
34-
val duration = measureTime {
35-
checkBinaries()
36-
forgetHosts()
37-
downloadAll()
38-
backupAll()
39-
}
34+
val duration =
35+
measureTime {
36+
checkBinaries()
37+
forgetHosts()
38+
downloadAll()
39+
backupAll()
40+
}
4041
WebhookUtil.executeWebhook(
4142
BackupConfig.config.webhookUrl,
42-
":white_check_mark: バックアップが完了しました(${duration.toLocaleString(DurationLocale.JAPANESE)})"
43+
":white_check_mark: バックアップが完了しました(${duration.toLocaleString(DurationLocale.JAPANESE)})",
4344
)
4445
}
4546

@@ -85,48 +86,53 @@ class Application {
8586
command.add(info.from)
8687
command.add(info.to)
8788
var exitCode: Int
88-
val duration = measureTime {
89-
exitCode = ProcessExecutor.executeCommandStreamedOutput(File("."), *command.toTypedArray())
90-
logger.info("rsync process exited with code $exitCode")
91-
}
89+
val duration =
90+
measureTime {
91+
exitCode = ProcessExecutor.executeCommandStreamedOutput(File("."), *command.toTypedArray())
92+
logger.info("rsync process exited with code $exitCode")
93+
}
9294
if (exitCode == 0 || exitCode == 23 || exitCode == 24) {
9395
successfulDownloads.add(info.from)
9496
successfulDownloads.add(info.to)
9597
WebhookUtil.executeWebhook(
9698
BackupConfig.config.webhookUrl,
97-
":inbox_tray: `${info.webhookName}`のダウンロードが完了(${duration.toLocaleString(DurationLocale.JAPANESE)})"
99+
":inbox_tray: `${info.webhookName}`のダウンロードが完了(${duration.toLocaleString(DurationLocale.JAPANESE)})",
98100
)
99101
} else {
100102
WebhookUtil.executeWebhook(
101103
BackupConfig.config.webhookUrl,
102-
"${BackupConfig.config.prefixIfWarning}`${info.webhookName}`のダウンロードに失敗しました(${duration.toLocaleString(DurationLocale.JAPANESE)}) [$exitCode]"
104+
"${BackupConfig.config.prefixIfWarning}`${info.webhookName}`のダウンロードに失敗しました(${duration.toLocaleString(
105+
DurationLocale.JAPANESE,
106+
)}) [$exitCode]",
103107
)
104108
}
105109
}
106110

107111
private suspend fun backupAll() {
108-
val duration = measureTime {
109-
BackupConfig.config.backups.forEach { info ->
110-
backup(info)
112+
val duration =
113+
measureTime {
114+
BackupConfig.config.backups.forEach { info ->
115+
backup(info)
116+
}
111117
}
112-
}
113118
logger.info("Backup completed in ${duration.toLocaleString(DurationLocale.ENGLISH)}")
114119
}
115120

116121
private suspend fun backup(info: BackupInfo) {
117122
if (!ensureNotLocked(File(info.repo))) {
118123
WebhookUtil.executeWebhook(
119124
BackupConfig.config.webhookUrl,
120-
":warning: `${info.webhookName}`のリポジトリは他のプロセスによってロックされているためバックアップは作成されません。"
125+
":warning: `${info.webhookName}`のリポジトリは他のプロセスによってロックされているためバックアップは作成されません。",
121126
)
122127
return
123128
}
124129
val effectiveDepends = info.depend.filter { !it.startsWith("#") }
125-
if ((info.dependOp == DependOp.OR && !effectiveDepends.any { successfulDownloads.contains(it) })
126-
|| (info.dependOp == DependOp.AND && !effectiveDepends.all { successfulDownloads.contains(it) })) {
130+
if ((info.dependOp == DependOp.OR && !effectiveDepends.any { successfulDownloads.contains(it) }) ||
131+
(info.dependOp == DependOp.AND && !effectiveDepends.all { successfulDownloads.contains(it) })
132+
) {
127133
WebhookUtil.executeWebhook(
128134
BackupConfig.config.webhookUrl,
129-
":warning: `${info.webhookName}`はデータのダウンロードに失敗しているためバックアップは作成されません。"
135+
":warning: `${info.webhookName}`はデータのダウンロードに失敗しているためバックアップは作成されません。",
130136
)
131137
return
132138
}
@@ -145,56 +151,62 @@ class Application {
145151
command.add("backup")
146152
val file = File(info.path)
147153
val duration: Duration
148-
val success = if (info.backupChildDirectoriesOnly) {
149-
if (!file.exists()) {
150-
logger.warn("${info.path} does not exist")
151-
WebhookUtil.executeWebhook(
152-
BackupConfig.config.webhookUrl,
153-
"${BackupConfig.config.prefixIfWarning}`${info.webhookName}`のバックアップに失敗しました(`${info.path}`が見つかりません)"
154-
)
155-
return
156-
}
157-
if (!file.isDirectory) {
158-
logger.warn("${info.path} is not a directory")
159-
WebhookUtil.executeWebhook(
160-
BackupConfig.config.webhookUrl,
161-
"${BackupConfig.config.prefixIfWarning}`${info.webhookName}`のバックアップに失敗しました(`${info.path}`はディレクトリではありません)"
162-
)
163-
return
164-
}
165-
val result: Boolean
166-
duration = measureTime {
167-
result = file.listFiles(FileFilter { it.isDirectory })!!.all { child ->
168-
try {
169-
command.add(child.absolutePath)
170-
val exitCode =
171-
ProcessExecutor.executeCommandStreamedOutput(File("."), *command.toTypedArray())
172-
logger.info("restic process exited with code $exitCode")
173-
return@all exitCode == 0
174-
} finally {
175-
command.removeLast()
176-
}
154+
val success =
155+
if (info.backupChildDirectoriesOnly) {
156+
if (!file.exists()) {
157+
logger.warn("${info.path} does not exist")
158+
WebhookUtil.executeWebhook(
159+
BackupConfig.config.webhookUrl,
160+
"${BackupConfig.config.prefixIfWarning}`${info.webhookName}`のバックアップに失敗しました(`${info.path}`が見つかりません)",
161+
)
162+
return
177163
}
164+
if (!file.isDirectory) {
165+
logger.warn("${info.path} is not a directory")
166+
WebhookUtil.executeWebhook(
167+
BackupConfig.config.webhookUrl,
168+
"${BackupConfig.config.prefixIfWarning}`${info.webhookName}`のバックアップに失敗しました(`${info.path}`はディレクトリではありません)",
169+
)
170+
return
171+
}
172+
val result: Boolean
173+
duration =
174+
measureTime {
175+
result =
176+
file.listFiles(FileFilter { it.isDirectory })!!.all { child ->
177+
try {
178+
command.add(child.absolutePath)
179+
val exitCode =
180+
ProcessExecutor.executeCommandStreamedOutput(File("."), *command.toTypedArray())
181+
logger.info("restic process exited with code $exitCode")
182+
return@all exitCode == 0
183+
} finally {
184+
command.removeLast()
185+
}
186+
}
187+
}
188+
result
189+
} else {
190+
command.add(file.absolutePath)
191+
val exitCode: Int
192+
duration =
193+
measureTime {
194+
exitCode = ProcessExecutor.executeCommandStreamedOutput(File("."), *command.toTypedArray())
195+
}
196+
logger.info("restic process exited with code $exitCode")
197+
exitCode == 0
178198
}
179-
result
180-
} else {
181-
command.add(file.absolutePath)
182-
val exitCode: Int
183-
duration = measureTime {
184-
exitCode = ProcessExecutor.executeCommandStreamedOutput(File("."), *command.toTypedArray())
185-
}
186-
logger.info("restic process exited with code $exitCode")
187-
exitCode == 0
188-
}
189199
if (success) {
190200
WebhookUtil.executeWebhook(
191201
BackupConfig.config.webhookUrl,
192-
":pencil: `${info.webhookName}`のバックアップが完了(${duration.toLocaleString(DurationLocale.JAPANESE)})"
202+
":pencil: `${info.webhookName}`のバックアップが完了(${duration.toLocaleString(DurationLocale.JAPANESE)})",
193203
)
194204
} else {
195205
WebhookUtil.executeWebhook(
196206
BackupConfig.config.webhookUrl,
197-
"${BackupConfig.config.prefixIfWarning}`${info.webhookName}`のバックアップに失敗しました(${duration.toLocaleString(DurationLocale.JAPANESE)})"
207+
"${BackupConfig.config.prefixIfWarning}`${info.webhookName}`のバックアップに失敗しました(${duration.toLocaleString(
208+
DurationLocale.JAPANESE,
209+
)})",
198210
)
199211
}
200212
}
@@ -204,7 +216,10 @@ class Application {
204216
BackupConfig.config.forgetHosts.forEach { forgetHost(it, BackupConfig.config.knownHostsFile) }
205217
}
206218

207-
private fun forgetHost(host: String, file: String?) {
219+
private fun forgetHost(
220+
host: String,
221+
file: String?,
222+
) {
208223
val command = mutableListOf<String>()
209224
if (BackupConfig.config.runAsSudo) {
210225
command.add("sudo")

src/main/kotlin/net/azisaba/automaticbackupscript/config/BackupConfig.kt

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,23 @@ data class BackupConfig(
1212
val knownHostsFile: String? = null,
1313
val forgetHosts: List<String> = listOf("[github.com]:22"),
1414
val binaries: BinariesInfo = BinariesInfo(),
15-
val downloads: List<DownloadInfo> = listOf(
16-
DownloadInfo("server:/tmp/exampleA", "server:/tmp/exampleA", "/tmp/exampleB")
17-
),
18-
val backups: List<BackupInfo> = listOf(
19-
BackupInfo("server:/tmp/exampleA", "/mnt/backup", "plain-password", "/tmp/exampleB", false, 1)
20-
)
15+
val downloads: List<DownloadInfo> =
16+
listOf(
17+
DownloadInfo("server:/tmp/exampleA", "server:/tmp/exampleA", "/tmp/exampleB"),
18+
),
19+
val backups: List<BackupInfo> =
20+
listOf(
21+
BackupInfo("server:/tmp/exampleA", "/mnt/backup", "plain-password", "/tmp/exampleB", false, 1),
22+
),
2123
) {
2224
companion object {
2325
lateinit var config: BackupConfig
2426

2527
fun load(file: File) {
2628
if (!file.parentFile.exists()) file.parentFile.mkdirs()
27-
if (!file.exists()) file.writeText(CoreConfig.json.encodeToString(BackupConfig()))
28-
config = CoreConfig.json.decodeFromString(serializer(), file.readText())
29-
file.writeText(CoreConfig.json.encodeToString(config))
29+
if (!file.exists()) file.writeText(CoreConfig.toml.encodeToString(BackupConfig()))
30+
config = CoreConfig.toml.decodeFromString(serializer(), file.readText())
31+
file.writeText(CoreConfig.toml.encodeToString(config))
3032
}
3133
}
3234
}
@@ -64,5 +66,6 @@ data class BackupInfo(
6466
)
6567

6668
enum class DependOp {
67-
OR, AND
69+
OR,
70+
AND,
6871
}

src/main/kotlin/net/azisaba/automaticbackupscript/config/CoreConfig.kt

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,44 @@
11
package net.azisaba.automaticbackupscript.config
22

3+
import com.akuleshov7.ktoml.Toml
4+
import com.akuleshov7.ktoml.TomlIndentation
5+
import com.akuleshov7.ktoml.TomlInputConfig
6+
import com.akuleshov7.ktoml.TomlOutputConfig
37
import kotlinx.serialization.ExperimentalSerializationApi
48
import kotlinx.serialization.Serializable
59
import kotlinx.serialization.encodeToString
6-
import kotlinx.serialization.json.Json
710
import net.azisaba.automaticbackupscript.Main
811
import net.azisaba.automaticbackupscript.util.ProcessExecutor
912
import java.io.File
1013

1114
@Serializable
1215
data class CoreConfig(
13-
val preExecuteScript: List<List<String>> = listOf(listOf("echo", "pre-execute script!"))
16+
val preExecuteScript: List<List<String>> = listOf(listOf("echo", "pre-execute script!")),
1417
) {
1518
companion object {
1619
@OptIn(ExperimentalSerializationApi::class)
17-
val json = Json {
18-
encodeDefaults = true
19-
prettyPrint = true
20-
prettyPrintIndent = " "
21-
ignoreUnknownKeys = true
22-
}
20+
val toml =
21+
Toml(
22+
inputConfig =
23+
TomlInputConfig(
24+
ignoreUnknownNames = true,
25+
allowEmptyValues = true,
26+
allowNullValues = true,
27+
allowEscapedQuotesInLiteralStrings = true,
28+
allowEmptyToml = true,
29+
),
30+
outputConfig =
31+
TomlOutputConfig(
32+
indentation = TomlIndentation.NONE,
33+
),
34+
)
2335

24-
val config: CoreConfig = File(Main.configFile).let { file ->
25-
if (!file.parentFile.exists()) file.parentFile.mkdirs()
26-
if (!file.exists()) file.writeText(json.encodeToString(CoreConfig()))
27-
json.decodeFromString(serializer(), file.readText())
28-
}
36+
val config: CoreConfig =
37+
File(Main.configFile).let { file ->
38+
if (!file.parentFile.exists()) file.parentFile.mkdirs()
39+
if (!file.exists()) file.writeText(toml.encodeToString(CoreConfig()))
40+
toml.decodeFromString(serializer(), file.readText())
41+
}
2942
}
3043

3144
fun executePreExecuteScript() {

src/main/kotlin/net/azisaba/automaticbackupscript/util/ProcessExecutor.kt

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,21 @@ object ProcessExecutor {
1111
private val logger = LoggerFactory.getLogger(ProcessExecutor::class.java)!!
1212
private var streamThreadIndex = 0
1313

14-
fun executeCommandCaptureOutput(workingDir: File, vararg command: String, logCommand: Boolean = true, mergeStderr: Boolean = false): Result {
14+
fun executeCommandCaptureOutput(
15+
workingDir: File,
16+
vararg command: String,
17+
logCommand: Boolean = true,
18+
mergeStderr: Boolean = false,
19+
): Result {
1520
if (logCommand) {
1621
logger.info("Executing command: ${command.contentToString()}")
1722
}
18-
val proc = ProcessBuilder(*command)
19-
.directory(workingDir)
20-
.redirectOutput(ProcessBuilder.Redirect.PIPE)
21-
.apply { if (mergeStderr) redirectErrorStream(true) }
22-
.start()
23+
val proc =
24+
ProcessBuilder(*command)
25+
.directory(workingDir)
26+
.redirectOutput(ProcessBuilder.Redirect.PIPE)
27+
.apply { if (mergeStderr) redirectErrorStream(true) }
28+
.start()
2329

2430
if (!proc.waitFor(60, TimeUnit.MINUTES)) {
2531
error("Timeout")
@@ -31,24 +37,33 @@ object ProcessExecutor {
3137
)
3238
}
3339

34-
fun executeCommandStreamedOutput(workingDir: File, vararg command: String, logCommand: Boolean = true, mergeStderr: Boolean = false): Int {
40+
fun executeCommandStreamedOutput(
41+
workingDir: File,
42+
vararg command: String,
43+
logCommand: Boolean = true,
44+
mergeStderr: Boolean = false,
45+
): Int {
3546
if (logCommand) {
3647
logger.info("Executing command: ${command.contentToString()}")
3748
}
38-
val proc = ProcessBuilder(*command)
39-
.directory(workingDir)
40-
.redirectOutput(ProcessBuilder.Redirect.PIPE)
41-
.apply { if (mergeStderr) redirectErrorStream(true) }
42-
.start()
43-
.setupPrinter()
49+
val proc =
50+
ProcessBuilder(*command)
51+
.directory(workingDir)
52+
.redirectOutput(ProcessBuilder.Redirect.PIPE)
53+
.apply { if (mergeStderr) redirectErrorStream(true) }
54+
.start()
55+
.setupPrinter()
4456

4557
if (!proc.waitFor(60, TimeUnit.MINUTES)) {
4658
error("Timeout")
4759
}
4860
return proc.exitValue()
4961
}
5062

51-
private fun setupPrinter(input: InputStream, log: (String) -> Unit) {
63+
private fun setupPrinter(
64+
input: InputStream,
65+
log: (String) -> Unit,
66+
) {
5267
Thread({
5368
InputStreamReader(input).use { isr ->
5469
BufferedReader(isr).use { br ->
@@ -67,5 +82,9 @@ object ProcessExecutor {
6782
return this
6883
}
6984

70-
data class Result(val stdout: String, val stderr: String, val exitValue: Int)
85+
data class Result(
86+
val stdout: String,
87+
val stderr: String,
88+
val exitValue: Int,
89+
)
7190
}

0 commit comments

Comments
 (0)