001package jmri.jmrit.decoderdefn;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import java.awt.event.ActionEvent;
005import java.io.File;
006import java.io.FileNotFoundException;
007import java.io.FileOutputStream;
008import java.io.IOException;
009import java.io.InputStream;
010import java.io.OutputStream;
011import java.net.URI;
012import java.net.URL;
013import javax.swing.Icon;
014import javax.swing.JPanel;
015import jmri.jmrit.XmlFile;
016import jmri.util.FileUtil;
017import jmri.util.swing.JmriAbstractAction;
018import jmri.util.swing.WindowInterface;
019import jmri.util.swing.JmriJOptionPane;
020
021import org.jdom2.Element;
022
023/**
024 * Install decoder definition from URL
025 *
026 * @author Bob Jacobsen Copyright (C) 2008
027 * @see jmri.jmrit.XmlFile
028 */
029public class InstallDecoderURLAction extends JmriAbstractAction {
030
031    public InstallDecoderURLAction(String s, WindowInterface wi) {
032        super(s, wi);
033    }
034
035    public InstallDecoderURLAction(String s, Icon i, WindowInterface wi) {
036        super(s, i, wi);
037    }
038
039    public InstallDecoderURLAction(String s) {
040        super(s);
041    }
042
043    public InstallDecoderURLAction(String s, JPanel who) {
044        super(s);
045    }
046
047    JPanel _who;
048
049    URL pickURL(JPanel who) {
050        // show input dialog
051        String urlname = JmriJOptionPane.showInputDialog(who, Bundle.getMessage("InputURL"),"");
052        if ( urlname == null || urlname.isBlank() ){
053            JmriJOptionPane.showMessageDialog(who, Bundle.getMessage("NoURL"));
054            return null;
055        }
056        try {
057            return new URI(urlname).toURL();
058        } catch (java.net.MalformedURLException | java.net.URISyntaxException e) {
059            JmriJOptionPane.showMessageDialog(who, Bundle.getMessage("MalformedURL"));
060        }
061        return null;
062    }
063
064    @Override
065    public void actionPerformed(ActionEvent e) {
066
067        // get the input URL
068        URL url = pickURL(_who);
069        if (url == null) {
070            return;
071        }
072
073        if (checkFile(url, _who)) {
074            // OK, do the actual copy
075            copyAndInstall(url, _who);
076        }
077    }
078
079    @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value="SLF4J_SIGN_ONLY_FORMAT",
080                                                        justification="Specific log message format")
081    void copyAndInstall(URL from, JPanel who) {
082        log.debug("[{}]", from.getFile());
083
084        // get output name
085        File temp = new File(from.getFile());
086
087        log.debug("File [{}]", temp.toString());
088
089        // ensure directories exist
090        FileUtil.createDirectory(FileUtil.getUserFilesPath() + "decoders");
091
092        // output file
093        File toFile = new File(FileUtil.getUserFilesPath() + "decoders" + File.separator + temp.getName());
094        log.debug("file [{}]", toFile.toString());
095
096        // first do the copy, but not if source and output files are the same
097        if (!temp.toString().equals(toFile.toString())) {
098            if (!copyfile(from, toFile, _who)) {
099                return;
100            }
101        } else {
102            // write a log entry
103            log.info("Source and destination files identical - file not copied");
104            log.info("  source file: {}", temp.toString());
105            log.info("  destination: {}", toFile.toString());
106        }
107
108        // and rebuild index
109        DecoderIndexFile.forceCreationOfNewIndex();
110
111        // Done OK
112        JmriJOptionPane.showMessageDialog(who, Bundle.getMessage("CompleteOK"));
113    }
114
115    @SuppressFBWarnings(value = "OBL_UNSATISFIED_OBLIGATION", justification = "Looks like false positive")
116    boolean copyfile(URL from, File toFile, JPanel who) {
117        InputStream in = null;
118        OutputStream out = null;
119        try {
120            in = from.openConnection().getInputStream();
121
122            // open for overwrite
123            out = new FileOutputStream(toFile);
124
125            byte[] buf = new byte[1024];
126            int len;
127            while ((len = in.read(buf)) > 0) {
128                out.write(buf, 0, len);
129            }
130            // done - finally cleans up
131        } catch (FileNotFoundException ex) {
132            log.debug("unexpected", ex);
133            JmriJOptionPane.showMessageDialog(who, Bundle.getMessage("CopyError1"));
134            return false;
135        } catch (IOException e) {
136            log.debug("IO Exception ", e);
137            JmriJOptionPane.showMessageDialog(who, Bundle.getMessage("CopyError2"));
138            return false;
139        } finally {
140            try {
141                if (in != null) {
142                    in.close();
143                }
144            } catch (IOException e1) {
145                log.error("exception closing in stream", e1);
146            }
147            try {
148                if (out != null) {
149                    out.close();
150                }
151            } catch (IOException e2) {
152                log.error("exception closing out stream", e2);
153            }
154        }
155
156        return true;
157    }
158
159    boolean checkFile(URL url, JPanel who) {
160        // read the definition to check it (later should be outside this thread?)
161        try {
162            Element root = readFile(url);
163            if (log.isDebugEnabled()) {
164                log.debug("parsing complete");
165            }
166
167            // check to see if there's a decoder element
168            if (root.getChild("decoder") == null) {
169                JmriJOptionPane.showMessageDialog(who, Bundle.getMessage("WrongContent"));
170                return false;
171            }
172            return true;
173
174        } catch (java.io.IOException | org.jdom2.JDOMException ex) {
175            log.debug("Exception checking file", ex);
176            JmriJOptionPane.showMessageDialog(who, Bundle.getMessage("ParseError"));
177            return false;
178        }
179    }
180
181    /**
182     * Read and verify an XML file.
183     *
184     * @param url the URL of the file
185     * @return the root element in the file
186     * @throws org.jdom2.JDOMException if the file cannot be parsed
187     * @throws java.io.IOException     if the file cannot be read
188     */
189    Element readFile(URL url) throws org.jdom2.JDOMException, java.io.IOException {
190        XmlFile xf = new XmlFile() {
191        };   // odd syntax is due to XmlFile being abstract
192
193        return xf.rootFromURL(url);
194
195    }
196
197    // never invoked, because we overrode actionPerformed above
198    @Override
199    public jmri.util.swing.JmriPanel makePanel() {
200        throw new IllegalArgumentException("Should not be invoked");
201    }
202
203    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(InstallDecoderURLAction.class);
204
205}