mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Consolidated media wiki api calls in a single place
This commit is contained in:
parent
5396fc6ed0
commit
599e7bb453
18 changed files with 467 additions and 376 deletions
|
|
@ -8,41 +8,30 @@ import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Build;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.v4.util.LruCache;
|
import android.support.v4.util.LruCache;
|
||||||
|
|
||||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||||
import com.facebook.stetho.Stetho;
|
import com.facebook.stetho.Stetho;
|
||||||
|
|
||||||
import fr.free.nrw.commons.caching.CacheController;
|
|
||||||
import fr.free.nrw.commons.category.Category;
|
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
|
||||||
import fr.free.nrw.commons.data.DBOpenHelper;
|
|
||||||
import fr.free.nrw.commons.modifications.ModifierSequence;
|
|
||||||
import fr.free.nrw.commons.auth.AccountUtil;
|
|
||||||
import fr.free.nrw.commons.nearby.NearbyPlaces;
|
|
||||||
|
|
||||||
import com.squareup.leakcanary.LeakCanary;
|
import com.squareup.leakcanary.LeakCanary;
|
||||||
|
|
||||||
import org.acra.ACRA;
|
import org.acra.ACRA;
|
||||||
import org.acra.ReportingInteractionMode;
|
import org.acra.ReportingInteractionMode;
|
||||||
import org.acra.annotation.ReportsCrashes;
|
import org.acra.annotation.ReportsCrashes;
|
||||||
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 java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.auth.AccountUtil;
|
||||||
|
import fr.free.nrw.commons.caching.CacheController;
|
||||||
|
import fr.free.nrw.commons.category.Category;
|
||||||
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
|
import fr.free.nrw.commons.data.DBOpenHelper;
|
||||||
|
import fr.free.nrw.commons.modifications.ModifierSequence;
|
||||||
|
import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi;
|
||||||
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
|
import fr.free.nrw.commons.nearby.NearbyPlaces;
|
||||||
import fr.free.nrw.commons.utils.FileUtils;
|
import fr.free.nrw.commons.utils.FileUtils;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
|
@ -76,9 +65,8 @@ public class CommonsApplication extends Application {
|
||||||
public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App (%s) Feedback";
|
public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App (%s) Feedback";
|
||||||
|
|
||||||
private static CommonsApplication instance = null;
|
private static CommonsApplication instance = null;
|
||||||
private AbstractHttpClient httpClient = null;
|
private MediaWikiApi api = null;
|
||||||
private MWApi api = null;
|
private LruCache<String, String> thumbnailUrlCache = new LruCache<>(1024);
|
||||||
LruCache<String, String> thumbnailUrlCache = new LruCache<>(1024);
|
|
||||||
private CacheController cacheData = null;
|
private CacheController cacheData = null;
|
||||||
private DBOpenHelper dbOpenHelper = null;
|
private DBOpenHelper dbOpenHelper = null;
|
||||||
private NearbyPlaces nearbyPlaces = null;
|
private NearbyPlaces nearbyPlaces = null;
|
||||||
|
|
@ -98,35 +86,13 @@ public class CommonsApplication extends Application {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AbstractHttpClient getHttpClient() {
|
public MediaWikiApi getMWApi() {
|
||||||
if (httpClient == null) {
|
|
||||||
httpClient = newHttpClient();
|
|
||||||
}
|
|
||||||
return httpClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
private 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MWApi getMWApi() {
|
|
||||||
if (api == null) {
|
if (api == null) {
|
||||||
api = newMWApi();
|
api = new ApacheHttpClientMediaWikiApi(API_URL);
|
||||||
}
|
}
|
||||||
return api;
|
return api;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MWApi newMWApi() {
|
|
||||||
return new MWApi(API_URL, getHttpClient());
|
|
||||||
}
|
|
||||||
|
|
||||||
public CacheController getCacheData() {
|
public CacheController getCacheData() {
|
||||||
if (cacheData == null) {
|
if (cacheData == null) {
|
||||||
cacheData = new CacheController();
|
cacheData = new CacheController();
|
||||||
|
|
@ -174,9 +140,6 @@ public class CommonsApplication extends Application {
|
||||||
|
|
||||||
Fresco.initialize(this);
|
Fresco.initialize(this);
|
||||||
|
|
||||||
// Initialize EventLogging
|
|
||||||
EventLog.setApp(this);
|
|
||||||
|
|
||||||
//For caching area -> categories
|
//For caching area -> categories
|
||||||
cacheData = new CacheController();
|
cacheData = new CacheController();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
package fr.free.nrw.commons;
|
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
|
|
||||||
import org.apache.http.HttpResponse;
|
|
||||||
import org.apache.http.impl.client.AbstractHttpClient;
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.settings.Prefs;
|
|
||||||
import in.yuvi.http.fluent.Http;
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
public class EventLog {
|
|
||||||
|
|
||||||
private static CommonsApplication app;
|
|
||||||
|
|
||||||
private static class LogTask extends AsyncTask<LogBuilder, Void, Boolean> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Boolean doInBackground(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();
|
|
||||||
AbstractHttpClient httpClient = CommonsApplication.getInstance().getHttpClient();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final String DEVICE;
|
|
||||||
static {
|
|
||||||
if (Build.MODEL.startsWith(Build.MANUFACTURER)) {
|
|
||||||
DEVICE = Utils.capitalize(Build.MODEL);
|
|
||||||
} else {
|
|
||||||
DEVICE = Utils.capitalize(Build.MANUFACTURER) + " " + Build.MODEL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setApp(CommonsApplication app) {
|
|
||||||
EventLog.app = app;
|
|
||||||
}
|
|
||||||
|
|
||||||
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", CommonsApplication.EVENTLOG_WIKI);
|
|
||||||
data.put("device", 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(app);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public 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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
package fr.free.nrw.commons;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.apache.http.impl.client.AbstractHttpClient;
|
|
||||||
import org.mediawiki.api.ApiResult;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Addshore
|
|
||||||
*/
|
|
||||||
public class MWApi extends org.mediawiki.api.MWApi {
|
|
||||||
|
|
||||||
/** We don't actually use this but need to pass it in requests */
|
|
||||||
private static String LOGIN_RETURN_TO_URL = "https://commons.wikimedia.org";
|
|
||||||
|
|
||||||
public MWApi(String apiURL, AbstractHttpClient client) {
|
|
||||||
super(apiURL, client);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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 {
|
|
||||||
String token = this.getLoginToken();
|
|
||||||
ApiResult loginApiResult = this.action("clientlogin")
|
|
||||||
.param("rememberMe", "1")
|
|
||||||
.param("username", username)
|
|
||||||
.param("password", password)
|
|
||||||
.param("logintoken", token)
|
|
||||||
.param("loginreturnurl", LOGIN_RETURN_TO_URL)
|
|
||||||
.post();
|
|
||||||
return this.getErrorCodeToReturn( loginApiResult );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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 {
|
|
||||||
String token = this.getLoginToken();//TODO cache this instead of calling again when 2FAing
|
|
||||||
ApiResult loginApiResult = this.action("clientlogin")
|
|
||||||
.param("rememberMe", "1")
|
|
||||||
.param("username", username)
|
|
||||||
.param("password", password)
|
|
||||||
.param("logintoken", token)
|
|
||||||
.param("logincontinue", "1")
|
|
||||||
.param("OATHToken", twoFactorCode)
|
|
||||||
.post();
|
|
||||||
|
|
||||||
return this.getErrorCodeToReturn( loginApiResult );
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getLoginToken() throws IOException {
|
|
||||||
ApiResult tokenResult = this.action("query")
|
|
||||||
.param("action", "query")
|
|
||||||
.param("meta", "tokens")
|
|
||||||
.param("type", "login")
|
|
||||||
.post();
|
|
||||||
return tokenResult.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -21,6 +21,7 @@ import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
|
||||||
import fr.free.nrw.commons.location.LatLng;
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -62,16 +63,8 @@ public class MediaDataExtractor {
|
||||||
throw new IllegalStateException("Tried to call MediaDataExtractor.fetch() again.");
|
throw new IllegalStateException("Tried to call MediaDataExtractor.fetch() again.");
|
||||||
}
|
}
|
||||||
|
|
||||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
|
||||||
ApiResult result = api.action("query")
|
processResult(api.fetchMediaByFilename(filename));
|
||||||
.param("prop", "revisions")
|
|
||||||
.param("titles", filename)
|
|
||||||
.param("rvprop", "content")
|
|
||||||
.param("rvlimit", 1)
|
|
||||||
.param("rvgeneratexml", 1)
|
|
||||||
.get();
|
|
||||||
|
|
||||||
processResult(result);
|
|
||||||
fetched = true;
|
fetched = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,9 @@ package fr.free.nrw.commons;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
import org.mediawiki.api.ApiResult;
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
|
|
||||||
class MediaThumbnailFetchTask extends AsyncTask<String, String, String> {
|
class MediaThumbnailFetchTask extends AsyncTask<String, String, String> {
|
||||||
private static final String THUMB_SIZE = "640";
|
|
||||||
protected final Media media;
|
protected final Media media;
|
||||||
|
|
||||||
public MediaThumbnailFetchTask(@NonNull Media media) {
|
public MediaThumbnailFetchTask(@NonNull Media media) {
|
||||||
|
|
@ -16,15 +15,8 @@ class MediaThumbnailFetchTask extends AsyncTask<String, String, String> {
|
||||||
@Override
|
@Override
|
||||||
protected String doInBackground(String... params) {
|
protected String doInBackground(String... params) {
|
||||||
try {
|
try {
|
||||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
|
||||||
ApiResult result =api.action("query")
|
return api.findThumbnailByFilename(params[0]);
|
||||||
.param("format", "xml")
|
|
||||||
.param("prop", "imageinfo")
|
|
||||||
.param("iiprop", "url")
|
|
||||||
.param("iiurlwidth", THUMB_SIZE)
|
|
||||||
.param("titles", params[0])
|
|
||||||
.get();
|
|
||||||
return result.getString("/api/query/pages/page/imageinfo/ii/@thumburl");
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Do something better!
|
// Do something better!
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,13 @@ import android.accounts.NetworkErrorException;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.MWApi;
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
|
|
||||||
public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
|
public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
|
||||||
|
|
||||||
|
|
@ -75,7 +75,7 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getAuthCookie(String username, String password) throws IOException {
|
private String getAuthCookie(String username, String password) throws IOException {
|
||||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
|
||||||
//TODO add 2fa support here
|
//TODO add 2fa support here
|
||||||
String result = api.login(username, password);
|
String result = api.login(username, password);
|
||||||
if(result.equals("PASS")) {
|
if(result.equals("PASS")) {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package fr.free.nrw.commons.category;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import fr.free.nrw.commons.MWApi;
|
|
||||||
import org.mediawiki.api.ApiResult;
|
import org.mediawiki.api.ApiResult;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -12,6 +11,7 @@ import java.util.Calendar;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -79,20 +79,13 @@ public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||||
protected ArrayList<String> doInBackground(Void... voids) {
|
protected ArrayList<String> doInBackground(Void... voids) {
|
||||||
|
|
||||||
//otherwise if user has typed something in that isn't in cache, search API for matching categories
|
//otherwise if user has typed something in that isn't in cache, search API for matching categories
|
||||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
|
||||||
ApiResult result;
|
ApiResult result;
|
||||||
ArrayList<String> categories = new ArrayList<>();
|
ArrayList<String> categories = new ArrayList<>();
|
||||||
|
|
||||||
//URL https://commons.wikimedia.org/w/api.php?action=query&format=xml&list=search&srwhat=text&srenablerewrites=1&srnamespace=14&srlimit=10&srsearch=
|
//URL https://commons.wikimedia.org/w/api.php?action=query&format=xml&list=search&srwhat=text&srenablerewrites=1&srnamespace=14&srlimit=10&srsearch=
|
||||||
try {
|
try {
|
||||||
result = api.action("query")
|
result = api.searchCategories(CategorizationFragment.SEARCH_CATS_LIMIT, filter);
|
||||||
.param("format", "xml")
|
|
||||||
.param("list", "search")
|
|
||||||
.param("srwhat", "text")
|
|
||||||
.param("srnamespace", "14")
|
|
||||||
.param("srlimit", catFragment.SEARCH_CATS_LIMIT)
|
|
||||||
.param("srsearch", filter)
|
|
||||||
.get();
|
|
||||||
Timber.d("Method A URL filter %s", result);
|
Timber.d("Method A URL filter %s", result);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Timber.e(e, "IO Exception: ");
|
Timber.e(e, "IO Exception: ");
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import android.os.AsyncTask;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import fr.free.nrw.commons.MWApi;
|
|
||||||
import org.mediawiki.api.ApiResult;
|
import org.mediawiki.api.ApiResult;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -13,6 +12,7 @@ import java.util.Calendar;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -95,15 +95,11 @@ public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||||
|
|
||||||
//otherwise if user has typed something in that isn't in cache, search API for matching categories
|
//otherwise if user has typed something in that isn't in cache, search API for matching categories
|
||||||
//URL: https://commons.wikimedia.org/w/api.php?action=query&list=allcategories&acprefix=filter&aclimit=25
|
//URL: https://commons.wikimedia.org/w/api.php?action=query&list=allcategories&acprefix=filter&aclimit=25
|
||||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
|
||||||
ApiResult result;
|
ApiResult result;
|
||||||
ArrayList<String> categories = new ArrayList<>();
|
ArrayList<String> categories = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
result = api.action("query")
|
result = api.allCategories(CategorizationFragment.SEARCH_CATS_LIMIT, this.filter);
|
||||||
.param("list", "allcategories")
|
|
||||||
.param("acprefix", filter)
|
|
||||||
.param("aclimit", catFragment.SEARCH_CATS_LIMIT)
|
|
||||||
.get();
|
|
||||||
Timber.d("Prefix URL filter %s", result);
|
Timber.d("Prefix URL filter %s", result);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Timber.e(e, "IO Exception: ");
|
Timber.e(e, "IO Exception: ");
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@ package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
|
||||||
import fr.free.nrw.commons.MWApi;
|
|
||||||
import org.mediawiki.api.ApiResult;
|
import org.mediawiki.api.ApiResult;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -34,20 +34,13 @@ public class TitleCategories extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||||
@Override
|
@Override
|
||||||
protected ArrayList<String> doInBackground(Void... voids) {
|
protected ArrayList<String> doInBackground(Void... voids) {
|
||||||
|
|
||||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
|
||||||
ApiResult result;
|
ApiResult result;
|
||||||
ArrayList<String> items = new ArrayList<>();
|
ArrayList<String> items = new ArrayList<>();
|
||||||
|
|
||||||
//URL https://commons.wikimedia.org/w/api.php?action=query&format=xml&list=search&srwhat=text&srenablerewrites=1&srnamespace=14&srlimit=10&srsearch=
|
//URL https://commons.wikimedia.org/w/api.php?action=query&format=xml&list=search&srwhat=text&srenablerewrites=1&srnamespace=14&srlimit=10&srsearch=
|
||||||
try {
|
try {
|
||||||
result = api.action("query")
|
result = api.searchTitles(SEARCH_CATS_LIMIT, this.title);
|
||||||
.param("format", "xml")
|
|
||||||
.param("list", "search")
|
|
||||||
.param("srwhat", "text")
|
|
||||||
.param("srnamespace", "14")
|
|
||||||
.param("srlimit", SEARCH_CATS_LIMIT)
|
|
||||||
.param("srsearch", title)
|
|
||||||
.get();
|
|
||||||
Timber.d("Searching for cats for title: %s", result);
|
Timber.d("Searching for cats for title: %s", result);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Timber.e(e, "IO Exception: ");
|
Timber.e(e, "IO Exception: ");
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.MWApi;
|
|
||||||
import fr.free.nrw.commons.Utils;
|
import fr.free.nrw.commons.Utils;
|
||||||
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
|
|
@ -61,7 +61,7 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
public void onPerformSync(Account account, Bundle bundle, String s, ContentProviderClient contentProviderClient, SyncResult syncResult) {
|
public void onPerformSync(Account account, Bundle bundle, String s, ContentProviderClient contentProviderClient, SyncResult syncResult) {
|
||||||
// This code is fraught with possibilities of race conditions, but lalalalala I can't hear you!
|
// This code is fraught with possibilities of race conditions, but lalalalala I can't hear you!
|
||||||
String user = account.name;
|
String user = account.name;
|
||||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
|
||||||
SharedPreferences prefs = this.getContext().getSharedPreferences("prefs", Context.MODE_PRIVATE);
|
SharedPreferences prefs = this.getContext().getSharedPreferences("prefs", Context.MODE_PRIVATE);
|
||||||
String lastModified = prefs.getString("lastSyncTimestamp", "");
|
String lastModified = prefs.getString("lastSyncTimestamp", "");
|
||||||
Date curTime = new Date();
|
Date curTime = new Date();
|
||||||
|
|
@ -71,19 +71,7 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
while(!done) {
|
while(!done) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
MWApi.RequestBuilder builder = api.action("query")
|
result = api.logEvents(user, lastModified, queryContinue, getLimit());
|
||||||
.param("list", "logevents")
|
|
||||||
.param("letype", "upload")
|
|
||||||
.param("leprop", "title|timestamp|ids")
|
|
||||||
.param("leuser", user)
|
|
||||||
.param("lelimit", getLimit());
|
|
||||||
if(!TextUtils.isEmpty(lastModified)) {
|
|
||||||
builder.param("leend", lastModified);
|
|
||||||
}
|
|
||||||
if(!TextUtils.isEmpty(queryContinue)) {
|
|
||||||
builder.param("lestart", queryContinue);
|
|
||||||
}
|
|
||||||
result = builder.get();
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// There isn't really much we can do, eh?
|
// There isn't really much we can do, eh?
|
||||||
// FIXME: Perhaps add EventLogging?
|
// FIXME: Perhaps add EventLogging?
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import android.database.Cursor;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
|
|
||||||
import fr.free.nrw.commons.MWApi;
|
|
||||||
import org.mediawiki.api.ApiResult;
|
import org.mediawiki.api.ApiResult;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -21,6 +20,7 @@ import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.Utils;
|
import fr.free.nrw.commons.Utils;
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
||||||
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
|
|
@ -61,7 +61,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
|
||||||
api.setAuthCookie(authCookie);
|
api.setAuthCookie(authCookie);
|
||||||
String editToken;
|
String editToken;
|
||||||
|
|
||||||
|
|
@ -98,11 +98,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
if(contrib.getState() == Contribution.STATE_COMPLETED) {
|
if(contrib.getState() == Contribution.STATE_COMPLETED) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
requestResult = api.action("query")
|
requestResult = api.revisionsByFilename(contrib.getFilename());
|
||||||
.param("prop", "revisions")
|
|
||||||
.param("rvprop", "timestamp|content")
|
|
||||||
.param("titles", contrib.getFilename())
|
|
||||||
.get();
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Timber.d("Network fuckup on modifications sync!");
|
Timber.d("Network fuckup on modifications sync!");
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -113,12 +109,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
String processedPageContent = sequence.executeModifications(contrib.getFilename(), pageContent);
|
String processedPageContent = sequence.executeModifications(contrib.getFilename(), pageContent);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
responseResult = api.action("edit")
|
responseResult = api.edit(editToken, processedPageContent, contrib.getFilename(), sequence.getEditSummary());
|
||||||
.param("title", contrib.getFilename())
|
|
||||||
.param("token", editToken)
|
|
||||||
.param("text", processedPageContent)
|
|
||||||
.param("summary", sequence.getEditSummary())
|
|
||||||
.post();
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Timber.d("Network fuckup on modifications sync!");
|
Timber.d("Network fuckup on modifications sync!");
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
28
app/src/main/java/fr/free/nrw/commons/mwapi/EventLog.java
Normal file
28
app/src/main/java/fr/free/nrw/commons/mwapi/EventLog.java
Normal 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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
71
app/src/main/java/fr/free/nrw/commons/mwapi/LogBuilder.java
Normal file
71
app/src/main/java/fr/free/nrw/commons/mwapi/LogBuilder.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
12
app/src/main/java/fr/free/nrw/commons/mwapi/LogTask.java
Normal file
12
app/src/main/java/fr/free/nrw/commons/mwapi/LogTask.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -12,9 +12,9 @@ import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.MWApi;
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||||
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -49,16 +49,13 @@ public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Boolean doInBackground(Void... voids) {
|
protected Boolean doInBackground(Void... voids) {
|
||||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
|
||||||
ApiResult result;
|
ApiResult result;
|
||||||
|
|
||||||
// https://commons.wikimedia.org/w/api.php?action=query&list=allimages&format=xml&aisha1=801957214aba50cb63bb6eb1b0effa50188900ba
|
// https://commons.wikimedia.org/w/api.php?action=query&list=allimages&format=xml&aisha1=801957214aba50cb63bb6eb1b0effa50188900ba
|
||||||
try {
|
try {
|
||||||
result = api.action("query")
|
String fileSha1 = this.fileSha1;
|
||||||
.param("format", "xml")
|
result = api.existingFile(fileSha1);
|
||||||
.param("list", "allimages")
|
|
||||||
.param("aisha1", fileSha1)
|
|
||||||
.get();
|
|
||||||
Timber.d("Searching Commons API for existing file: %s", result);
|
Timber.d("Searching Commons API for existing file: %s", result);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Timber.e(e, "IO Exception: ");
|
Timber.e(e, "IO Exception: ");
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import android.support.v4.app.NotificationCompat;
|
||||||
import android.webkit.MimeTypeMap;
|
import android.webkit.MimeTypeMap;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import fr.free.nrw.commons.*;
|
|
||||||
import org.mediawiki.api.ApiResult;
|
import org.mediawiki.api.ApiResult;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
|
|
@ -25,10 +24,16 @@ import java.util.Set;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
|
import fr.free.nrw.commons.HandlerService;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.Utils;
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
||||||
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
||||||
|
import fr.free.nrw.commons.mwapi.EventLog;
|
||||||
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import in.yuvi.http.fluent.ProgressListener;
|
import in.yuvi.http.fluent.ProgressListener;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
|
@ -176,7 +181,7 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void uploadContribution(Contribution contribution) {
|
private void uploadContribution(Contribution contribution) {
|
||||||
MWApi api = app.getMWApi();
|
MediaWikiApi api = app.getMWApi();
|
||||||
|
|
||||||
ApiResult result;
|
ApiResult result;
|
||||||
InputStream file = null;
|
InputStream file = null;
|
||||||
|
|
@ -304,7 +309,7 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String findUniqueFilename(String fileName) throws IOException {
|
private String findUniqueFilename(String fileName) throws IOException {
|
||||||
MWApi api = app.getMWApi();
|
MediaWikiApi api = app.getMWApi();
|
||||||
String sequenceFileName;
|
String sequenceFileName;
|
||||||
for ( int sequenceNumber = 1; true; sequenceNumber++ ) {
|
for ( int sequenceNumber = 1; true; sequenceNumber++ ) {
|
||||||
if (sequenceNumber == 1) {
|
if (sequenceNumber == 1) {
|
||||||
|
|
@ -320,7 +325,7 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
sequenceFileName = regexMatcher.replaceAll("$1 " + sequenceNumber + "$2");
|
sequenceFileName = regexMatcher.replaceAll("$1 " + sequenceNumber + "$2");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ( fileExistsWithName(api, sequenceFileName) || unfinishedUploads.contains(sequenceFileName) ) {
|
if ( api.fileExistsWithName(sequenceFileName) || unfinishedUploads.contains(sequenceFileName) ) {
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
|
|
@ -328,15 +333,4 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
}
|
}
|
||||||
return sequenceFileName;
|
return sequenceFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean fileExistsWithName(MWApi api, String fileName) throws IOException {
|
|
||||||
ApiResult result;
|
|
||||||
|
|
||||||
result = api.action("query")
|
|
||||||
.param("prop", "imageinfo")
|
|
||||||
.param("titles", "File:" + fileName)
|
|
||||||
.get();
|
|
||||||
|
|
||||||
return result.getNodes("/api/query/pages/page/imageinfo").size() > 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue