Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitafdc641

Browse files
committed
impl: visual text progress during Coder CLI downloading
This PR implements a mechanism to provide recurrent stats aboutthe number of the KB and MB of Coder CLI downloaded.
1 parent01651f0 commitafdc641

File tree

5 files changed

+80
-33
lines changed

5 files changed

+80
-33
lines changed

‎CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
##Unreleased
44

5+
###Added
6+
7+
- visual text progress during Coder CLI downloading
8+
59
###Changed
610

711
- the plugin will now remember the SSH connection state for each workspace, and it will try to automatically

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

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import java.net.HttpURLConnection
3232
importjava.net.URL
3333
importjava.nio.file.Files
3434
importjava.nio.file.Path
35-
importjava.nio.file.StandardCopyOption
35+
importjava.nio.file.StandardOpenOption
3636
importjava.util.zip.GZIPInputStream
3737
importjavax.net.ssl.HttpsURLConnection
3838

@@ -44,6 +44,8 @@ internal data class Version(
4444
@Json(name="version")valversion:String,
4545
)
4646

47+
privateconstvalDOWNLOADING_CODER_CLI="Downloading Coder CLI..."
48+
4749
/**
4850
* Do as much as possible to get a valid, up-to-date CLI.
4951
*
@@ -60,6 +62,7 @@ fun ensureCLI(
6062
context:CoderToolboxContext,
6163
deploymentURL:URL,
6264
buildVersion:String,
65+
showTextProgress: (String)->Unit
6366
):CoderCLIManager {
6467
val settings= context.settingsStore.readOnly()
6568
val cli=CoderCLIManager(deploymentURL, context.logger, settings)
@@ -76,9 +79,10 @@ fun ensureCLI(
7679

7780
// If downloads are enabled download the new version.
7881
if (settings.enableDownloads) {
79-
context.logger.info("Downloading Coder CLI...")
82+
context.logger.info(DOWNLOADING_CODER_CLI)
83+
showTextProgress(DOWNLOADING_CODER_CLI)
8084
try {
81-
cli.download()
85+
cli.download(showTextProgress)
8286
return cli
8387
}catch (e: java.nio.file.AccessDeniedException) {
8488
// Might be able to fall back to the data directory.
@@ -98,8 +102,9 @@ fun ensureCLI(
98102
}
99103

100104
if (settings.enableDownloads) {
101-
context.logger.info("Downloading Coder CLI...")
102-
dataCLI.download()
105+
context.logger.info(DOWNLOADING_CODER_CLI)
106+
showTextProgress(DOWNLOADING_CODER_CLI)
107+
dataCLI.download(showTextProgress)
103108
return dataCLI
104109
}
105110

@@ -137,7 +142,7 @@ class CoderCLIManager(
137142
/**
138143
* Download the CLI from the deployment if necessary.
139144
*/
140-
fundownload():Boolean {
145+
fundownload(showTextProgress: (String)->Unit):Boolean {
141146
val eTag= getBinaryETag()
142147
val conn= remoteBinaryURL.openConnection()asHttpURLConnection
143148
if (!settings.headerCommand.isNullOrBlank()) {
@@ -163,12 +168,25 @@ class CoderCLIManager(
163168
HttpURLConnection.HTTP_OK-> {
164169
logger.info("Downloading binary to$localBinaryPath")
165170
Files.createDirectories(localBinaryPath.parent)
166-
conn.inputStream.use {
167-
Files.copy(
168-
if (conn.contentEncoding=="gzip")GZIPInputStream(it)else it,
169-
localBinaryPath,
170-
StandardCopyOption.REPLACE_EXISTING,
171-
)
171+
val outputStream=Files.newOutputStream(
172+
localBinaryPath,
173+
StandardOpenOption.CREATE,
174+
StandardOpenOption.TRUNCATE_EXISTING
175+
)
176+
val sourceStream=if (conn.isGzip())GZIPInputStream(conn.inputStream)else conn.inputStream
177+
178+
val buffer=ByteArray(DEFAULT_BUFFER_SIZE)
179+
var bytesRead:Int
180+
var totalRead=0L
181+
182+
sourceStream.use { source->
183+
outputStream.use { sink->
184+
while (source.read(buffer).also { bytesRead= it }!=-1) {
185+
sink.write(buffer,0, bytesRead)
186+
totalRead+= bytesRead
187+
showTextProgress("Downloaded${totalRead.toHumanReadableSize()}...")
188+
}
189+
}
172190
}
173191
if (getOS()!=OS.WINDOWS) {
174192
localBinaryPath.toFile().setExecutable(true)
@@ -178,6 +196,7 @@ class CoderCLIManager(
178196

179197
HttpURLConnection.HTTP_NOT_MODIFIED-> {
180198
logger.info("Using cached binary at$localBinaryPath")
199+
showTextProgress("Using cached binary")
181200
returnfalse
182201
}
183202
}
@@ -190,6 +209,21 @@ class CoderCLIManager(
190209
throwResponseException("Unexpected response from$remoteBinaryURL", conn.responseCode)
191210
}
192211

212+
privatefun HttpURLConnection.isGzip():Boolean=this.contentEncoding.equals("gzip", ignoreCase=true)
213+
214+
fun Long.toHumanReadableSize():String {
215+
if (this<1024)return"$this B"
216+
217+
val kb=this/1024.0
218+
if (kb<1024)returnString.format("%.1f KB", kb)
219+
220+
val mb= kb/1024.0
221+
if (mb<1024)returnString.format("%.1f MB", mb)
222+
223+
val gb= mb/1024.0
224+
returnString.format("%.1f GB", gb)
225+
}
226+
193227
/**
194228
* Return the entity tag for the binary on disk, if any.
195229
*/

‎src/main/kotlin/com/coder/toolbox/util/CoderProtocolHandler.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import kotlin.time.Duration.Companion.seconds
2424
importkotlin.time.toJavaDuration
2525

2626
privateconstvalCAN_T_HANDLE_URI_TITLE="Can't handle URI"
27+
privateval noOpTextProgress: (String)->Unit= { _-> }
2728

2829
@Suppress("UnstableApiUsage")
2930
openclassCoderProtocolHandler(
@@ -304,7 +305,8 @@ open class CoderProtocolHandler(
304305
val cli= ensureCLI(
305306
context,
306307
deploymentURL.toURL(),
307-
restClient.buildInfo().version
308+
restClient.buildInfo().version,
309+
noOpTextProgress
308310
)
309311

310312
// We only need to log in if we are using token-based auth.

‎src/main/kotlin/com/coder/toolbox/views/ConnectStep.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,16 @@ class ConnectStep(
8686
// allows interleaving with the back/cancel action
8787
yield()
8888
client.initializeSession()
89-
statusField.textState.update { (context.i18n.ptrl("Checking Coder binary...")) }
90-
val cli= ensureCLI(context, client.url, client.buildVersion)
89+
statusField.textState.update { (context.i18n.ptrl("Checking Coder CLI...")) }
90+
val cli= ensureCLI(
91+
context, client.url,
92+
client.buildVersion
93+
) { progress->
94+
statusField.textState.update { (context.i18n.pnotr(progress)) }
95+
}
9196
// We only need to log in if we are using token-based auth.
9297
if (client.token!=null) {
93-
statusField.textState.update { (context.i18n.ptrl("Configuring CLI...")) }
98+
statusField.textState.update { (context.i18n.ptrl("ConfiguringCoderCLI...")) }
9499
// allows interleaving with the back/cancel action
95100
yield()
96101
cli.login(client.token)

‎src/test/kotlin/com/coder/toolbox/cli/CoderCLIManagerTest.kt

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ import kotlin.test.assertFalse
6262
importkotlin.test.assertNotEquals
6363
importkotlin.test.assertTrue
6464

65+
privateval noOpTextProgress: (String)->Unit= { _-> }
66+
6567
internalclassCoderCLIManagerTest {
6668
privateval context=CoderToolboxContext(
6769
mockk<ToolboxUi>(),
@@ -145,7 +147,7 @@ internal class CoderCLIManagerTest {
145147
val ex=
146148
assertFailsWith(
147149
exceptionClass=ResponseException::class,
148-
block= { ccm.download() },
150+
block= { ccm.download(noOpTextProgress) },
149151
)
150152
assertEquals(HttpURLConnection.HTTP_INTERNAL_ERROR, ex.code)
151153

@@ -200,7 +202,7 @@ internal class CoderCLIManagerTest {
200202

201203
assertFailsWith(
202204
exceptionClass=AccessDeniedException::class,
203-
block= { ccm.download() },
205+
block= { ccm.download(noOpTextProgress) },
204206
)
205207

206208
srv.stop(0)
@@ -229,11 +231,11 @@ internal class CoderCLIManagerTest {
229231
).readOnly(),
230232
)
231233

232-
assertTrue(ccm.download())
234+
assertTrue(ccm.download(noOpTextProgress))
233235
assertDoesNotThrow { ccm.version() }
234236

235237
// It should skip the second attempt.
236-
assertFalse(ccm.download())
238+
assertFalse(ccm.download(noOpTextProgress))
237239

238240
// Make sure login failures propagate.
239241
assertFailsWith(
@@ -258,11 +260,11 @@ internal class CoderCLIManagerTest {
258260
).readOnly(),
259261
)
260262

261-
assertEquals(true, ccm.download())
263+
assertEquals(true, ccm.download(noOpTextProgress))
262264
assertEquals(SemVer(url.port.toLong(),0,0), ccm.version())
263265

264266
// It should skip the second attempt.
265-
assertEquals(false, ccm.download())
267+
assertEquals(false, ccm.download(noOpTextProgress))
266268

267269
// Should use the source override.
268270
ccm=CoderCLIManager(
@@ -278,7 +280,7 @@ internal class CoderCLIManagerTest {
278280
).readOnly(),
279281
)
280282

281-
assertEquals(true, ccm.download())
283+
assertEquals(true, ccm.download(noOpTextProgress))
282284
assertContains(ccm.localBinaryPath.toFile().readText(),"0.0.0")
283285

284286
srv.stop(0)
@@ -326,7 +328,7 @@ internal class CoderCLIManagerTest {
326328
assertEquals("cli", ccm.localBinaryPath.toFile().readText())
327329
assertEquals(0, ccm.localBinaryPath.toFile().lastModified())
328330

329-
assertTrue(ccm.download())
331+
assertTrue(ccm.download(noOpTextProgress))
330332

331333
assertNotEquals("cli", ccm.localBinaryPath.toFile().readText())
332334
assertNotEquals(0, ccm.localBinaryPath.toFile().lastModified())
@@ -351,8 +353,8 @@ internal class CoderCLIManagerTest {
351353
val ccm1=CoderCLIManager(url1, context.logger, settings)
352354
val ccm2=CoderCLIManager(url2, context.logger, settings)
353355

354-
assertTrue(ccm1.download())
355-
assertTrue(ccm2.download())
356+
assertTrue(ccm1.download(noOpTextProgress))
357+
assertTrue(ccm2.download(noOpTextProgress))
356358

357359
srv1.stop(0)
358360
srv2.stop(0)
@@ -883,12 +885,12 @@ internal class CoderCLIManagerTest {
883885
Result.ERROR-> {
884886
assertFailsWith(
885887
exceptionClass=AccessDeniedException::class,
886-
block= { ensureCLI(localContext, url, it.buildVersion) },
888+
block= { ensureCLI(localContext, url, it.buildVersion, noOpTextProgress) },
887889
)
888890
}
889891

890892
Result.NONE-> {
891-
val ccm= ensureCLI(localContext, url, it.buildVersion)
893+
val ccm= ensureCLI(localContext, url, it.buildVersion, noOpTextProgress)
892894
assertEquals(settings.binPath(url), ccm.localBinaryPath)
893895
assertFailsWith(
894896
exceptionClass=ProcessInitException::class,
@@ -897,25 +899,25 @@ internal class CoderCLIManagerTest {
897899
}
898900

899901
Result.DL_BIN-> {
900-
val ccm= ensureCLI(localContext, url, it.buildVersion)
902+
val ccm= ensureCLI(localContext, url, it.buildVersion, noOpTextProgress)
901903
assertEquals(settings.binPath(url), ccm.localBinaryPath)
902904
assertEquals(SemVer(url.port.toLong(),0,0), ccm.version())
903905
}
904906

905907
Result.DL_DATA-> {
906-
val ccm= ensureCLI(localContext, url, it.buildVersion)
908+
val ccm= ensureCLI(localContext, url, it.buildVersion, noOpTextProgress)
907909
assertEquals(settings.binPath(url,true), ccm.localBinaryPath)
908910
assertEquals(SemVer(url.port.toLong(),0,0), ccm.version())
909911
}
910912

911913
Result.USE_BIN-> {
912-
val ccm= ensureCLI(localContext, url, it.buildVersion)
914+
val ccm= ensureCLI(localContext, url, it.buildVersion, noOpTextProgress)
913915
assertEquals(settings.binPath(url), ccm.localBinaryPath)
914916
assertEquals(SemVer.parse(it.version?:""), ccm.version())
915917
}
916918

917919
Result.USE_DATA-> {
918-
val ccm= ensureCLI(localContext, url, it.buildVersion)
920+
val ccm= ensureCLI(localContext, url, it.buildVersion, noOpTextProgress)
919921
assertEquals(settings.binPath(url,true), ccm.localBinaryPath)
920922
assertEquals(SemVer.parse(it.fallbackVersion?:""), ccm.version())
921923
}
@@ -955,7 +957,7 @@ internal class CoderCLIManagerTest {
955957
context.logger,
956958
).readOnly(),
957959
)
958-
assertEquals(true, ccm.download())
960+
assertEquals(true, ccm.download(noOpTextProgress))
959961
assertEquals(it.second, ccm.features,"version:${it.first}")
960962

961963
srv.stop(0)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp