/*******************************************************************************
* Copyright (c) 2011, 2015 Wind River Systems, Inc. and others. All rights reserved.
* This program and the accompanying materials are made available under the terms
* of the Eclipse Public License v1.0 which accompanies this distribution, and is
* available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.te.tcf.filesystem.core.internal.operations;
import static java.text.MessageFormat.format;
import java.io.File;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.osgi.util.NLS;
import org.eclipse.tcf.protocol.IChannel;
import org.eclipse.tcf.protocol.IToken;
import org.eclipse.tcf.protocol.Protocol;
import org.eclipse.tcf.services.IFileSystem;
import org.eclipse.tcf.services.IFileSystem.DirEntry;
import org.eclipse.tcf.services.IFileSystem.DoneRoots;
import org.eclipse.tcf.services.IFileSystem.DoneStat;
import org.eclipse.tcf.services.IFileSystem.FileAttrs;
import org.eclipse.tcf.services.IFileSystem.FileSystemException;
import org.eclipse.tcf.te.runtime.callback.Callback;
import org.eclipse.tcf.te.tcf.core.concurrent.TCFOperationMonitor;
import org.eclipse.tcf.te.tcf.filesystem.core.interfaces.IOperation;
import org.eclipse.tcf.te.tcf.filesystem.core.interfaces.runtime.IFSTreeNode;
import org.eclipse.tcf.te.tcf.filesystem.core.interfaces.runtime.IRuntimeModel.Delegate;
import org.eclipse.tcf.te.tcf.filesystem.core.internal.FSTreeNode;
import org.eclipse.tcf.te.tcf.filesystem.core.internal.utils.FileState;
import org.eclipse.tcf.te.tcf.filesystem.core.internal.utils.PersistenceManager;
import org.eclipse.tcf.te.tcf.filesystem.core.nls.Messages;
/**
* FSRefresh refreshes a specified tree node and its children and grand children recursively.
*/
public class OpRefresh extends AbstractOperation {
private static Map<FSTreeNode, TCFOperationMonitor<?>> fPendingResults = new HashMap<FSTreeNode, TCFOperationMonitor<?>>();
final LinkedList<FSTreeNode> fWork = new LinkedList<FSTreeNode>();
final boolean fRecursive;
private long fStartTime;
public OpRefresh(FSTreeNode node, boolean recursive) {
fWork.add(node);
fRecursive = recursive;
}
public OpRefresh(List<IFSTreeNode> nodes, boolean recursive) {
fRecursive = recursive;
for (IFSTreeNode node : nodes) {
if (node instanceof FSTreeNode) {
fWork.add((FSTreeNode) node);
}
}
}
@Override
public IStatus doRun(IProgressMonitor monitor) {
FSTreeNode needsNotification = null;
FSTreeNode trigger = fWork.peek();
fStartTime = System.currentTimeMillis();
monitor.beginTask(getName(), IProgressMonitor.UNKNOWN);
while (!fWork.isEmpty()) {
FSTreeNode node = fWork.remove();
boolean isTop = trigger == node;
if (isTop) {
if (needsNotification != null) {
needsNotification.notifyChange();
}
needsNotification = node;
trigger = fWork.peek();
}
IStatus s = refreshNode(node, isTop, monitor);
if (!s.isOK()) {
if (needsNotification != null)
needsNotification.notifyChange();
return s;
}
}
if (needsNotification != null)
needsNotification.notifyChange();
return Status.OK_STATUS;
}
private IStatus refreshNode(final FSTreeNode node, final boolean isTop, IProgressMonitor monitor) {
if (node.getLastRefresh() >= fStartTime) {
FSTreeNode[] children = node.getChildren();
if (children != null) {
for (FSTreeNode child : children) {
fWork.addFirst(child);
}
}
return Status.OK_STATUS;
}
boolean isDir = node.isDirectory();
boolean isFile = node.isFile();
if (!node.isFileSystem() && !isDir && !isFile)
return Status.OK_STATUS;
if (!isTop && !isFile && node.getChildren() == null)
return Status.OK_STATUS;
monitor.subTask(format(Messages.OpRefresh_name, node.getLocation()));
IStatus status;
synchronized (fPendingResults) {
TCFOperationMonitor<?> result = fPendingResults.get(node);
if (result == null) {
result = new TCFOperationMonitor<Object>(false);
fPendingResults.put(node, result);
scheduleTcfRefresh(node, isTop, result);
}
status = result.waitDone(monitor);
if (!result.hasWaiters()) {
result.setCancelled();
fPendingResults.remove(node);
}
}
return status;
}
private void scheduleTcfRefresh(final FSTreeNode node, final boolean isTop, final TCFOperationMonitor<?> result) {
if (!result.checkCancelled()) {
Protocol.invokeLater(new Runnable() {
@Override
public void run() {
if (node.isFileSystem()) {
tcfRefreshRoots(node, result);
} else if (isTop && !node.isRootDirectory()) {
tcfStatAndRefresh(node, result);
} else {
tcfRefresh(node, result);
}
}
});
}
}
protected void tcfRefreshRoots(final FSTreeNode node, final TCFOperationMonitor<?> result) {
if (!result.checkCancelled()) {
final IFileSystem fs = node.getRuntimeModel().getFileSystem();
if (fs == null) {
result.setCancelled();
return;
}
fs.roots(new DoneRoots() {
@Override
public void doneRoots(IToken token, FileSystemException error, DirEntry[] entries) {
if (node.getRuntimeModel().getChannel() == null || node.getRuntimeModel().getChannel().getState() != IChannel.STATE_CLOSED) {
if (error != null) {
result.setError(format(Messages.OpRefresh_errorGetRoots, node.getRuntimeModel().getName()), error);
} else if (!result.checkCancelled()) {
Delegate delegate = node.getRuntimeModel().getDelegate();
List<FSTreeNode> nodes = new ArrayList<FSTreeNode>(entries.length);
for (DirEntry entry : entries) {
if (delegate.filterRoot(entry)) {
nodes.add(new FSTreeNode(node, entry.filename, true, entry.attrs));
}
}
node.setContent(nodes.toArray(new FSTreeNode[nodes.size()]), false);
for (FSTreeNode node : node.getChildren()) {
if (fRecursive || node.isFile()) {
fWork.addFirst(node);
}
}
result.setDone(null);
}
}
}
});
}
}
protected void tcfStatAndRefresh(final FSTreeNode node, final TCFOperationMonitor<?> result) {
if (!result.checkCancelled()) {
final IFileSystem fs = node.getRuntimeModel().getFileSystem();
if (fs == null) {
result.setCancelled();
return;
}
fs.stat(node.getLocation(true), new DoneStat() {
@Override
public void doneStat(IToken token, FileSystemException error, FileAttrs attrs) {
if (error != null) {
handleFSError(node, Messages.OpRefresh_errorReadAttributes, error, result);
} else {
node.setAttributes(attrs, false);
tcfRefresh(node, result);
}
}
});
}
}
protected void tcfRefresh(final FSTreeNode node, final TCFOperationMonitor<?> result) {
if (!result.checkCancelled()) {
if (node.isFile()) {
tcfUpdateCacheDigest(node, result);
} else if (node.isDirectory()) {
final String path = node.getLocation(true);
final IFileSystem fs = node.getRuntimeModel().getFileSystem();
if (fs == null) {
result.setCancelled();
return;
}
tcfReadDir(fs, path, new IReadDirDone() {
@Override
public void error(FileSystemException error) {
node.setContent(NO_CHILDREN, false);
result.setError(format(Messages.OpRefresh_errorOpenDir, path), error);
}
@Override
public boolean checkCancelled() {
return result.checkCancelled();
}
@Override
public void done(List<DirEntry> entries) {
int i = 0;
FSTreeNode[] nodes = new FSTreeNode[entries.size()];
for (DirEntry entry : entries) {
nodes[i++] = new FSTreeNode(node, entry.filename, false, entry.attrs);
}
node.setContent(nodes, false);
for (FSTreeNode node : node.getChildren()) {
if (fRecursive || node.isFile()) {
fWork.addFirst(node);
}
}
result.setDone(null);
}
});
} else {
result.setDone(null);
}
}
}
protected void tcfUpdateCacheDigest(final FSTreeNode node, final TCFOperationMonitor<?> result) {
File cacheFile = node.getCacheFile();
if (!cacheFile.exists()) {
result.setDone(null);
return;
}
final FileState digest = PersistenceManager.getInstance().getFileDigest(node);
final long cacheMTime = cacheFile.lastModified();
if (digest.getCacheDigest() == null || digest.getCacheMTime() != cacheMTime) {
final OpCacheFileDigest op = new OpCacheFileDigest(node);
op.runInJob(new Callback() {
@Override
protected void internalDone(Object caller, IStatus status) {
if (status.isOK()) {
digest.updateCacheDigest(op.getDigest(), cacheMTime);
tcfUpdateTargetDigest(digest, node, result);
} else {
result.setDone(status, null);
}
}
});
} else {
tcfUpdateTargetDigest(digest, node, result);
}
}
protected void tcfUpdateTargetDigest(FileState digest, final FSTreeNode node, final TCFOperationMonitor<?> result) {
if (digest.getTargetDigest() == null || digest.getTargetMTime() != node.getModificationTime()) {
final IOperation op = node.operationDownload(new OutputStream() {
@Override
public void write(int b) {
}
});
op.runInJob(new Callback() {
@Override
protected void internalDone(Object caller, IStatus status) {
result.setDone(status, null);
}
});
} else {
result.setDone(null);
}
}
@Override
public String getName() {
return NLS.bind(Messages.OpRefresh_name, ""); //$NON-NLS-1$
}
}