package greenfoot.export.mygame;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.entity.mime.FormBodyPart;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import bluej.Config;
import greenfoot.event.PublishEvent;
import greenfoot.event.PublishListener;
| MyGame client.
|
| @author Davin McCall
|
public class MyGameClient
{
private PublishListener listener;
| Construct a MyGameClient instance, which issues updates/error responses to a specified listener.
|
| @param listener The listener to receive progress update / error notifications
|
public MyGameClient(PublishListener listener)
{
this.listener = listener;
LogFactory.getFactory().setAttribute("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.NoOpLog");
}
public final MyGameClient submit(String hostAddress, String uid, String password,
String jarFileName, File sourceFile, File screenshotFile, int width, int height,
ExportInfo info)
throws IOException
{
DefaultHttpClient httpClient = getHttpClient();
HttpConnectionParams.setConnectionTimeout(httpClient.getParams(), 20 * 1000);
HttpPost postMethod = new HttpPost(hostAddress + "account/authenticate");
MultipartEntity postParams = new MultipartEntity();
Charset utf8 = StandardCharsets.UTF_8;
postParams.addPart(new FormBodyPart("user[username]", new StringBody(uid, utf8)));
postParams.addPart(new FormBodyPart("user[password]", new StringBody(password, utf8)));
postMethod.setEntity(postParams);
HttpResponse httpResponse = httpClient.execute(postMethod);
int response = httpResponse.getStatusLine().getStatusCode();
if (response == 407 && listener != null) {
String[] authDetails = listener.needProxyAuth();
if (authDetails != null) {
Object defProxy = httpClient.getParams().getParameter(ConnRoutePNames.DEFAULT_PROXY);
if (defProxy instanceof HttpHost) {
HttpHost proxy = (HttpHost) defProxy;
AuthScope authScope = new AuthScope(proxy.getHostName(), proxy.getPort());
Credentials proxyCreds =
new UsernamePasswordCredentials(authDetails[0], authDetails[1]);
httpClient.getCredentialsProvider().setCredentials(authScope, proxyCreds);
httpResponse = httpClient.execute(postMethod);
response = httpResponse.getStatusLine().getStatusCode();
}
}
}
if (response > 400) {
error(Config.getString("export.publish.errorResponse") + " - " + response);
httpClient.getConnectionManager().shutdown();
return this;
}
if (! handleResponse(httpResponse)) {
httpClient.getConnectionManager().shutdown();
return this;
}
EntityUtils.consume(httpResponse.getEntity());
List<String> tagsList = info.getTags();
boolean hasSource = sourceFile != null;
Map<String, String> partsMap = new HashMap<>();
if (info.isUpdate())
{
partsMap.put("scenario[update_description]", info.getUpdateDescription());
}
else
{
partsMap.put("scenario[long_description]", info.getLongDescription());
partsMap.put("scenario[short_description]", info.getShortDescription());
}
MultipartEntity mpe = new MultipartEntity();
mpe.addPart(new FormBodyPart("scenario[title]", new StringBody(info.getTitle(), utf8)));
mpe.addPart(new FormBodyPart("scenario[main_class]",
new StringBody("greenfoot.export.GreenfootScenarioViewer", utf8)));
mpe.addPart(new FormBodyPart("scenario[width]", new StringBody("" + width, utf8)));
mpe.addPart(new FormBodyPart("scenario[height]", new StringBody("" + height, utf8)));
mpe.addPart(new FormBodyPart("scenario[url]", new StringBody(info.getUrl(), utf8)));
mpe.addPart(new ProgressTrackingPart("scenario[uploaded_data]", new File(jarFileName), this));
for (String key : partsMap.keySet())
{
mpe.addPart(new FormBodyPart(key, new StringBody(partsMap.get(key), utf8)));
}
if (hasSource)
{
mpe.addPart(new ProgressTrackingPart("scenario[source_data]", sourceFile, this));
}
if (screenshotFile!= null)
{
mpe.addPart(new ProgressTrackingPart("scenario[screenshot_data]", screenshotFile, this));
}
int tagNum = 0;
for (String tag : tagsList)
{
mpe.addPart(new FormBodyPart("scenario[tag" + tagNum++ + "]", new StringBody(tag, utf8)));
}
postMethod = new HttpPost(hostAddress + "upload-scenario");
postMethod.setEntity(mpe);
httpResponse = httpClient.execute(postMethod);
response = httpResponse.getStatusLine().getStatusCode();
if (response > 400)
{
error(Config.getString("export.publish.errorResponse") + " - " + response);
httpClient.getConnectionManager().shutdown();
return this;
}
if (!handleResponse(httpResponse))
{
httpClient.getConnectionManager().shutdown();
return this;
}
EntityUtils.consume(httpResponse.getEntity());
listener.uploadComplete(new PublishEvent(PublishEvent.STATUS));
httpClient.getConnectionManager().shutdown();
return this;
}
| Checks the result of of the given method. Should only be called after the
| postMethod has been executed.
|
| If the check is successful the method will return true. Otherwise it will
| print an error message and return false.
|
| @param postMethod The method to check the result for.
| @return True if the execution was successful, false otherwise.
|
private boolean handleResponse(HttpResponse postMethod)
{
Header statusHeader = postMethod.getLastHeader("X-mygame-status");
if (statusHeader == null)
{
error(Config.getString("export.publish.errorResponse"));
return false;
}
String responseString = statusHeader.getValue();
int spaceIndex = responseString.indexOf(" ");
if (spaceIndex == -1)
{
error(Config.getString("export.publish.errorResponse"));
return false;
}
try
{
int statusCode = Integer.parseInt(responseString.substring(0, spaceIndex));
switch(statusCode)
{
case 0 :
return true;
case 1 :
error(Config.getString("export.publish.errorPassword"));
return false;
case 2 :
error(Config.getString("export.publish.errorTooLarge"));
return false;
default :
error(responseString.substring(spaceIndex + 1));
return false;
}
}
catch (NumberFormatException nfe)
{
error(Config.getString("export.publish.errorResponse"));
return false;
}
}
| Get a http client, configured to use the proxy if specified in Greenfoot config
|
private DefaultHttpClient getHttpClient()
{
HttpParams params = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(params, 20 * 1000);
DefaultHttpClient httpClient = new DefaultHttpClient(params);
String proxyHost = Config.getPropString("proxy.host", null);
String proxyPortStr = Config.getPropString("proxy.port", null);
if (proxyHost != null && proxyHost.length() != 0 && proxyPortStr != null)
{
int proxyPort = 80;
try
{
proxyPort = Integer.parseInt(proxyPortStr);
}
catch (NumberFormatException nfe) {
}
HttpHost proxy = new HttpHost(proxyHost, proxyPort, "http");
httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
String proxyUser = Config.getPropString("proxy.user", null);
String proxyPass = Config.getPropString("proxy.password", null);
if (proxyUser != null)
{
AuthScope authScope = new AuthScope(proxyHost, proxyPort);
Credentials proxyCreds = new UsernamePasswordCredentials(proxyUser, proxyPass);
httpClient.getCredentialsProvider().setCredentials(authScope, proxyCreds);
}
}
return httpClient;
}
| Check whether a pre-existing scenario with the given title exists. Returns true
| if the scenario exists or false if not.
|
| @param hostAddress The game server address
| @param uid The username of the user
| @param gameName The scenario title
| @param info If not null, this will on return have the title, short description,
| long description and tags set to whatever the existing scenario has.
| @return true if the scenario exists on the server
|
public boolean checkExistingScenario(String hostAddress, String uid,
String gameName, ScenarioInfo info)
throws IOException
{
HttpClient client = getHttpClient();
HttpConnectionParams.setConnectionTimeout(client.getParams(), 20 * 1000);
String encodedName = URLEncoder.encode(gameName, "UTF-8");
encodedName = encodedName.replace("+", "%20");
HttpGet getMethod = new HttpGet(hostAddress +
"user/"+ uid + "/check_scenario/" + encodedName);
HttpResponse httpResponse = client.execute(getMethod);
int response = httpResponse.getStatusLine().getStatusCode();
if (response > 400)
{
throw new IOException("HTTP error response " + response + " from server.");
}
Header statusHeader = httpResponse.getLastHeader("X-mygame-scenario");
if (statusHeader == null)
{
throw new IOException("X-mygame-scenario header missing from server response");
}
else if (!statusHeader.getValue().equals("0 FOUND"))
{
return false;
}
if (info != null)
{
InputStream responseStream = httpResponse.getEntity().getContent();
parseScenarioXml(info, responseStream);
info.setTitle(gameName);
}
return true;
}
private void parseScenarioXml(ScenarioInfo info, InputStream xmlStream)
throws IOException
{
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder dbuilder = dbf.newDocumentBuilder();
Document doc = dbuilder.parse(xmlStream);
Element root = doc.getDocumentElement();
if (root == null || !root.getTagName().equals("scenario")) {
return;
}
NodeList children = root.getChildNodes();
for (int i = 0; i < children.getLength(); i++)
{
Node childNode = children.item(i);
if (childNode.getNodeType() == Node.ELEMENT_NODE)
{
Element element = (Element) childNode;
switch (element.getTagName())
{
case "shortdescription":
info.setShortDescription(element.getTextContent());
break;
case "longdescription":
info.setLongDescription(element.getTextContent());
break;
case "taglist":
info.setTags(parseTagListXmlElement(element));
break;
case "webpage":
info.setUrl(element.getTextContent());
break;
case "hassource":
info.setIncludeSource(element.getTextContent().equals("true"));
break;
}
}
}
}
catch (ParserConfigurationException pce)
{
}
catch (SAXException saxe) {
}
}
private List parseTagListXmlElement(Element element)
{
List<String> tags = new ArrayList<>();
Node child = element.getFirstChild();
while (child != null)
{
if (child.getNodeType() == Node.ELEMENT_NODE)
{
element = (Element) child;
if (element.getTagName().equals("tag"))
{
tags.add(element.getTextContent());
}
}
child = child.getNextSibling();
}
return tags;
}
| Get a list of commonly used tags from the server.
|
| @throws UnknownHostException if the host is unknown
| @throws org.apache.http.conn.ConnectTimeoutException if the connection timed out
| @throws IOException if some other I/O exception occurs
|
public List getCommonTags(String hostAddress, int maxNumberOfTags)
throws UnknownHostException, IOException
{
HttpClient client = getHttpClient();
HttpGet getMethod = new HttpGet(hostAddress +
"common-tags/"+ maxNumberOfTags);
HttpResponse httpResponse = client.execute(getMethod);
int response = httpResponse.getStatusLine().getStatusCode();
if (response > 400) {
throw new IOException("HTTP error response " + response + " from server.");
}
try (InputStream responseStream = httpResponse.getEntity().getContent())
{
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
Document doc = documentBuilder.parse(responseStream);
Element root = doc.getDocumentElement();
if (root == null || !root.getTagName().equals("taglist"))
{
return Collections.emptyList();
}
return parseTagListXmlElement(root);
}
catch (SAXException | ParserConfigurationException ignore) {
}
return Collections.emptyList();
}
| An error occurred.
|
private void error(String s)
{
listener.errorRecieved(new PublishEvent(s, PublishEvent.ERROR));
}
| The specified number of bytes have just been sent.
|
public void progress(int bytes)
{
listener.progressMade(new PublishEvent(bytes, PublishEvent.PROGRESS));
}
| Prompt the user for proxy authentication details (username and password).
|
| @return A 2-element array with the username as the first element and the password as the second,
| or null if the user elected to cancel the upload.
|
public String[] promptProxyAuth()
{
return listener.needProxyAuth();
}
}
top,
use,
map,
class MyGameClient
. MyGameClient
. submit
. handleResponse
. getHttpClient
. checkExistingScenario
. parseScenarioXml
. parseTagListXmlElement
. getCommonTags
. error
. progress
. promptProxyAuth
561 neLoCode
+ 28 LoComm