99
1010import java .io .BufferedInputStream ;
1111import java .io .File ;
12+ import java .io .IOException ;
1213import java .io .InputStream ;
1314import java .nio .file .Files ;
1415import java .nio .file .StandardCopyOption ;
1920import java .util .concurrent .CountDownLatch ;
2021import java .util .concurrent .TimeUnit ;
2122import java .util .concurrent .atomic .AtomicLong ;
23+ import java .util .concurrent .atomic .AtomicReference ;
2224import java .util .logging .Level ;
2325import java .util .logging .Logger ;
2426import java .util .prefs .Preferences ;
@@ -163,38 +165,13 @@ protected File download(final JobMonitor monitor) throws Exception
163165 final File file = File .createTempFile ("phoebus_update" , ".zip" );
164166 file .deleteOnExit ();
165167
166- // Watcher thread that displays file size in monitor
168+ // Watcher thread that displays file size in monitor.
169+ // Reference to the download stream so the watcher can close it on 'cancel'.
167170 final CountDownLatch done = new CountDownLatch (1 );
168- final Thread download_thread = Thread .currentThread ();
169- final Thread watcher = new Thread (() ->
170- {
171- try
172- {
173- while (! done .await (1 , TimeUnit .SECONDS ))
174- {
175- final long size = file .length ();
176- final long full = full_size .get ();
177- if (full > 0 )
178- {
179- int percent = (int ) ((size *100 ) / full );
180- monitor .updateTaskName (String .format ("Downloading %d %% (%.3f/%.3f MB)" , percent , size /1.0e6 , full /1.0e6 ));
181- }
182- else
183- monitor .updateTaskName (String .format ("Downloading %.3f MB" , size /1.0e6 ));
184-
185- // Force the download thread to stop on 'cancel'.
186- // 'interrupt()' has no effect, and Files.copy is not
187- // otherwise instrumented.
188- if (monitor .isCanceled ())
189- download_thread .stop ();
190- }
191- monitor .updateTaskName (String .format ("Download Finished" ));
192- }
193- catch (Exception ex )
194- {
195- logger .log (Level .WARNING , "Download watch thread" , ex );
196- }
197- }, "Watch Download" );
171+ final AtomicReference <InputStream > download_stream = new AtomicReference <>();
172+ final Thread watcher = new Thread (
173+ () -> watchDownload (monitor , file , full_size , done , download_stream ),
174+ "Watch Download" );
198175 watcher .setDaemon (true );
199176 watcher .start ();
200177
@@ -203,6 +180,7 @@ protected File download(final JobMonitor monitor) throws Exception
203180 final InputStream src = getDownloadStream ();
204181 )
205182 {
183+ download_stream .set (src );
206184 logger .info ("Download into " + file );
207185 Files .copy (src , file .toPath (), StandardCopyOption .REPLACE_EXISTING );
208186 return file ;
@@ -218,6 +196,74 @@ protected File download(final JobMonitor monitor) throws Exception
218196 }
219197 }
220198
199+ /** Watch an in-progress download, reporting size to the monitor and
200+ * aborting on 'cancel'.
201+ *
202+ * <p>Runs on the "Watch Download" thread until {@code done} is counted
203+ * down by {@link #download(JobMonitor)}.
204+ *
205+ * @param monitor {@link JobMonitor} to update and poll for cancellation
206+ * @param file Partially downloaded file, polled for its current size
207+ * @param full_size Expected total size, or ≤ 0 if unknown
208+ * @param done Latch that the download signals once the copy has finished
209+ * @param download_stream Holds the source stream to close on 'cancel'
210+ */
211+ private void watchDownload (final JobMonitor monitor ,
212+ final File file ,
213+ final AtomicLong full_size ,
214+ final CountDownLatch done ,
215+ final AtomicReference <InputStream > download_stream )
216+ {
217+ try
218+ {
219+ while (! done .await (1 , TimeUnit .SECONDS ))
220+ {
221+ final long size = file .length ();
222+ final long full = full_size .get ();
223+ if (full > 0 )
224+ {
225+ int percent = (int ) ((size *100 ) / full );
226+ monitor .updateTaskName (String .format ("Downloading %d %% (%.3f/%.3f MB)" , percent , size /1.0e6 , full /1.0e6 ));
227+ }
228+ else
229+ monitor .updateTaskName (String .format ("Downloading %.3f MB" , size /1.0e6 ));
230+
231+ // Force the download to stop on 'cancel'.
232+ // 'interrupt()' has no effect, and Files.copy is not
233+ // otherwise instrumented, so close the source stream to
234+ // break the in-progress copy out of its blocking read.
235+ if (monitor .isCanceled ())
236+ closeDownloadStream (download_stream .get ());
237+ }
238+ monitor .updateTaskName (String .format ("Download Finished" ));
239+ }
240+ catch (Exception ex )
241+ {
242+ logger .log (Level .WARNING , "Download watch thread" , ex );
243+ }
244+ }
245+
246+ /** Close the source stream to abort a cancelled download.
247+ *
248+ * <p>A failed close is logged rather than thrown so it doesn't end the
249+ * watcher; it will retry on the next poll.
250+ *
251+ * @param src Stream to close, may be <code>null</code>
252+ */
253+ private void closeDownloadStream (final InputStream src )
254+ {
255+ if (src == null )
256+ return ;
257+ try
258+ {
259+ src .close ();
260+ }
261+ catch (IOException ex )
262+ {
263+ logger .log (Level .WARNING , "Cannot close download stream on cancel" , ex );
264+ }
265+ }
266+
221267 /** Update installation
222268 * @param monitor {@link JobMonitor}
223269 * @param install_location Existing {@link Locations#install()}
0 commit comments