Skip to content

Commit fb6e784

Browse files
committed
impl: support for downloading the cli signature
From the same source where the cli binary was downloaded. Some of the previous classes like download result were updated to incorporate details like where the file was saved or whether a file was found on the remote
1 parent 021e53a commit fb6e784

File tree

5 files changed

+81
-14
lines changed

5 files changed

+81
-14
lines changed

src/main/kotlin/com/coder/toolbox/cli/CoderCLIManager.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package com.coder.toolbox.cli
33
import com.coder.toolbox.CoderToolboxContext
44
import com.coder.toolbox.cli.downloader.CoderDownloadApi
55
import com.coder.toolbox.cli.downloader.CoderDownloadService
6-
import com.coder.toolbox.cli.downloader.DownloadResult
76
import com.coder.toolbox.cli.ex.MissingVersionException
87
import com.coder.toolbox.cli.ex.SSHConfigFormatException
98
import com.coder.toolbox.sdk.v2.models.Workspace
@@ -160,7 +159,11 @@ class CoderCLIManager(
160159
downloader.downloadCli(buildVersion, showTextProgress)
161160
}
162161

163-
return cliDownloadResult == DownloadResult.Downloaded
162+
var singatureDownloadResult = withContext(Dispatchers.IO) {
163+
downloader.downloadSignature(showTextProgress)
164+
}
165+
166+
return cliDownloadResult.isDownloaded()
164167
}
165168

