source: main/trunk/gli/src/org/greenstone/gatherer/download/DownloadProgressBar.java@ 31692

Last change on this file since 31692 was 31692, checked in by ak19, 7 years ago

Major changes to SafeProcess and classes of the download package, by bringing the final GLI (and final Greenstone) class DownloadJob over to use SafeProcess. Significant changes to SafeProcess: 1. Introduction of cancelRunningProcess as a new method, so that callers don't need to know how cancelling a process is implemented (as an interrupt) nor do they need to know what thread they ought to interrupt (which should be the thread that launched SafeProcess), nor do they need to know of the complexity surrounding the join() blocking call which should not be interrupted. 2. Introduction of the SafeProcess.MainHandler interface that provides methods that allow implementers to write hooks to various stages of the SafeProcess' internal process' life cycle. 3. moved process cleanUp() code into a reusable method within SafeProcess. Significant changes to DownloadJob and its associated DownloadProgressBar and DownloadScrollPane classes: 1. Unused member vars removed or commented out and those that can be declared final are now declared so, as a programming pricinple for thread safety, since DownloadJob and the other download classes will have to interact with multiple threads and could be called by different threads. 2. Replaced existing actionPerformed() and callDownload() of DownloadJob with SafeProcess and implemented the necessary Hooks in the SafeProcess.MainHandler() interface to ensure that perl is still told to terminate wget on a cancel action. 3. Replaced DownloadScrollPane's deleteDownloadJob() with a new version that now more responsively removes its DownloadProgressBar (with controls) for a DownloadJob. It's called by the SafeProcess.MainHandler interface hooks implemented by DownloadJob, so DownloadScrollPane no longer needs to wait() to be notify()ed when a process has cleaned up on premature termination by a cancel action. 4. Made the necessary methods in DownloadProgressBar synchronized for thread safety. 5. GShell now uses SafeProcess' new cancelRunningProcess() method in place of directly calling interrupt on the (GShell) thread that launched SafeProcess.

  • Property svn:keywords set to Author Date Id Revision
