/**
* Squidy Interaction Library is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* Squidy Interaction Library is distributed in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Squidy Interaction Library. If not, see
* <http://www.gnu.org/licenses/>.
*
* 2009 Human-Computer Interaction Group, University of Konstanz.
* <http://hci.uni-konstanz.de>
*
* Please contact info@squidy-lib.de or visit our website
* <http://www.squidy-lib.de> for further information.
*/
package org.squidy.nodes;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowStateListener;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlType;
import org.squidy.manager.controls.CheckBox;
import org.squidy.manager.controls.ComboBox;
import org.squidy.manager.controls.TextField;
import org.squidy.manager.controls.ComboBoxControl.ComboBoxItemWrapper;
import org.squidy.manager.data.IData;
import org.squidy.manager.data.IDataContainer;
import org.squidy.manager.data.Processor;
import org.squidy.manager.data.Property;
import org.squidy.manager.data.domainprovider.DomainProvider;
import org.squidy.manager.data.impl.DefaultDataContainer;
import org.squidy.manager.model.AbstractNode;
import org.squidy.nodes.recorder.DataPlayer;
import org.squidy.nodes.recorder.FileHelper;
import org.squidy.nodes.recorder.LoggingObjectFactory;
import org.squidy.nodes.recorder.RecorderGUI;
import org.squidy.nodes.recorder.LoggingObjectFactory.LoggingObject;
/**
* @author Markus Nitsche
* @author Mario Ganzeboom (MaGaM) - contributed added functionality and bug fixes
*/
@XmlType(name = "DataRecorder")
@Processor(
name = "Data Recorder",
icon = "/org/squidy/nodes/image/48x48/recorder.png",
description = "/org/squidy/nodes/html/DataRecorder.html",
types = { Processor.Type.FILTER },
tags = { "recorder", "player", "log", "logging" }
)
public class DataRecorder extends AbstractNode implements ActionListener, WindowStateListener {
//GUI
private RecorderGUI controlPanel;
//File handling
//private RingBuffer<IData[]> buffer = new RingBuffer<IData[]>(100);
private FileHelper fileHelper = new FileHelper(this);
private DataPlayer player;
private File currentLogFile = null;
//Replay Mode
public static final int MODE_STOP = 0;
public static final int MODE_PLAY = 1;
public static final int MODE_RECORD = 2;
public static final int MODE_PAUSE_PLAY = 3;
public static final int MODE_PAUSE_RECORD = 4;
public static final int MODE_SINGLESTEP = 5;
private int mode = MODE_STOP;
private long recordStartTime = -1;
private long lastPauseBegan = -1;
private long totalPauseTime = 0;
// ################################################################################
// BEGIN OF ADJUSTABLES
// ################################################################################
@XmlAttribute(name = "replay-mode")
@Property(
name = "Replay mode",
description = "Default mode does ignore pauses in the log file which were created by pressing 'pause'. With pause does include all pauses."
)
@ComboBox(domainProvider = ReplayModeDomainProvider.class)
private int replayMode = 0;
public int getReplayMode() {
return replayMode;
}
public void setReplayMode(int replayMode) {
if(player != null) {
player.setIgnorePause(replayMode == REPLAYMODE_DEFAULT);
}
this.replayMode = replayMode;
}
@XmlAttribute(name = "keep-timestamp")
@Property(name = "Keep original timestamp", description = "If set, the original timestamp from the logfile is used. Otherwise it will be replaced by the current timestamp")
@CheckBox
private boolean keepTimestamp = false;
public boolean isKeepTimestamp() {
return keepTimestamp;
}
public void setKeepTimestamp(boolean keep) {
this.keepTimestamp = keep;
}
//#################################################################
@XmlAttribute(name = "value-separator")
@Property(
name = "Logfile values separator",
description = "The seperator which will be used to seperate the single values of an object in the logfile"
)
@TextField
private String valueSeparator = ",";
public String getValueSeparator() {
return valueSeparator;
}
public void setValueSeparator(String separator) {
this.valueSeparator = separator;
LoggingObjectFactory.getInstance().setValueSeparator(separator);
}
//#################################################################
@XmlAttribute(name = "object-separator")
@Property(
name = "Logfile obejcts separator",
description = "The seperator which will be used to seperate the single object of a container in the logfile"
)
@TextField
private String objectSeparator = "::";
public String getObjectSeparator() {
return objectSeparator;
}
public void setObjectSeparator(String objectSeparator) {
this.objectSeparator = objectSeparator;
LoggingObjectFactory.getInstance().setObjectSeparator(objectSeparator);
}
//#################################################################
@XmlAttribute(name = "logging-folder")
@Property(
name = "Logging folder",
description = "The path to the log folder."
)
@TextField
private String loggingFolder = "log";
public String getLoggingFolder() {
return loggingFolder;
}
public void setLoggingFolder(String loggingFolder) {
if (!this.loggingFolder.equals(loggingFolder)) {
//logStream = createLogOutput();
//fileCounter = 0;
}
this.loggingFolder = loggingFolder;
}
//#################################################################
@XmlAttribute(name = "logfilename")
@Property(
name = "Name of Logfile",
description = "The name of the logfile(s) which will be created by the recorder."
)
@TextField
private String filename = "SquidyLog";
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
//#################################################################
@XmlAttribute(name = "logfileextension")
@Property(
name = "Logfile Extension",
description = "File extension of Squidy Logfiles."
)
@TextField
private String logFileExtension = ".sdl";
public String getLogFileExtension() {
return logFileExtension;
}
public void setLogFileExtension(String logFileExtension) {
this.logFileExtension = logFileExtension;
}
// ################################################################################
// END OF ADJUSTABLES
// ################################################################################
@Override
public void onStart() {
super.onStart();
if(controlPanel == null) {
controlPanel = new RecorderGUI("Data Recorder", this);
}
if(fileHelper == null) {
fileHelper = new FileHelper(this);
}
controlPanel.setVisible(true);
LoggingObjectFactory.getInstance().setObjectSeparator(objectSeparator);
LoggingObjectFactory.getInstance().setValueSeparator(valueSeparator);
}
@Override
public void onStop() {
super.onStop();
fileHelper.terminate();
controlPanel.setVisible(false);
controlPanel = null;
fileHelper = null;
currentLogFile = null;
player = null;
}
public IDataContainer preProcess(IDataContainer dataContainer) {
if(mode != MODE_RECORD) {
return super.preProcess(dataContainer);
}
LoggingObject o = LoggingObjectFactory.getInstance().getLoggingObject(System.currentTimeMillis(), dataContainer, LoggingObject.TYPE_DATA);
fileHelper.write(o.serialize());
return super.preProcess(dataContainer);
}
public void publishData(IData i) {
publish(i);
}
public void publishData(List<IData> datas) {
publish(datas);
}
private void setMode(int newMode, long time) {
if (newMode == MODE_PLAY) {
if(mode == MODE_RECORD || mode == MODE_PLAY) {
return;
}
if(mode == MODE_PAUSE_PLAY) {
player.proceed();
mode = MODE_PLAY;
controlPanel.setStatusText("Playing");
return;
}
if(mode == MODE_SINGLESTEP) {
player.proceed();
mode = MODE_PLAY;
controlPanel.setStatusText("Playing");
return;
}
if(currentLogFile == null) {
if(!selectInputFile()) {
return;
}
}
if(player != null) {
player.stopPlayer();
player = null;
}
player = fileHelper.getPlayer(replayMode == REPLAYMODE_DEFAULT);
player.start();
mode = MODE_PLAY;
controlPanel.setStatusText("Playing");
}
else if (newMode == MODE_PAUSE_PLAY) {
player.pausePlayer();
mode = MODE_PAUSE_PLAY;
controlPanel.setStatusText("Paused playback");
}
else if(newMode == MODE_RECORD) {
if(mode == MODE_PAUSE_RECORD) {
writePause(time, false);
}
else {
fileHelper.startRecord(loggingFolder, filename, logFileExtension);
File curLogFile = fileHelper.getCurrentLogFile();
String fileName = controlPanel.getMultiLineLabelForLogFile(curLogFile);
fileName = fileName.replace(".tmp", getLogFileExtension());
controlPanel.setFileLabelText(fileName, true);
recordStartTime = time;
//write a null logging object, such that the duration of the file is correct
LoggingObject o = LoggingObjectFactory.getInstance().getLoggingObject(time, null, LoggingObject.TYPE_NULL);
fileHelper.write(o.serialize());
}
mode = MODE_RECORD;
controlPanel.setStatusText("Recording");
}
else if (newMode == MODE_PAUSE_RECORD) {
writePause(time, true);
mode = MODE_PAUSE_RECORD;
controlPanel.setStatusText("Paused recording");
}
else if(newMode == MODE_STOP) {
if(mode == MODE_RECORD) {
//write a Null Logging Object at end of file
LoggingObject o = LoggingObjectFactory.getInstance().getLoggingObject(time, null, LoggingObject.TYPE_NULL);
fileHelper.write(o.serialize());
//write Logfile header
long recordTime = time - recordStartTime;
fileHelper.writeHeader(recordTime, totalPauseTime);
controlPanel.setStatusText("Stopped recording");
//TODO: open recorded file directly
}
if(mode == MODE_PLAY) {
player.stopPlayer();
player = null;
controlPanel.setStatusText("Stopped playback");
controlPanel.setTimeLabelText("00:00:00,000");
controlPanel.setSliderPosition(0);
}
mode = MODE_STOP;
}
else if (newMode == MODE_SINGLESTEP && (mode == MODE_PLAY || mode == MODE_PAUSE_PLAY || mode == MODE_SINGLESTEP)){
player.stepForward();
mode = MODE_SINGLESTEP;
}
}
private void writePause(long time, boolean pauseBegins) {
if(pauseBegins)
lastPauseBegan = time;
if(!pauseBegins && lastPauseBegan != -1) {
totalPauseTime += time - lastPauseBegan;
lastPauseBegan = -1;
}
LoggingObject l = LoggingObjectFactory.getInstance().getLoggingObject(time, null, LoggingObject.TYPE_PAUSE);
fileHelper.write(l.serialize());
}
/* (non-Javadoc)
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
public void actionPerformed(ActionEvent e) {
String action = e.getActionCommand().toLowerCase();
if(action.equals("rec")){
setMode(MODE_RECORD, e.getWhen());
} else if (action.equals("stop")) {
setMode(MODE_STOP, e.getWhen());
} else if (action.equals("play")) {
setMode(MODE_PLAY, e.getWhen());
} else if (action.equals("pause")) {
if(mode == MODE_PLAY) {
setMode(MODE_PAUSE_PLAY, e.getWhen());
} else if (mode == MODE_RECORD) {
setMode(MODE_PAUSE_RECORD, e.getWhen());
} else if (mode == MODE_PAUSE_PLAY) {
setMode(MODE_PLAY, e.getWhen());
} else if (mode == MODE_PAUSE_RECORD) {
setMode(MODE_RECORD, e.getWhen());
}
} else if (action.equals("open")) {
selectInputFile();
} else if (action.equals("step")) {
setMode(MODE_SINGLESTEP, e.getWhen());
}
}
private boolean selectInputFile() {
File logfile = controlPanel.openFile(getLoggingFolder());
if(logfile != null) {
currentLogFile = logfile;
return true;
}
return false;
}
// ################################################################################
// BEGIN OF DOMAIN PROVIDERS
// ################################################################################
public static final int REPLAYMODE_DEFAULT = 0;
public static final int REPLAYMODE_WITHPAUSE= 1;
public static class ReplayModeDomainProvider implements DomainProvider {
/*
* (non-Javadoc)
*
* @see org.squidy.manager.data.domainprovider.DomainProvider#getValues()
*/
public Object[] getValues() {
ComboBoxItemWrapper[] values = new ComboBoxItemWrapper[2];
values[0] = new ComboBoxItemWrapper(REPLAYMODE_DEFAULT, "Default");
values[1] = new ComboBoxItemWrapper(REPLAYMODE_WITHPAUSE, "With pauses");
return values;
}
}
// ################################################################################
// END OF DOMAIN PROVIDERS
// ################################################################################
// ###############################################################################
// BEGIN OF WINDOW STATE LISTENER METHODS
// ###############################################################################
public void windowStateChanged(WindowEvent e) {
//TODO
if(e.getID() == WindowEvent.WINDOW_CLOSING) {
}
else if (e.getID() == WindowEvent.WINDOW_CLOSED) {
}
}
public File getCurrentLogFile() {
return currentLogFile;
}
public void setCurrentLogFile(File currentLogFile) {
this.currentLogFile = currentLogFile;
}
public void playerHasfinished() {
fileHelper.terminate();
setMode(MODE_STOP, System.currentTimeMillis());
}
public void doPublish(IDataContainer dataContainer) {
if(!isKeepTimestamp()){
dataContainer.setTimestamp(System.currentTimeMillis());
}
publish(dataContainer);
}
public void updateGUI(float percentagePlayed, long timeStamp) {
controlPanel.setSliderPosition((int) (percentagePlayed*1000.0f));
controlPanel.setTimeLabelText(new SimpleDateFormat("HH:mm:ss,SSS").format(new Date(timeStamp)));
}
// ###############################################################################
// END OF WINDOW STATE LISTENER METHODS
// ###############################################################################
}