/*
* Copyright 2000-2006 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.communicator.p2p;
import com.intellij.util.Time;
import gnu.trove.THashSet;
import jetbrains.communicator.core.Pico;
import jetbrains.communicator.core.users.User;
import jetbrains.communicator.core.users.UserPresence;
import jetbrains.communicator.ide.ProgressIndicator;
import jetbrains.communicator.util.StringUtil;
import jetbrains.communicator.util.WaitFor;
import org.apache.log4j.Logger;
import java.io.IOException;
import java.net.InetAddress;
import java.net.NoRouteToHostException;
import java.net.UnknownHostException;
import java.util.*;
/**
* @author Kir Maximov
* <p/>
* This thread periodically scans the local network via multicast
* request and passes obtained users to UserMonitorClient
*/
@SuppressWarnings({"HardCodedStringLiteral"})
public class UserMonitorThread extends Thread {
public static final Logger LOG = Logger.getLogger(UserMonitorThread.class);
static final long WAIT_USER_RESPONSES_TIMEOUT = 3000;
static final String SCAN_TIMEOUT_PROPERTY = "ideTalk.scanTimeout";
static final long TIMEOUT_BETWEEN_SCANS = 3 * Time.MINUTE;
private final MulticastPingThread[] myMulticastThreads;
private final UserMonitorClient myClient;
private final long myWaitUserResponsesTimeout;
private final long myScansTimeout;
private final Set<User> myAvailableUsers = Collections.synchronizedSet(new THashSet<User>());
private Thread myThread;
private long myStartFindingAt;
private final Object myLock = new Object();
public UserMonitorThread(P2PTransport client, long waitUserResponsesTimeout) {
this(createMulticastThreads(client), client, waitUserResponsesTimeout);
}
UserMonitorThread(MulticastPingThread[] multicastPingThread, UserMonitorClient client, long waitUserResponsesTimeout) {
super("User Monitor Thread");
setDaemon(true);
assert multicastPingThread != null;
myClient = client;
myMulticastThreads = multicastPingThread;
myWaitUserResponsesTimeout = waitUserResponsesTimeout;
String timeout = System.getProperty(SCAN_TIMEOUT_PROPERTY);
if (com.intellij.openapi.util.text.StringUtil.isEmptyOrSpaces(timeout)) {
myScansTimeout = TIMEOUT_BETWEEN_SCANS;
}
else {
try {
myScansTimeout = Long.parseLong(timeout) * Time.SECOND;
}
catch (NumberFormatException e) {
LOG.error("Invalid timeout for interval between scans: " + SCAN_TIMEOUT_PROPERTY + '=' + timeout);
throw e;
}
}
}
private static MulticastPingThread[] createMulticastThreads(P2PTransport client) {
List<MulticastPingThread> result = new ArrayList<>();
for (InetAddress selfAddress : NetworkUtil.getSelfAddresses()) {
result.add(new MulticastPingThread(selfAddress, client.getIdeFacade(), client));
}
return result.toArray(new MulticastPingThread[result.size()]);
}
public void shutdown() {
shutdownMulticastThreads();
if (isRunning()) {
final Thread thr = myThread;
myThread = null;
synchronized(myLock) {
myLock.notifyAll();
}
if (thr.isAlive()) {
thr.interrupt();
}
}
}
private void shutdownMulticastThreads() {
for (MulticastPingThread multicastThread : myMulticastThreads) {
if (multicastThread.isAlive()) {
multicastThread.shutdown();
}
}
}
@Override
public void run() {
super.run();
LOG.info("Start " + getName());
startupMulticastThreads();
myThread = Thread.currentThread();
while (isRunning()) {
try {
waitForNextSearch();
if (!isRunning()) return;
synchronized (myLock) {
myStartFindingAt = System.currentTimeMillis();
LOG.debug("Start finding users ");
}
try {
sendMulticastRequests(getListeningThreads());
//noinspection BusyWait
Thread.sleep(myWaitUserResponsesTimeout);
flushOnlineUsers();
}
finally {
synchronized (myLock) {
myStartFindingAt = 0;
LOG.debug("Done finding users. Timeout for " + myScansTimeout);
}
}
}
catch (UnknownHostException e) {
LOG.error(e.getMessage(), e);
}
catch (NoRouteToHostException e) {
LOG.info(e.getMessage(), e);
}
catch (IOException e) {
LOG.error(e.getMessage(), e);
}
catch (InterruptedException ignored) {
myThread = null;
}
catch (Throwable e) {
LOG.error(e.getMessage(), e);
myThread = null;
}
}
LOG.info("Shut down");
}
boolean isRunning() {
return myThread != null;
}
private static void sendMulticastRequests(List<MulticastPingThread> listeningThreads) throws IOException {
for (MulticastPingThread thread : listeningThreads) {
thread.sendMulticastPingRequest();
}
}
private List<MulticastPingThread> getListeningThreads() {
List<MulticastPingThread> result = new ArrayList<>();
for (MulticastPingThread multicastThread : myMulticastThreads) {
if (multicastThread.isAlive()) {
result.add(multicastThread);
}
}
return result;
}
private void startFindingUsers() {
synchronized (myLock) {
myStartFindingAt = System.currentTimeMillis();
myAvailableUsers.clear();
}
}
private void startupMulticastThreads() {
for (MulticastPingThread multicastThread : myMulticastThreads) {
multicastThread.start();
}
new WaitFor(Time.SECOND) {
@Override
protected boolean condition() {
for (MulticastPingThread multicastThread : myMulticastThreads) {
if (!multicastThread.isStarted()) return false;
}
return true;
}
};
}
private void waitForNextSearch() {
try {
synchronized (myLock) {
while (isRunning() && !isFinding()) {
myLock.wait(myScansTimeout);
startFindingUsers();
}
}
}
catch (InterruptedException ignored) {
myThread = null;
}
}
public void addOnlineUser(String remoteAddress, String remoteUsername, Integer remotePort, Collection<String> projects, UserPresence presence) {
try {
if (LOG.isDebugEnabled()) {
LOG.debug("Got Online Response from " + remoteUsername + " at " + remoteAddress + '/' + remotePort);
}
OnlineUserInfo onlineUserInfo = new OnlineUserInfo(InetAddress.getByName(remoteAddress), remotePort.intValue(), projects, presence);
if (!onlineUserInfo.getAddress().isLoopbackAddress() || Pico.isUnitTest()) {
myAvailableUsers.add(myClient.createUser(remoteUsername, onlineUserInfo));
}
}
catch (UnknownHostException ignored) {
LOG.info("Unable to find host for " + remoteAddress + ", user " + remoteUsername);
}
}
boolean isFinding() {
synchronized(myLock) {
return isRunning() && myStartFindingAt > 0;
}
}
void flushOnlineUsers() {
Set<User> users;
synchronized (myAvailableUsers) {
users = new THashSet<>(myAvailableUsers);
}
if (LOG.isDebugEnabled()) {
LOG.debug("Setting online users: \n" + Arrays.toString(users.toArray()));
}
myClient.setOnlineUsers(users);
}
public long getWaitUserResponsesTimeout() {
return myWaitUserResponsesTimeout;
}
public void findNow(ProgressIndicator progressIndicator) {
if (!isRunning()) return;
triggerFindNow();
while (isFinding()) {
progressIndicator.checkCanceled();
setIndicatorText(progressIndicator);
synchronized (myLock) {
double fraction = (System.currentTimeMillis() - myStartFindingAt) / (50.0 + myWaitUserResponsesTimeout);
progressIndicator.setFraction(fraction);
}
try {
//noinspection BusyWait
Thread.sleep(50);
}
catch (InterruptedException ignored) {
break;
}
}
progressIndicator.setFraction(1.0);
}
void triggerFindNow() {
if (!isFinding()) {
LOG.info("Force finding users");
synchronized(myLock) {
startFindingUsers();
myLock.notifyAll();
}
}
}
private void setIndicatorText(ProgressIndicator progressIndicator) {
int size = myAvailableUsers.size();
progressIndicator.setText(StringUtil.getMsg("p2p.finder.progressText", String.valueOf(size), StringUtil.getText("user", size)));
}
boolean _isAlive() {
for (MulticastPingThread multicastThread : myMulticastThreads) {
if (multicastThread.isRunning()) return true;
}
return super.isAlive();
}
}