package freenet.client.connection;
import com.google.gwt.user.client.Cookies;
import com.google.gwt.user.client.Timer;
import freenet.client.FreenetJs;
import freenet.client.UpdaterConstants;
import freenet.client.tools.Base64;
import freenet.client.tools.FreenetRequest;
import freenet.client.tools.QueryParameter;
import freenet.client.update.DefaultUpdateManager;
import freenet.client.update.IUpdateManager;
/** This ConnectionManager manages the notifications in a shared environment. It makes one leader, and it will send the notifications to others via cookies */
public class SharedConnectionManager implements IConnectionManager, IUpdateManager {
/** The keepalive cookie should be updated in this frequency */
private static final int sharedConnectionKeepaliveIntervalInMs = 1000;
/** The name of the keepalive cookie */
private static final String LEADER_KEEPALIVE = "LeaderKeepalive";
/** The name of the leader cookie */
private static final String LEADER_NAME = "LeaderName";
/** The name of the message counter cookie */
private static final String MESSAGE_COUNTER = "MessageCounter";
/** The prefix of the massage cookie's name */
private static final String MESSAGE_PREFIX = "Message";
/** Maximum number of messages cookies. It needs to be maximalized, because browsers handle just a limit number of cookies */
private static final int MAX_MESSAGES = 10;
/** The internal message counter */
private long messageCounter;
/** This ConnectionManager manages the actual connection to the server if this is the leader */
private LongPollingConnectionManager longPollingManager = new LongPollingConnectionManager(this);
/** If this request received a notification, this UpdateManager will be notified */
private IUpdateManager updateManager = null;
/** The timer that periodically checks if the leader is failing */
private Timer followerTakeOverTimer;
/** The timer that checks for received messages via cookies */
private Timer followerNotifierTimer;
public SharedConnectionManager(IUpdateManager updateManager) {
this.updateManager = updateManager;
followerTakeOverTimer = new Timer() {
@Override
public void run() {
// Checks if the leader keepalive cookie was updated not long ago
if ((Long.parseLong(Cookies.getCookie(LEADER_KEEPALIVE)) + sharedConnectionKeepaliveIntervalInMs * 3) < getTime()) {
// If it isn't updated for a while, then getting the leadership
FreenetJs.log("Getting leadership lastKeepalive:" + Cookies.getCookie(LEADER_KEEPALIVE));
// Cancells the follower timers
followerTakeOverTimer.cancel();
followerNotifierTimer.cancel();
// The old leader's name
String originalLeader = Cookies.getCookie(LEADER_NAME);
if (originalLeader != null) {
// If there was an old leader, then notifies the server about the takeover
FreenetRequest.sendRequest(UpdaterConstants.failoverPath, new QueryParameter[] { new QueryParameter("requestId", FreenetJs.requestId),
new QueryParameter("originalRequestId", originalLeader) });
}
// Starts leading
startLeading();
}
}
};
followerNotifierTimer = new Timer() {
@Override
public void run() {
// Process cookie messages every now and then
processMessages();
}
};
// Sets the message counter
if (Cookies.getCookie(MESSAGE_COUNTER) != null) {
messageCounter = Long.parseLong(Cookies.getCookie(MESSAGE_COUNTER));
} else {
messageCounter = 0;
}
FreenetJs.log("messageCounter initial value "+messageCounter);
}
@Override
public void closeConnection() {
stopLeading();
longPollingManager.closeConnection();
}
@Override
public void openConnection() {
if (updateManager == null) {
throw new RuntimeException("You must set the UpdateManager before opening the connection!");
}
// If there is no leader yet or the keepalive was not updated for a while, then it will take the lead
if (Cookies.getCookie(LEADER_NAME) == null || Cookies.getCookie(LEADER_NAME).trim().compareTo("") == 0 || (Cookies.getCookie(LEADER_KEEPALIVE) == null || (Long.parseLong(Cookies.getCookie(LEADER_KEEPALIVE)) + sharedConnectionKeepaliveIntervalInMs * 3) < getTime())) {
startLeading();
} else {
// If there is a leader, then it starts the follower timers
followerTakeOverTimer.scheduleRepeating(sharedConnectionKeepaliveIntervalInMs * 3);
followerNotifierTimer.scheduleRepeating(100);
}
}
/** Starts leading */
private void startLeading() {
FreenetJs.log("Starting leading");
// Sets the cookies for other tabs
Cookies.setCookie(LEADER_NAME, FreenetJs.requestId, null, null, "/", false);
Cookies.setCookie(LEADER_KEEPALIVE, "" + getTime(), null, null, "/", false);
// Process messages, so there are no messages unprocessed
processMessages();
// Opens a connection to the server
longPollingManager.openConnection();
// Starts a timer to update the leader keepalive periodically
new Timer() {
public void run() {
Cookies.setCookie(LEADER_KEEPALIVE, "" + getTime(), null, null, "/", false);
FreenetJs.log("Setting leader keepalive:" + Cookies.getCookie(LEADER_KEEPALIVE));
};
}.scheduleRepeating(sharedConnectionKeepaliveIntervalInMs);
}
/** Stops leading */
private void stopLeading() {
FreenetJs.log("Stopping leading");
// It just sets the leader name to null, so another tab can take leadership
Cookies.setCookie(LEADER_NAME, "", null, null, "/", false);
}
@Override
public void updated(String message) {
// The requestId that is notified
int idx = message.indexOf(UpdaterConstants.SEPARATOR);
String requestId = Base64.decode(message.substring(0, idx));
// The message
String msg = message.substring(idx + 1);
FreenetJs.log("SharedConnectionManagaer updated:requestId:" + requestId);
// Checks if this tab is the destination of the message
if (requestId.compareTo(FreenetJs.requestId) == 0) {
// If it is, then call the UpdateManager
updateManager.updated(msg);
FreenetJs.log("Updating");
} else {
// If not, then set a message and increase the counter, so other tabs will read it
FreenetJs.log("Setting cookie: name:" + MESSAGE_PREFIX + (messageCounter % MAX_MESSAGES) + " value:" + message);
Cookies.setCookie(MESSAGE_PREFIX + (messageCounter % MAX_MESSAGES), message, null, null, "/", false);
++messageCounter;
FreenetJs.log("Setting message counter:" + messageCounter);
Cookies.setCookie(MESSAGE_COUNTER, "" + messageCounter, null, null, "/", false);
}
// Sets the leader keepalive. It may not be needed
Cookies.setCookie(LEADER_KEEPALIVE, "" + getTime(), null, null, "/", false);
}
/** Processes the messages in the cookies */
private void processMessages() {
FreenetJs.log("Processing messages");
if (Cookies.getCookie(MESSAGE_COUNTER) == null) {
// If there is no message counter set, then there are no messages
return;
}
FreenetJs.log("Message counter set, value:" + Cookies.getCookie(MESSAGE_COUNTER) + " internal message counter:" + messageCounter);
// Cycle until all messages are processed
while (messageCounter < Long.parseLong(Cookies.getCookie(MESSAGE_COUNTER))) {
FreenetJs.log("Inside the loop: internal counter:" + messageCounter + " cookie counter:" + Cookies.getCookie(MESSAGE_COUNTER));
String message = Cookies.getCookie(MESSAGE_PREFIX + (messageCounter % MAX_MESSAGES));
FreenetJs.log("Got messsage:" + message);
String requestId = Base64.decode(message.substring(0, message.indexOf(UpdaterConstants.SEPARATOR)));
FreenetJs.log("Got requestId:" + requestId);
String msg = message.substring(message.indexOf(UpdaterConstants.SEPARATOR) + 1);
FreenetJs.log("Processing message:requestId:" + requestId + " msg:" + msg);
if (requestId.compareTo(FreenetJs.requestId) == 0) {
// If a message is found destined to this request, then call the UpdateManager
updateManager.updated(msg);
}
messageCounter++;
}
FreenetJs.log("Finished processing messages");
}
/**
* Returns the time. It may be needed to provide smaller numbers
*
* @return The time
*/
private long getTime() {
return System.currentTimeMillis();
}
}