166169
/**

src/main/kotlin/com/coder/toolbox/cli/downloader/CoderDownloadApi.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,10 @@ interface CoderDownloadApi {
2020
@HeaderMap headers: Map<String, String> = emptyMap(),
2121
@Header("Accept-Encoding") acceptEncoding: String = "gzip",
2222
): Response<ResponseBody>
23+
24+
@GET
25+
suspend fun downloadSignature(
26+
@Url url: String,
27+
@HeaderMap headers: Map<String, String> = emptyMap()
28+
): Response<ResponseBody>
2329
}

src/main/kotlin/com/coder/toolbox/cli/downloader/CoderDownloadService.kt

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,19 @@ import com.coder.toolbox.util.OS
66
import com.coder.toolbox.util.getHeaders
77
import com.coder.toolbox.util.getOS
88
import com.coder.toolbox.util.sha1
9+
import com.coder.toolbox.util.withLastSegment
910
import okhttp3.ResponseBody
1011
import retrofit2.Response
1112
import java.io.FileInputStream
13+
import java.net.HttpURLConnection.HTTP_NOT_FOUND
1214
import java.net.HttpURLConnection.HTTP_NOT_MODIFIED
1315
import java.net.HttpURLConnection.HTTP_OK
1416
import java.net.URL
1517
import java.nio.file.Files
1618
import java.nio.file.Path
1719
import java.nio.file.StandardOpenOption
1820
import java.util.zip.GZIPInputStream
21+
import kotlin.io.path.name
1922
import kotlin.io.path.notExists
2023

2124
/**
@@ -32,21 +35,20 @@ class CoderDownloadService(
3235

3336
suspend fun downloadCli(buildVersion: String, showTextProgress: (String) -> Unit): DownloadResult {
3437
val eTag = calculateLocalETag()
35-
val headers = getRequestHeaders()
3638
if (eTag != null) {
3739
context.logger.info("Found existing binary at $localBinaryPath; calculated hash as $eTag")
3840
}
3941
val response = downloadApi.downloadCli(
4042
url = remoteBinaryURL.toString(),
4143
eTag = eTag?.let { "\"$it\"" },
42-
headers = headers
44+
headers = getRequestHeaders()
4345
)
4446

4547
return when (response.code()) {
4648
HTTP_OK -> {
4749
context.logger.info("Downloading binary to $localBinaryPath")
48-
response.saveBinaryToDisk(buildVersion, showTextProgress)?.makeExecutable()
49-
DownloadResult.Downloaded
50+
response.saveToDisk(localBinaryPath, showTextProgress, buildVersion)?.makeExecutable()
51+
DownloadResult.Downloaded(localBinaryPath)
5052
}
5153

5254
HTTP_NOT_MODIFIED -> {
@@ -84,16 +86,17 @@ class CoderDownloadService(
8486
}
8587
}
8688

87-
private fun Response<ResponseBody>.saveBinaryToDisk(
88-
buildVersion: String,
89-
showTextProgress: (String) -> Unit
89+
private fun Response<ResponseBody>.saveToDisk(
90+
localPath: Path,
91+
showTextProgress: (String) -> Unit,
92+
buildVersion: String? = null
9093
): Path? {
9194
val responseBody = this.body() ?: return null
92-
Files.deleteIfExists(localBinaryPath)
93-
Files.createDirectories(localBinaryPath.parent)
95+
Files.deleteIfExists(localPath)
96+
Files.createDirectories(localPath.parent)
9497

9598
val outputStream = Files.newOutputStream(
96-
localBinaryPath,
99+
localPath,
97100
StandardOpenOption.CREATE,
98101
StandardOpenOption.TRUNCATE_EXISTING
99102
)
@@ -108,7 +111,7 @@ class CoderDownloadService(
108111
var bytesRead: Int
109112
var totalRead = 0L
110113
// caching this because the settings store recomputes it every time
111-
val binaryName = context.settingsStore.defaultCliBinaryNameByOsAndArch
114+
val binaryName = localPath.name
112115
sourceStream.use { source ->
113116
outputStream.use { sink ->
114117
while (source.read(buffer).also { bytesRead = it } != -1) {
@@ -143,4 +146,39 @@ class CoderDownloadService(
143146
val gb = mb / 1024.0
144147
return String.format("%.1f GB", gb)
145148
}
149+
150+
suspend fun downloadSignature(showTextProgress: (String) -> Unit): DownloadResult {
151+
val defaultCliNameWithoutExt = context.settingsStore.defaultCliBinaryNameByOsAndArch.split('.').first()
152+
val signatureName = "$defaultCliNameWithoutExt.asc"
153+
154+
val signatureURL = remoteBinaryURL.withLastSegment(signatureName)
155+
val localSignaturePath = localBinaryPath.parent.resolve(signatureName)
156+
context.logger.info("Downloading signature from $signatureURL")
157+
158+
val response = downloadApi.downloadSignature(
159+
url = signatureURL.toString(),
160+
headers = getRequestHeaders()
161+
)
162+
163+
return when (response.code()) {
164+
HTTP_OK -> {
165+
response.saveToDisk(localSignaturePath, showTextProgress)
166+
DownloadResult.Downloaded(localSignaturePath)
167+
}
168+
169+
HTTP_NOT_FOUND -> {
170+
context.logger.warn("Signature file not found at $signatureURL")
171+
DownloadResult.NotFound
172+
}
173+
174+
else -> {
175+
DownloadResult.Failed(
176+
ResponseException(
177+
"Failed to download signature from $signatureURL",
178+
response.code()
179+
)
180+
)
181+
}
182+
}
183+
}
146184
}
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
package com.coder.toolbox.cli.downloader
22

3+
import java.nio.file.Path
4+
5+
36
/**
47
* Result of a download operation
58
*/
69
sealed class DownloadResult {
710
object Skipped : DownloadResult()
8-
object Downloaded : DownloadResult()
11+
object NotFound : DownloadResult()
12+
data class Downloaded(val savePath: Path) : DownloadResult()
913
data class Failed(val error: Exception) : DownloadResult()
14+
15+
fun isDownloaded(): Boolean = this is Downloaded
1016
}

src/main/kotlin/com/coder/toolbox/util/URLExtensions.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,20 @@ fun URL.withPath(path: String): URL = URL(
1313
if (path.startsWith("/")) path else "/$path",
1414
)
1515

16+
fun URL.withLastSegment(segment: String): URL {
17+
val uri = this.toURI()
18+
val basePath = uri.path.substringBeforeLast('/')
19+
val newPath = "$basePath/$segment"
20+
val newUri = URI(
21+
uri.scheme,
22+
uri.authority,
23+
newPath,
24+
uri.query,
25+
uri.fragment
26+
)
27+
return newUri.toURL()
28+
}
29+
1630
/**
1731
* Return the host, converting IDN to ASCII in case the file system cannot
1832
* support the necessary character set.

0 commit comments

Comments
 (0)