package org.intellij.vcs.mks.realtime;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ApplicationComponent;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.vcs.VcsException;
import org.intellij.vcs.mks.MKSHelper;
import org.intellij.vcs.mks.MksConfiguration;
import org.intellij.vcs.mks.MksVcs;
import org.intellij.vcs.mks.sicommands.cli.ListSandboxes;
import org.intellij.vcs.mks.sicommands.SandboxInfo;
import org.intellij.vcs.mks.sicommands.cli.SandboxesCommand;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.locks.ReentrantLock;
/**
* starts a si sandboxes --persist and listens to updates but doesn't actually use the output.
* Whenever a new update is detected, issues a si sandboxes (without --persist), compares the output to
* the previous state and issues events accordingly to any registered listeners.
* <p/>
* A listener being registered while comparison is in progress should get the new list
*
* @author Thibaut Fagart
*/
public class SandboxListSynchronizerImpl extends AbstractMKSSynchronizer
implements ApplicationComponent, SandboxListSynchronizer {
private final ArrayList<SandboxListListener> listeners = new ArrayList<SandboxListListener>();
private ArrayList<SandboxInfo> currentList = new ArrayList<SandboxInfo>();
private final ReentrantLock sandboxCacheLock = new ReentrantLock();
/**
* used to not process sandbox list update immediately to avoid doing it for every subsandbox being checkedout
*/
private Timer timer;
/**
* delay while new update may be ignored
*/
private static final int DELAY = 5000;
/**
* lock to manipulate the timer
*/
private final Object fireUpdateLock = new Object();
public SandboxListSynchronizerImpl() {
this(ApplicationManager.getApplication().getComponent(MksConfiguration.class));
}
protected SandboxListSynchronizerImpl(MksConfiguration config) {
super(ListSandboxes.COMMAND, config);
}
public void addListener(@NotNull SandboxListListener listener) {
if (this.listeners.contains(listener)) {
return;
}
sandboxCacheLock.lock();
try {
final ArrayList<SandboxInfo> newList =
new ArrayList<SandboxInfo>(currentList);
this.listeners.add(listener);
final ArrayList<SandboxListListener> listeners = new ArrayList<SandboxListListener>();
// only fire updates to the newly added listener
listeners.add(listener);
compareAndFireUpdates(new ArrayList<SandboxInfo>(), newList, listeners);
} finally {
sandboxCacheLock.unlock();
}
}
public void removeListener(@NotNull SandboxListListener sandboxListListener) {
this.listeners.remove(sandboxListListener);
}
@NonNls
@NotNull
public String getComponentName() {
return "MKS.sandboxListSaynchronizer";
}
public void initComponent() {
MKSHelper.startClient();
addIgnoredFiles();
/*
no need to fill in the sandbox list, si sandboxes --refresh issues 2 lists, one without the date, and
the next one with it
*/
start();
}
public void disposeComponent() {
stop();
currentList = null;
listeners.clear();
}
private static void addIgnoredFiles() {
String patterns = FileTypeManager.getInstance().getIgnoredFilesList();
StringBuffer newPattern = new StringBuffer(patterns);
if (patterns.indexOf(MksVcs.PROJECT_PJ_FILE) == -1) {
newPattern.append((newPattern.charAt(newPattern.length() - 1) == ';') ? "" : ";")
.append(MksVcs.PROJECT_PJ_FILE);
}
final String newPatternString = newPattern.toString();
if (!newPatternString.equals(patterns)) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
FileTypeManager.getInstance().setIgnoredFilesList(newPatternString);
}
}
);
}
}
@Override
protected void handleLine(String line) {
try {
if (shoudIgnore(line)) {
return;
}
if (line.startsWith("-----")) {
// detection of a new update
LOGGER.debug("update notification : " + line);
fireUpdateSandboxList();
}
} catch (Exception e) {
LOGGER.error("error parsing mks synchronizer output [" + line + "], skipping that line because : " +
e.getMessage(), e);
}
}
/**
* don't process the update right now, only do it after a bit, to avoid updating sandbox list for every subsandbox
*/
private void fireUpdateSandboxList() {
synchronized (this.fireUpdateLock) {
if (timer != null) {
timer.cancel();
}
timer = new Timer(true);
final TimerTask task = new TimerTask() {
@Override
public void run() {
updateSandboxList();
}
};
timer = new Timer();
timer.schedule(task, DELAY);
}
}
private void updateSandboxList() {
this.sandboxCacheLock.lock();
ArrayList<SandboxInfo> oldList;
final ArrayList<SandboxListListener> listeners;
ArrayList<SandboxInfo> newSandboxList;
try {
oldList = this.currentList;
listeners = new ArrayList<SandboxListListener>(this.listeners);
newSandboxList = getNewSandboxList();
this.currentList = newSandboxList;
} finally {
this.sandboxCacheLock.unlock();
}
compareAndFireUpdates(oldList, newSandboxList, listeners);
}
/**
* compares the 2 (sorted) lists of sandbox and fire updates appropriately to the provided list of listeners
*
* @param oldList supposed to be sorted
* @param newList supposed to be sorted
* @param listeners
*/
protected void compareAndFireUpdates(ArrayList<SandboxInfo> oldList,
ArrayList<SandboxInfo> newList,
ArrayList<SandboxListListener> listeners) {
int oldIndex = 0;
int newIndex = 0;
while (oldIndex < oldList.size() && newIndex < newList.size()) {
final SandboxInfo oldSandbox = oldList.get(oldIndex);
final SandboxInfo newSandbox = newList.get(newIndex);
final int compareCode = oldSandbox.sandboxPath.compareTo(newSandbox.sandboxPath);
if (0 == compareCode) {
if (!oldSandbox.equals(newSandbox)) {
fireSandboxUpdated(newSandbox, listeners);
}
oldIndex++;
newIndex++;
} else if (compareCode < 0) {
fireSandboxRemoved(oldSandbox, listeners);
oldIndex++;
} else {
fireSandboxAdded(newSandbox, listeners);
newIndex++;
}
}
while (oldIndex < oldList.size()) {
fireSandboxRemoved(oldList.get(oldIndex), listeners);
oldIndex++;
}
while (newIndex < newList.size()) {
fireSandboxAdded(newList.get(newIndex), listeners);
newIndex++;
}
}
private void fireSandboxAdded(SandboxInfo sandbox, ArrayList<SandboxListListener> listeners) {
for (SandboxListListener listener : listeners) {
listener.addSandboxPath(sandbox.sandboxPath, sandbox.serverHostAndPort, sandbox.projectPath,
sandbox.projectVersion, sandbox.subSandbox);
}
}
private void fireSandboxRemoved(SandboxInfo sandbox, ArrayList<SandboxListListener> listeners) {
for (SandboxListListener listener : listeners) {
listener.removeSandboxPath(sandbox.sandboxPath, sandbox.subSandbox);
}
}
private void fireSandboxUpdated(SandboxInfo sandbox, ArrayList<SandboxListListener> listeners) {
for (SandboxListListener listener : listeners) {
listener.updateSandboxPath(sandbox.sandboxPath, sandbox.serverHostAndPort, sandbox.projectPath,
sandbox.projectVersion, sandbox.subSandbox);
}
}
public String getDescription() {
return "sandbox list listener";
}
protected ArrayList<SandboxInfo> getNewSandboxList() {
final SandboxesCommand command = new SandboxesCommand(new ArrayList<VcsException>(),
ApplicationManager.getApplication().getComponent(MksConfiguration.class));
command.execute();
Collections.sort(command.result, SandboxInfo.COMPARATOR);
return command.result;
}
}