/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2008-2012, Open Source Geospatial Foundation (OSGeo) * (C) 2009-2012, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.internal.setup; import java.io.*; import javax.swing.*; import java.awt.GridBagLayout; import java.awt.GridBagConstraints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.net.URL; import java.net.URLConnection; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.FileTime; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import java.util.concurrent.ExecutionException; import org.geotoolkit.resources.Vocabulary; /** * The panel displaying available data and giving the opportunity to download them. * * @author Martin Desruisseaux (Geomatys) * @module */ @SuppressWarnings("serial") final class DataPanel extends JComponent { /** * Index of items to reports. The {@code COUNT} field is the total number of items. */ static final int EPSG=0, NADCON=1, RGF93=2, COUNT=3; /** * User for fetching localized words. */ final Vocabulary resources; /** * The status about item to reports. */ private final JProgressBar[] status = new JProgressBar[COUNT]; /** * The download buttons. */ private final JButton[] downloads = new JButton[COUNT]; /** * The panel which contains connection parameter to the EPSG database. * Used when the client click on the "Install" button. This must be * set by the caller after construction. */ EPSGPanel epsgPanel; /** * Creates the panel. */ DataPanel(final Vocabulary resources) { this.resources = resources; setLayout(new GridBagLayout()); setBorder(BorderFactory.createEmptyBorder(12, 6, 12, 6)); final GridBagConstraints c = new GridBagConstraints(); c.insets.left = c.insets.right = 6; c.anchor = GridBagConstraints.WEST; c.fill = GridBagConstraints.HORIZONTAL; for (int i=0; i<COUNT; i++) { c.gridy = i; final String label; final short button; switch (i) { case EPSG: { label = resources.getString(Vocabulary.Keys.DataBase_1, "EPSG"); button = Vocabulary.Keys.Install; break; } case NADCON: { label = resources.getString(Vocabulary.Keys.Data_1, "NADCON"); button = Vocabulary.Keys.Download; break; } case RGF93: { label = resources.getString(Vocabulary.Keys.Data_1, "RGF93"); button = Vocabulary.Keys.Download; break; } default: throw new AssertionError(i); } final int type = i; final JProgressBar state = status[i] = new JProgressBar(); state.setStringPainted(true); final JButton download = downloads[i] = new JButton(resources.getString(button)); download.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { new Download(type).execute(); } }); c.insets.top = c.insets.bottom = 0; c.weightx = 0; c.fill = GridBagConstraints.HORIZONTAL; c.gridx = 0; add(new JLabel(label + ':'), c); c.gridx = 2; add(download, c); c.insets.top = c.insets.bottom = 2; c.weightx = 1; c.fill = GridBagConstraints.BOTH; c.gridx = 1; add(state, c); download.setEnabled(false); // TODO } addComponentListener(new LoadWhenShown()); } /** * Determines the presence of data only when first needed, which may never happen. * One advantage of this deferred loading mechanism is to popup the error dialog box * (if they was a SQL error) only if the user actually wanted to see this widget. */ private final class LoadWhenShown extends ComponentAdapter { @Override public void componentShown(final ComponentEvent e) { removeComponentListener(this); refresh(); } } /** * Refreshes all data. The operation is run in a background thread. */ final void refresh() { for (int i=0; i<COUNT; i++) { final JProgressBar state = status[i]; state.setEnabled(false); downloads[i].setEnabled(false); state.setString(resources.getMenuLabel(Vocabulary.Keys.Verifying)); } refresh(RGF93, NADCON, EPSG); // Fastest refrest first. } /** * Refreshes the data for the given items. The items must be one or some of * the {@link #EPSG}, {@link #NADCON} or similar constants. */ final void refresh(final int... items) { new SwingWorker<Object,Integer>() { /** * The exception to shown on failure, or {@code null} if none. */ private Exception failure; /* * Invoked in a background thread for checking the existence of various items. */ @Override protected Object doInBackground() { for (final int item : items) { boolean found = false; switch (item) { case EPSG: { // found = epsgPanel.installer().exists(); break; } case NADCON: { // final Path directory = Installation.NADCON.directory(true); // if (Files.isRegularFile(directory.resolve("conus.las")) && // Files.isRegularFile(directory.resolve("conus.los"))) // { // found = true; // } break; } case RGF93: { // final Path directory = Installation.NTv2.directory(true); // if (Files.isRegularFile(directory.resolve("ntf_r93.gsb"))) { // found = true; // } break; } } publish(found ? item : ~item); } return null; } /** * Processes in the Swing thread the results sent by {@link #doInBackground()}. * The item is positive if presents, negative (all bits inverted) if absent. */ @Override protected void process(final List<Integer> results) { for (int item : results) { final boolean found = (item >= 0); if (!found) item = ~item; final JProgressBar state = status[item]; state.setEnabled(true); state.setString(resources.getString(found ? Vocabulary.Keys.DataArePresent : Vocabulary.Keys.Nodata)); state.setValue(found ? 100 : 0); downloads[item].setEnabled(!found); } } /** * Invoked in the Swing thread when we are done. If we got an exception, show it now. */ @Override protected void done() { if (failure != null) { JOptionPane.showMessageDialog(DataPanel.this, failure.getLocalizedMessage(), failure.getClass().getSimpleName(), JOptionPane.ERROR_MESSAGE); } } }.execute(); } /** * The action to be executed in a background thread when the user * pressed the "Download" button. In the particular case of EPSG * data, this is actually an "Install" action. * * @author Martin Desruisseaux (Geomatys) * @version 3.05 * * @since 3.00 * @module */ private final class Download extends SwingWorker<Object,Object> implements PropertyChangeListener { /** * The button that has been clicked. */ private final int item; /** * Creates a new download action. */ Download(final int item) { this.item = item; status[item].setString(resources.getMenuLabel(Vocabulary.Keys.Downloading)); downloads[item].setEnabled(false); addPropertyChangeListener(this); /* * Some action can not report progress. Set the status bar of those * to the indeterminate state. */ switch (item) { case EPSG: { status[item].setIndeterminate(true); } } } /** * Processes to the download in a background thread. */ @Override protected Object doInBackground() throws Exception { switch (item) { case EPSG: { // TODO epsgPanel.installer().call(); break; } case NADCON: { // final Path directory = Installation.NADCON.validDirectory(true); // unzip(new URL("http://www.ngs.noaa.gov/PC_PROD/NADCON/GRIDS.zip"), directory); break; } case RGF93: { // final Path directory = Installation.NTv2.validDirectory(true); // String url = "http://lambert93.ign.fr/fileadmin/files/" + "ntf_r93.gsb"; // if (ByteOrder.BIG_ENDIAN.equals(ByteOrder.nativeOrder())) { // final int split = url.lastIndexOf('.'); // url = new StringBuilder(url.length() + 2).append(url, 0, split) // .append("_b").append(url, split, url.length()).toString(); // } // copy(new URL(url), directory.resolve("ntf_r93.gsb")); break; } } return null; } /** * Invoked in the Swing thread when the downloading is finished. * Displays an error message if the operation failed. */ @Override protected void done() { status[item].setIndeterminate(false); try { get(); } catch (InterruptedException e) { // Should not happen since we are done. } catch (ExecutionException e) { JOptionPane.showMessageDialog(DataPanel.this, e.getCause().toString(), resources.getString(Vocabulary.Keys.Error), JOptionPane.ERROR_MESSAGE); } refresh(item); } /** * Copies the given stream to the given target file. * * @param in The input stream to copy. The stream will be closed. * @param target The destination file. * @throws IOException If an error occurred while copying the entries. */ private void copy(final URL url, final Path target) throws IOException { final URLConnection connection = url.openConnection(); final int progressDivisor = connection.getContentLength() / 100; try (InputStream in = connection.getInputStream(); OutputStream out = Files.newOutputStream(target)) { int done = 0; final byte[] buffer = new byte[4096]; int n; while ((n = in.read(buffer)) > 0) { out.write(buffer, 0, n); if (progressDivisor > 0) { setProgress(Math.min(100, (done += n) / progressDivisor)); } } } } /** * Unzip the given stream to the given target directory. * * @param url The input stream to unzip. The stream will be closed. * @param target The destination directory. * @throws IOException If an error occurred while unzipping the entries. */ private void unzip(final URL url, final Path target) throws IOException { final URLConnection connection = url.openConnection(); final int progressDivisor = connection.getContentLength() / 100; int done = 0; try (ZipInputStream zin = new ZipInputStream(url.openStream())) { final byte[] buffer = new byte[4096]; ZipEntry entry; while ((entry = zin.getNextEntry()) != null) { final Path file = target.resolve(entry.getName()); try (OutputStream out = Files.newOutputStream(file)) { int n; while ((n = zin.read(buffer)) >= 0) { out.write(buffer, 0, n); if (progressDivisor > 0) { setProgress(Math.min(100, (done += n) / progressDivisor)); } } } final long time = entry.getTime(); if (time >= 0) { Files.setLastModifiedTime(file, FileTime.fromMillis(time)); } zin.closeEntry(); } } } /** * Reports progress. */ @Override public void propertyChange(final PropertyChangeEvent event) { if ("progress".equals(event.getPropertyName())) { status[item].setValue((Integer) event.getNewValue()); } } } }