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