mirror of
https://github.com/fergalmoran/chrometophone.git
synced 2025-12-22 09:41:51 +00:00
Few minor changes in preparation to the GCM migration:
- fixed warnings (like un-thrown exceptions) - fixed code that unregister in case of NotRegistered error - updated server code to use AppEngine 1.7 - changed code so it can be used in a local app engine server - added a new device type called gcm - created a servlet that displays how many devices are registered per type (ac2dm, chrome, gcm) - created a servlet used to send messages without the need for the chrome plugin
This commit is contained in:
@@ -4,5 +4,6 @@
|
||||
<classpathentry kind="src" path="gen"/>
|
||||
<classpathentry kind="src" path="c2dm"/>
|
||||
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
|
||||
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
|
||||
<classpathentry kind="output" path="bin/classes"/>
|
||||
</classpath>
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
# This file is automatically generated by Android Tools.
|
||||
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
|
||||
#
|
||||
# This file must be checked in Version Control Systems.
|
||||
#
|
||||
# To customize properties used by the Ant build system use,
|
||||
# "build.properties", and override values to adapt the script to your
|
||||
# project structure.
|
||||
|
||||
# Indicates whether an apk should be generated for each density.
|
||||
split.density=false
|
||||
# Project target.
|
||||
target=android-11
|
||||
@@ -29,6 +29,7 @@ import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.params.HttpClientParams;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.apache.http.params.BasicHttpParams;
|
||||
import org.apache.http.params.HttpParams;
|
||||
|
||||
@@ -46,6 +47,14 @@ import android.util.Log;
|
||||
*/
|
||||
public class AppEngineClient {
|
||||
static final String BASE_URL = "https://chrometophone.appspot.com";
|
||||
/*
|
||||
* When running AppEngine locally, set BASE_LOCAL_URL with your server's address.
|
||||
* (make sure to start AppEngine passing the -a server_address flag, otherwise it will run on
|
||||
* localhost and the device won't be able to connect.
|
||||
*/
|
||||
// static final String BASE_LOCAL_URL = null;
|
||||
// TODO: tmp
|
||||
static final String BASE_LOCAL_URL = "http://snpp.mtv.corp.google.com:8888";
|
||||
private static final String AUTH_URL = BASE_URL + "/_ah/login";
|
||||
private static final String AUTH_TOKEN_TYPE = "ah";
|
||||
|
||||
@@ -79,10 +88,15 @@ public class AppEngineClient {
|
||||
authToken = getAuthToken(mContext, account);
|
||||
}
|
||||
|
||||
// Get ACSID cookie
|
||||
DefaultHttpClient client = new DefaultHttpClient();
|
||||
String baseUrl;
|
||||
URI uri;
|
||||
String ascidCookie = null;
|
||||
HttpResponse res;
|
||||
if (BASE_LOCAL_URL == null) {
|
||||
// Get ACSID cookie so it can be used to authenticate on AppEngine
|
||||
String continueURL = BASE_URL;
|
||||
URI uri = new URI(AUTH_URL + "?continue=" +
|
||||
uri = new URI(AUTH_URL + "?continue=" +
|
||||
URLEncoder.encode(continueURL, "UTF-8") +
|
||||
"&auth=" + authToken);
|
||||
HttpGet method = new HttpGet(uri);
|
||||
@@ -90,14 +104,13 @@ public class AppEngineClient {
|
||||
HttpClientParams.setRedirecting(getParams, false); // continue is not used
|
||||
method.setParams(getParams);
|
||||
|
||||
HttpResponse res = client.execute(method);
|
||||
res = client.execute(method);
|
||||
Header[] headers = res.getHeaders("Set-Cookie");
|
||||
if (res.getStatusLine().getStatusCode() != 302 ||
|
||||
headers.length == 0) {
|
||||
return res;
|
||||
}
|
||||
|
||||
String ascidCookie = null;
|
||||
for (Header header: headers) {
|
||||
if (header.getValue().indexOf("ACSID=") >=0) {
|
||||
// let's parse it
|
||||
@@ -106,14 +119,22 @@ public class AppEngineClient {
|
||||
ascidCookie = pairs[0];
|
||||
}
|
||||
}
|
||||
baseUrl = BASE_URL;
|
||||
} else {
|
||||
// local app server, pass user directly
|
||||
baseUrl = BASE_LOCAL_URL;
|
||||
params.add(new BasicNameValuePair("account", account.name));
|
||||
}
|
||||
|
||||
// Make POST request
|
||||
uri = new URI(BASE_URL + urlPath);
|
||||
uri = new URI(baseUrl + urlPath);
|
||||
HttpPost post = new HttpPost(uri);
|
||||
UrlEncodedFormEntity entity =
|
||||
new UrlEncodedFormEntity(params, "UTF-8");
|
||||
post.setEntity(entity);
|
||||
if (ascidCookie != null) {
|
||||
post.setHeader("Cookie", ascidCookie);
|
||||
}
|
||||
post.setHeader("X-Same-Domain", "1"); // XSRF
|
||||
res = client.execute(post);
|
||||
return res;
|
||||
|
||||
@@ -48,6 +48,7 @@ public class DeviceRegistrar {
|
||||
public static void registerWithServer(final Context context,
|
||||
final String deviceRegistrationID) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Intent updateUIIntent = new Intent("com.google.ctp.UPDATE_UI");
|
||||
try {
|
||||
@@ -83,6 +84,7 @@ public class DeviceRegistrar {
|
||||
public static void unregisterWithServer(final Context context,
|
||||
final String deviceRegistrationID) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Intent updateUIIntent = new Intent("com.google.ctp.UPDATE_UI");
|
||||
try {
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="src" path="c2dm"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="con" path="com.google.appengine.eclipse.core.GAE_CONTAINER/appengine-java-sdk-1.5.2"/>
|
||||
<classpathentry kind="con" path="com.google.appengine.eclipse.core.GAE_CONTAINER"/>
|
||||
<classpathentry kind="output" path="war/WEB-INF/classes"/>
|
||||
</classpath>
|
||||
|
||||
@@ -10,11 +10,6 @@
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.google.appengine.eclipse.core.enhancerbuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.google.appengine.eclipse.core.projectValidator</name>
|
||||
<arguments>
|
||||
@@ -25,6 +20,11 @@
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>com.google.appengine.eclipse.core.enhancerbuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#Sun Aug 21 01:00:53 BST 2011
|
||||
eclipse.preferences.version=1
|
||||
filesCopiedToWebInfLib=appengine-api-1.0-sdk-1.5.2.jar|appengine-api-labs-1.5.2.jar|appengine-jsr107cache-1.5.2.jar|jsr107cache-1.1.jar|datanucleus-appengine-1.0.9.final.jar|datanucleus-core-1.1.5.jar|datanucleus-jpa-1.1.5.jar|geronimo-jpa_3.0_spec-1.1.1.jar|geronimo-jta_1.1_spec-1.1.1.jar|jdo2-api-2.3-eb.jar
|
||||
filesCopiedToWebInfLib=appengine-api-labs.jar|appengine-endpoints.jar|appengine-jsr107cache-1.7.0.jar|jsr107cache-1.1.jar|appengine-api-1.0-sdk-1.7.0.jar|datanucleus-core-1.1.5.jar|jdo2-api-2.3-eb.jar|geronimo-jta_1.1_spec-1.1.1.jar|geronimo-jpa_3.0_spec-1.1.1.jar|datanucleus-jpa-1.1.5.jar|datanucleus-appengine-1.0.10.final.jar
|
||||
ormEnhancementInclusions=src/**|c2dm/**
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package com.google.android.c2dm.server;
|
||||
|
||||
import com.google.appengine.api.datastore.Key;
|
||||
import com.google.appengine.api.datastore.KeyFactory;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -26,14 +29,12 @@ import javax.jdo.JDOObjectNotFoundException;
|
||||
import javax.jdo.PersistenceManager;
|
||||
import javax.jdo.PersistenceManagerFactory;
|
||||
|
||||
import com.google.appengine.api.datastore.Key;
|
||||
import com.google.appengine.api.datastore.KeyFactory;
|
||||
|
||||
/**
|
||||
* Stores config information related to data messaging.
|
||||
*
|
||||
*/
|
||||
public class C2DMConfigLoader {
|
||||
private static final String TOKEN_FILE = "/dataMessagingToken.txt";
|
||||
private final PersistenceManagerFactory PMF;
|
||||
private static final Logger log = Logger.getLogger(C2DMConfigLoader.class.getName());
|
||||
|
||||
@@ -72,8 +73,6 @@ public class C2DMConfigLoader {
|
||||
/**
|
||||
* Return the auth token from the database. Should be called
|
||||
* only if the old token expired.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getToken() {
|
||||
if (currentToken == null) {
|
||||
@@ -110,12 +109,19 @@ public class C2DMConfigLoader {
|
||||
dmConfig.setKey(key);
|
||||
// Must be in classpath, before sending. Do not checkin !
|
||||
try {
|
||||
InputStream is = C2DMConfigLoader.class.getClassLoader().getResourceAsStream("/dataMessagingToken.txt");
|
||||
InputStream is = C2DMConfigLoader.class.getClassLoader().getResourceAsStream(TOKEN_FILE);
|
||||
String token;
|
||||
if (is != null) {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
||||
String token = reader.readLine();
|
||||
dmConfig.setAuthToken(token);
|
||||
token = reader.readLine();
|
||||
} else {
|
||||
// happens on developement: delete entity from viewer, change
|
||||
// token below, and run it again
|
||||
log.log(Level.WARNING, "File " + TOKEN_FILE +
|
||||
" not found on classpath, using hardcoded token");
|
||||
token = "please_change_me";
|
||||
}
|
||||
dmConfig.setAuthToken(token);
|
||||
} catch (Throwable t) {
|
||||
log.log(Level.SEVERE,
|
||||
"Can't load initial token, use admin console", t);
|
||||
|
||||
@@ -20,7 +20,6 @@ import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@@ -48,7 +47,7 @@ public class C2DMRetryServlet extends HttpServlet {
|
||||
*/
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws ServletException, IOException {
|
||||
throws IOException {
|
||||
|
||||
String registrationId = req.getParameter(C2DMessaging.PARAM_REGISTRATION_ID);
|
||||
String retryCount = req.getHeader(RETRY_COUNT);
|
||||
@@ -61,6 +60,7 @@ public class C2DMRetryServlet extends HttpServlet {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String[]> params = req.getParameterMap();
|
||||
String collapse = req.getParameter(C2DMessaging.PARAM_COLLAPSE_KEY);
|
||||
boolean delayWhenIdle =
|
||||
|
||||
@@ -34,10 +34,10 @@ import javax.jdo.PersistenceManagerFactory;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.google.appengine.api.labs.taskqueue.Queue;
|
||||
import com.google.appengine.api.labs.taskqueue.QueueFactory;
|
||||
import com.google.appengine.api.labs.taskqueue.TaskHandle;
|
||||
import com.google.appengine.api.labs.taskqueue.TaskOptions;
|
||||
import com.google.appengine.api.taskqueue.Queue;
|
||||
import com.google.appengine.api.taskqueue.QueueFactory;
|
||||
import com.google.appengine.api.taskqueue.TaskHandle;
|
||||
import com.google.appengine.api.taskqueue.TaskOptions;
|
||||
|
||||
/**
|
||||
*/
|
||||
@@ -243,8 +243,7 @@ public class C2DMessaging {
|
||||
|
||||
public boolean sendNoRetry(String token, String collapseKey,
|
||||
String name1, String value1, String name2, String value2,
|
||||
String name3, String value3)
|
||||
throws IOException {
|
||||
String name3, String value3) {
|
||||
|
||||
Map<String, String[]> params = new HashMap<String, String[]>();
|
||||
if (value1 != null) params.put("data." + name1, new String[] {value1});
|
||||
@@ -258,9 +257,15 @@ public class C2DMessaging {
|
||||
}
|
||||
}
|
||||
|
||||
private static final ThreadLocal<IOException> C2DM_EXCEPTION =
|
||||
new ThreadLocal<IOException>();
|
||||
|
||||
public static IOException getC2dmException() {
|
||||
return C2DM_EXCEPTION.get();
|
||||
}
|
||||
|
||||
public boolean sendNoRetry(String token, String collapseKey,
|
||||
String... nameValues)
|
||||
throws IOException {
|
||||
String... nameValues) {
|
||||
|
||||
Map<String, String[]> params = new HashMap<String, String[]>();
|
||||
int len = nameValues.length;
|
||||
@@ -276,8 +281,12 @@ public class C2DMessaging {
|
||||
}
|
||||
|
||||
try {
|
||||
C2DM_EXCEPTION.remove();
|
||||
return sendNoRetry(token, collapseKey, params, true);
|
||||
} catch (IOException ex) {
|
||||
// save exception in a thread-local object so it can be cheked for
|
||||
// unregistration later
|
||||
C2DM_EXCEPTION.set(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -287,7 +296,7 @@ public class C2DMessaging {
|
||||
Queue dmQueue = QueueFactory.getQueue("c2dm");
|
||||
try {
|
||||
TaskOptions url =
|
||||
TaskOptions.Builder.url(C2DMRetryServlet.URI)
|
||||
TaskOptions.Builder.withUrl(C2DMRetryServlet.URI)
|
||||
.param(C2DMessaging.PARAM_REGISTRATION_ID, token)
|
||||
.param(C2DMessaging.PARAM_COLLAPSE_KEY, collapseKey);
|
||||
if (delayWhileIdle) {
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
*/
|
||||
package com.google.android.chrometophone.server;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@@ -11,7 +9,7 @@ import javax.servlet.http.HttpServletResponse;
|
||||
public class DebugServlet extends HttpServlet {
|
||||
|
||||
@Override
|
||||
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
|
||||
public void doGet(HttpServletRequest req, HttpServletResponse resp) {
|
||||
// Nothing, we're just looking for logs to find response times and delivery
|
||||
// confirmation.
|
||||
// TODO: use memcache to dynamically get statistics.
|
||||
|
||||
@@ -16,9 +16,15 @@
|
||||
|
||||
package com.google.android.chrometophone.server;
|
||||
|
||||
import com.google.appengine.api.datastore.Key;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.jdo.PersistenceManager;
|
||||
import javax.jdo.Query;
|
||||
@@ -27,8 +33,6 @@ import javax.jdo.annotations.PersistenceCapable;
|
||||
import javax.jdo.annotations.Persistent;
|
||||
import javax.jdo.annotations.PrimaryKey;
|
||||
|
||||
import com.google.appengine.api.datastore.Key;
|
||||
|
||||
/**
|
||||
* Registration info.
|
||||
*
|
||||
@@ -39,8 +43,12 @@ import com.google.appengine.api.datastore.Key;
|
||||
*/
|
||||
@PersistenceCapable(identityType = IdentityType.APPLICATION)
|
||||
public class DeviceInfo {
|
||||
private static final Logger log =
|
||||
Logger.getLogger(DeviceInfo.class.getName());
|
||||
|
||||
public static final String TYPE_AC2DM = "ac2dm";
|
||||
public static final String TYPE_CHROME = "chrome";
|
||||
public static final String TYPE_GCM = "gcm";
|
||||
|
||||
/**
|
||||
* User-email # device-id
|
||||
@@ -147,11 +155,11 @@ public class DeviceInfo {
|
||||
/**
|
||||
* Helper function - will query all registrations for a user.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<DeviceInfo> getDeviceInfoForUser(PersistenceManager pm, String user) {
|
||||
Query query = pm.newQuery(DeviceInfo.class);
|
||||
query.setFilter("key >= '" +
|
||||
user + "' && key < '" + user + "$'");
|
||||
@SuppressWarnings("unchecked")
|
||||
List<DeviceInfo> qresult = (List<DeviceInfo>) query.execute();
|
||||
// Copy to array - we need to close the query
|
||||
List<DeviceInfo> result = new ArrayList<DeviceInfo>();
|
||||
@@ -161,4 +169,27 @@ public class DeviceInfo {
|
||||
query.closeAll();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function - get number of devices registered by type.
|
||||
*/
|
||||
public static Map<String, Integer> getDevicesUsage(PersistenceManager pm) {
|
||||
Map<String, Integer> result = new HashMap<String, Integer>();
|
||||
Query query = pm.newQuery(DeviceInfo.class);
|
||||
addStats(query, result, TYPE_AC2DM);
|
||||
addStats(query, result, TYPE_GCM);
|
||||
addStats(query, result, TYPE_CHROME);
|
||||
query.closeAll();
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void addStats(Query query, Map<String, Integer> result, String type) {
|
||||
query.setResult("count(this)");
|
||||
query.setFilter("type == '" + type + "'");
|
||||
Integer total = (Integer) query.execute();
|
||||
log.log(Level.INFO, "Number of records of type {0}: {1}",
|
||||
new Object[] {type, total});
|
||||
result.put(type, total);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -33,9 +33,9 @@ import com.google.android.c2dm.server.C2DMessaging;
|
||||
import com.google.appengine.api.channel.ChannelServiceFactory;
|
||||
import com.google.appengine.api.datastore.Key;
|
||||
import com.google.appengine.api.datastore.KeyFactory;
|
||||
import com.google.appengine.repackaged.org.json.JSONArray;
|
||||
import com.google.appengine.repackaged.org.json.JSONException;
|
||||
import com.google.appengine.repackaged.org.json.JSONObject;
|
||||
import com.google.appengine.labs.repackaged.org.json.JSONArray;
|
||||
import com.google.appengine.labs.repackaged.org.json.JSONException;
|
||||
import com.google.appengine.labs.repackaged.org.json.JSONObject;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class RegisterServlet extends HttpServlet {
|
||||
|
||||
@@ -23,8 +23,8 @@ import com.google.appengine.api.oauth.OAuthServiceFactory;
|
||||
import com.google.appengine.api.users.User;
|
||||
import com.google.appengine.api.users.UserService;
|
||||
import com.google.appengine.api.users.UserServiceFactory;
|
||||
import com.google.appengine.repackaged.org.json.JSONException;
|
||||
import com.google.appengine.repackaged.org.json.JSONObject;
|
||||
import com.google.appengine.labs.repackaged.org.json.JSONException;
|
||||
import com.google.appengine.labs.repackaged.org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* Common code and helpers to handle a request and manipulate device info.
|
||||
@@ -112,7 +112,9 @@ public class RequestInfo {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
ri.parameterMap = req.getParameterMap();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String[]> castMap = req.getParameterMap();
|
||||
ri.parameterMap = castMap;
|
||||
}
|
||||
|
||||
ri.deviceRegistrationID = ri.getParameter("devregid");
|
||||
@@ -130,6 +132,15 @@ public class RequestInfo {
|
||||
return null;
|
||||
}
|
||||
|
||||
// check if account was really set on development environment
|
||||
if (ri.userName.endsWith("@example.com")) {
|
||||
String account = req.getParameter("account");
|
||||
if (account != null) {
|
||||
log.log(Level.INFO, "Using " + account + " instead of " + ri.userName);
|
||||
ri.userName = account;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx != null) {
|
||||
ri.initDevices(ctx);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
package com.google.android.chrometophone.server;
|
||||
|
||||
import com.google.android.c2dm.server.C2DMessaging;
|
||||
import com.google.appengine.api.channel.ChannelMessage;
|
||||
import com.google.appengine.api.channel.ChannelServiceFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -23,10 +27,6 @@ import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.google.android.c2dm.server.C2DMessaging;
|
||||
import com.google.appengine.api.channel.ChannelMessage;
|
||||
import com.google.appengine.api.channel.ChannelServiceFactory;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class SendServlet extends HttpServlet {
|
||||
static final Logger log =
|
||||
@@ -112,7 +112,6 @@ public class SendServlet extends HttpServlet {
|
||||
continue; // user-specified device type
|
||||
}
|
||||
|
||||
try {
|
||||
if (deviceInfo.getType().equals(DeviceInfo.TYPE_CHROME)) {
|
||||
res = doSendViaBrowserChannel(url, deviceInfo);
|
||||
} else {
|
||||
@@ -126,10 +125,9 @@ public class SendServlet extends HttpServlet {
|
||||
} else {
|
||||
log.warning("Error: Unable to send link to device: " +
|
||||
deviceInfo.getDeviceRegistrationID());
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
if ("NotRegistered".equals(ex.getMessage()) ||
|
||||
"InvalidRegistration".equals(ex.getMessage())) {
|
||||
IOException ex = C2DMessaging.getC2dmException();
|
||||
if (ex != null) {
|
||||
if ("InvalidRegistration".equals(ex.getMessage())) {
|
||||
// Prune device, it no longer works
|
||||
reqInfo.deleteRegistration(deviceInfo.getDeviceRegistrationID());
|
||||
reqInfo.devices.remove(deviceInfo);
|
||||
@@ -139,6 +137,7 @@ public class SendServlet extends HttpServlet {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
// TODO: return a count of devices we sent to, maybe names as well
|
||||
@@ -157,7 +156,7 @@ public class SendServlet extends HttpServlet {
|
||||
}
|
||||
|
||||
private boolean doSendViaC2dm(String url, String title, String sel, C2DMessaging push,
|
||||
String collapseKey, DeviceInfo deviceInfo, boolean reqDebug) throws IOException {
|
||||
String collapseKey, DeviceInfo deviceInfo, boolean reqDebug) {
|
||||
|
||||
// Trim title, sel if needed.
|
||||
if (url.length() + title.length() + sel.length() > 1000) {
|
||||
@@ -176,6 +175,7 @@ public class SendServlet extends HttpServlet {
|
||||
}
|
||||
|
||||
boolean res;
|
||||
System.out.println(">>>>> REG_ID: " + deviceInfo.getDeviceRegistrationID()); // TODO: tmp
|
||||
res = push.sendNoRetry(deviceInfo.getDeviceRegistrationID(),
|
||||
collapseKey,
|
||||
"url", url,
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* 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 com.google.android.chrometophone.server;
|
||||
|
||||
import com.google.android.c2dm.server.C2DMessaging;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.jdo.PersistenceManager;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Sends a link to a device without using the Chrome extension.
|
||||
*
|
||||
* Useful for debugging purposes.
|
||||
*/
|
||||
public class SenderServlet extends HttpServlet {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(SenderServlet.class.getName());
|
||||
|
||||
private static final String ATTR_LOG = "log";
|
||||
private static final String PARAM_ACCOUNT = "account";
|
||||
private static final String PARAM_URL = "url";
|
||||
|
||||
@Override
|
||||
public void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws IOException {
|
||||
String account = getParameter(req, PARAM_ACCOUNT);
|
||||
String url = getParameter(req, PARAM_URL);
|
||||
String log = (String) req.getAttribute(ATTR_LOG);
|
||||
resp.setContentType("text/html");
|
||||
PrintWriter out = resp.getWriter();
|
||||
out.println("<html>");
|
||||
out.println("<head>");
|
||||
out.println("<title>Sender</title>");
|
||||
out.println("</head>");
|
||||
out.println("<body>");
|
||||
out.println("<h1>Send a link to the phone</h1>");
|
||||
if (log != null) {
|
||||
out.println("<h3>Log from previous request</h3>");
|
||||
out.println(log);
|
||||
out.println("<br/>");
|
||||
}
|
||||
out.write("<form method='POST'>");
|
||||
out.println("<table>");
|
||||
out.println("<tr><td align='right'>Account:</td><td>" +
|
||||
"<input type='text' name='" + PARAM_ACCOUNT + "' value='" + account + "' size='40'>" +
|
||||
"</td></tr>");
|
||||
out.println("<tr><td align='right'>Link:</td><td>" +
|
||||
"<input type='text' name='" + PARAM_URL + "' value='" + url + "' size='80'>" +
|
||||
"</td></tr>");
|
||||
out.println("<tr><td colspan='2' align='center'>" +
|
||||
"<input type='submit' value='Send'/></td></tr>");
|
||||
out.println("</table>");
|
||||
out.println("</form>");
|
||||
|
||||
out.println("</body></html>");
|
||||
resp.setStatus(HttpServletResponse.SC_OK);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws IOException {
|
||||
String account = getParameter(req, PARAM_ACCOUNT);
|
||||
String url = getParameter(req, PARAM_URL);
|
||||
ServletContext ctx = getServletContext();
|
||||
PersistenceManager pm = C2DMessaging.getPMF(ctx).getPersistenceManager();
|
||||
StringBuilder log = new StringBuilder();
|
||||
log.append("Getting devices for account ").append(account).append("<br>");
|
||||
List<DeviceInfo> devices = DeviceInfo.getDeviceInfoForUser(pm, account);
|
||||
pm.close();
|
||||
log.append("Number of devices found: ").append(devices.size()).append("<br>");
|
||||
for (DeviceInfo device : devices) {
|
||||
String type = device.getType();
|
||||
String regId = device.getDeviceRegistrationID();
|
||||
log.append("Sending ").append(url).append(" to device of type ")
|
||||
.append(type).append("<br/>");
|
||||
sendLink(req, account, regId, type, url, log);
|
||||
}
|
||||
|
||||
req.setAttribute(ATTR_LOG, log.toString());
|
||||
doGet(req, resp);
|
||||
}
|
||||
|
||||
private void sendLink(HttpServletRequest req, String account, String regId, String type,
|
||||
String url, StringBuilder log) throws IOException {
|
||||
// use the /send servlet to send the link - we could use C2DMessaging
|
||||
// directly, but the whole idea of this servlet is to emulate the
|
||||
// Chrome plugin work
|
||||
String sendServletUrl = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() +
|
||||
"/" + req.getContextPath() + "send";
|
||||
String postUrl = sendServletUrl + "?url=" + URLEncoder.encode(url, "UTF-8") +
|
||||
"&deviceType=" + type + "&devregid=" + URLEncoder.encode(regId, "UTF-8") +
|
||||
"&account=" + URLEncoder.encode(account, "UTF-8");
|
||||
|
||||
HttpURLConnection conn = (HttpURLConnection) new URL(postUrl).openConnection();
|
||||
conn.setDoOutput(true);
|
||||
conn.setUseCaches(false);
|
||||
conn.setFixedLengthStreamingMode(0); // no content, just parameters
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("X-Same-Domain", "1");
|
||||
OutputStream out = null;
|
||||
String status = null;
|
||||
String responseBody = null;
|
||||
try {
|
||||
out = conn.getOutputStream();
|
||||
out.close();
|
||||
int statusCode = conn.getResponseCode();
|
||||
status = Integer.toString(statusCode);
|
||||
try {
|
||||
InputStream stream = (statusCode == 200) ? conn.getInputStream() : conn.getErrorStream();
|
||||
responseBody = getString(stream);
|
||||
log.append("\tPOST status: ").append(statusCode).append(" Body: ").append(responseBody).append("<br/>");
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Exception posting to " + postUrl, e);
|
||||
log.append("POST threw exception: ").append(e);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, "Exception posting to " + postUrl, e);
|
||||
log.append("POST threw exception: ").append(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getParameter(HttpServletRequest req, String name) {
|
||||
String value = req.getParameter(name);
|
||||
return (value == null || value.trim().length() == 0) ? "" : value.trim();
|
||||
}
|
||||
|
||||
private static String getString(InputStream stream) throws IOException {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
|
||||
StringBuilder content = new StringBuilder();
|
||||
String newLine;
|
||||
do {
|
||||
newLine = reader.readLine();
|
||||
if (newLine != null) {
|
||||
content.append(newLine).append('\n');
|
||||
}
|
||||
} while (newLine != null);
|
||||
if (content.length() > 0) {
|
||||
// strip last newline
|
||||
content.setLength(content.length() - 1);
|
||||
}
|
||||
return content.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2012 Google Inc.
|
||||
*
|
||||
* 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 com.google.android.chrometophone.server;
|
||||
|
||||
import com.google.android.c2dm.server.C2DMessaging;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.jdo.PersistenceManager;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* Shows the statistics of how many devices use C2DM or GCM.
|
||||
*
|
||||
* Useful for debugging purposes.
|
||||
*/
|
||||
public class StatsServlet extends HttpServlet {
|
||||
|
||||
@Override
|
||||
public void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||
throws IOException{
|
||||
ServletContext ctx = getServletContext();
|
||||
PersistenceManager pm = C2DMessaging.getPMF(ctx).getPersistenceManager();
|
||||
Map<String, Integer> stats = DeviceInfo.getDevicesUsage(pm);
|
||||
pm.close();
|
||||
|
||||
resp.setContentType("text/html");
|
||||
PrintWriter out = resp.getWriter();
|
||||
out.println("<html><body>");
|
||||
out.println("<head>");
|
||||
out.println("<title>Device stats</title>");
|
||||
out.println("</head>");
|
||||
out.println("<body>");
|
||||
out.println("<h3>Device stats</h3>");
|
||||
if (stats.isEmpty()) {
|
||||
out.println("<p>No devices registered yet!</p>");
|
||||
} else {
|
||||
int total = 0;
|
||||
for (Integer count : stats.values()) {
|
||||
total += count;
|
||||
}
|
||||
|
||||
out.println("<table cellspacing='2' cellpadding='2'><tr><th>Type</th>" +
|
||||
"<th>Count</th><th>Share</th></tr>");
|
||||
for (Entry<String, Integer> entry : stats.entrySet()) {
|
||||
String type = entry.getKey();
|
||||
int count = entry.getValue();
|
||||
float share = (100*count) / total;
|
||||
out.println("<tr>" +
|
||||
"<td align='right'>" + type + "</td>" +
|
||||
"<td align='right'>" + count + "</td>" +
|
||||
"<td align='right'>" + share + "%</td></tr>");
|
||||
}
|
||||
out.println("<tr>" +
|
||||
"<td align='right'>Total</td>" +
|
||||
"<td align='right'>" + total+ "</td>" +
|
||||
"<td align='right'>100.0%</td></tr>");
|
||||
out.println("</table>");
|
||||
}
|
||||
|
||||
out.println("</body></html>");
|
||||
resp.setStatus(HttpServletResponse.SC_OK);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
|
||||
<application>chrometophone</application>
|
||||
<version>11</version>
|
||||
<threadsafe>true</threadsafe>
|
||||
<system-properties>
|
||||
<property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
|
||||
</system-properties>
|
||||
|
||||
@@ -62,6 +62,18 @@
|
||||
</servlet-class>
|
||||
</servlet>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>SenderServlet</servlet-name>
|
||||
<servlet-class>com.google.android.chrometophone.server.SenderServlet
|
||||
</servlet-class>
|
||||
</servlet>
|
||||
|
||||
<servlet>
|
||||
<servlet-name>StatsServlet</servlet-name>
|
||||
<servlet-class>com.google.android.chrometophone.server.StatsServlet
|
||||
</servlet-class>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>RegisterServlet</servlet-name>
|
||||
<url-pattern>/register</url-pattern>
|
||||
@@ -97,6 +109,16 @@
|
||||
<url-pattern>/signout</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>SenderServlet</servlet-name>
|
||||
<url-pattern>/admin/sender</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>StatsServlet</servlet-name>
|
||||
<url-pattern>/admin/stats</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>dataMessagingServlet</servlet-name>
|
||||
<url-pattern>/tasks/c2dm</url-pattern>
|
||||
@@ -111,4 +133,13 @@
|
||||
<role-name>admin</role-name>
|
||||
</auth-constraint>
|
||||
</security-constraint>
|
||||
<security-constraint>
|
||||
<web-resource-collection>
|
||||
<web-resource-name>admin</web-resource-name>
|
||||
<url-pattern>/admin/*</url-pattern>
|
||||
</web-resource-collection>
|
||||
<auth-constraint>
|
||||
<role-name>admin</role-name>
|
||||
</auth-constraint>
|
||||
</security-constraint>
|
||||
</web-app>
|
||||
|
||||
Reference in New Issue
Block a user