// This file is part of Penn TotalRecall <http://memory.psych.upenn.edu/TotalRecall>.
//
// TotalRecall is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 only.
//
// TotalRecall 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with TotalRecall. If not, see <http://www.gnu.org/licenses/>.
package components.audiofiles;
import info.Constants;
import java.io.File;
import java.util.HashSet;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import util.OSPath;
/**
* A <code>File</code> that represents an audio file for the purpose of representation in the <code>AudioFileDisplay</code>.
*
* <p><code>AudioFiles</code> keep track of whether they are done being annotated or not ("completion status"), and provide file system sanity checks that guarantee the <code>AudioFile</code>'s directory does not contain both temporary and final annotation files for this <code>AudioFile</code>.
* Please note that the audio file, temporary annotation file, and final annotation file must be in the same directory and must share the same filename up to file extension.
*
* <p>NOTE: This class does NOT represent the actual audio data.
* For that, see {@link control.AudioMaster}.
*
* @author Yuvi Masory
*/
public class AudioFile extends File {
private HashSet<ChangeListener> listeners; //notified when completion status changes
private boolean done; //whether or not the AudioFile is done being annotated.
/**
* Creates a new <code>AudioFile</code> from the given path.
*
* <p>Automatically determines if the file is done being annotated, using the presence sister annotation files in the same directory to judge.
* An AudioFile is either done or not done, so this constructor enforces the requirement that the new <code>AudioFile</code>'s directory can't contain both temporary and final annotation files.
*
* @param pathname The path of the file to be created
* @throws AudioFilePathException If the new file's directory contains both temporary and final annotation files
*/
public AudioFile(String pathname) throws AudioFilePathException {
super(pathname);
listeners = new HashSet<ChangeListener>();
updateDoneStatus();
}
/**
* Provide a shorter String representation of this object, for the benefit of the graphical display of the object in the <code>AudioFileList</code>.
*
* @return The <code>AudioFile</code>'s name, using the inherited {@link java.io.File#getName()}.
*/
@Override
public String toString() {
return getName();
}
/**
* Getter for the <code>AudioFile</code>'s completion status.
*
* @return <code>true</code> iff this <code>AudioFile</code> is done being annotated.
*/
public boolean isDone() {
return done;
}
/**
* Determines how <code>AudioFiles</code> are sorted.
*
* <p>The following three rules are applied, in order if precedence:
* <OL>
* <LI> <code>AudioFiles</code> come before other types of <code>Objects</code>.
* <LI> <code>AudioFiles</code> that are still incomplete come before those that are already done.
* <LI> <code>AudioFiles</code> sort by alphabetical order.
* </OL>
*/
@Override
public int compareTo(File f) {
if(f instanceof AudioFile == false) {
return 1;
}
else {
AudioFile ff = (AudioFile)f;
if((ff.isDone() && isDone()) || (ff.isDone() == false && isDone() == false)) {
return toString().compareTo(ff.toString());
}
else {
if(isDone()){
return 1;
}
else {
return -1;
}
}
}
}
/**
* Finds hash value based on file path.
*
* Returns <code>getAbsolutePath().hashCode()</code>.
*/
@Override
public int hashCode() {
return getAbsolutePath().hashCode();
}
/**
* Two <code>AudioFiles</code> are equal if they have the same absolute path.
*
* @return <code>true</code> iff <code>o</code> is an <code>AudioFile</code> with the same absolute path
*/
@Override
public boolean equals(Object o) {
if(o instanceof AudioFile) {
if(((AudioFile)o).getAbsolutePath().equals(getAbsolutePath())) {
return true;
}
}
return false;
}
/**
* Sets the <code>done</done> field by finding if a temporary annotation file or a final annotation file is present in the same directory as the this <code>File</code>.
* Informs listeners if the completion status changes.
*
* @throws AudioFilePathException If both the temporary and final annotation files are present
*/
//it's okay that the listeners update loop is entered even if this call came from the constructor
//since there's no constructor that takes in ChangeListeners, that loop will iterate zero times
public void updateDoneStatus() throws AudioFilePathException {
boolean savedStatus = done;
boolean updatedStatus = savedStatus;
boolean annFileExists = false;
boolean tmpFileExists = false;
File annFile = new File(OSPath.basename(getAbsolutePath()) + "." + Constants.completedAnnotationFileExtension);
File tmpFile = new File(OSPath.basename(getAbsolutePath()) + "." + Constants.temporaryAnnotationFileExtension);
if(annFile.exists()) {
annFileExists = true;
updatedStatus = true;
}
if(tmpFile.exists()) {
tmpFileExists = true;
updatedStatus = false;
}
if(annFileExists && tmpFileExists) {
throw new AudioFilePathException(
"Both exist, so I don't know if I'm completed or not:\n" +
annFile.getPath() + "\n" +
tmpFile.getPath());
}
done = updatedStatus;
if(savedStatus != done) {
for(ChangeListener listener: listeners) {
listener.stateChanged(new ChangeEvent(this));
}
}
}
/**
* Adds a <code>ChangeListener</code> to be notified of updates in this <code>AudioFile</code>'s completion status.
*
* @param listen The <code>ChangeListener</code> to be added.
*/
public void addChangeListener(ChangeListener listen) {
listeners.add(listen);
}
/**
* Removes all the <code>ChangeListeners</code> registered to receive updates from this <code>AudioFile</code>.
*/
public void removeAllChangeListeners() {
for(Object o: listeners.toArray()) {
listeners.remove(o);
}
}
/**
* Exception thrown when this <code>AudioFile</code>'s directory contains both temporary and final annotation files for this <code>AudioFile</code>.
*/
public class AudioFilePathException extends Exception {
private AudioFilePathException(String str) {
super(str);
}
}
}