Consolidated media wiki api calls in a single place

This commit is contained in:
Paul Hawke 2017-07-04 14:24:08 -05:00
parent 5396fc6ed0
commit 599e7bb453
18 changed files with 467 additions and 376 deletions

View file

@ -0,0 +1,258 @@
package fr.free.nrw.commons.mwapi;
import android.os.Build;
import android.text.TextUtils;
import org.apache.http.HttpResponse;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.AbstractHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreProtocolPNames;
import org.mediawiki.api.ApiResult;
import java.io.IOException;
import java.net.URL;
import fr.free.nrw.commons.BuildConfig;
import in.yuvi.http.fluent.Http;
import timber.log.Timber;
/**
* @author Addshore
*/
public class ApacheHttpClientMediaWikiApi extends org.mediawiki.api.MWApi implements MediaWikiApi {
private static final String THUMB_SIZE = "640";
private static AbstractHttpClient httpClient;
public ApacheHttpClientMediaWikiApi(String apiURL) {
super(apiURL, getHttpClient());
}
private static AbstractHttpClient getHttpClient() {
if (httpClient == null) {
httpClient = newHttpClient();
}
return httpClient;
}
private static AbstractHttpClient newHttpClient() {
BasicHttpParams params = new BasicHttpParams();
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
final SSLSocketFactory sslSocketFactory = SSLSocketFactory.getSocketFactory();
schemeRegistry.register(new Scheme("https", sslSocketFactory, 443));
ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
params.setParameter(CoreProtocolPNames.USER_AGENT, "Commons/" + BuildConfig.VERSION_NAME + " (https://mediawiki.org/wiki/Apps/Commons) Android/" + Build.VERSION.RELEASE);
return new DefaultHttpClient(cm, params);
}
/**
* @param username String
* @param password String
* @return String as returned by this.getErrorCodeToReturn()
* @throws IOException On api request IO issue
*/
public String login(String username, String password) throws IOException {
return getErrorCodeToReturn(action("clientlogin")
.param("rememberMe", "1")
.param("username", username)
.param("password", password)
.param("logintoken", this.getLoginToken())
.param("loginreturnurl", "https://commons.wikimedia.org")
.post());
}
/**
* @param username String
* @param password String
* @param twoFactorCode String
* @return String as returned by this.getErrorCodeToReturn()
* @throws IOException On api request IO issue
*/
public String login(String username, String password, String twoFactorCode) throws IOException {
return getErrorCodeToReturn(action("clientlogin")
.param("rememberMe", "1")
.param("username", username)
.param("password", password)
.param("logintoken", getLoginToken())
.param("logincontinue", "1")
.param("OATHToken", twoFactorCode)
.post());
}
private String getLoginToken() throws IOException {
return this.action("query")
.param("action", "query")
.param("meta", "tokens")
.param("type", "login")
.post()
.getString("/api/query/tokens/@logintoken");
}
/**
* @param loginApiResult ApiResult Any clientlogin api result
* @return String On success: "PASS"
* continue: "2FA" (More information required for 2FA)
* failure: A failure message code (defined by mediawiki)
* misc: genericerror-UI, genericerror-REDIRECT, genericerror-RESTART
*/
private String getErrorCodeToReturn(ApiResult loginApiResult) {
String status = loginApiResult.getString("/api/clientlogin/@status");
if (status.equals("PASS")) {
this.isLoggedIn = true;
return status;
} else if (status.equals("FAIL")) {
return loginApiResult.getString("/api/clientlogin/@messagecode");
} else if (
status.equals("UI")
&& loginApiResult.getString("/api/clientlogin/requests/_v/@id").equals("TOTPAuthenticationRequest")
&& loginApiResult.getString("/api/clientlogin/requests/_v/@provider").equals("Two-factor authentication (OATH).")
) {
return "2FA";
}
// UI, REDIRECT, RESTART
return "genericerror-" + status;
}
// Moved / consolidated methods
@Override
public boolean fileExistsWithName(String fileName) throws IOException {
return action("query")
.param("prop", "imageinfo")
.param("titles", "File:" + fileName)
.get()
.getNodes("/api/query/pages/page/imageinfo").size() > 0;
}
@Override
public ApiResult edit(String editToken, String processedPageContent, String filename, String summary) throws IOException {
return action("edit")
.param("title", filename)
.param("token", editToken)
.param("text", processedPageContent)
.param("summary", summary)
.post();
}
@Override
public String findThumbnailByFilename(String filename) throws IOException {
return action("query")
.param("format", "xml")
.param("prop", "imageinfo")
.param("iiprop", "url")
.param("iiurlwidth", THUMB_SIZE)
.param("titles", filename)
.get()
.getString("/api/query/pages/page/imageinfo/ii/@thumburl");
}
@Override
public ApiResult fetchMediaByFilename(String filename) throws IOException {
return action("query")
.param("prop", "revisions")
.param("titles", filename)
.param("rvprop", "content")
.param("rvlimit", 1)
.param("rvgeneratexml", 1)
.get();
}
@Override
public ApiResult searchCategories(int searchCatsLimit, String filterValue) throws IOException {
return action("query")
.param("format", "xml")
.param("list", "search")
.param("srwhat", "text")
.param("srnamespace", "14")
.param("srlimit", searchCatsLimit)
.param("srsearch", filterValue)
.get();
}
@Override
public ApiResult allCategories(int searchCatsLimit, String filterValue) throws IOException {
return action("query")
.param("list", "allcategories")
.param("acprefix", filterValue)
.param("aclimit", searchCatsLimit)
.get();
}
@Override
public ApiResult searchTitles(int searchCatsLimit, String title) throws IOException {
return action("query")
.param("format", "xml")
.param("list", "search")
.param("srwhat", "text")
.param("srnamespace", "14")
.param("srlimit", searchCatsLimit)
.param("srsearch", title)
.get();
}
@Override
public ApiResult logEvents(String user, String lastModified, String queryContinue, int limit) throws IOException {
org.mediawiki.api.MWApi.RequestBuilder builder = action("query")
.param("list", "logevents")
.param("letype", "upload")
.param("leprop", "title|timestamp|ids")
.param("leuser", user)
.param("lelimit", limit);
if (!TextUtils.isEmpty(lastModified)) {
builder.param("leend", lastModified);
}
if (!TextUtils.isEmpty(queryContinue)) {
builder.param("lestart", queryContinue);
}
return builder.get();
}
@Override
public ApiResult revisionsByFilename(String filename) throws IOException {
return action("query")
.param("prop", "revisions")
.param("rvprop", "timestamp|content")
.param("titles", filename)
.get();
}
@Override
public ApiResult existingFile(String fileSha1) throws IOException {
return action("query")
.param("format", "xml")
.param("list", "allimages")
.param("aisha1", fileSha1)
.get();
}
@Override
public boolean logEvents(LogBuilder[] logBuilders) {
boolean allSuccess = true;
// Not using the default URL connection, since that seems to have different behavior than the rest of the code
for (LogBuilder logBuilder : logBuilders) {
try {
URL url = logBuilder.toUrl();
HttpResponse response = Http.get(url.toString()).use(httpClient).asResponse();
if (response.getStatusLine().getStatusCode() != 204) {
allSuccess = false;
}
Timber.d("EventLog hit %s", url);
} catch (IOException e) {
// Probably just ignore for now. Can be much more robust with a service, etc later on.
Timber.d("IO Error, EventLog hit skipped");
}
}
return allSuccess;
}
}

