001package jmri.jmrit.operations.trains;
002
003import java.io.File;
004import java.text.SimpleDateFormat;
005
006import org.jdom2.*;
007import org.slf4j.Logger;
008import org.slf4j.LoggerFactory;
009
010import jmri.*;
011import jmri.jmrit.operations.OperationsManager;
012import jmri.jmrit.operations.OperationsXml;
013import jmri.jmrit.operations.automation.AutomationManager;
014import jmri.jmrit.operations.setup.Setup;
015import jmri.jmrit.operations.trains.manualtrainbuilder.TrainManualBuildManager;
016import jmri.jmrit.operations.trains.schedules.TrainScheduleManager;
017import jmri.util.FileUtil;
018
019/**
020 * Loads and stores trains using xml files. Also stores various train parameters
021 * managed by the TrainManager.
022 *
023 * @author Daniel Boudreau Copyright (C) 2008, 2010, 2015
024 */
025public class TrainManagerXml extends OperationsXml implements InstanceManagerAutoDefault, InstanceManagerAutoInitialize {
026
027    private boolean fileLoaded = false;
028    private String operationsFileName = "OperationsTrainRoster.xml";// NOI18N
029
030    private static final String BUILD_REPORT_FILE_NAME = Bundle.getMessage("train") + " (";
031    private static final String MANIFEST_FILE_NAME = Bundle.getMessage("train") + " (";
032    private static final String SWITCH_LIST_FILE_NAME = Bundle.getMessage("location") + " (";
033    private static final String BACKUP_BUILD_REPORT_FILE_NAME = Bundle.getMessage("Report") + " " + Bundle.getMessage("train") + " (";
034    private static final String FILE_TYPE_TXT = ").txt"; // NOI18N
035    private static final String FILE_TYPE_CSV = ").csv"; // NOI18N
036
037    // the directories under operations
038    protected static final String BUILD_STATUS = "buildstatus"; // NOI18N
039    protected static final String MANIFESTS = "manifests"; // NOI18N
040    protected static final String SWITCH_LISTS = "switchLists"; // NOI18N
041    public static final String CSV_MANIFESTS = "csvManifests"; // NOI18N
042    public static final String CSV_SWITCH_LISTS = "csvSwitchLists"; // NOI18N
043    protected static final String JSON_MANIFESTS = "jsonManifests"; // NOI18N
044    protected static final String MANIFESTS_BACKUPS = "manifestsBackups"; // NOI18N
045    protected static final String SWITCH_LISTS_BACKUPS = "switchListsBackups"; // NOI18N
046    protected static final String BUILD_STATUS_BACKUPS = "buildStatusBackups"; // NOI18N
047
048    public TrainManagerXml() {
049    }
050
051    @Override
052    public void writeFile(String name) throws java.io.FileNotFoundException, java.io.IOException {
053        log.debug("writeFile {}", name);
054        // This is taken in large part from "Java and XML" page 368
055        File file = findFile(name);
056        if (file == null) {
057            file = new File(name);
058        }
059        // create root element
060        Element root = new Element("operations-config"); // NOI18N
061        Document doc = newDocument(root, dtdLocation + "operations-trains.dtd"); // NOI18N
062
063        // add XSLT processing instruction
064        java.util.Map<String, String> m = new java.util.HashMap<>();
065        m.put("type", "text/xsl"); // NOI18N
066        m.put("href", xsltLocation + "operations-trains.xsl"); // NOI18N
067        ProcessingInstruction p = new ProcessingInstruction("xml-stylesheet", m); // NOI18N
068        doc.addContent(0, p);
069
070        InstanceManager.getDefault(TrainManager.class).store(root);
071        InstanceManager.getDefault(TrainManualBuildManager.class).store(root);
072        InstanceManager.getDefault(TrainScheduleManager.class).store(root);
073        InstanceManager.getDefault(AutomationManager.class).store(root);
074
075        writeXML(file, doc);
076
077        // done - train file now stored, so can't be dirty
078        setDirty(false);
079    }
080
081    /**
082     * Read the contents of a roster XML file into this object. Note that this
083     * does not clear any existing entries.
084     */
085    @Override
086    public void readFile(String name) throws org.jdom2.JDOMException, java.io.IOException {
087
088        // suppress rootFromName(name) warning message by checking to see if file exists
089        if (findFile(name) == null) {
090            log.debug("{} file could not be found", name);
091            fileLoaded = true; // set flag, could be the first time
092            return;
093        }
094        // find root
095        Element root = rootFromName(name);
096        if (root == null) {
097            log.debug("{} file could not be read", name);
098            return;
099        }
100
101        if (!root.getName().equals("operations-config")) {
102            log.warn("OperationsPro train file corrupted");
103            return;
104        }
105
106        InstanceManager.getDefault(TrainManager.class).load(root);
107        InstanceManager.getDefault(TrainManualBuildManager.class).load(root);
108        InstanceManager.getDefault(TrainScheduleManager.class).load(root);
109
110        fileLoaded = true; // set flag trains are loaded
111        InstanceManager.getDefault(AutomationManager.class).load(root);
112
113        log.debug("Trains have been loaded!");
114        
115        for (Train train : InstanceManager.getDefault(TrainManager.class).getTrainsByIdList()) {
116            if (train.isBuilding()) {
117                log.warn("Reseting train ({}), was building when saved", train.getName());
118                train.reset();
119            }
120        }
121
122        setDirty(false); // clear dirty flag
123
124        // loading complete run startup scripts
125        InstanceManager.getDefault(TrainManager.class).runStartUpScripts();
126        InstanceManager.getDefault(AutomationManager.class).runStartupAutomation();
127    }
128
129    public boolean isTrainFileLoaded() {
130        return fileLoaded;
131    }
132
133    /**
134     * Store the train's build report
135     *
136     * @param name Full path name for train build report
137     * @return Build report File.
138     */
139    public File createTrainBuildReportFile(String name) {
140        return createFile(defaultBuildReportFileName(name)); // don't backup
141    }
142
143    public File getTrainBuildReportFile(String name) {
144        File file = new File(defaultBuildReportFileName(name));
145        return file;
146    }
147
148    public String defaultBuildReportFileName(String name) {
149        return OperationsXml.getFileLocation()
150                + OperationsXml.getOperationsDirectoryName()
151                + File.separator
152                + BUILD_STATUS
153                + File.separator
154                + BUILD_REPORT_FILE_NAME
155                + name
156                + FILE_TYPE_TXT; // NOI18N
157    }
158
159    /**
160     * Creates the train's manifest file.
161     *
162     * @param name Full path name for manifest file.
163     * @return Manifest File.
164     */
165    public File createTrainManifestFile(String name) {
166        savePreviousManifestFile(name);
167        return createFile(getDefaultManifestFileName(name)); // don't backup
168    }
169
170    public File getTrainManifestFile(String name) {
171        File file = new File(getDefaultManifestFileName(name));
172        return file;
173    }
174
175    public String getDefaultManifestFileName(String name) {
176        return OperationsXml.getFileLocation()
177                + OperationsXml.getOperationsDirectoryName()
178                + File.separator
179                + MANIFESTS
180                + File.separator
181                + MANIFEST_FILE_NAME
182                + name
183                + FILE_TYPE_TXT;// NOI18N
184    }
185
186    public String getBackupManifestFileName(String name, String lastModified) {
187        return getBackupManifestDirectoryName()
188                + name
189                + File.separator
190                + MANIFEST_FILE_NAME
191                + name
192                + ") "
193                + lastModified
194                + ".txt";// NOI18N
195    }
196
197    public String getBackupManifestDirectoryName() {
198        return OperationsXml.getFileLocation()
199                + OperationsXml.getOperationsDirectoryName()
200                + File.separator
201                + MANIFESTS_BACKUPS
202                + File.separator;
203    }
204
205    public String getBackupManifestDirectoryName(String name) {
206        return getBackupManifestDirectoryName() + name + File.separator;
207    }
208
209    public String getBackupSwitchListFileName(String name, String lastModified) {
210        return getBackupSwitchListDirectoryName()
211                + name
212                + File.separator
213                + SWITCH_LIST_FILE_NAME
214                + name
215                + ") "
216                + lastModified
217                + ".txt";// NOI18N
218    }
219
220    public String getBackupSwitchListDirectoryName() {
221        return OperationsXml.getFileLocation()
222                + OperationsXml.getOperationsDirectoryName()
223                + File.separator
224                + SWITCH_LISTS_BACKUPS
225                + File.separator;
226    }
227
228    public String getBackupSwitchListDirectoryName(String name) {
229        return getBackupSwitchListDirectoryName() + name + File.separator;
230    }
231
232    public String getBackupBuildStatusFileName(String name, String lastModified) {
233        return getBackupBuildStatusDirectoryName()
234                + name
235                + File.separator
236                + BACKUP_BUILD_REPORT_FILE_NAME
237                + name
238                + ") "
239                + lastModified
240                + ".txt";// NOI18N
241    }
242
243    public String getBackupBuildStatusDirectoryName() {
244        return OperationsXml.getFileLocation()
245                + OperationsXml.getOperationsDirectoryName()
246                + File.separator
247                + BUILD_STATUS_BACKUPS
248                + File.separator;
249    }
250
251    public String getBackupBuildStatusDirectoryName(String name) {
252        return getBackupBuildStatusDirectoryName() + name + File.separator;
253    }
254
255    /**
256     * Store the CSV train manifest
257     *
258     * @param name Full path name to CSV train manifest file.
259     * @return Train CSV manifest File.
260     */
261    public File createTrainCsvManifestFile(String name) {
262        return createFile(getDefaultCsvManifestFileName(name)); // don't backup
263    }
264
265    public File getTrainCsvManifestFile(String name) {
266        File file = new File(getDefaultCsvManifestFileName(name));
267        return file;
268    }
269
270    public String getDefaultCsvManifestFileName(String name) {
271        return getDefaultCsvManifestDirectory() + MANIFEST_FILE_NAME + name + FILE_TYPE_CSV;
272    }
273
274    private String getDefaultCsvManifestDirectory() {
275        return OperationsXml.getFileLocation()
276                + OperationsXml.getOperationsDirectoryName()
277                + File.separator
278                + CSV_MANIFESTS
279                + File.separator;
280    }
281
282    public void createDefaultCsvManifestDirectory() {
283        FileUtil.createDirectory(getDefaultCsvManifestDirectory());
284    }
285
286    /**
287     * Store the Json manifest for a train
288     *
289     * @param name file name
290     * @param ext  file extension to use
291     * @return Json manifest File
292     */
293    public File createManifestFile(String name, String ext) {
294        return createFile(getDefaultManifestFileName(name, ext)); // don't backup
295    }
296
297    public File getManifestFile(String name, String ext) {
298        return new File(getDefaultManifestFileName(name, ext));
299    }
300
301    private String getDefaultManifestFileName(String name, String ext) {
302        return InstanceManager.getDefault(OperationsManager.class).getPath(JSON_MANIFESTS) + File.separator + "train-" + name + "." + ext; // NOI18N
303    }
304
305    /**
306     * Store the switch list for a location
307     *
308     * @param name The location's name, to become file name.
309     * @return Switch list File.
310     */
311    public File createSwitchListFile(String name) {
312        savePreviousSwitchListFile(name);
313        return createFile(getDefaultSwitchListName(name)); // don't backup
314    }
315
316    public File getSwitchListFile(String name) {
317        File file = new File(getDefaultSwitchListName(name));
318        return file;
319    }
320
321    public String getDefaultSwitchListName(String name) {
322        return OperationsXml.getFileLocation()
323                + OperationsXml.getOperationsDirectoryName()
324                + File.separator
325                + SWITCH_LISTS
326                + File.separator
327                + SWITCH_LIST_FILE_NAME
328                + name
329                + FILE_TYPE_TXT; // NOI18N
330    }
331
332    /**
333     * Store the CSV switch list for a location
334     *
335     * @param name Location's name, to become file name.
336     * @return CSV switch list File.
337     */
338    public File createCsvSwitchListFile(String name) {
339        return createFile(getDefaultCsvSwitchListFileName(name), true); // create backup
340    }
341
342    public File getCsvSwitchListFile(String name) {
343        File file = new File(getDefaultCsvSwitchListFileName(name));
344        return file;
345    }
346
347    public String getDefaultCsvSwitchListFileName(String name) {
348        return getDefaultCsvSwitchListDirectoryName() + SWITCH_LIST_FILE_NAME + name + FILE_TYPE_CSV;
349    }
350
351    public String getDefaultCsvSwitchListDirectoryName() {
352        return OperationsXml.getFileLocation()
353                + OperationsXml.getOperationsDirectoryName()
354                + File.separator
355                + CSV_SWITCH_LISTS
356                + File.separator;
357    }
358
359    public void createDefaultCsvSwitchListDirectory() {
360        FileUtil.createDirectory(getDefaultCsvSwitchListDirectoryName());
361    }
362
363    @Override
364    public void setOperationsFileName(String name) {
365        operationsFileName = name;
366    }
367
368    @Override
369    public String getOperationsFileName() {
370        return operationsFileName;
371    }
372
373    /**
374     * Save previous manifest file in a separate directory called
375     * manifestBackups. Each train manifest is saved in a unique directory using
376     * the train's name.
377     */
378    private void savePreviousManifestFile(String name) {
379        if (Setup.isSaveTrainManifestsEnabled()) {
380            // create the manifest backup directory
381            createDirectory(getBackupManifestDirectoryName());
382            // now create unique backup directory for each train manifest
383            createDirectory(getBackupManifestDirectoryName(name));
384            // get old manifest file
385            File file = findFile(getDefaultManifestFileName(name));
386            if (file == null) {
387                log.debug("No ({}) manifest file to backup", name);
388            } else if (file.canWrite()) {
389                String lastModified = new SimpleDateFormat("yyyyMMdd-HHmmssSSS").format(file.lastModified()); // NOI18N
390                String backupName = getBackupManifestFileName(name, lastModified); // NOI18N
391                if (file.renameTo(new File(backupName))) {
392                    log.debug("created new manifest backup file {}", backupName);
393                } else {
394                    log.error("could not create manifest backup file {}", backupName);
395                }
396            }
397        }
398    }
399
400    /**
401     * Save previous switch list file in a separate directory called
402     * switchListBackups. Each switch list is saved in a unique directory using
403     * the location's name.
404     */
405    private void savePreviousSwitchListFile(String name) {
406        if (Setup.isSaveTrainManifestsEnabled()) {
407            // create the switch list backup directory
408            createDirectory(getBackupSwitchListDirectoryName());
409            // now create unique backup directory for location
410            createDirectory(getBackupSwitchListDirectoryName(name));
411            // get old switch list file
412            File file = findFile(getDefaultSwitchListName(name));
413            if (file == null) {
414                log.debug("No ({}) switch list file to backup", name);
415            } else if (file.canRead()) {
416                String lastModified = new SimpleDateFormat("yyyyMMdd-HHmmssSSS").format(file.lastModified()); // NOI18N
417                String backupName = getBackupSwitchListFileName(name, lastModified); // NOI18N
418                File backupCopy = new File(backupName);
419                try {
420                FileUtil.copy(file, backupCopy);
421                log.debug("created new switch list backup file {}", backupName);
422                } catch (Exception e) {
423                    log.error("could not create switch list backup file {}", backupName);
424                }
425            }
426        }
427    }
428
429    /**
430     * Save previous train build status file in a separate directory called
431     * BuildStatusBackups. Each build status is saved in a unique directory using
432     * the train's name.
433     * @param name train's name
434     */
435    public void savePreviousBuildStatusFile(String name) {
436        if (Setup.isSaveTrainManifestsEnabled()) {
437            // create the build status backup directory
438            createDirectory(getBackupBuildStatusDirectoryName());
439            // now create unique backup directory for each train
440            createDirectory(getBackupBuildStatusDirectoryName(name));
441            // get old build status file for this train
442            File file = findFile(defaultBuildReportFileName(name));
443            if (file == null) {
444                log.debug("No ({}) train build status file to backup", name);
445            } else if (file.canRead()) {
446                String lastModified = new SimpleDateFormat("yyyyMMdd-HHmmssSSS").format(file.lastModified()); // NOI18N
447                String backupName = getBackupBuildStatusFileName(name, lastModified); // NOI18N
448                File backupCopy = new File(backupName);
449                try {
450                FileUtil.copy(file, backupCopy);
451                log.debug("created new train build status backup file {}", backupName);
452                } catch (Exception e) {
453                    log.error("could not create train build status backup file {}", backupName);
454                }
455            }
456        }
457    }
458
459    public void dispose() {
460    }
461
462    private static final Logger log = LoggerFactory.getLogger(TrainManagerXml.class);
463
464    @Override
465    public void initialize() {
466        load();
467    }
468
469}