Newer
Older

Lyubomir Marinov
committed
/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jitsi.impl.fileaccess;
import java.io.*;
import org.jitsi.service.fileaccess.*;
/**
* A failsafe transaction class. By failsafe we mean here that the file
* concerned always stays in a coherent state. This class use the transactional
* model.
*

Lyubomir Marinov
committed
* @author Benoit Pradelle
*/
public class FailSafeTransactionImpl

Lyubomir Marinov
committed
implements FailSafeTransaction
{

Lyubomir Marinov
committed
/**
* Original file used by the transaction
*/
private File file;

Lyubomir Marinov
committed
/**
* Backup file used by the transaction
*/
private File backup;

Lyubomir Marinov
committed
/**
* Extension of a partial file
*/
private static final String PART_EXT = ".part";

Lyubomir Marinov
committed
/**
* Extension of a backup copy
*/
private static final String BAK_EXT = ".bak";

Lyubomir Marinov
committed
/**
* Creates a new transaction.
*

Lyubomir Marinov
committed
* @param file The file associated with this transaction
*

Lyubomir Marinov
committed
* @throws NullPointerException if the file is null
*/
protected FailSafeTransactionImpl(File file)
throws NullPointerException
{
if (file == null) {
throw new NullPointerException("null file provided");
}

Lyubomir Marinov
committed
this.file = file;
this.backup = null;
}

Lyubomir Marinov
committed
/**
* Ensure that the file accessed is in a coherent state. This function is
* useful to do a failsafe read without starting a transaction.
*

Lyubomir Marinov
committed
* @throws IllegalStateException if the file doesn't exists anymore
* @throws IOException if an IOException occurs during the file restoration
*/
public void restoreFile()
throws IllegalStateException, IOException
{
File back = new File(this.file.getAbsolutePath() + BAK_EXT);

Lyubomir Marinov
committed
// if a backup copy is still present, simply restore it
if (back.exists()) {
failsafeCopy(back.getAbsolutePath(),
this.file.getAbsolutePath());

Lyubomir Marinov
committed
back.delete();
}
}

Lyubomir Marinov
committed
/**
* Begins a new transaction. If a transaction is already active, commits the
* changes and begin a new transaction.
* A transaction can be closed by a commit or rollback operation.
* When the transaction begins, the file is restored to a coherent state if
* needed.
*

Lyubomir Marinov
committed
* @throws IllegalStateException if the file doesn't exists anymore
* @throws IOException if an IOException occurs during the transaction
* creation
*/
public void beginTransaction()

Lyubomir Marinov
committed
throws IllegalStateException, IOException
{
// if the last transaction hasn't been closed, commit it
if (this.backup != null) {
this.commit();
}

Lyubomir Marinov
committed
// if needed, restore the file in its previous state
restoreFile();

Lyubomir Marinov
committed
this.backup = new File(this.file.getAbsolutePath() + BAK_EXT);

Lyubomir Marinov
committed
// else backup the current file
failsafeCopy(this.file.getAbsolutePath(),
this.backup.getAbsolutePath());
}

Lyubomir Marinov
committed
/**
* Closes the transaction and commit the changes. Everything written in the
* file during the transaction is saved.
*

Lyubomir Marinov
committed
* @throws IllegalStateException if the file doesn't exists anymore
* @throws IOException if an IOException occurs during the operation
*/
public void commit()
throws IllegalStateException, IOException
{
if (this.backup == null) {
return;
}

Lyubomir Marinov
committed
// simply delete the backup file
this.backup.delete();
this.backup = null;
}

Lyubomir Marinov
committed
/**
* Closes the transaction and cancels the changes. Everything written in the

Lyubomir Marinov
committed
* file during the transaction is NOT saved.
* @throws IllegalStateException if the file doesn't exists anymore
* @throws IOException if an IOException occurs during the operation
*/
public void rollback()
throws IllegalStateException, IOException
{
if (this.backup == null)
{

Lyubomir Marinov
committed
return;
}

Lyubomir Marinov
committed
// restore the backup and delete it
failsafeCopy(this.backup.getAbsolutePath(),
this.file.getAbsolutePath());
this.backup.delete();
this.backup = null;
}

Lyubomir Marinov
committed
/**
* Copy a file in a fail-safe way. The destination is created in an atomic
* way.
*

Lyubomir Marinov
committed
* @param from The file to copy
* @param to The copy to create
*

Lyubomir Marinov
committed
* @throws IllegalStateException if the file doesn't exists anymore
* @throws IOException if an IOException occurs during the operation
*/
private void failsafeCopy(String from, String to)
throws IllegalStateException, IOException
{
FileInputStream in = null;
FileOutputStream out = null;

Lyubomir Marinov
committed
// to ensure a perfect copy, delete the destination if it exists
File toF = new File(to);
if (toF.exists())
{
if (!toF.delete())
throw new IOException("Failed to delete destination file: "
+ toF.getName());

Lyubomir Marinov
committed
}

Lyubomir Marinov
committed
File ptoF = new File(to + PART_EXT);
if (ptoF.exists())
{
if (!ptoF.delete())
throw new IOException("Failed to delete partial file: "
+ ptoF.getName());

Lyubomir Marinov
committed
}

Lyubomir Marinov
committed
in = new FileInputStream(from);
out = new FileOutputStream(to + PART_EXT);
} catch (FileNotFoundException e)
{

Lyubomir Marinov
committed
throw new IllegalStateException(e.getMessage());
}

Lyubomir Marinov
committed
// actually copy the file
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0)
{
out.write(buf, 0, len);
}
in.close();
out.close();

Lyubomir Marinov
committed
// once done, rename the partial file to the final copy
if (!ptoF.renameTo(toF))
throw new IOException("Failed to rename " + ptoF.getName() + " to"
+ toF.getName());