001package jmri.jmrit.operations.trains;
002
003import java.beans.PropertyChangeEvent;
004import java.beans.PropertyChangeListener;
005import java.io.*;
006import java.nio.charset.StandardCharsets;
007import java.text.ParseException;
008import java.text.SimpleDateFormat;
009import java.util.*;
010
011import org.apache.commons.csv.CSVFormat;
012import org.apache.commons.csv.CSVPrinter;
013import org.slf4j.Logger;
014import org.slf4j.LoggerFactory;
015
016import jmri.InstanceManager;
017import jmri.InstanceManagerAutoDefault;
018import jmri.jmrit.XmlFile;
019import jmri.jmrit.operations.setup.*;
020
021/**
022 * Logs train movements and status to a file.
023 *
024 * @author Daniel Boudreau Copyright (C) 2010, 2013
025 */
026public class TrainLogger extends XmlFile implements InstanceManagerAutoDefault, PropertyChangeListener {
027
028    File _fileLogger;
029    private boolean _trainLog = false; // when true logging train movements
030
031    public TrainLogger() {
032    }
033
034    public void enableTrainLogging(boolean enable) {
035        if (enable) {
036            addTrainListeners();
037        } else {
038            removeTrainListeners();
039        }
040    }
041
042    private void createFile() {
043        if (!Setup.isTrainLoggerEnabled()) {
044            return;
045        }
046        if (_fileLogger != null) {
047            return; // log file has already been created
048        } // create the logging file for this session
049        try {
050            if (!checkFile(getFullLoggerFileName())) {
051                // The file/directory does not exist, create it before writing
052                _fileLogger = new java.io.File(getFullLoggerFileName());
053                File parentDir = _fileLogger.getParentFile();
054                if (!parentDir.exists()) {
055                    if (!parentDir.mkdirs()) {
056                        log.error("logger directory not created");
057                    }
058                }
059                if (_fileLogger.createNewFile()) {
060                    log.debug("new file created");
061                    // add header
062                    fileOut(getHeader());
063                }
064            } else {
065                _fileLogger = new java.io.File(getFullLoggerFileName());
066            }
067        } catch (Exception e) {
068            log.error("Exception while making logging directory", e);
069        }
070
071    }
072
073    private void store(Train train) {
074        // create train file if needed
075        createFile();
076        // Note that train status can contain a comma
077        List<Object> line = Arrays.asList(new Object[]{train.getName(),
078                train.getDescription(),
079                train.getCurrentLocationName(),
080                train.getNextLocationName(),
081                train.getStatus(),
082                train.getBuildFailedMessage(),
083                getTime()});
084        fileOut(line);
085    }
086
087    private List<Object> getHeader() {
088        return Arrays.asList(new Object[]{Bundle.getMessage("Name"),
089            Bundle.getMessage("Description"),
090            Bundle.getMessage("Current"),
091            Bundle.getMessage("NextLocation"),
092            Bundle.getMessage("Status"),
093            Bundle.getMessage("BuildMessages"),
094            Bundle.getMessage("DateAndTime")});
095    }
096
097    /*
098     * Appends one line to file.
099     */
100    private void fileOut(List<Object> line) {
101        if (_fileLogger == null) {
102            log.error("Log file doesn't exist");
103            return;
104        }
105
106        // FileOutputStream is set to append
107        try (CSVPrinter fileOut = new CSVPrinter(new BufferedWriter(new OutputStreamWriter(
108                new FileOutputStream(_fileLogger, true), StandardCharsets.UTF_8)), CSVFormat.DEFAULT)) {
109            log.debug("Log: {}", line);
110            fileOut.printRecord(line);
111            fileOut.flush();
112            fileOut.close();
113        } catch (IOException e) {
114            log.error("Exception while opening log file: {}", e.getLocalizedMessage());
115        }
116    }
117
118    private void addTrainListeners() {
119        if (Setup.isTrainLoggerEnabled() && !_trainLog) {
120            log.debug("Train Logger adding train listerners");
121            _trainLog = true;
122            List<Train> trains = InstanceManager.getDefault(TrainManager.class).getTrainsByIdList();
123            trains.forEach(train -> train.addPropertyChangeListener(this));
124            // listen for new trains being added
125            InstanceManager.getDefault(TrainManager.class).addPropertyChangeListener(this);
126        }
127    }
128
129    private void removeTrainListeners() {
130        log.debug("Train Logger removing train listerners");
131        if (_trainLog) {
132            List<Train> trains = InstanceManager.getDefault(TrainManager.class).getTrainsByIdList();
133            trains.forEach(train -> train.removePropertyChangeListener(this));
134            InstanceManager.getDefault(TrainManager.class).removePropertyChangeListener(this);
135        }
136        _trainLog = false;
137    }
138
139    public void dispose() {
140        removeTrainListeners();
141    }
142
143    @Override
144    public void propertyChange(PropertyChangeEvent e) {
145        if (e.getPropertyName().equals(Train.STATUS_CHANGED_PROPERTY)
146                || e.getPropertyName().equals(Train.TRAIN_LOCATION_CHANGED_PROPERTY)) {
147            if (Control.SHOW_PROPERTY) {
148                log.debug("Train logger sees property change for train {}", e.getSource());
149            }
150            store((Train) e.getSource());
151        }
152        if (e.getPropertyName().equals(TrainManager.LISTLENGTH_CHANGED_PROPERTY)) {
153            if ((Integer) e.getNewValue() > (Integer) e.getOldValue()) {
154                // a car or engine has been added
155                removeTrainListeners();
156                addTrainListeners();
157            }
158        }
159    }
160
161    public String getFullLoggerFileName() {
162        return loggingDirectory + File.separator + getFileName();
163    }
164
165    private String operationsDirectory =
166            OperationsSetupXml.getFileLocation() + OperationsSetupXml.getOperationsDirectoryName();
167    private String loggingDirectory = operationsDirectory + File.separator + "logger" + File.separator + "trains"; // NOI18N
168
169    public String getDirectoryName() {
170        return loggingDirectory;
171    }
172
173    public void setDirectoryName(String name) {
174        loggingDirectory = name;
175    }
176
177    private String fileName;
178
179    public String getFileName() {
180        if (fileName == null) {
181            fileName = Bundle.getMessage("Trains") + "_" + getDate() + ".csv"; // NOI18N
182        }
183        return fileName;
184    }
185
186    private String getDate() {
187        Date date = Calendar.getInstance().getTime();
188        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy_MM_dd"); // NOI18N
189        return simpleDateFormat.format(date);
190    }
191
192    /**
193     * Return the date and time in an MS Excel friendly format yyyy/MM/dd
194     * HH:mm:ss
195     */
196    private String getTime() {
197        String time = Calendar.getInstance().getTime().toString();
198        SimpleDateFormat dt = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy"); // NOI18N
199        SimpleDateFormat dtout = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); // NOI18N
200        try {
201            return dtout.format(dt.parse(time));
202        } catch (ParseException e) {
203            return time; // there was an issue, use the old format
204        }
205    }
206
207    private final static Logger log = LoggerFactory.getLogger(TrainLogger.class);
208}