package com.limegroup.gnutella.bugs;
import java.io.IOException;
import java.net.URLEncoder;
import com.limegroup.gnutella.util.FixedsizeForgetfulHashMap;
/**
* This class handles creating the bug information for client reporting
* the bug. It constructs the appropriate bug information based
* on the data supplied by the client, such as the operating system, the
* LimeWire version, etc.<p>
*
* This class is reconstructed on the client side by the
* <tt>RemoteClientInfo</tt> class.
*/
//2345678|012345678|012345678|012345678|012345678|012345678|012345678|012345678|
public final class RemoteServletInfo extends RemoteAbstractInfo {
/**
* The instance to use for constructing response strings.
*/
private static final RemoteServletInfo INSTANCE = new RemoteServletInfo();
/**
* Map of recently seen stack traces.
* Each stack trace maps to an integer which represents
* the number of times we've seen this bug.
* A FixedsizeForgetfulHashMap is used so that only recently reported
* bugs are remembered. Those reported less frequently will eventually
* fall out of the map.
*
* The information is used to tell the host reporting the bug to not
* respond with this particular bug again for a long time period.
*
* LOCKING: Obtain this' monitor.
*/
private final FixedsizeForgetfulHashMap /* String -> Integer */ BUGS =
new FixedsizeForgetfulHashMap(TOTAL_BUGS);
/**
* The number of bugs to keep in the above map.
*/
private static final int TOTAL_BUGS = 100;
/**
* The amount of times we'll read a bug before telling them to stop
* reporting it.
*/
private static final int BUG_CUTOFF = 3;
/**
* The time, in milliseconds (as a string), that we'll tell people
* to wait before sending us another bug if the count was over
* the cutoff. (1 day)
*/
private static final String WAIT_TIME = "" + (24 * 60 * 60 * 1000);
/**
* The time, in milliseconds (as a string), that we'll tell people
* to wait before sending any bug after a single bug is sent.
* (10 minutes)
*/
private static final String ANY_TIME = "" + (10 * 60 * 1000);
/**
* The 'never' time to use for older clients that we just want to say:
* "SHUT UP" to. (1 year)
*/
private static final String SHUT_UP = "" + (365 * 24 * 60 * 60 * 1000);
/**
* The response to send back to these clients.
*/
private static final String OLD_RESPONSE =
NEXT_THIS_BUG_TIME + "=" + SHUT_UP + "&" +
NEXT_ANY_BUG_TIME + "=" + SHUT_UP;
/**
* The last version whose bug reports we'll accept without telling the
* client to be quiet.
*/
private static final int MAJOR_VERSION = 4;
private static final int MINOR_VERSION = 9;
private static final int SERVICE_VERSION = 30;
/**
* The only instance of this class to use.
*/
public static RemoteServletInfo instance() {
return INSTANCE;
}
/**
* Generates the appropriate response based on the information supplied
* by the client reporting the bug.
*
* Returns a string in url encoding containing the data for the remote
* remote update.<p>
*
* @param localInfo the <tt>LocalServletInfo</tt> instance
* containing data about the client reporting the bug
*
* @return an url-encoded <tt>String</tt> containing all of the
* necessary fields for responding to the bug report
*/
public String getURLEncodedString(LocalServletInfo localInfo)
throws IOException {
String version = localInfo.getLimeWireVersion();
String os = localInfo.getOS();
if(version == null || version.equals(""))
throw new IOException("invalid version");
if(os == null || os.equals(""))
throw new IOException("invalid operating system");
if( isOldClient(localInfo) )
return OLD_RESPONSE;
String nextThisBugTime;
String nextAnyBugTime;
synchronized(this) {
String bug = localInfo.getParsedBug();
Integer count = (Integer)BUGS.get(bug);
nextAnyBugTime = ANY_TIME;
if( count == null ) { // first time, insert.
BUGS.put(bug, new Integer(1));
nextThisBugTime = "0";
} else {
int newCount = count.intValue() + 1;
BUGS.put(bug, new Integer(newCount));
if( newCount >= BUG_CUTOFF ) {
nextThisBugTime = WAIT_TIME;
} else {
nextThisBugTime = "0";
}
}
}
StringBuffer sb = new StringBuffer();
append(sb, NEXT_THIS_BUG_TIME, nextThisBugTime);
append(sb, NEXT_ANY_BUG_TIME, nextAnyBugTime);
sb.setLength(sb.length() - 1);
return sb.toString();
}
/**
* Appends 'k=URLEncoder.encode(v)&' to sb if v is non-null.
*/
private final void append(StringBuffer sb, final String k, final String v) {
if( v != null ) {
sb.append(k);
sb.append("=");
sb.append(URLEncoder.encode(v));
sb.append("&");
}
}
/**
* Determines whether or not the specified client is considered 'old'.
*/
public static final boolean isOldClient(LocalServletInfo info) {
final String vers = info.getLimeWireVersion();
final String os = info.getOS();
int major, minor, service;
int dot1, dot2;
// too many bugs being reported under @version@.
if(vers.equals("@version@"))
return true;
// unsolvable.
if(os.equals("Mac OS"))
return true;
dot1 = vers.indexOf(".");
if(dot1 == -1)
return true; // unknown, tell'm to go away.
dot2 = vers.indexOf(".", dot1 + 1);
if(dot2 == -1)
return true; // unknown, tell'm to go away too.
try {
major = Integer.parseInt(vers.substring(0, dot1));
} catch(NumberFormatException nfe) {
return true; // unknown again.
}
try {
minor = Integer.parseInt(vers.substring(dot1 + 1, dot2));
} catch(NumberFormatException nfe) {
return true; // unknown still.
}
try {
int q = dot2 + 1;
while(q < vers.length() && Character.isDigit(vers.charAt(q)))
q++;
service = Integer.parseInt(vers.substring(dot2 + 1, q));
} catch(NumberFormatException nfe) {
return true; // unknown.
}
if( major < MAJOR_VERSION )
return true;
if( major == MAJOR_VERSION ) {
if( minor < MINOR_VERSION )
return true;
if( minor == MINOR_VERSION ) {
if( service < SERVICE_VERSION )
return true;
}
}
return false;
}
}