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        log.debug("Trains have been loaded!");
111        
112        for (Train train : InstanceManager.getDefault(TrainManager.class).getTrainsByIdList()) {
113            if (train.getStatusCode() == Train.CODE_BUILDING) {
114                log.warn("Reseting train ({}), was building when saved", train.getName());
115                train.reset();
116            }
117        }
118
119        setDirty(false); // clear dirty flag
120
121        // loading complete run startup scripts
122        InstanceManager.getDefault(TrainManager.class).runStartUpScripts();
123        InstanceManager.getDefault(AutomationManager.class).runStartupAutomation();
124    }
125
126    public boolean isTrainFileLoaded() {
127        return fileLoaded;
128    }
129
130    /**
131     * Store the train's build report
132     *
133     * @param name Full path name for train build report
134     * @return Build report File.
135     */
136    public File createTrainBuildReportFile(String name) {
137        return createFile(defaultBuildReportFileName(name)); // don't backup
138    }
139
140    public File getTrainBuildReportFile(String name) {
141        File file = new File(defaultBuildReportFileName(name));
142        return file;
143    }
144
145    public String defaultBuildReportFileName(String name) {
146        return OperationsXml.getFileLocation()
147                + OperationsXml.getOperationsDirectoryName()
148                + File.separator
149                + BUILD_STATUS
150                + File.separator
151                + BUILD_REPORT_FILE_NAME
152                + name
153                + FILE_TYPE_TXT; // NOI18N
154    }
155
156    /**
157     * Creates the train's manifest file.
158     *
159     * @param name Full path name for manifest file.
160     * @return Manifest File.
161     */
162    public File createTrainManifestFile(String name) {
163        savePreviousManifestFile(name);
164        return createFile(getDefaultManifestFileName(name)); // don't backup
165    }
166
167    public File getTrainManifestFile(String name) {
168        File file = new File(getDefaultManifestFileName(name));
169        return file;
170    }
171
172    public String getDefaultManifestFileName(String name) {
173        return OperationsXml.getFileLocation()
174                + OperationsXml.getOperationsDirectoryName()
175                + File.separator
176                + MANIFESTS
177                + File.separator
178                + MANIFEST_FILE_NAME
179                + name
180                + FILE_TYPE_TXT;// NOI18N
181    }
182
183    public String getBackupManifestFileName(String name, String lastModified) {
184        return getBackupManifestDirectoryName()
185                + name
186                + File.separator
187                + MANIFEST_FILE_NAME
188                + name
189                + ") "
190                + lastModified
191                + ".txt";// NOI18N
192    }
193
194    public String getBackupManifestDirectoryName() {
195        return OperationsXml.getFileLocation()
196                + OperationsXml.getOperationsDirectoryName()
197                + File.separator
198                + MANIFESTS_BACKUPS
199                + File.separator;
200    }
201
202    public String getBackupManifestDirectoryName(String name) {
203        return getBackupManifestDirectoryName() + name + File.separator;
204    }
205
206    public String getBackupSwitchListFileName(String name, String lastModified) {
207        return getBackupSwitchListDirectoryName()
208                + name
209                + File.separator
210                + SWITCH_LIST_FILE_NAME
211                + name
212                + ") "
213                + lastModified
214                + ".txt";// NOI18N
215    }
216
217    public String getBackupSwitchListDirectoryName() {
218        return OperationsXml.getFileLocation()
219                + OperationsXml.getOperationsDirectoryName()
220                + File.separator
221                + SWITCH_LISTS_BACKUPS
222                + File.separator;
223    }
224
225    public String getBackupSwitchListDirectoryName(String name) {
226        return getBackupSwitchListDirectoryName() + name + File.separator;
227    }
228
229    public String getBackupBuildStatusFileName(String name, String lastModified) {
230        return getBackupBuildStatusDirectoryName()
231                + name
232                + File.separator
233                + BACKUP_BUILD_REPORT_FILE_NAME
234                + name
235                + ") "
236                + lastModified
237                + ".txt";// NOI18N
238    }
239
240    public String getBackupBuildStatusDirectoryName() {
241        return OperationsXml.getFileLocation()
242                + OperationsXml.getOperationsDirectoryName()
243                + File.separator
244                + BUILD_STATUS_BACKUPS
245                + File.separator;
246    }
247
248    public String getBackupBuildStatusDirectoryName(String name) {
249        return getBackupBuildStatusDirectoryName() + name + File.separator;
250    }
251
252    /**
253     * Store the CSV train manifest
254     *
255     * @param name Full path name to CSV train manifest file.
256     * @return Train CSV manifest File.
257     */
258    public File createTrainCsvManifestFile(String name) {
259        return createFile(getDefaultCsvManifestFileName(name)); // don't backup
260    }
261
262    public File getTrainCsvManifestFile(String name) {
263        File file = new File(getDefaultCsvManifestFileName(name));
264        return file;
265    }
266
267    public String getDefaultCsvManifestFileName(String name) {
268        return getDefaultCsvManifestDirectory() + MANIFEST_FILE_NAME + name + FILE_TYPE_CSV;
269    }
270
271    private String getDefaultCsvManifestDirectory() {
272        return OperationsXml.getFileLocation()
273                + OperationsXml.getOperationsDirectoryName()
274                + File.separator
275                + CSV_MANIFESTS
276                + File.separator;
277    }
278
279    public void createDefaultCsvManifestDirectory() {
280        FileUtil.createDirectory(getDefaultCsvManifestDirectory());
281    }
282
283    /**
284     * Store the Json manifest for a train
285     *
286     * @param name file name
287     * @param ext  file extension to use
288     * @return Json manifest File
289     */
290    public File createManifestFile(String name, String ext) {
291        return createFile(getDefaultManifestFileName(name, ext)); // don't backup
292    }
293
294    public File getManifestFile(String name, String ext) {
295        return new File(getDefaultManifestFileName(name, ext));
296    }
297
298    private String getDefaultManifestFileName(String name, String ext) {
299        return InstanceManager.getDefault(OperationsManager.class).getPath(JSON_MANIFESTS) + File.separator + "train-" + name + "." + ext; // NOI18N
300    }
301
302    /**
303     * Store the switch list for a location
304     *
305     * @param name The location's name, to become file name.
306     * @return Switch list File.
307     */
308    public File createSwitchListFile(String name) {
309        savePreviousSwitchListFile(name);
310        return createFile(getDefaultSwitchListName(name)); // don't backup
311    }
312
313    public File getSwitchListFile(String name) {
314        File file = new File(getDefaultSwitchListName(name));
315        return file;
316    }
317
318    public String getDefaultSwitchListName(String name) {
319        return OperationsXml.getFileLocation()
320                + OperationsXml.getOperationsDirectoryName()
321                + File.separator
322                + SWITCH_LISTS
323                + File.separator
324                + SWITCH_LIST_FILE_NAME
325                + name
326                + FILE_TYPE_TXT; // NOI18N
327    }
328
329    /**
330     * Store the CSV switch list for a location
331     *
332     * @param name Location's name, to become file name.
333     * @return CSV switch list File.
334     */
335    public File createCsvSwitchListFile(String name) {
336        return createFile(getDefaultCsvSwitchListFileName(name), true); // create backup
337    }
338
339    public File getCsvSwitchListFile(String name) {
340        File file = new File(getDefaultCsvSwitchListFileName(name));
341        return file;
342    }
343
344    public String getDefaultCsvSwitchListFileName(String name) {
345        return getDefaultCsvSwitchListDirectoryName() + SWITCH_LIST_FILE_NAME + name + FILE_TYPE_CSV;
346    }
347
348    public String getDefaultCsvSwitchListDirectoryName() {
349        return OperationsXml.getFileLocation()
350                + OperationsXml.getOperationsDirectoryName()
351                + File.separator
352                + CSV_SWITCH_LISTS
353                + File.separator;
354    }
355
356    public void createDefaultCsvSwitchListDirectory() {
357        FileUtil.createDirectory(getDefaultCsvSwitchListDirectoryName());
358    }
359
360    @Override
361    public void setOperationsFileName(String name) {
362        operationsFileName = name;
363    }
364
365    @Override
366    public String getOperationsFileName() {
367        return operationsFileName;
368    }
369
370    /**
371     * Save previous manifest file in a separate directory called
372     * manifestBackups. Each train manifest is saved in a unique directory using
373     * the train's name.
374     */
375    private void savePreviousManifestFile(String name) {
376        if (Setup.isSaveTrainManifestsEnabled()) {
377            // create the manifest backup directory
378            createDirectory(getBackupManifestDirectoryName());
379            // now create unique backup directory for each train manifest
380            createDirectory(getBackupManifestDirectoryName(name));
381            // get old manifest file
382            File file = findFile(getDefaultManifestFileName(name));
383            if (file == null) {
384                log.debug("No ({}) manifest file to backup", name);
385            } else if (file.canWrite()) {
386                String lastModified = new SimpleDateFormat("yyyyMMdd-HHmmssSSS").format(file.lastModified()); // NOI18N
387                String backupName = getBackupManifestFileName(name, lastModified); // NOI18N
388                if (file.renameTo(new File(backupName))) {
389                    log.debug("created new manifest backup file {}", backupName);
390                } else {
391                    log.error("could not create manifest backup file {}", backupName);
392                }
393            }
394        }
395    }
396
397    /**
398     * Save previous switch list file in a separate directory called
399     * switchListBackups. Each switch list is saved in a unique directory using
400     * the location's name.
401     */
402    private void savePreviousSwitchListFile(String name) {
403        if (Setup.isSaveTrainManifestsEnabled()) {
404            // create the switch list backup directory
405            createDirectory(getBackupSwitchListDirectoryName());
406            // now create unique backup directory for location
407            createDirectory(getBackupSwitchListDirectoryName(name));
408            // get old switch list file
409            File file = findFile(getDefaultSwitchListName(name));
410            if (file == null) {
411                log.debug("No ({}) switch list file to backup", name);
412            } else if (file.canRead()) {
413                String lastModified = new SimpleDateFormat("yyyyMMdd-HHmmssSSS").format(file.lastModified()); // NOI18N
414                String backupName = getBackupSwitchListFileName(name, lastModified); // NOI18N
415                File backupCopy = new File(backupName);
416                try {
417                FileUtil.copy(file, backupCopy);
418                log.debug("created new switch list backup file {}", backupName);
419                } catch (Exception e) {
420                    log.error("could not create switch list backup file {}", backupName);
421                }
422            }
423        }
424    }
425
426    /**
427     * Save previous train build status file in a separate directory called
428     * BuildStatusBackups. Each build status is saved in a unique directory using
429     * the train's name.
430     * @param name train's name
431     */
432    public void savePreviousBuildStatusFile(String name) {
433        if (Setup.isSaveTrainManifestsEnabled()) {
434            // create the build status backup directory
435            createDirectory(getBackupBuildStatusDirectoryName());
436            // now create unique backup directory for each train
437            createDirectory(getBackupBuildStatusDirectoryName(name));
438            // get old build status file for this train
439            File file = findFile(defaultBuildReportFileName(name));
440            if (file == null) {
441                log.debug("No ({}) train build status file to backup", name);
442            } else if (file.canRead()) {
443                String lastModified = new SimpleDateFormat("yyyyMMdd-HHmmssSSS").format(file.lastModified()); // NOI18N
444                String backupName = getBackupBuildStatusFileName(name, lastModified); // NOI18N
445                File backupCopy = new File(backupName);
446                try {
447                FileUtil.copy(file, backupCopy);
448                log.debug("created new train build status backup file {}", backupName);
449                } catch (Exception e) {
450                    log.error("could not create train build status backup file {}", backupName);
451                }
452            }
453        }
454    }
455
456    public void dispose() {
457    }
458
459    private final static Logger log = LoggerFactory.getLogger(TrainManagerXml.class);
460
461    @Override
462    public void initialize() {
463        load();
464    }
465
466}