001package jmri.jmrix.nce.consist;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.io.BufferedReader;
006import java.io.File;
007import java.io.FileReader;
008import java.io.IOException;
009
010import javax.swing.JFileChooser;
011import javax.swing.JPanel;
012
013import jmri.jmrix.nce.NceBinaryCommand;
014import jmri.jmrix.nce.NceMessage;
015import jmri.jmrix.nce.NceReply;
016import jmri.jmrix.nce.NceTrafficController;
017import jmri.util.FileUtil;
018import jmri.util.StringUtil;
019import jmri.util.swing.JmriJOptionPane;
020import jmri.util.swing.TextFilter;
021
022/**
023 * Restores NCE consists from a text file defined by NCE.
024 * <p>
025 * NCE file format:
026 * <p>
027 * :F500 (16 bytes per line, grouped as 8 words with space delimiters) :F510 . .
028 * :FAF0 :0000
029 * <p>
030 * The restore routine checks that each line of the file begins with the
031 * appropriate consist address.
032 *
033 * @author Dan Boudreau Copyright (C) 2007
034 * @author Ken Cameron Copyright (C) 2023
035 */
036public class NceConsistRestore extends Thread implements jmri.jmrix.nce.NceListener {
037
038    private static final int CONSIST_LNTH = 16; // 16 bytes per consist line
039    private static final int REPLY_1 = 1; // reply length of 1 byte expected
040    private int replyLen = 0; // expected byte length
041    private int waiting = 0; // to catch responses not intended for this module
042    private boolean fileValid = false; // used to flag status messages
043
044    javax.swing.JLabel textConsist = new javax.swing.JLabel();
045    javax.swing.JLabel consistNumber = new javax.swing.JLabel();
046
047    private NceTrafficController tc = null;
048
049    public NceConsistRestore(NceTrafficController t) {
050        super();
051        this.tc = t;
052    }
053
054    @Override
055    public void run() {
056
057        // Get file to read from
058        JFileChooser fc = new jmri.util.swing.JmriJFileChooser(FileUtil.getUserFilesPath());
059        fc.addChoosableFileFilter(new TextFilter());
060        int retVal = fc.showOpenDialog(null);
061        if (retVal != JFileChooser.APPROVE_OPTION) {
062            return; // Canceled
063        }
064        if (fc.getSelectedFile() == null) {
065            return; // Canceled
066        }
067        File f = fc.getSelectedFile();
068        try (BufferedReader in = new BufferedReader(new FileReader(f))) {
069            // create a status frame
070            JPanel ps = new JPanel();
071            jmri.util.JmriJFrame fstatus = new jmri.util.JmriJFrame(Bundle.getMessage("NceConsistRestore"));
072            fstatus.setLocationRelativeTo(null);
073            fstatus.setSize(300, 100);
074            fstatus.getContentPane().add(ps);
075
076            ps.add(textConsist);
077            ps.add(consistNumber);
078
079            textConsist.setText(Bundle.getMessage("ConsistLineNumber"));
080            textConsist.setVisible(true);
081            consistNumber.setVisible(true);
082
083            // Now read the file and check the consist address
084            waiting = 0;
085            fileValid = false; // in case we break out early
086            int consistNum = 0; // for user status messages
087            int curConsist = tc.csm.getConsistHeadAddr(); // load the start address of the NCE consist memory
088            byte[] consistData = new byte[CONSIST_LNTH]; // NCE Consist data
089            String line;
090
091            while (true) {
092                line = in.readLine();
093
094                consistNumber.setText(Integer.toString(consistNum++));
095
096                if (line == null) { // while loop does not break out quick enough
097                    log.error("NCE consist file terminator :0000 not found"); // NOI18N
098                    break;
099                }
100                
101                log.debug("consist {}", line);
102                
103                // check that each line contains the NCE memory address of the consist
104                String consistAddr = ":" + Integer.toHexString(curConsist);
105                String[] consistLine = line.split(" ");
106
107                // check for end of consist terminator
108                if (consistLine[0].equalsIgnoreCase(":0000")) {
109                    fileValid = true; // success!
110                    break;
111                }
112
113                if (!consistAddr.equalsIgnoreCase(consistLine[0])) {
114                    log.error("Restore file selected is not a valid backup file"); // NOI18N
115                    log.error("Consist memory address in restore file should be {}, read {}",
116                            consistAddr, consistLine[0]); // NOI18N
117                    break;
118                }
119
120                // consist file found, give the user the choice to continue
121                if (curConsist == tc.csm.getConsistHeadAddr()) {
122                    if (JmriJOptionPane.showConfirmDialog(null,
123                            Bundle.getMessage("RestoreTakesAwhile"),
124                            Bundle.getMessage("NceConsistRestore"),
125                            JmriJOptionPane.YES_NO_OPTION) != JmriJOptionPane.YES_OPTION) {
126                        break;
127                    }
128                }
129
130                fstatus.setVisible(true);
131
132                // now read the entire line from the file and create NCE message
133                for (int i = 0; i < 8; i++) {
134                    int j = i << 1; // i = word index, j = byte index
135
136                    byte b[] = StringUtil.bytesFromHexString(consistLine[i + 1]);
137
138                    consistData[j] = b[0];
139                    consistData[j + 1] = b[1];
140                }
141
142                NceMessage m = writeNceConsistMemory(curConsist, consistData);
143                tc.sendNceMessage(m, this);
144
145                curConsist += CONSIST_LNTH;
146
147                // wait for write to NCE CS to complete
148                if (waiting > 0) {
149                    synchronized (this) {
150                        try {
151                            wait(20000);
152                        } catch (InterruptedException e) {
153                            Thread.currentThread().interrupt(); // retain if needed later
154                        }
155                    }
156                }
157                // failed
158                if (waiting > 0) {
159                    log.error("timeout waiting for reply"); // NOI18N
160                    break;
161                }
162            }
163            in.close();
164
165            // kill status panel
166            fstatus.dispose();
167
168            if (fileValid) {
169                JmriJOptionPane.showMessageDialog(null,
170                        Bundle.getMessage("SuccessfulRestore"),
171                        Bundle.getMessage("NceConsistRestore"),
172                        JmriJOptionPane.INFORMATION_MESSAGE);
173            } else {
174                JmriJOptionPane.showMessageDialog(null,
175                        Bundle.getMessage("RestoreFailed"),
176                        Bundle.getMessage("NceConsistRestore"),
177                        JmriJOptionPane.ERROR_MESSAGE);
178            }
179        } catch (IOException e) {
180            // this is the end of the try-with-resources that opens in.
181        }
182    }
183
184    // writes 16 bytes of NCE consist memory
185    private NceMessage writeNceConsistMemory(int curConsist, byte[] b) {
186
187        replyLen = REPLY_1; // Expect 1 byte response
188        waiting++;
189
190        byte[] bl = NceBinaryCommand.accMemoryWriteN(curConsist, b);
191        NceMessage m = NceMessage.createBinaryMessage(tc, bl, REPLY_1);
192        return m;
193    }
194
195    @Override
196    public void message(NceMessage m) {
197    } // ignore replies
198
199    @SuppressFBWarnings(value = "NN_NAKED_NOTIFY")
200    @Override
201    public void reply(NceReply r) {
202        log.debug("waiting for {} responses", waiting); // NOI18N
203
204        if (waiting <= 0) {
205            log.error("unexpected response"); // NOI18N
206            return;
207        }
208        waiting--;
209        if (r.getNumDataElements() != replyLen) {
210            log.error("reply length incorrect"); // NOI18N
211            return;
212        }
213        if (replyLen == REPLY_1) {
214            // Looking for proper response
215            if (r.getElement(0) != NceMessage.NCE_OKAY) {
216                log.error("reply incorrect"); // NOI18N
217            }
218        }
219
220        // wake up restore thread
221        if (waiting == 0) {
222            synchronized (this) {
223                notify();
224            }
225        }
226    }
227
228    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NceConsistRestore.class);
229
230}