View file

@ -0,0 +1,28 @@
package fr.free.nrw.commons.mwapi;
import android.os.Build;
import fr.free.nrw.commons.Utils;
public class EventLog {
static final String DEVICE;
static {
if (Build.MODEL.startsWith(Build.MANUFACTURER)) {
DEVICE = Utils.capitalize(Build.MODEL);
} else {
DEVICE = Utils.capitalize(Build.MANUFACTURER) + " " + Build.MODEL;
}
}
private static LogBuilder schema(String schema, long revision) {
return new LogBuilder(schema, revision);
}
public static LogBuilder schema(Object[] scid) {
if (scid.length != 2) {
throw new IllegalArgumentException("Needs an object array with schema as first param and revision as second");
}
return schema((String) scid[0], (Long) scid[1]);
}
}

View file

@ -0,0 +1,71 @@
package fr.free.nrw.commons.mwapi;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Build;
import android.preference.PreferenceManager;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.MalformedURLException;
import java.net.URL;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.settings.Prefs;
public class LogBuilder {
private JSONObject data;
private long rev;
private String schema;
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;
}
URL toUrl() {
JSONObject fullData = new JSONObject();
try {
fullData.put("schema", schema);
fullData.put("revision", rev);
fullData.put("wiki", CommonsApplication.EVENTLOG_WIKI);
data.put("device", EventLog.DEVICE);
data.put("platform", "Android/" + Build.VERSION.RELEASE);
data.put("appversion", "Android/" + BuildConfig.VERSION_NAME);
fullData.put("event", data);
return new URL(CommonsApplication.EVENTLOG_URL + "?" + Utils.urlEncode(fullData.toString()) + ";");
} catch (MalformedURLException | JSONException e) {
throw new RuntimeException(e);
}
}
// force param disregards user preference
// Use *only* for tracking the user preference change for EventLogging
// Attempting to use anywhere else will cause kitten explosions
public void log(boolean force) {
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(CommonsApplication.getInstance());
if (!settings.getBoolean(Prefs.TRACKING_ENABLED, true) && !force) {
return; // User has disabled tracking
}
LogTask logTask = new LogTask();
logTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, this);
}
public void log() {
log(false);
}
}

