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