From 5271cc57d1fb6d86f39eadf9396e70d74d2ecc93 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Thu, 14 Feb 2013 22:58:52 +0530 Subject: [PATCH] Initial cut of EventLogging support --- .../wikimedia/commons/CommonsApplication.java | 4 + .../java/org/wikimedia/commons/EventLog.java | 99 +++++++++++++++++++ .../org/wikimedia/commons/ShareActivity.java | 21 +++- .../org/wikimedia/commons/UploadService.java | 17 ++++ .../java/org/wikimedia/commons/Utils.java | 12 +++ .../commons/contributions/Contribution.java | 24 ++++- .../contributions/ContributionsActivity.java | 2 + 7 files changed, 175 insertions(+), 4 deletions(-) create mode 100644 commons/src/main/java/org/wikimedia/commons/EventLog.java diff --git a/commons/src/main/java/org/wikimedia/commons/CommonsApplication.java b/commons/src/main/java/org/wikimedia/commons/CommonsApplication.java index 53352bab1..4e638dd80 100644 --- a/commons/src/main/java/org/wikimedia/commons/CommonsApplication.java +++ b/commons/src/main/java/org/wikimedia/commons/CommonsApplication.java @@ -56,6 +56,10 @@ public class CommonsApplication extends Application { private Account currentAccount = null; // Unlike a savings account... public static final String API_URL = "https://test.wikipedia.org/w/api.php"; public static final String IMAGE_URL_BASE = "https://upload.wikimedia.org/wikipedia/test"; + public static final String EVENTLOG_URL = "https://bits.wikimedia.org/event.gif"; + + public static final Object[] EVENT_UPLOAD_ATTEMPT = {"MobileAppUploadAttempts", 5241449}; + public static final Object[] EVENT_LOGIN_ATTEMPT = {"MobileAppLoginAttempts", 5240393}; public static final String DEFAULT_EDIT_SUMMARY = "Uploaded using Android Commons app"; diff --git a/commons/src/main/java/org/wikimedia/commons/EventLog.java b/commons/src/main/java/org/wikimedia/commons/EventLog.java new file mode 100644 index 000000000..dbe2836a5 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/EventLog.java @@ -0,0 +1,99 @@ +package org.wikimedia.commons; + +import android.os.AsyncTask; +import de.akquinet.android.androlog.Log; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; + +public class EventLog { + private static class LogTask extends AsyncTask { + + @Override + protected Boolean doInBackground(LogBuilder... logBuilders) { + + boolean allSuccess = true; + + // Going to simply use the default URLConnection. + // This should be as lightweight as possible, and doesn't really do any fancy stuff + for(LogBuilder logBuilder: logBuilders) { + HttpURLConnection conn; + try { + URL url = logBuilder.toUrl(); + conn = (HttpURLConnection) url.openConnection(); + int respCode = conn.getResponseCode(); + if(respCode != 204) { + allSuccess = false; + } + Log.d("Commons", "EventLog hit " + url.toString()); + + } catch (IOException e) { + // Probably just ignore for now. Can be much more robust with a service, etc later on. + // But in the interest of debugging + throw new RuntimeException(e); + } + + } + + return allSuccess; + } + } + + public static class LogBuilder { + private JSONObject data; + private long rev; + private String schema; + + private LogBuilder(String schema, long revision) { + data = new JSONObject(); + this.schema = schema; + this.rev = revision; + } + + public LogBuilder param(String key, Object value) { + try { + data.put(key, value); + } catch (JSONException e) { + throw new RuntimeException(e); + } + return this; + } + + private URL toUrl() { + JSONObject fullData = new JSONObject(); + try { + fullData.put("schema", schema); + fullData.put("revision", rev); + fullData.put("wiki", "commonswiki"); + fullData.put("isValid", true); // Hehe + fullData.put("event", data); + return new URL(CommonsApplication.EVENTLOG_URL + "?" + Utils.urlEncode(fullData.toString()) + ";"); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } catch (JSONException e) { + throw new RuntimeException(e); + } + } + + public void log() { + LogTask logTask = new LogTask(); + Utils.executeAsyncTask(logTask, this); + } + + } + + public static LogBuilder schema(String schema, long revision) { + return new LogBuilder(schema, revision); + } + + public static LogBuilder schema(Object[] schema_rev) { + if(schema_rev.length != 2) { + throw new IllegalArgumentException("Needs an object array with schema as first param and revision as second"); + } + return schema((String)schema_rev[0], (Long)schema_rev[1]); + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/ShareActivity.java b/commons/src/main/java/org/wikimedia/commons/ShareActivity.java index f290b9d05..bfdd9f34c 100644 --- a/commons/src/main/java/org/wikimedia/commons/ShareActivity.java +++ b/commons/src/main/java/org/wikimedia/commons/ShareActivity.java @@ -29,9 +29,19 @@ public class ShareActivity extends AuthenticatedActivity { private Button uploadButton; private EditText titleEdit; private EditText descEdit; - + private Uri mediaUri; - + + @Override + public void onBackPressed() { + super.onBackPressed(); + EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT) + .param("username", app.getCurrentAccount().name) + .param("source", getIntent().getStringExtra(UploadService.EXTRA_SOURCE)) + .param("result", "cancelled") + .log(); + } + @Override protected void onAuthCookieAcquired(String authCookie) { super.onAuthCookieAcquired(authCookie); @@ -42,6 +52,12 @@ public class ShareActivity extends AuthenticatedActivity { if(intent.getAction().equals(Intent.ACTION_SEND)) { mediaUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM); + final String source; + if(intent.hasExtra(UploadService.EXTRA_SOURCE)) { + source = intent.getStringExtra(UploadService.EXTRA_SOURCE); + } else { + source = Contribution.SOURCE_EXTERNAL; + } final String mimeType = intent.getType(); if(mimeType.startsWith("image/")) { @@ -58,6 +74,7 @@ public class ShareActivity extends AuthenticatedActivity { uploadIntent.putExtra(UploadService.EXTRA_DESCRIPTION, descEdit.getText().toString()); uploadIntent.putExtra(UploadService.EXTRA_MIMETYPE, mimeType); uploadIntent.putExtra(UploadService.EXTRA_EDIT_SUMMARY, "Mobile upload from Wikimedia Commons Android app"); + uploadIntent.putExtra(UploadService.EXTRA_SOURCE, source); startService(uploadIntent); Toast startingToast = Toast.makeText(that, R.string.uploading_started, Toast.LENGTH_LONG); startingToast.show(); diff --git a/commons/src/main/java/org/wikimedia/commons/UploadService.java b/commons/src/main/java/org/wikimedia/commons/UploadService.java index 32f396728..0a8fed771 100644 --- a/commons/src/main/java/org/wikimedia/commons/UploadService.java +++ b/commons/src/main/java/org/wikimedia/commons/UploadService.java @@ -34,6 +34,7 @@ public class UploadService extends HandlerService { public static final int ACTION_UPLOAD_FILE = 1; public static final String ACTION_START_SERVICE = EXTRA_PREFIX + ".upload"; + public static final String EXTRA_SOURCE = EXTRA_PREFIX + ".source"; private NotificationManager notificationManager; private ContentProviderClient contributionsProviderClient; @@ -118,6 +119,7 @@ public class UploadService extends HandlerService { String description = intent.getStringExtra(EXTRA_DESCRIPTION); String editSummary = intent.getStringExtra(EXTRA_EDIT_SUMMARY); String mimeType = intent.getStringExtra(EXTRA_MIMETYPE); + String source = intent.getStringExtra(EXTRA_SOURCE); Date dateCreated = null; Long length = null; @@ -144,6 +146,7 @@ public class UploadService extends HandlerService { Removed Audio implementationf or now } */ Contribution contribution = new Contribution(mediaUri, null, filename, description, length, dateCreated, null, app.getCurrentAccount().name, editSummary); + contribution.setSource(source); return contribution; } @@ -271,7 +274,14 @@ public class UploadService extends HandlerService { String resultStatus = result.getString("/api/upload/@result"); if(!resultStatus.equals("Success")) { + String errorCode = result.getString("/api/error/@code"); showFailedNotification(contribution); + EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT) + .param("username", app.getCurrentAccount().name) + .param("source", contribution.getSource()) + .param("result", errorCode) + .param("filename", contribution.getFilename()) + .log(); } else { Date dateUploaded = null; dateUploaded = Utils.parseMWDate(result.getString("/api/upload/imageinfo/@timestamp")); @@ -282,6 +292,13 @@ public class UploadService extends HandlerService { contribution.setState(Contribution.STATE_COMPLETED); contribution.setDateUploaded(dateUploaded); contribution.save(); + + EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT) + .param("username", app.getCurrentAccount().name) + .param("source", contribution.getSource()) //FIXME + .param("filename", contribution.getFilename()) + .param("result", "success") + .log(); } } diff --git a/commons/src/main/java/org/wikimedia/commons/Utils.java b/commons/src/main/java/org/wikimedia/commons/Utils.java index 3ddfc3d98..1bf21b13a 100644 --- a/commons/src/main/java/org/wikimedia/commons/Utils.java +++ b/commons/src/main/java/org/wikimedia/commons/Utils.java @@ -2,8 +2,10 @@ package org.wikimedia.commons; import android.os.AsyncTask; import android.os.Build; +import org.apache.commons.codec.EncoderException; import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.codec.net.URLCodec; import org.w3c.dom.Node; import javax.xml.transform.TransformerConfigurationException; @@ -90,6 +92,16 @@ public class Utils { } } + private static final URLCodec urlCodec = new URLCodec(); + + public static String urlEncode(String url) { + try { + return urlCodec.encode(url); + } catch (EncoderException e) { + throw new RuntimeException(e); + } + } + public static long countBytes(InputStream stream) throws IOException { long count = 0; BufferedInputStream bis = new BufferedInputStream(stream); diff --git a/commons/src/main/java/org/wikimedia/commons/contributions/Contribution.java b/commons/src/main/java/org/wikimedia/commons/contributions/Contribution.java index 40aae4ebe..1aba161e9 100644 --- a/commons/src/main/java/org/wikimedia/commons/contributions/Contribution.java +++ b/commons/src/main/java/org/wikimedia/commons/contributions/Contribution.java @@ -11,6 +11,7 @@ import android.net.*; import android.os.RemoteException; import android.text.TextUtils; import org.wikimedia.commons.CommonsApplication; +import org.wikimedia.commons.EventLog; import org.wikimedia.commons.Media; public class Contribution extends Media { @@ -21,8 +22,15 @@ public class Contribution extends Media { public static final int STATE_QUEUED = 2; public static final int STATE_IN_PROGRESS = 3; + public static final String SOURCE_CAMERA = "camera"; + public static final String SOURCE_GALLERY = "gallery"; + public static final String SOURCE_EXTERNAL = "external"; + private ContentProviderClient client; private Uri contentUri; + private String source; + + public EventLog.LogBuilder event; public long getTransferred() { return transferred; @@ -132,6 +140,7 @@ public class Contribution extends Media { cv.put(Table.COLUMN_TIMESTAMP, getTimestamp().getTime()); cv.put(Table.COLUMN_STATE, getState()); cv.put(Table.COLUMN_TRANSFERRED, transferred); + cv.put(Table.COLUMN_SOURCE, source); return cv; } @@ -159,9 +168,17 @@ public class Contribution extends Media { c.dataLength = cursor.getLong(6); c.dateUploaded = cursor.getLong(7) == 0 ? null : new Date(cursor.getLong(7)); c.transferred = cursor.getLong(8); + c.source = cursor.getString(9); return c; } + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } public static class Table { @@ -176,6 +193,7 @@ public class Contribution extends Media { public static final String COLUMN_LENGTH = "length"; public static final String COLUMN_UPLOADED = "uploaded"; public static final String COLUMN_TRANSFERRED = "transferred"; // Currently transferred number of bytes + public static final String COLUMN_SOURCE = "source"; // NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES. public static final String[] ALL_FIELDS = { @@ -187,7 +205,8 @@ public class Contribution extends Media { COLUMN_STATE, COLUMN_LENGTH, COLUMN_UPLOADED, - COLUMN_TRANSFERRED + COLUMN_TRANSFERRED, + COLUMN_SOURCE }; @@ -200,7 +219,8 @@ public class Contribution extends Media { + "timestamp INTEGER," + "state INTEGER," + "length INTEGER," - + "transferred INTEGER" + + "transferred INTEGER," + + "source STRING" + ");"; diff --git a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsActivity.java b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsActivity.java index aaced3fa3..cd799d0cb 100644 --- a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsActivity.java +++ b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsActivity.java @@ -226,6 +226,7 @@ public class ContributionsActivity extends AuthenticatedActivity implements Load Log.d("Commons", "Type is " + data.getType() + " Uri is " + data.getData()); shareIntent.setType("image/*"); //FIXME: Find out appropriate mime type shareIntent.putExtra(Intent.EXTRA_STREAM, data.getData()); + shareIntent.putExtra(UploadService.EXTRA_SOURCE, Contribution.SOURCE_GALLERY); startActivity(shareIntent); } break; @@ -236,6 +237,7 @@ public class ContributionsActivity extends AuthenticatedActivity implements Load Log.d("Commons", "Uri is " + lastGeneratedCaptureURI); shareIntent.setType("image/jpeg"); //FIXME: Find out appropriate mime type shareIntent.putExtra(Intent.EXTRA_STREAM, lastGeneratedCaptureURI); + shareIntent.putExtra(UploadService.EXTRA_SOURCE, Contribution.SOURCE_CAMERA); startActivity(shareIntent); } break;