View file

@ -0,0 +1,12 @@
package fr.free.nrw.commons.mwapi;
import android.os.AsyncTask;
import fr.free.nrw.commons.CommonsApplication;
class LogTask extends AsyncTask<LogBuilder, Void, Boolean> {
@Override
protected Boolean doInBackground(LogBuilder... logBuilders) {
return CommonsApplication.getInstance().getMWApi().logEvents(logBuilders);
}
}

View file

@ -0,0 +1,46 @@
package fr.free.nrw.commons.mwapi;
import org.mediawiki.api.ApiResult;
import java.io.IOException;
import java.io.InputStream;
import in.yuvi.http.fluent.ProgressListener;
public interface MediaWikiApi {
String getAuthCookie();
void setAuthCookie(String authCookie);
String login(String username, String password) throws IOException;
String login(String username, String password, String twoFactorCode) throws IOException;
boolean validateLogin() throws IOException;
String getEditToken() throws IOException;
ApiResult upload(String filename, InputStream file, long dataLength, String pageContents, String editSummary, ProgressListener progressListener) throws IOException;
boolean fileExistsWithName(String fileName) throws IOException;
ApiResult edit(String editToken, String processedPageContent, String filename, String summary) throws IOException;
String findThumbnailByFilename(String filename) throws IOException;
ApiResult fetchMediaByFilename(String filename) throws IOException;
ApiResult searchCategories(int searchCatsLimit, String filterValue) throws IOException;
ApiResult allCategories(int searchCatsLimit, String filter) throws IOException;
ApiResult searchTitles(int searchCatsLimit, String title) throws IOException;
ApiResult logEvents(String user, String lastModified, String queryContinue, int limit) throws IOException;
ApiResult revisionsByFilename(String filename) throws IOException;
ApiResult existingFile(String fileSha1) throws IOException;
boolean logEvents(LogBuilder[] logBuilders);
}