001    /*
002     * 
003     * $Revision: 13085 $ $Date: 2008-02-06 18:27:24 +0100 (Mi, 06 Feb 2008) $
004     *
005     * This file is part of ***  M y C o R e  ***
006     * See http://www.mycore.de/ for details.
007     *
008     * This program is free software; you can use it, redistribute it
009     * and / or modify it under the terms of the GNU General Public License
010     * (GPL) as published by the Free Software Foundation; either version 2
011     * of the License or (at your option) any later version.
012     *
013     * This program is distributed in the hope that it will be useful, but
014     * WITHOUT ANY WARRANTY; without even the implied warranty of
015     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016     * GNU General Public License for more details.
017     *
018     * You should have received a copy of the GNU General Public License
019     * along with this program, in a file called gpl.txt or license.txt.
020     * If not, write to the Free Software Foundation Inc.,
021     * 59 Temple Place - Suite 330, Boston, MA  02111-1307 USA
022     */
023    
024    package org.mycore.frontend.fileupload;
025    
026    import java.awt.Dimension;
027    import java.awt.Frame;
028    import java.awt.GridBagConstraints;
029    import java.awt.GridBagLayout;
030    import java.awt.Insets;
031    import java.awt.Toolkit;
032    import java.awt.event.ActionEvent;
033    import java.awt.event.ActionListener;
034    import java.awt.event.WindowAdapter;
035    import java.awt.event.WindowEvent;
036    import java.io.File;
037    import java.io.FileInputStream;
038    import java.text.DecimalFormat;
039    
040    import javax.swing.BorderFactory;
041    import javax.swing.JButton;
042    import javax.swing.JDialog;
043    import javax.swing.JLabel;
044    import javax.swing.JOptionPane;
045    import javax.swing.JPanel;
046    import javax.swing.JProgressBar;
047    import javax.swing.SwingUtilities;
048    import javax.swing.UIManager;
049    import javax.swing.WindowConstants;
050    
051    /**
052     * This class implements a Dialog that shows messages and a progress bar while
053     * creating, updating or deleting the derivates of a document. This class is a
054     * singleton, there is only one instance at a time that you get with
055     * getDialog(). The MCRUploadProgressMonitor provides methods to set the next
056     * message that should be displayed and to start, update and finish the progress
057     * bar.
058     * 
059     * @author Frank Lützenkirchen
060     * @author Jens Kupferschmidt
061     * @version $Revision: 13085 $ $Date: 2008-02-06 18:27:24 +0100 (Mi, 06 Feb 2008) $
062     */
063    public class MCRUploadProgressMonitor extends JDialog {
064        protected MCRUploadApplet applet;
065    
066        protected boolean canceled; // if true, upload is canceled
067    
068        protected boolean finished; // if true, upload process is finished
069    
070        protected String filename;
071    
072        protected JLabel lbFilename; // Current filename without path
073    
074        protected long sizeFile; // Size of file currently uploaded
075    
076        protected long bytesFile; // Number of bytes uploaded for current file
077    
078        protected String fileTrans;
079    
080        protected JLabel lbBytesFile; // Bytes current / total for this file
081    
082        protected int fileProg;
083    
084        protected JProgressBar pbFile; // Progress bar for current file
085    
086        protected int numFiles; // Total number of files uploading
087    
088        protected int fileCount; // Number of file currently uploading
089    
090        protected JLabel lbNumFiles; // Number of files transferred / total
091    
092        protected long sizeTotal; // Total size of all files uploading
093    
094        protected long bytesTotal; // Total number of bytes uploaded so far
095    
096        protected JLabel lbBytesTotal; // Bytes transferred / total of all files
097    
098        protected JProgressBar pbTotal; // Progress bar for total upload
099    
100        protected long startTime; // Time the upload startet
101    
102        protected long endTime; // Time the upload finished
103    
104        protected long lastUpdate; // Time the last data update was drawn
105    
106        protected JLabel lbThroughput; // Average number of bytes per second
107    
108        protected JLabel lbTime; // Time elapsed / estimated time remaining
109    
110        protected JButton button; // OK / Cancel button to close window
111    
112        /**
113         * Creates a new upload progress monitor and makes it visible.
114         * 
115         * @param numFiles
116         *            total number of files to be uploaded
117         * @param sizeTotal
118         *            total byte size of all files
119         * @param applet
120         *            the UploadApplet this monitor belongs to
121         */
122        protected MCRUploadProgressMonitor(int numFiles, long sizeTotal, MCRUploadApplet applet) {
123            super((Frame) null, "Dateien werden hochgeladen", true);
124    
125            int width = 600;
126            int height = 300;
127    
128            this.applet = applet;
129            this.canceled = false;
130            this.finished = false;
131            this.filename = "";
132            this.lbFilename = new JLabel(" ");
133            this.sizeFile = 0;
134            this.bytesFile = 0;
135            this.lbBytesFile = new JLabel(" ");
136            this.pbFile = new JProgressBar(0, 1000);
137            this.numFiles = numFiles;
138            this.fileCount = 0;
139            this.lbNumFiles = new JLabel(" ");
140            this.sizeTotal = sizeTotal;
141            this.bytesTotal = 0;
142            this.lbBytesTotal = new JLabel(" ");
143            this.pbTotal = new JProgressBar(0, 1000);
144            this.startTime = System.currentTimeMillis();
145            this.lbThroughput = new JLabel(" ");
146            this.lbTime = new JLabel(" ");
147            this.button = new JButton("Abbrechen");
148    
149            button.setEnabled(true);
150            button.addActionListener(new ActionListener() {
151                public void actionPerformed(ActionEvent evt) {
152                    if (finished)
153                        MCRUploadProgressMonitor.this.close();
154                    else
155                        MCRUploadProgressMonitor.this.cancel();
156                }
157            });
158    
159            pbFile.setStringPainted(true);
160            pbTotal.setStringPainted(true);
161    
162            try {
163                UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
164            } catch (Exception ignored) {
165            }
166    
167            JPanel content = new JPanel();
168            content.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
169            setContentPane(content);
170    
171            GridBagLayout gbl = new GridBagLayout();
172            GridBagConstraints gbc = new GridBagConstraints();
173            content.setLayout(gbl);
174    
175            gbc.anchor = GridBagConstraints.CENTER;
176            gbc.fill = GridBagConstraints.HORIZONTAL;
177            gbc.gridx = 0;
178            gbc.gridy = 0;
179            gbc.insets = new Insets(0, (width / 2) - 10, 0, (width / 2) - 10);
180    
181            JLabel dummy = new JLabel("");
182            gbl.setConstraints(dummy, gbc);
183            content.add(dummy);
184    
185            gbc.anchor = GridBagConstraints.WEST;
186            gbc.fill = GridBagConstraints.NONE;
187            gbc.insets = new Insets(2, 0, 2, 0);
188    
189            gbc.gridy = 1;
190            gbl.setConstraints(lbNumFiles, gbc);
191            content.add(lbNumFiles);
192    
193            gbc.gridy = 2;
194            gbl.setConstraints(lbBytesTotal, gbc);
195            content.add(lbBytesTotal);
196    
197            gbc.gridy = 4;
198            gbl.setConstraints(lbFilename, gbc);
199            content.add(lbFilename);
200    
201            gbc.gridy = 5;
202            gbl.setConstraints(lbBytesFile, gbc);
203            content.add(lbBytesFile);
204    
205            gbc.gridy = 7;
206            gbl.setConstraints(lbThroughput, gbc);
207            content.add(lbThroughput);
208    
209            gbc.gridy = 8;
210            gbl.setConstraints(lbTime, gbc);
211            content.add(lbTime);
212    
213            gbc.anchor = GridBagConstraints.CENTER;
214            gbc.fill = GridBagConstraints.HORIZONTAL;
215            gbc.insets = new Insets(2, 0, 20, 0);
216    
217            gbc.gridy = 3;
218            gbl.setConstraints(pbTotal, gbc);
219            content.add(pbTotal);
220    
221            gbc.gridy = 6;
222            gbl.setConstraints(pbFile, gbc);
223            content.add(pbFile);
224    
225            gbc.anchor = GridBagConstraints.EAST;
226            gbc.fill = GridBagConstraints.NONE;
227            gbc.insets = new Insets(20, 0, 0, 0);
228    
229            gbc.gridy = 9;
230            gbl.setConstraints(button, gbc);
231            content.add(button);
232    
233            setSize(width, height);
234    
235            Dimension size = this.getSize();
236            Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
237            setLocation((screen.width - size.width) / 2, (screen.height - size.height) / 2);
238    
239            setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
240    
241            Runnable starter = new Runnable() {
242                public void run() {
243                    MCRUploadProgressMonitor.this.setVisible(true);
244                    MCRUploadProgressMonitor.this.requestFocus();
245                }
246            };
247    
248            // This thread will update display even if upload gets very slow
249            Thread updater = new Thread(new Runnable() {
250                public void run() {
251    
252                    while (MCRUploadProgressMonitor.this.lastUpdate == 0) {
253                        try {
254                            Thread.sleep(1500);
255                        } catch (InterruptedException ex) {
256                        }
257                    }
258    
259                    while (!(MCRUploadProgressMonitor.this.finished || MCRUploadProgressMonitor.this.canceled)) {
260                        try {
261                            Thread.sleep(500);
262                        } catch (InterruptedException ex) {
263                        }
264    
265                        if (MCRUploadProgressMonitor.this.finished || MCRUploadProgressMonitor.this.canceled)
266                            return;
267    
268                        long now = System.currentTimeMillis();
269                        if (now - MCRUploadProgressMonitor.this.lastUpdate > 900)
270                            MCRUploadProgressMonitor.this.update();
271                    }
272                }
273            });
274    
275            SwingUtilities.invokeLater(starter);
276            updater.start();
277        }
278    
279        protected void cancel() {
280            canceled = true;
281            endTime = System.currentTimeMillis();
282            end();
283        }
284    
285        protected void close() {
286            button.setEnabled(false);
287            setVisible(false);
288            dispose();
289    
290            if (applet != null)
291                applet.returnToURL();
292        }
293    
294        protected DecimalFormat df = new DecimalFormat("00");
295    
296        protected String formatTime(int sec) {
297            if (sec < 60) {
298                return sec + " Sek.";
299            }
300    
301            int min = sec / 60;
302            sec = sec - (min * 60);
303    
304            if (min < 60) {
305                return min + ":" + df.format(sec) + " Min.";
306            }
307    
308            int hh = min / 60;
309            min = min - (hh * 60);
310    
311            return hh + ":" + df.format(min) + ":" + df.format(sec) + " Std.";
312        }
313    
314        protected String formatSize(long bytes) {
315            if (bytes < 1024) {
316                return bytes + " Byte";
317            }
318    
319            long kb = bytes / 1024L;
320            bytes = bytes - (kb * 1024L);
321    
322            long nk = Math.round((bytes / 1024d) * 100d);
323    
324            if (kb < 1024) {
325                return kb + "," + df.format(nk) + " KB";
326            }
327    
328            long mb = kb / 1024L;
329            kb = kb - (mb * 1024L);
330            nk = Math.round((kb / 1024d) * 100d);
331    
332            return mb + "," + df.format(nk) + " MB";
333        }
334    
335        protected void update() {
336            final int permilleFile;
337    
338            if (sizeFile > 0) {
339                permilleFile = (int) (((double) bytesFile / (double) sizeFile) * 1000);
340            } else {
341                permilleFile = 0;
342            }
343    
344            final int permilleTotal;
345    
346            if (sizeTotal > 0) {
347                permilleTotal = (int) (((double) bytesTotal / (double) sizeTotal) * 1000);
348            } else {
349                permilleTotal = 0;
350            }
351    
352            final String sFile = formatSize(bytesFile) + " von " + formatSize(sizeFile) + " übertragen";
353    
354            lastUpdate = System.currentTimeMillis();
355            long now = (endTime > 0 ? endTime : lastUpdate);
356            final int sec = Math.max((int) (now - startTime) / 1000, 1);
357    
358            long bytesPerMilli = 1;
359            if (now > startTime)
360                bytesPerMilli = Math.max(1, bytesTotal / (now - startTime));
361            int rest = (int) ((sizeTotal - bytesTotal) / bytesPerMilli / 1000);
362    
363            int throughput = Math.round(bytesTotal / sec);
364    
365            final String sTotal = formatSize(bytesTotal) + " von " + formatSize(sizeTotal) + " insgesamt übertragen";
366            final String sThrough = formatSize(throughput) + " pro Sekunde";
367            final String sName;
368    
369            if (canceled) {
370                sName = "ABGEBROCHEN: Datei " + filename;
371            } else {
372                sName = "Datei " + filename;
373            }
374    
375            final String sCounter;
376            final String sTime;
377    
378            if (canceled) {
379                sCounter = "ABGEBROCHEN: " + fileCount + " von " + numFiles + " Dateien übertragen";
380                sTime = "Übertragung abgebrochen, Gesamtdauer " + formatTime(sec);
381            } else if (bytesTotal < sizeTotal) {
382                sCounter = "Übertrage Datei " + fileCount + " von " + numFiles;
383                sTime = "Dauer bisher " + formatTime(sec) + " / geschätzt noch " + formatTime(rest);
384            } else {
385                sCounter = "Alle " + numFiles + " Dateien übertragen";
386                sTime = "Übertragung beendet, Gesamtdauer " + formatTime(sec);
387            }
388    
389            Runnable updater = new Runnable() {
390                public void run() {
391                    lbFilename.setText(sName);
392                    pbFile.setValue(permilleFile);
393                    lbBytesFile.setText(sFile);
394                    lbNumFiles.setText(sCounter);
395                    lbBytesTotal.setText(sTotal);
396                    pbTotal.setValue(permilleTotal);
397                    lbThroughput.setText(sThrough);
398                    lbTime.setText(sTime);
399    
400                    MCRUploadProgressMonitor.this.repaint();
401                }
402            };
403    
404            SwingUtilities.invokeLater(updater);
405        }
406    
407        /**
408         * Informs the monitor that the next file is being uploaded.
409         * 
410         * @param name
411         *            the name of the file without path
412         * @param size
413         *            the byte size of the file
414         */
415        public void startFile(String name, long size) {
416            filename = name;
417            sizeFile = size;
418            bytesFile = 0;
419            fileCount++;
420            update();
421        }
422    
423        /**
424         * Informs the monitor that a number of bytes was read for the current file.
425         * This is the single number of bytes read in this iteration, not the total
426         * number of files read from this file.
427         * 
428         * @param bytesToAdd
429         *            the single number of bytes transferred in this step
430         */
431        public void progressFile(long bytesToAdd) {
432            bytesFile += bytesToAdd;
433            bytesTotal += bytesToAdd;
434            update();
435        }
436    
437        /**
438         * Informs the monitor that the upload of the current file is finished.
439         */
440        public void endFile() {
441            bytesFile = sizeFile;
442            update();
443        }
444    
445        /**
446         * Informs the monitor that uploading of all files is finished.
447         */
448        public void finish() {
449            bytesFile = sizeFile;
450            bytesTotal = sizeTotal;
451            fileCount = numFiles;
452            finished = true;
453            endTime = System.currentTimeMillis();
454            end();
455        }
456    
457        /**
458         * Informs the monitor that uploading is canceled because some error
459         * occured.
460         */
461        public void cancel(Exception ex) {
462            canceled = true;
463            finished = true;
464            endTime = System.currentTimeMillis();
465            MCRUploadProgressMonitor.reportException(ex);
466            end();
467        }
468    
469        /**
470         * Shows a message dialog that displays an exception that occured.
471         * 
472         * @param ex
473         *            the Exception to be shown
474         */
475        public static void reportException(Exception ex) {
476            String title = "Fehler bei der Übertragung";
477            StringBuffer msg = new StringBuffer();
478            msg.append(title).append(":\n");
479            msg.append(ex.getClass().getName()).append("\n");
480            msg.append(ex.getLocalizedMessage()).append("\n");
481    
482            if (ex instanceof MCRUploadException) {
483                MCRUploadException uex = (MCRUploadException) ex;
484                msg.append("Fehlermeldung des Servers:\n");
485                msg.append(uex.getServerSideClassName()).append("\n");
486                msg.append(uex.getMessage()).append("\n");
487                msg.append(uex.getServerSideStackTrace()).append("\n");
488            }
489    
490            JOptionPane.showMessageDialog(null, msg, title, JOptionPane.ERROR_MESSAGE);
491        }
492    
493        protected void end() {
494            finished = true;
495            button.setText("Schliessen");
496            button.setEnabled(true);
497    
498            addWindowListener(new WindowAdapter() {
499                public void windowClosing(WindowEvent e) {
500                    MCRUploadProgressMonitor.this.close();
501                }
502            });
503            update();
504        }
505    
506        public boolean isCanceled() {
507            return canceled;
508        }
509    
510        /**
511         * A small test that reads all files in a given directory.
512         * 
513         * @param args
514         *            first arg is the path of the directory to read from
515         * @throws Exception
516         *             if anything goes wrong
517         */
518        public static void main(String[] args) throws Exception {
519            File dir = new File(args[0]);
520            File[] files = dir.listFiles();
521    
522            int numFiles = files.length;
523            long sizeTotal = 0;
524    
525            for (int i = 0; i < numFiles; i++)
526                sizeTotal += files[i].length();
527    
528            MCRUploadProgressMonitor upm = new MCRUploadProgressMonitor(numFiles, sizeTotal, null);
529    
530            for (int i = 0; i < numFiles; i++) {
531                if (upm.isCanceled())
532                    break;
533                upm.startFile(files[i].getName(), files[i].length());
534    
535                FileInputStream fin = new FileInputStream(files[i]);
536                byte[] buffer = new byte[65536];
537                long num = 0;
538    
539                if (upm.isCanceled())
540                    break;
541                while ((num = fin.read(buffer, 0, buffer.length)) != -1) {
542                    // Simulate a read error and the following cancel() invocation
543                    // if( i == 2 ) { upm.cancel( new java.io.IOException( "Simulierter Lesefehler" ) ); return; }
544    
545                    if (upm.isCanceled())
546                        break;
547                    upm.progressFile(num);
548                    Thread.sleep(300); // Simulate network transfer time
549                    if (upm.isCanceled())
550                        break;
551                }
552    
553                if (!upm.isCanceled())
554                    upm.endFile();
555            }
556    
557            if (!upm.isCanceled())
558                upm.finish();
559        }
560    }