/*
* JFileSync
* Copyright (C) 2002-2007, Jens Heidrich
*
* This program 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; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA, 02110-1301, USA
*/
package jfs.sync;
import java.util.List;
import jfs.conf.JFSConfig;
import jfs.conf.JFSHistoryManager;
import jfs.conf.JFSSyncMode.SyncAction;
import jfs.conf.JFSSyncModes;
import jfs.sync.JFSProgress.ProgressActivity;
import jfs.sync.JFSQuestion.QuestionAnswer;
/**
* This class is responsible for synchronizing a table of JFS elements. In order to perform a complete synchronization
* cycle, the following actions have to be taken: (1) perform a comparison for each directory pair, (2) compute the
* actions applied to the comparison table according to the chosen synchronization mode, (3) compute the lists of copy
* and delete statements from the actions, and finally (4) perform a synchronization.
*
* @author Jens Heidrich
* @version $Id: JFSSynchronization.java,v 1.33 2007/06/05 16:09:41 heidrich Exp $
*/
public final class JFSSynchronization {
/**
* Stores the only instance of the class.
*
* SingletonHolder is loaded on the first execution of JFSSynchronization.getInstance()
* or the first access to SingletonHolder.INSTANCE, not before.
*/
private static class SingletonHolder {
public static final JFSSynchronization INSTANCE = new JFSSynchronization();
}
/**
* Answers questions during before the algorithm is performed.
*/
private final JFSQuestion question = new JFSQuestion();
/**
* Creates a new synchronization object.
*/
protected JFSSynchronization() {
// Avoid external instanciation
}
/**
* Returns the reference of the only instance.
*
* @return The only instance.
*/
public static JFSSynchronization getInstance() {
return SingletonHolder.INSTANCE;
}
/**
* Returns the JFS question object (e.g., in order to set the oracle).
*
* @return The used JFS question object.
*/
public JFSQuestion getQuestion() {
return question;
}
/**
* Computes two lists of files according to the actions specified in the comparison table mode. The first list
* contains copy statements and represents all files that have to copied from source to target or the other way
* around. The second list contains delete statements and represents all files that have to be deleted. When the
* lists are computed, the user is asked some questions regarding JFS elements for which no action could be computed
* automatically.
*/
public void computeSynchronizationLists() {
// Get table and reset copy and delete statements:
JFSTable table = JFSTable.getInstance();
List<JFSCopyStatement> copyStatements = table.getCopyStatements();
List<JFSDeleteStatement> deleteStatements = table.getDeleteStatements();
copyStatements.clear();
deleteStatements.clear();
boolean skipAll = false;
// Go through table and compute actions:
JFSFileProducer srcProducer = null;
JFSFileProducer tgtProducer = null;
for (int i = 0; i<table.getTableSize(); i++) {
JFSElement element = table.getTableElement(i);
// Get producers for the current element:
srcProducer = element.getRoot().getSrcProducer();
tgtProducer = element.getRoot().getTgtProducer();
// Check whether element and corresponding action is active:
if (!element.isActive()) {
continue;
}
JFSFile srcFile = element.getSrcFile();
JFSFile tgtFile = element.getTgtFile();
// Ask the user first:
if (!skipAll
&&(element.getAction()==SyncAction.ASK_LENGTH_INCONSISTENT
||element.getAction()==SyncAction.ASK_FILES_GT_HISTORY||element.getAction()==SyncAction.ASK_FILES_NOT_IN_HISTORY)) {
QuestionAnswer a = question.answer(element);
if (a==QuestionAnswer.SKIP_ALL) {
skipAll = true;
}
}
// Case 1: Copy the file on source side to the target side:
if (element.getAction()==SyncAction.COPY_SRC) {
JFSFile newFile = tgtProducer.getJfsFile(srcFile.getRelativePath(), srcFile.isDirectory());
copyStatements.add(new JFSCopyStatement(element, srcFile, newFile));
}
// Case 2: Copy the file on target side to the source side:
if (element.getAction()==SyncAction.COPY_TGT) {
JFSFile newFile = srcProducer.getJfsFile(tgtFile.getRelativePath(), tgtFile.isDirectory());
copyStatements.add(new JFSCopyStatement(element, tgtFile, newFile));
}
// Case 3: Delete the file on target side:
if (element.getAction()==SyncAction.DELETE_TGT||element.getAction()==SyncAction.DELETE_SRC_AND_TGT) {
deleteStatements.add(new JFSDeleteStatement(element, tgtFile));
}
// Case 4: Delete the file on source side:
if (element.getAction()==SyncAction.DELETE_SRC||element.getAction()==SyncAction.DELETE_SRC_AND_TGT) {
deleteStatements.add(new JFSDeleteStatement(element, srcFile));
}
}
// The list of delete statements has to be inverted, because the
// directories must be empty before they can be deleted:
invert(deleteStatements);
}
/**
* Synchronizes the directory structures specified in the directory pair if and only if the test run flag is not
* set.
*/
public void synchronize() {
JFSProgress progress = JFSProgress.getInstance();
JFSConfig config = JFSConfig.getInstance();
JFSTable table = JFSTable.getInstance();
List<JFSCopyStatement> copyStatements = table.getCopyStatements();
List<JFSDeleteStatement> deleteStatements = table.getDeleteStatements();
// Handle all files to delete first:
// (This is important for DOS and Windows Operating Systems.
// Let us assume a file on the source side named "TEST.txt"
// and on the target side "test.txt" and we want to copy files
// from source and delete from target.
// Because JFileSync works with a case-sensitive algorithm,
// the file "TEST.txt" would be copied first and the file
// "test.txt" would be deleted afterwards if we would
// perform the copy statements first. Because DOS and Windows
// don't distinguish between both file names, there wouldn't exist
// a file with one of names on the target side after the
// synchronization.)
JFSDeleteMonitor dm = JFSDeleteMonitor.getInstance();
progress.prepare(ProgressActivity.SYNCHRONIZATION_DELETE);
dm.clean();
dm.setFilesToDelete(deleteStatements.size());
progress.start();
boolean success;
int i = 0;
while (i<deleteStatements.size()&&!progress.isCanceled()) {
JFSDeleteStatement ds = deleteStatements.get(i);
dm.setCurrentFile(ds.getFile());
// Delete only if the delete flag is set and the success flag is
// false:
if (ds.getDeleteFlag()&&!ds.getSuccess()) {
success = ds.getFile().delete();
ds.setSuccess(success);
// Remove from table if action was successfully performed:
if (success) {
JFSElement element = ds.getElement();
element.setAction(SyncAction.NOP);
table.removeElement(element);
}
}
i++;
dm.setFilesDeleted(i);
progress.fireUpdate();
}
progress.end();
// Handle all files to copy:
JFSCopyMonitor cm = JFSCopyMonitor.getInstance();
progress.prepare(ProgressActivity.SYNCHRONIZATION_COPY);
cm.clean();
cm.setFilesToCopy(copyStatements.size());
cm.setBytesToTransfer(JFSCopyMonitor.getBytesToTransfer(copyStatements));
progress.start();
i = 0;
while (i<copyStatements.size()&&!progress.isCanceled()) {
JFSCopyStatement cs = copyStatements.get(i);
cm.setCurrentSrc(cs.getSrc());
cm.setCurrentTgt(cs.getTgt());
cm.setBytesTransferedCurrentFile(0);
// Copy only if the copy flag is set and the success flag is false:
if (cs.getCopyFlag()&&!cs.getSuccess()) {
cm.setBytesToTransferCurrentFile(cs.getSrc().getLength());
success = cs.getSrc().copy(cs.getTgt());
cs.setSuccess(success);
// Update table element if action was successfully performed:
if (success) {
JFSElement element = cs.getElement();
if (cs.isCopyFromSource()) {
element.setTgtFile(cs.getTgt());
} else {
element.setSrcFile(cs.getTgt());
}
// Revalidate element, compute action and update view:
element.revalidate();
JFSSyncModes.getInstance().getCurrentMode().computeAction(element);
table.updateElement(cs.getElement());
}
cm.setBytesTransfered(cm.getBytesTransfered()+cm.getBytesToTransferCurrentFile());
}
i++;
cm.setFilesCopied(i);
progress.fireUpdate();
}
progress.end();
// Store the history, even if the synchronization process was
// cancelled:
if (config.isStoreHistory()) {
JFSHistoryManager.getInstance().updateHistories();
}
// Update the current view. This has to be done, if a synchronization
// action fails and new elements would have to be added to the current
// view:
table.recomputeView();
}
/**
* Inverts all elements within a vector; i.e., the first element will become the last one and so on.
*
* @param <T>
* The elements contained in the vector.
* @param v
* The vector to be inverted.
*/
private static <T> void invert(List<T> v) {
T temp;
int size = v.size();
for (int i = 0; i<size/2; i++) {
temp = v.get(i);
v.set(i, v.get(size-1-i));
v.set(size-1-i, temp);
}
}
}