blob: 18f8e49cf6db0bdf96443257e30e4eca890592cc (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
/*
* SPDX-FileCopyrightText: 2023 Linnea Gräf <nea@nea.moe>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package moe.nea.firmament.repo
import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.utils.io.jvm.nio.*
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import moe.nea.firmament.Firmament
import moe.nea.firmament.Firmament.logger
import moe.nea.firmament.util.iterate
import java.io.IOException
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardOpenOption
import java.util.zip.ZipInputStream
import kotlin.io.path.*
object RepoDownloadManager {
val repoSavedLocation = Firmament.DATA_DIR.resolve("repo-extracted")
val repoMetadataLocation = Firmament.DATA_DIR.resolve("loaded-repo-sha.txt")
private fun loadSavedVersionHash(): String? =
if (repoSavedLocation.exists()) {
if (repoMetadataLocation.exists()) {
try {
repoMetadataLocation.readText().trim()
} catch (e: IOException) {
null
}
} else {
null
}
} else null
private fun saveVersionHash(versionHash: String) {
latestSavedVersionHash = versionHash
repoMetadataLocation.writeText(versionHash)
}
var latestSavedVersionHash: String? = loadSavedVersionHash()
private set
@Serializable
private class GithubCommitsResponse(val sha: String)
private suspend fun requestLatestGithubSha(): String? {
val response =
Firmament.httpClient.get("https://api.github.com/repos/${RepoManager.Config.username}/${RepoManager.Config.reponame}/commits/${RepoManager.Config.branch}")
if (response.status.value != 200) {
return null
}
return response.body<GithubCommitsResponse>().sha
}
private suspend fun downloadGithubArchive(url: String): Path = withContext(IO) {
val response = Firmament.httpClient.get(url)
val targetFile = Files.createTempFile("firmament-repo", ".zip")
val outputChannel = Files.newByteChannel(targetFile, StandardOpenOption.CREATE, StandardOpenOption.WRITE)
response.bodyAsChannel().copyTo(outputChannel)
targetFile
}
/**
* Downloads the latest repository from github, setting [latestSavedVersionHash].
* @return true, if an update was performed, false, otherwise (no update needed, or wasn't able to complete update)
*/
suspend fun downloadUpdate(force: Boolean): Boolean = withContext(CoroutineName("Repo Update Check")) {
val latestSha = requestLatestGithubSha()
if (latestSha == null) {
logger.warn("Could not request github API to retrieve latest REPO sha.")
return@withContext false
}
val currentSha = loadSavedVersionHash()
if (latestSha != currentSha || force) {
val requestUrl = "https://github.com/${RepoManager.Config.username}/${RepoManager.Config.reponame}/archive/$latestSha.zip"
logger.info("Planning to upgrade repository from $currentSha to $latestSha from $requestUrl")
val zipFile = downloadGithubArchive(requestUrl)
logger.info("Download repository zip file to $zipFile. Deleting old repository")
withContext(IO) { repoSavedLocation.toFile().deleteRecursively() }
logger.info("Extracting new repository")
withContext(IO) { extractNewRepository(zipFile) }
logger.info("Repository loaded on disk.")
saveVersionHash(latestSha)
return@withContext true
} else {
logger.debug("Repository on latest sha $currentSha. Not performing update")
return@withContext false
}
}
private fun extractNewRepository(zipFile: Path) {
repoSavedLocation.createDirectories()
ZipInputStream(zipFile.inputStream()).use { cis ->
while (true) {
val entry = cis.nextEntry ?: break
if (entry.isDirectory) continue
val extractedLocation =
repoSavedLocation.resolve(
entry.name.substringAfter('/', missingDelimiterValue = "")
)
if (repoSavedLocation !in extractedLocation.iterate { it.parent }) {
logger.error("Not Enough Updates detected an invalid zip file. This is a potential security risk, please report this in the Moulberry discord.")
throw RuntimeException("Not Enough Updates detected an invalid zip file. This is a potential security risk, please report this in the Moulberry discord.")
}
extractedLocation.parent.createDirectories()
cis.copyTo(extractedLocation.outputStream())
cis.closeEntry()
}
}
}
}
|