File size: 14.9 KB
Line 
1/**
2 *#########################################################################
3 *
4 * A component of the Gatherer application, part of the Greenstone digital
5 * library suite from the New Zealand Digital Library Project at the
6 * University of Waikato, New Zealand.
7 *
8 * <BR><BR>
9 *
10 * Author: John Thompson, Greenstone Digital Library, University of Waikato
11 *
12 * <BR><BR>
13 *
14 * Copyright (C) 1999 New Zealand Digital Library Project
15 *
16 * <BR><BR>
17 *
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
22 *
23 * <BR><BR>
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
29 *
30 * <BR><BR>
31 *
32 * You should have received a copy of the GNU General Public License
33 * along with this program; if not, write to the Free Software
34 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
35 *########################################################################
36 */
37package org.greenstone.gatherer.download;
38
39import java.awt.*;
40import java.awt.event.*;
41import javax.swing.*;
42import javax.swing.border.*;
43import org.greenstone.gatherer.Dictionary;
44import org.greenstone.gatherer.Gatherer;
45//import org.greenstone.gatherer.collection.DownloadJob;
46import org.greenstone.gatherer.util.AppendLineOnlyFileDocument;
47import org.greenstone.gatherer.util.Utility;
48import org.greenstone.gatherer.gui.*;
49
50public class DownloadProgressBar
51 extends JPanel
52 implements ActionListener {
53
54 private boolean simple = false;
55
56 private Dimension bar_size = new Dimension(520, 25);
57
58 private Dimension MINIMUM_BUTTON_SIZE = new Dimension(100, 25);
59 private Dimension PROGRESS_BAR_SIZE = new Dimension(580,75);
60
61 private int current_action;
62 private int err_count;
63 private int file_count;
64 private int total_count;
65 private int warning_count;
66
67 private JLabel current_status;
68 private JLabel main_status;
69 private JLabel results_status;
70
71 private JPanel center_pane;
72 private JPanel inner_pane;
73
74 private JProgressBar progress;
75
76 private long file_size;
77 private long total_size;
78
79 private String current_url;
80 private String initial_url;
81
82 private DownloadJob owner;
83
84 public JButton stop_start_button;
85 public JButton log_button;
86 public JButton close_button;
87
88 public DownloadProgressBar(DownloadJob owner, String initial_url, boolean simple) {
89 this.owner = owner;
90 this.current_url = null;
91 this.err_count = 0;
92 this.initial_url = initial_url;
93 this.file_count = 0;
94 this.file_size = 0;
95 this.simple = simple;
96 this.total_count = 0;
97 this.total_size = 0;
98 this.warning_count = 0;
99
100 this.setLayout(new BorderLayout());
101 this.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED));
102
103 this.current_action = DownloadJob.STOPPED;
104
105 inner_pane = new JPanel(new BorderLayout());
106 inner_pane.setBorder(BorderFactory.createEmptyBorder(2,2,2,2));
107
108 center_pane = new JPanel(new GridLayout(3,1));
109
110 main_status = new JLabel();
111
112 current_status = new JLabel();
113
114 results_status = new JLabel();
115
116 progress = new JProgressBar();
117 progress.setStringPainted(true);
118 progress.setMinimum(0);
119 progress.setMaximum(0);
120 progress.setEnabled(false);
121 progress.setString(Dictionary.get("Mirroring.DownloadJob.Waiting"));
122 inner_pane.add(progress, BorderLayout.CENTER);
123
124 center_pane.add(main_status);
125 center_pane.add(current_status);
126 center_pane.add(results_status);
127
128 JPanel button_pane = new JPanel();
129
130 // our "pause" button never paused before, it always did a process.destroy() on being pressed.
131 // See http://trac.greenstone.org/browser/trunk/gli/src/org/greenstone/gatherer/download/DownloadJob.java?rev=13594
132 // However, now we additionally ensure that the wget launched by the perl is stopped before
133 // process.destroy(), so at least it cleans up better. I'm therefore changing it to a "Stop" button.
134 stop_start_button = new GLIButton(Dictionary.get("Mirroring.DownloadJob.Pause"),Dictionary.get("Mirroring.DownloadJob.Pause_Tooltip"));
135 //stop_start_button = new GLIButton(Dictionary.get("Mirroring.DownloadJob.Stop"),Dictionary.get("Mirroring.DownloadJob.Stop_Tooltip"));
136 stop_start_button.addActionListener(this);
137 stop_start_button.addActionListener(owner);
138 stop_start_button.setMinimumSize(MINIMUM_BUTTON_SIZE);
139 stop_start_button.setMnemonic(KeyEvent.VK_P);
140 stop_start_button.setEnabled(true);
141
142
143 log_button = new GLIButton(Dictionary.get("Mirroring.DownloadJob.Log"),Dictionary.get("Mirroring.DownloadJob.Log_Tooltip"));
144 log_button.addActionListener(owner);
145 log_button.addActionListener(this);
146 log_button.setEnabled(false);
147 log_button.setMinimumSize(MINIMUM_BUTTON_SIZE);
148 log_button.setMnemonic(KeyEvent.VK_L);
149
150
151 close_button = new GLIButton(Dictionary.get("Mirroring.DownloadJob.Close"),Dictionary.get("Mirroring.DownloadJob.Close_Tooltip"));
152 close_button.addActionListener(owner);
153 close_button.addActionListener(this);
154 close_button.setMinimumSize(MINIMUM_BUTTON_SIZE);
155 close_button.setMnemonic(KeyEvent.VK_C);
156 close_button.setEnabled(true);
157
158
159 // Layout - or at least some of it
160
161 inner_pane.add(center_pane, BorderLayout.NORTH);
162
163 button_pane.setLayout(new GridLayout(3,1));
164 button_pane.add(stop_start_button);
165 button_pane.add(log_button);
166 button_pane.add(close_button);
167
168 this.add(inner_pane, BorderLayout.CENTER);
169 this.add(button_pane, BorderLayout.EAST);
170
171 // Make the labels, etc update.
172 refresh();
173 }
174
175 // internally thread-safe
176 public void enableCancelJob(boolean isEnabled) {
177 synchronized(stop_start_button) {
178 stop_start_button.setEnabled(isEnabled);
179 }
180 synchronized(stop_start_button) {
181 close_button.setEnabled(isEnabled);
182 }
183 }
184
185 public void actionPerformed(ActionEvent event) {
186 Object source = event.getSource();
187 if(source == stop_start_button) {
188 // If we are running, stop.
189 if (current_action == DownloadJob.RUNNING) {
190 current_action = DownloadJob.STOPPED;
191 stop_start_button.setText(Dictionary.get("Mirroring.DownloadJob.Resume"));
192 stop_start_button.setToolTipText(Dictionary.get("Mirroring.DownloadJob.Resume_Tooltip"));
193
194 progress.setString(Dictionary.get("Mirroring.DownloadJob.Download_Stopped"));
195 progress.setIndeterminate(false);
196 } else {
197 current_action = DownloadJob.RUNNING;
198 // we are stopped, so restart
199 stop_start_button.setText(Dictionary.get("Mirroring.DownloadJob.Pause"));
200 stop_start_button.setToolTipText(Dictionary.get("Mirroring.DownloadJob.Pause_Tooltip"));
201 progress.setString(Dictionary.get("Mirroring.DownloadJob.Download_Progress"));
202 progress.setIndeterminate(true);
203 }
204 }
205 else if(source == close_button) {
206 // If we are running, stop.
207 if (current_action == DownloadJob.RUNNING) {
208 current_action = DownloadJob.STOPPED;
209 progress.setString(Dictionary.get("Mirroring.DownloadJob.Download_Stopped"));
210
211 progress.setIndeterminate(false);
212 }
213 }
214 else if(source == log_button) {
215
216 LogDialog dialog = new LogDialog(owner.getLogDocument());
217 dialog.setVisible(true);
218 dialog = null;
219 }
220
221 refresh();
222 }
223
224 /** This method is called when a new download is begun. The
225 * details of the download are updated and a new JProgressBar
226 * assigned to track the download.
227 * @param url The url String of the file that is being downloaded.
228 */
229 public synchronized void addDownload(String url) {
230 current_url = url;
231 file_size = 0;
232 refresh();
233 }
234
235 /** When the download of the current url is completed, this method
236 * is called to enlighten the DownloadProgressBar of this fact.
237 */
238 public synchronized void downloadComplete() {
239 current_url = null;
240 file_count++;
241 if(total_count < (file_count + err_count + warning_count)) {
242 total_count = (file_count + err_count + warning_count);
243 }
244 progress.setValue(progress.getMaximum());
245 //Dictionary.setText(progress, "Mirroring.DownloadJob.Download_Complete");
246 refresh();
247 }
248
249 public synchronized void downloadFailed() {
250 err_count++;
251 if(total_count < (file_count + err_count + warning_count)) {
252 total_count = (file_count + err_count + warning_count);
253 }
254 refresh();
255 }
256
257 public synchronized void downloadWarning() {
258 warning_count++;
259 if(total_count < (file_count + err_count + warning_count)) {
260 total_count = (file_count + err_count + warning_count);
261 }
262 refresh();
263 }
264
265 // need to make these methods synchronized too, as they modify variables
266 // that other synchronized methods work with. And if any methods that modify
267 // such variables were to remain unsynchronized, can end up with race conditions
268 // http://stackoverflow.com/questions/574240/is-there-an-advantage-to-use-a-synchronized-method-instead-of-a-synchronized-blo
269 // "Not only do synchronized methods not lock the whole class, but they don't lock the whole instance either. Unsynchronized methods in the class may still proceed on the instance."
270 // "Only the syncronized methods are locked. If there are fields you use within synced methods that are accessed by unsynced methods, you can run into race conditions."
271 public synchronized void setTotalDownload(int total_download) {
272 total_count = total_download;
273 refresh();
274 }
275
276 public Dimension getPreferredSize() {
277 return PROGRESS_BAR_SIZE;
278 }
279
280 public synchronized void increaseFileCount() {
281 file_count++;
282 refresh();
283 }
284
285 public synchronized void increaseFileCount(int amount) {
286 file_count += amount;
287 refresh();
288 }
289
290 public synchronized void resetFileCount() {
291 file_count = 0;
292 refresh();
293 }
294
295 /** When a mirroring task is first initiated this function is called
296 * to set initial values for the variables if necessary and to
297 * fiddle visual components such as the tool tip etc.
298 * @param reset A Boolean specifying whether the variables should be
299 * reset to zero.
300 */
301 public synchronized void mirrorBegun(boolean reset, boolean simple) {
302 if(reset) {
303 this.file_count = 0;
304 this.file_size = 0;
305 this.total_count = 0;
306 this.total_size = 0;
307 this.err_count = 0;
308 this.warning_count = 0;
309 }
310 current_action = DownloadJob.RUNNING;
311 stop_start_button.setEnabled(true);
312 log_button.setEnabled(true);
313 if(simple) {
314 progress.setIndeterminate(true);
315 progress.setString(Dictionary.get("Mirroring.DownloadJob.Download_Progress"));
316 }
317 }
318
319 /** Once a mirroring task is complete, if the DownloadJob returns from the
320 * native call but the status is still running, then this method
321 * is called to once again tinker with the pretty visual
322 * components.
323 */
324 public synchronized void mirrorComplete() {
325 current_action = DownloadJob.COMPLETE;
326 current_url = null;
327 if(simple) {
328 progress.setIndeterminate(false);
329 }
330 progress.setValue(progress.getMaximum());
331 progress.setString(Dictionary.get("Mirroring.DownloadJob.Download_Complete"));
332 current_status.setText(Dictionary.get("Mirroring.DownloadJob.Download_Complete"));
333 // will display "Download Complete" once an OAI download session has finished running
334
335 stop_start_button.setEnabled(false);
336 this.updateUI();
337 }
338
339 /** When called this method updates the DownloadProgressBar to reflect
340 * the amount of the current file downloaded.
341 */
342 public synchronized void updateProgress(long current, long expected) {
343 file_size = file_size + current;
344 if(!progress.isIndeterminate()) {
345 // If current is zero, then this is the 'precall' before the
346 // downloading actually starts.
347 if(current == 0) {
348 // Remove the old progress bar, then deallocate it.
349 inner_pane.remove(progress);
350 progress = null;
351 if(expected == 0) {
352 // We don't have a content length. This bar will go from 0 to 100 only!
353 progress = new JProgressBar(0, 100);
354 }
355 else {
356 // Assign a new progress bar of length expected content length.
357 progress = new JProgressBar(0, (new Long(expected)).intValue());
358 }
359 progress.setEnabled(true);
360 // Add the new progress bar.
361 inner_pane.add(progress, BorderLayout.CENTER);
362 inner_pane.updateUI();
363 }
364 // Otherwise if expected is not zero move the progress bar and
365 // update percent complete.
366 else if (expected != 0) {
367 progress.setValue((new Long(file_size)).intValue());
368 int p_c = (new Double(progress.getPercentComplete() * 100)).intValue();
369 progress.setString(p_c + "%");
370 progress.setStringPainted(true);
371 }
372 // Finally, in the case we have no content length, we'll instead
373 // write the current number of bytes downloaded again.
374 else {
375 progress.setString(file_size + " b");
376 progress.setStringPainted(true);
377 }
378 }
379 refresh();
380 }
381
382 /** Causes the two labels associated with this DownloadProgressBar object to
383 * update, thus reflecting the progression of the download. This
384 * method is called by any of the other public setter methods in this
385 * class.
386 */
387 private void refresh() {
388 // Refresh the contents of main label.
389 String args1[] = new String[1];
390 args1[0] = initial_url.toString();
391 main_status.setText(Dictionary.get("Mirroring.DownloadJob.Downloading", args1));
392
393 if (current_url != null) {
394 // Refresh the current label contents.
395 String args2[] = new String[1];
396 args2[0] = current_url;
397 current_status.setText(Dictionary.get("Mirroring.DownloadJob.Downloading", args2));
398 }
399 else if (current_action == DownloadJob.STOPPED || current_action == DownloadJob.PAUSED) {
400 current_status.setText(Dictionary.get("Mirroring.DownloadJob.Waiting_User"));
401 }
402 else {
403 current_status.setText(Dictionary.get("Mirroring.DownloadJob.Download_Complete"));
404 }
405
406 // Refresh the contents of results label
407 String args3[] = new String[4];
408 args3[0] = file_count + "";
409 args3[1] = total_count + "";
410 args3[2] = warning_count + "";
411 args3[3] = err_count + "";
412 results_status.setText(Dictionary.get("Mirroring.DownloadJob.Status", args3));
413
414 this.updateUI();
415 }
416
417 static final private Dimension DIALOG_SIZE = new Dimension(640,480);
418
419 private class LogDialog
420 extends JDialog {
421
422 public LogDialog(AppendLineOnlyFileDocument document) {
423 super(Gatherer.g_man, Dictionary.get("Mirroring.DownloadJob.Log_Title"));
424 setSize(DIALOG_SIZE);
425 JPanel content_pane = (JPanel) getContentPane();
426 JTextArea text_area = new JTextArea(document);
427 JButton button = new GLIButton(Dictionary.get("General.Close"));
428 // Connection
429 button.addActionListener(new CloseActionListener());
430 // Layout
431 content_pane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
432 content_pane.setLayout(new BorderLayout());
433 content_pane.add(new JScrollPane(text_area), BorderLayout.CENTER);
434 content_pane.add(button, BorderLayout.SOUTH);
435 }
436
437 private class CloseActionListener
438 implements ActionListener {
439 public void actionPerformed(ActionEvent event) {
440 LogDialog.this.dispose();
441 }
442 }
443 }
444}
Note: See TracBrowser for help on using the repository browser.