package net.sf.jabref.export;
import net.sf.jabref.Globals;
import net.sf.jabref.Util;
import net.sf.jabref.GUIGlobals;
import java.io.File;
import java.io.IOException;
import java.io.FileOutputStream;
import java.nio.charset.UnsupportedCharsetException;
/**
* Class used to handle safe storage to disk.
*
* Usage: create a SaveSession giving the file to save to, the
* encoding, and whether to make a backup. The SaveSession will provide a Writer to store to, which actually
* goes to a temporary file. The Writer keeps track of whether all characters could be saved, and if not,
* which characters were not encodable.
*
* After saving is finished, the client should close the Writer. If the save should be put into effect, call
* commit(), otherwise call cancel(). When cancelling, the temporary file is simply deleted and the target
* file remains unchanged. When committing, the temporary file is copied to the target file after making
* a backup if requested and if the target file already existed, and finally the temporary file is deleted.
*
* If committing fails, the temporary file will not be deleted.
*/
public class SaveSession {
public static final String LOCKFILE_SUFFIX = ".lock";
// The age in ms of a lockfile before JabRef will offer to "steal" the locked file:
public static final long LOCKFILE_CRITICAL_AGE = 60000;
private static final String TEMP_PREFIX = "jabref";
private static final String TEMP_SUFFIX = "save.bib";
File file, tmp, backupFile;
String encoding;
boolean backup, useLockFile;
VerifyingWriter writer;
public SaveSession(File file, String encoding, boolean backup) throws IOException,
UnsupportedCharsetException {
this.file = file;
tmp = File.createTempFile(TEMP_PREFIX, TEMP_SUFFIX);
useLockFile = Globals.prefs.getBoolean("useLockFiles");
this.backup = backup;
this.encoding = encoding;
writer = new VerifyingWriter(new FileOutputStream(tmp), encoding);
}
public VerifyingWriter getWriter() {
return writer;
}
public String getEncoding() {
return encoding;
}
public void setUseBackup(boolean useBackup) {
this.backup = useBackup;
}
public void commit() throws SaveException {
if (file == null)
return;
if (file.exists() && backup) {
String name = file.getName();
String path = file.getParent();
File backupFile = new File(path, name + GUIGlobals.backupExt);
try {
Util.copyFile(file, backupFile, true);
} catch (IOException ex) {
ex.printStackTrace();
throw SaveException.BACKUP_CREATION;
//throw new SaveException(Globals.lang("Save failed during backup creation")+": "+ex.getMessage());
}
}
try {
if (useLockFile) {
try {
if (createLockFile()) {
// Oops, the lock file already existed. Try to wait it out:
if (!Util.waitForFileLock(file, 10))
throw SaveException.FILE_LOCKED;
}
} catch (IOException ex) {
System.err.println("Error when creating lock file");
ex.printStackTrace();
}
}
Util.copyFile(tmp, file, true);
} catch (IOException ex2) {
// If something happens here, what can we do to correct the problem? The file is corrupted, but we still
// have a clean copy in tmp. However, we just failed to copy tmp to file, so it's not likely that
// repeating the action will have a different result.
// On the other hand, our temporary file should still be clean, and won't be deleted.
throw new SaveException(Globals.lang("Save failed while committing changes")+": "+ex2.getMessage());
} finally {
if (useLockFile) {
try {
deleteLockFile();
} catch (IOException ex) {
System.err.println("Error when deleting lock file");
ex.printStackTrace();
}
}
}
tmp.delete();
}
public void cancel() throws IOException {
tmp.delete();
}
/**
* Check if a lock file exists, and create it if it doesn't.
* @return true if the lock file already existed
* @throws IOException if something happens during creation.
*/
private boolean createLockFile() throws IOException {
File lock = new File(file.getPath()+LOCKFILE_SUFFIX);
if (lock.exists()) {
return true;
}
FileOutputStream out = new FileOutputStream(lock);
out.write(0);
try {
out.close();
} catch (IOException ex) {
System.err.println("Error when creating lock file");
ex.printStackTrace();
}
lock.deleteOnExit();
return false;
}
/**
* Check if a lock file exists, and delete it if it does.
* @return true if the lock file existed, false otherwise.
* @throws IOException if something goes wrong.
*/
private boolean deleteLockFile() throws IOException {
File lock = new File(file.getPath()+LOCKFILE_SUFFIX);
if (!lock.exists()) {
return false;
}
lock.delete();
return true;
}
public File getTemporaryFile() {
return tmp;
}
}