@@ -148,6 +148,24 @@ public Optional<String> installedVersion() {
148148 * @throws IOException если сервер не установлен и его не удалось скачать
149149 */
150150 public Path downloadIfNeeded (BslLanguageServerReleaseChannel channel ) throws IOException {
151+ return downloadIfNeeded (channel , DownloadProgressListener .NONE );
152+ }
153+
154+ /**
155+ * Скачивает сервер при необходимости и возвращает путь к его исполняемому файлу, сообщая о
156+ * прогрессе скачивания ассета.
157+ *
158+ * <p>Поведение совпадает с {@link #downloadIfNeeded(BslLanguageServerReleaseChannel)}; отличие —
159+ * в передаче {@code progressListener}, который вызывается по мере вычитывания тела ответа
160+ * (только когда действительно происходит скачивание новой версии).
161+ *
162+ * @param channel канал релизов (стабильный / pre-release)
163+ * @param progressListener слушатель прогресса скачивания ассета
164+ * @return путь к исполняемому файлу сервера
165+ * @throws IOException если сервер не установлен и его не удалось скачать
166+ */
167+ public Path downloadIfNeeded (BslLanguageServerReleaseChannel channel ,
168+ DownloadProgressListener progressListener ) throws IOException {
151169 var installed = installedVersion ().orElse (null );
152170
153171 if (installed != null && !checkIntervalElapsed ()) {
@@ -173,7 +191,7 @@ public Path downloadIfNeeded(BslLanguageServerReleaseChannel channel) throws IOE
173191 if (installed == null || compareVersions (latestVersion , installed ) > 0 ) {
174192 LOGGER .info ("Downloading BSL Language Server {}" , latestVersion );
175193 try {
176- downloadAndExtract (release , latestVersion );
194+ downloadAndExtract (release , latestVersion , progressListener );
177195 cleanupOtherVersions (latestVersion );
178196 installed = latestVersion ;
179197 } catch (IOException e ) {
@@ -234,7 +252,8 @@ private GHRelease latestRelease(BslLanguageServerReleaseChannel channel) throws
234252 return release ;
235253 }
236254
237- private void downloadAndExtract (GHRelease release , String version ) throws IOException {
255+ private void downloadAndExtract (GHRelease release , String version ,
256+ DownloadProgressListener progressListener ) throws IOException {
238257 var assetName = assetName ();
239258 var downloadUrl = release .listAssets ().toList ().stream ()
240259 .filter (asset -> asset .getName ().equals (assetName ))
@@ -248,15 +267,16 @@ private void downloadAndExtract(GHRelease release, String version) throws IOExce
248267 var versionDir = installDir .resolve (version );
249268
250269 try {
251- download (downloadUrl , archive );
270+ download (downloadUrl , archive , progressListener );
252271 deleteRecursively (versionDir );
253272 extract (archive , versionDir );
254273 } finally {
255274 Files .deleteIfExists (archive );
256275 }
257276 }
258277
259- private void download (String url , Path destination ) throws IOException {
278+ private void download (String url , Path destination , DownloadProgressListener progressListener )
279+ throws IOException {
260280 var client = HttpClient .newBuilder ()
261281 .followRedirects (HttpClient .Redirect .NORMAL )
262282 .connectTimeout (CONNECT_TIMEOUT )
@@ -267,16 +287,35 @@ private void download(String url, Path destination) throws IOException {
267287 .GET ()
268288 .build ();
269289 try {
270- var response = client .send (request , HttpResponse .BodyHandlers .ofFile ( destination ));
290+ var response = client .send (request , HttpResponse .BodyHandlers .ofInputStream ( ));
271291 if (response .statusCode () != 200 ) {
272- throw new IOException ("Failed to download " + url + ": HTTP " + response .statusCode ());
292+ try (var ignored = response .body ()) {
293+ throw new IOException ("Failed to download " + url + ": HTTP " + response .statusCode ());
294+ }
273295 }
296+ var totalBytes = response .headers ().firstValueAsLong ("Content-Length" ).orElse (-1L );
297+ copyToFile (response .body (), destination , totalBytes , progressListener );
274298 } catch (InterruptedException e ) {
275299 Thread .currentThread ().interrupt ();
276300 throw new IOException ("BSL Language Server download was interrupted" , e );
277301 }
278302 }
279303
304+ static void copyToFile (InputStream source , Path destination , long totalBytes ,
305+ DownloadProgressListener progressListener ) throws IOException {
306+ var buffer = new byte [16 * 1024 ];
307+ long readTotal = 0 ;
308+ progressListener .onProgress (0 , totalBytes );
309+ try (var input = source ; var output = Files .newOutputStream (destination )) {
310+ int read ;
311+ while ((read = input .read (buffer )) != -1 ) {
312+ output .write (buffer , 0 , read );
313+ readTotal += read ;
314+ progressListener .onProgress (readTotal , totalBytes );
315+ }
316+ }
317+ }
318+
280319 private static void extract (Path archive , Path targetDir ) throws IOException {
281320 Files .createDirectories (targetDir );
282321 try (var zip = ZipFile .builder ().setPath (archive ).get ()) {
0 commit comments