mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 04:13:53 +01:00
commit
53d6792f5b
41 changed files with 1061 additions and 551 deletions
|
|
@ -1,6 +1,7 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'jacoco-android'
|
||||
apply from: 'quality.gradle'
|
||||
apply plugin: 'com.getkeepsafe.dexcount'
|
||||
|
||||
dependencies {
|
||||
compile 'com.github.nicolas-raoul:Quadtree:ac16ea8035bf07'
|
||||
|
|
@ -13,11 +14,13 @@ dependencies {
|
|||
compile "com.android.support:support-v4:${project.supportLibVersion}"
|
||||
compile "com.android.support:appcompat-v7:${project.supportLibVersion}"
|
||||
compile "com.android.support:design:${project.supportLibVersion}"
|
||||
compile 'com.google.code.gson:gson:2.7'
|
||||
compile 'com.google.code.gson:gson:2.8.0'
|
||||
compile "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
|
||||
compile 'com.github.pedrovgs:renderers:3.3.0'
|
||||
annotationProcessor "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
|
||||
compile 'com.jakewharton.timber:timber:4.5.1'
|
||||
compile 'com.squareup.okhttp3:okhttp:3.8.1'
|
||||
compile 'com.squareup.okio:okio:1.13.0'
|
||||
compile ('com.mapbox.mapboxsdk:mapbox-android-sdk:5.1.0@aar'){
|
||||
transitive=true
|
||||
}
|
||||
|
|
@ -29,6 +32,9 @@ dependencies {
|
|||
testCompile ('org.robolectric:robolectric:3.3.2') {
|
||||
exclude module: 'guava'
|
||||
}
|
||||
|
||||
testCompile 'com.squareup.okhttp3:mockwebserver:3.8.1'
|
||||
androidTestCompile 'com.squareup.okhttp3:mockwebserver:3.8.1'
|
||||
androidTestCompile "com.android.support:support-annotations:${project.supportLibVersion}"
|
||||
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
|
||||
|
||||
|
|
|
|||
|
|
@ -8,41 +8,30 @@ import android.app.Application;
|
|||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.util.LruCache;
|
||||
|
||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||
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 org.acra.ACRA;
|
||||
import org.acra.ReportingInteractionMode;
|
||||
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.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 timber.log.Timber;
|
||||
|
||||
|
|
@ -76,9 +65,8 @@ public class CommonsApplication extends Application {
|
|||
public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App (%s) Feedback";
|
||||
|
||||
private static CommonsApplication instance = null;
|
||||
private AbstractHttpClient httpClient = null;
|
||||
private MWApi api = null;
|
||||
LruCache<String, String> thumbnailUrlCache = new LruCache<>(1024);
|
||||
private MediaWikiApi api = null;
|
||||
private LruCache<String, String> thumbnailUrlCache = new LruCache<>(1024);
|
||||
private CacheController cacheData = null;
|
||||
private DBOpenHelper dbOpenHelper = null;
|
||||
private NearbyPlaces nearbyPlaces = null;
|
||||
|
|
@ -98,35 +86,13 @@ public class CommonsApplication extends Application {
|
|||
return instance;
|
||||
}
|
||||
|
||||
public AbstractHttpClient getHttpClient() {
|
||||
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() {
|
||||
public MediaWikiApi getMWApi() {
|
||||
if (api == null) {
|
||||
api = newMWApi();
|
||||
api = new ApacheHttpClientMediaWikiApi(API_URL);
|
||||
}
|
||||
return api;
|
||||
}
|
||||
|
||||
private MWApi newMWApi() {
|
||||
return new MWApi(API_URL, getHttpClient());
|
||||
}
|
||||
|
||||
public CacheController getCacheData() {
|
||||
if (cacheData == null) {
|
||||
cacheData = new CacheController();
|
||||
|
|
@ -174,9 +140,6 @@ public class CommonsApplication extends Application {
|
|||
|
||||
Fresco.initialize(this);
|
||||
|
||||
// Initialize EventLogging
|
||||
EventLog.setApp(this);
|
||||
|
||||
//For caching area -> categories
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package fr.free.nrw.commons;
|
||||
|
||||
import org.mediawiki.api.ApiResult;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
|
|
@ -21,6 +20,8 @@ import javax.xml.parsers.DocumentBuilderFactory;
|
|||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.mwapi.MediaResult;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
|
|
@ -62,29 +63,15 @@ public class MediaDataExtractor {
|
|||
throw new IllegalStateException("Tried to call MediaDataExtractor.fetch() again.");
|
||||
}
|
||||
|
||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||
ApiResult result = api.action("query")
|
||||
.param("prop", "revisions")
|
||||
.param("titles", filename)
|
||||
.param("rvprop", "content")
|
||||
.param("rvlimit", 1)
|
||||
.param("rvgeneratexml", 1)
|
||||
.get();
|
||||
|
||||
processResult(result);
|
||||
fetched = true;
|
||||
}
|
||||
|
||||
private void processResult(ApiResult result) throws IOException {
|
||||
|
||||
String wikiSource = result.getString("/api/query/pages/page/revisions/rev");
|
||||
String parseTreeXmlSource = result.getString("/api/query/pages/page/revisions/rev/@parsetree");
|
||||
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
|
||||
MediaResult result = api.fetchMediaByFilename(filename);
|
||||
|
||||
// In-page category links are extracted from source, as XML doesn't cover [[links]]
|
||||
extractCategories(wikiSource);
|
||||
extractCategories(result.getWikiSource());
|
||||
|
||||
// Description template info is extracted from preprocessor XML
|
||||
processWikiParseTree(parseTreeXmlSource);
|
||||
processWikiParseTree(result.getParseTreeXmlSource());
|
||||
fetched = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,10 +3,9 @@ package fr.free.nrw.commons;
|
|||
import android.os.AsyncTask;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.mediawiki.api.ApiResult;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
|
||||
class MediaThumbnailFetchTask extends AsyncTask<String, String, String> {
|
||||
private static final String THUMB_SIZE = "640";
|
||||
protected final Media media;
|
||||
|
||||
public MediaThumbnailFetchTask(@NonNull Media media) {
|
||||
|
|
@ -16,15 +15,8 @@ class MediaThumbnailFetchTask extends AsyncTask<String, String, String> {
|
|||
@Override
|
||||
protected String doInBackground(String... params) {
|
||||
try {
|
||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||
ApiResult result =api.action("query")
|
||||
.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");
|
||||
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
|
||||
return api.findThumbnailByFilename(params[0]);
|
||||
} catch (Exception e) {
|
||||
// Do something better!
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import com.viewpagerindicator.CirclePageIndicator;
|
|||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||
import fr.free.nrw.commons.theme.BaseActivity;
|
||||
|
||||
public class WelcomeActivity extends BaseActivity {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import android.content.ContentResolver;
|
|||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
||||
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
||||
|
|
|
|||
|
|
@ -19,9 +19,10 @@ import android.widget.Toast;
|
|||
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.PageTitle;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.WelcomeActivity;
|
||||
|
||||
import fr.free.nrw.commons.PageTitle;
|
||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ import android.os.Bundle;
|
|||
import java.io.IOException;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.EventLog;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.mwapi.EventLog;
|
||||
import timber.log.Timber;
|
||||
|
||||
class LoginTask extends AsyncTask<String, String, String> {
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ import android.accounts.NetworkErrorException;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
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.MWApi;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
|
||||
public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
|
|||
}
|
||||
|
||||
private String getAuthCookie(String username, String password) throws IOException {
|
||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
|
||||
//TODO add 2fa support here
|
||||
String result = api.login(username, password);
|
||||
if(result.equals("PASS")) {
|
||||
|
|
|
|||
|
|
@ -134,7 +134,7 @@ public class CategorizationFragment extends Fragment {
|
|||
//Override onPostExecute to access the results of async API call
|
||||
titleCategoriesSub = new TitleCategories(title) {
|
||||
@Override
|
||||
protected void onPostExecute(ArrayList<String> result) {
|
||||
protected void onPostExecute(List<String> result) {
|
||||
super.onPostExecute(result);
|
||||
Timber.d("Results in onPostExecute: %s", result);
|
||||
titleCatItems.addAll(result);
|
||||
|
|
@ -277,8 +277,8 @@ public class CategorizationFragment extends Fragment {
|
|||
|
||||
prefixUpdaterSub = new PrefixUpdater(this) {
|
||||
@Override
|
||||
protected ArrayList<String> doInBackground(Void... voids) {
|
||||
ArrayList<String> result = new ArrayList<>();
|
||||
protected List<String> doInBackground(Void... voids) {
|
||||
List<String> result = new ArrayList<>();
|
||||
try {
|
||||
result = super.doInBackground();
|
||||
latch.await();
|
||||
|
|
@ -291,7 +291,7 @@ public class CategorizationFragment extends Fragment {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(ArrayList<String> result) {
|
||||
protected void onPostExecute(List<String> result) {
|
||||
super.onPostExecute(result);
|
||||
|
||||
results.addAll(result);
|
||||
|
|
@ -309,7 +309,7 @@ public class CategorizationFragment extends Fragment {
|
|||
|
||||
methodAUpdaterSub = new MethodAUpdater(this) {
|
||||
@Override
|
||||
protected void onPostExecute(ArrayList<String> result) {
|
||||
protected void onPostExecute(List<String> result) {
|
||||
results.clear();
|
||||
super.onPostExecute(result);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,15 +3,14 @@ package fr.free.nrw.commons.category;
|
|||
import android.os.AsyncTask;
|
||||
import android.view.View;
|
||||
|
||||
import fr.free.nrw.commons.MWApi;
|
||||
import org.mediawiki.api.ApiResult;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
|
|
@ -19,12 +18,12 @@ import timber.log.Timber;
|
|||
* the keyword typed in by the user. The 'srsearch' action-specific parameter is used for this
|
||||
* purpose. This class should be subclassed in CategorizationFragment.java to aggregate the results.
|
||||
*/
|
||||
public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||
class MethodAUpdater extends AsyncTask<Void, Void, List<String>> {
|
||||
|
||||
private String filter;
|
||||
CategorizationFragment catFragment;
|
||||
private CategorizationFragment catFragment;
|
||||
|
||||
public MethodAUpdater(CategorizationFragment catFragment) {
|
||||
MethodAUpdater(CategorizationFragment catFragment) {
|
||||
this.catFragment = catFragment;
|
||||
}
|
||||
|
||||
|
|
@ -42,10 +41,11 @@ public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
|||
* Remove categories that contain a year in them (starting with 19__ or 20__), except for this year
|
||||
* and previous year
|
||||
* Rationale: https://github.com/commons-app/apps-android-commons/issues/47
|
||||
*
|
||||
* @param items Unfiltered list of categories
|
||||
* @return Filtered category list
|
||||
*/
|
||||
private ArrayList<String> filterYears(ArrayList<String> items) {
|
||||
private List<String> filterYears(List<String> items) {
|
||||
|
||||
Iterator<String> iterator;
|
||||
|
||||
|
|
@ -60,12 +60,12 @@ public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
|||
Timber.d("Previous year: %s", prevYearInString);
|
||||
|
||||
//Copy to Iterator to prevent ConcurrentModificationException when removing item
|
||||
for(iterator = items.iterator(); iterator.hasNext();) {
|
||||
for (iterator = items.iterator(); iterator.hasNext(); ) {
|
||||
String s = iterator.next();
|
||||
|
||||
//Check if s contains a 4-digit word anywhere within the string (.* is wildcard)
|
||||
//And that s does not equal the current year or previous year
|
||||
if(s.matches(".*(19|20)\\d{2}.*") && !s.contains(yearInString) && !s.contains(prevYearInString)) {
|
||||
if (s.matches(".*(19|20)\\d{2}.*") && !s.contains(yearInString) && !s.contains(prevYearInString)) {
|
||||
Timber.d("Filtering out year %s", s);
|
||||
iterator.remove();
|
||||
}
|
||||
|
|
@ -76,37 +76,22 @@ public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected ArrayList<String> doInBackground(Void... voids) {
|
||||
protected List<String> doInBackground(Void... voids) {
|
||||
|
||||
//otherwise if user has typed something in that isn't in cache, search API for matching categories
|
||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||
ApiResult result;
|
||||
ArrayList<String> categories = new ArrayList<>();
|
||||
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
|
||||
List<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=
|
||||
try {
|
||||
result = api.action("query")
|
||||
.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);
|
||||
categories = api.searchCategories(CategorizationFragment.SEARCH_CATS_LIMIT, filter);
|
||||
Timber.d("Method A URL filter %s", categories);
|
||||
} catch (IOException e) {
|
||||
Timber.e(e, "IO Exception: ");
|
||||
//Return empty arraylist
|
||||
return categories;
|
||||
}
|
||||
|
||||
ArrayList<ApiResult> categoryNodes = result.getNodes("/api/query/search/p/@title");
|
||||
for(ApiResult categoryNode: categoryNodes) {
|
||||
String cat = categoryNode.getDocument().getTextContent();
|
||||
String catString = cat.replace("Category:", "");
|
||||
categories.add(catString);
|
||||
}
|
||||
|
||||
Timber.d("Found categories from Method A search, waiting for filter");
|
||||
return new ArrayList<>(filterYears(categories));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,15 +4,14 @@ import android.os.AsyncTask;
|
|||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
|
||||
import fr.free.nrw.commons.MWApi;
|
||||
import org.mediawiki.api.ApiResult;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
|
|
@ -21,7 +20,7 @@ import timber.log.Timber;
|
|||
* for this purpose. This class should be subclassed in CategorizationFragment.java to aggregate
|
||||
* the results.
|
||||
*/
|
||||
public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||
public class PrefixUpdater extends AsyncTask<Void, Void, List<String>> {
|
||||
|
||||
private String filter;
|
||||
private CategorizationFragment catFragment;
|
||||
|
|
@ -44,10 +43,11 @@ public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
|||
* Remove categories that contain a year in them (starting with 19__ or 20__), except for this year
|
||||
* and previous year
|
||||
* Rationale: https://github.com/commons-app/apps-android-commons/issues/47
|
||||
*
|
||||
* @param items Unfiltered list of categories
|
||||
* @return Filtered category list
|
||||
*/
|
||||
private ArrayList<String> filterYears(ArrayList<String> items) {
|
||||
private List<String> filterYears(List<String> items) {
|
||||
|
||||
Iterator<String> iterator;
|
||||
|
||||
|
|
@ -62,12 +62,12 @@ public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
|||
Timber.d("Previous year: %s", prevYearInString);
|
||||
|
||||
//Copy to Iterator to prevent ConcurrentModificationException when removing item
|
||||
for(iterator = items.iterator(); iterator.hasNext();) {
|
||||
for (iterator = items.iterator(); iterator.hasNext(); ) {
|
||||
String s = iterator.next();
|
||||
|
||||
//Check if s contains a 4-digit word anywhere within the string (.* is wildcard)
|
||||
//And that s does not equal the current year or previous year
|
||||
if(s.matches(".*(19|20)\\d{2}.*") && !s.contains(yearInString) && !s.contains(prevYearInString)) {
|
||||
if (s.matches(".*(19|20)\\d{2}.*") && !s.contains(yearInString) && !s.contains(prevYearInString)) {
|
||||
Timber.d("Filtering out year %s", s);
|
||||
iterator.remove();
|
||||
}
|
||||
|
|
@ -78,16 +78,16 @@ public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected ArrayList<String> doInBackground(Void... voids) {
|
||||
protected List<String> doInBackground(Void... voids) {
|
||||
//If user hasn't typed anything in yet, get GPS and recent items
|
||||
if(TextUtils.isEmpty(filter)) {
|
||||
if (TextUtils.isEmpty(filter)) {
|
||||
ArrayList<String> mergedItems = new ArrayList<>(catFragment.mergeItems());
|
||||
Timber.d("Merged items, waiting for filter");
|
||||
return new ArrayList<>(filterYears(mergedItems));
|
||||
}
|
||||
|
||||
//if user types in something that is in cache, return cached category
|
||||
if(catFragment.categoriesCache.containsKey(filter)) {
|
||||
if (catFragment.categoriesCache.containsKey(filter)) {
|
||||
ArrayList<String> cachedItems = new ArrayList<>(catFragment.categoriesCache.get(filter));
|
||||
Timber.d("Found cache items, waiting for filter");
|
||||
return new ArrayList<>(filterYears(cachedItems));
|
||||
|
|
@ -95,27 +95,17 @@ 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
|
||||
//URL: https://commons.wikimedia.org/w/api.php?action=query&list=allcategories&acprefix=filter&aclimit=25
|
||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||
ApiResult result;
|
||||
ArrayList<String> categories = new ArrayList<>();
|
||||
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
|
||||
List<String> categories = new ArrayList<>();
|
||||
try {
|
||||
result = api.action("query")
|
||||
.param("list", "allcategories")
|
||||
.param("acprefix", filter)
|
||||
.param("aclimit", catFragment.SEARCH_CATS_LIMIT)
|
||||
.get();
|
||||
Timber.d("Prefix URL filter %s", result);
|
||||
categories = api.allCategories(CategorizationFragment.SEARCH_CATS_LIMIT, this.filter);
|
||||
Timber.d("Prefix URL filter %s", categories);
|
||||
} catch (IOException e) {
|
||||
Timber.e(e, "IO Exception: ");
|
||||
//Return empty arraylist
|
||||
return categories;
|
||||
}
|
||||
|
||||
ArrayList<ApiResult> categoryNodes = result.getNodes("/api/query/allcategories/c");
|
||||
for(ApiResult categoryNode: categoryNodes) {
|
||||
categories.add(categoryNode.getDocument().getTextContent());
|
||||
}
|
||||
|
||||
Timber.d("Found categories from Prefix search, waiting for filter");
|
||||
return new ArrayList<>(filterYears(categories));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,12 @@ package fr.free.nrw.commons.category;
|
|||
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import fr.free.nrw.commons.MWApi;
|
||||
import org.mediawiki.api.ApiResult;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
|
|
@ -16,13 +15,13 @@ import timber.log.Timber;
|
|||
* the title entered in previous screen. The 'srsearch' action-specific parameter is used for this
|
||||
* purpose. This class should be subclassed in CategorizationFragment.java to add the results to recent and GPS cats.
|
||||
*/
|
||||
public class TitleCategories extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||
class TitleCategories extends AsyncTask<Void, Void, List<String>> {
|
||||
|
||||
private final static int SEARCH_CATS_LIMIT = 25;
|
||||
|
||||
private String title;
|
||||
|
||||
public TitleCategories(String title) {
|
||||
TitleCategories(String title) {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
|
|
@ -32,39 +31,23 @@ public class TitleCategories extends AsyncTask<Void, Void, ArrayList<String>> {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected ArrayList<String> doInBackground(Void... voids) {
|
||||
protected List<String> doInBackground(Void... voids) {
|
||||
|
||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||
ApiResult result;
|
||||
ArrayList<String> items = new ArrayList<>();
|
||||
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
|
||||
List<String> titleCategories = 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=
|
||||
try {
|
||||
result = api.action("query")
|
||||
.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);
|
||||
titleCategories = api.searchTitles(SEARCH_CATS_LIMIT, this.title);
|
||||
} catch (IOException e) {
|
||||
Timber.e(e, "IO Exception: ");
|
||||
//Return empty arraylist
|
||||
return items;
|
||||
return titleCategories;
|
||||
}
|
||||
|
||||
ArrayList<ApiResult> categoryNodes = result.getNodes("/api/query/search/p/@title");
|
||||
for(ApiResult categoryNode: categoryNodes) {
|
||||
String cat = categoryNode.getDocument().getTextContent();
|
||||
String catString = cat.replace("Category:", "");
|
||||
items.add(catString);
|
||||
}
|
||||
Timber.d("Title cat query results: %s", titleCategories);
|
||||
|
||||
Timber.d("Title cat query results: %s", items);
|
||||
|
||||
return items;
|
||||
return titleCategories;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package fr.free.nrw.commons.concurrency;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
|
||||
public class BackgroundPoolExceptionHandler implements ExceptionHandler {
|
||||
|
|
|
|||
|
|
@ -15,10 +15,9 @@ import java.util.Locale;
|
|||
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.EventLog;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
|
||||
public class Contribution extends Media {
|
||||
|
||||
|
|
@ -63,8 +62,6 @@ public class Contribution extends Media {
|
|||
isMultiple = multiple;
|
||||
}
|
||||
|
||||
public EventLog.LogBuilder event;
|
||||
|
||||
public Contribution(Uri localUri, String remoteUri, String filename, String description, long dataLength, Date dateCreated, Date dateUploaded, String creator, String editSummary, String decimalCoords) {
|
||||
super(localUri, remoteUri, filename, description, dataLength, dateCreated, dateUploaded, creator);
|
||||
this.decimalCoords = decimalCoords;
|
||||
|
|
@ -132,14 +129,14 @@ public class Contribution extends Media {
|
|||
public String getPageContents() {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
|
||||
|
||||
|
||||
buffer
|
||||
.append("== {{int:filedesc}} ==\n")
|
||||
.append("== {{int:filedesc}} ==\n")
|
||||
.append("{{Information\n")
|
||||
.append("|description=").append(getDescription()).append("\n")
|
||||
.append("|source=").append("{{own}}\n")
|
||||
.append("|author=[[User:").append(creator).append("|").append(creator).append("]]\n");
|
||||
if(dateCreated != null) {
|
||||
.append("|description=").append(getDescription()).append("\n")
|
||||
.append("|source=").append("{{own}}\n")
|
||||
.append("|author=[[User:").append(creator).append("|").append(creator).append("]]\n");
|
||||
if (dateCreated != null) {
|
||||
buffer
|
||||
.append("|date={{According to EXIF data|").append(isoFormat.format(dateCreated)).append("}}\n");
|
||||
}
|
||||
|
|
@ -148,13 +145,13 @@ public class Contribution extends Media {
|
|||
|
||||
//Only add Location template (e.g. {{Location|37.51136|-77.602615}} ) if coords is not null
|
||||
if (decimalCoords != null) {
|
||||
buffer.append("{{Location|").append(decimalCoords).append("}}").append("\n");
|
||||
buffer.append("{{Location|").append(decimalCoords).append("}}").append("\n");
|
||||
}
|
||||
|
||||
buffer.append("== {{int:license-header}} ==\n")
|
||||
.append(Utils.licenseTemplateFor(getLicense())).append("\n\n")
|
||||
.append("{{Uploaded from Mobile|platform=Android|version=").append(BuildConfig.VERSION_NAME).append("}}\n")
|
||||
.append(getTrackingTemplates());
|
||||
.append("{{Uploaded from Mobile|platform=Android|version=").append(BuildConfig.VERSION_NAME).append("}}\n")
|
||||
.append(getTrackingTemplates());
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
|
|
@ -164,19 +161,19 @@ public class Contribution extends Media {
|
|||
|
||||
public void save() {
|
||||
try {
|
||||
if(contentUri == null) {
|
||||
if (contentUri == null) {
|
||||
contentUri = client.insert(ContributionsContentProvider.BASE_URI, this.toContentValues());
|
||||
} else {
|
||||
client.update(contentUri, toContentValues(), null, null);
|
||||
}
|
||||
} catch(RemoteException e) {
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
try {
|
||||
if(contentUri == null) {
|
||||
if (contentUri == null) {
|
||||
// noooo
|
||||
throw new RuntimeException("tried to delete item with no content URI");
|
||||
} else {
|
||||
|
|
@ -191,20 +188,20 @@ public class Contribution extends Media {
|
|||
public ContentValues toContentValues() {
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(Table.COLUMN_FILENAME, getFilename());
|
||||
if(getLocalUri() != null) {
|
||||
if (getLocalUri() != null) {
|
||||
cv.put(Table.COLUMN_LOCAL_URI, getLocalUri().toString());
|
||||
}
|
||||
if(getImageUrl() != null) {
|
||||
if (getImageUrl() != null) {
|
||||
cv.put(Table.COLUMN_IMAGE_URL, getImageUrl());
|
||||
}
|
||||
if(getDateUploaded() != null) {
|
||||
if (getDateUploaded() != null) {
|
||||
cv.put(Table.COLUMN_UPLOADED, getDateUploaded().getTime());
|
||||
}
|
||||
cv.put(Table.COLUMN_LENGTH, getDataLength());
|
||||
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);
|
||||
cv.put(Table.COLUMN_SOURCE, source);
|
||||
cv.put(Table.COLUMN_DESCRIPTION, description);
|
||||
cv.put(Table.COLUMN_CREATOR, creator);
|
||||
cv.put(Table.COLUMN_MULTIPLE, isMultiple ? 1 : 0);
|
||||
|
|
@ -240,7 +237,7 @@ public class Contribution extends Media {
|
|||
c.timestamp = cursor.getLong(4) == 0 ? null : new Date(cursor.getLong(4));
|
||||
c.state = cursor.getInt(5);
|
||||
c.dataLength = cursor.getLong(6);
|
||||
c.dateUploaded = cursor.getLong(7) == 0 ? null : new Date(cursor.getLong(7));
|
||||
c.dateUploaded = cursor.getLong(7) == 0 ? null : new Date(cursor.getLong(7));
|
||||
c.transferred = cursor.getLong(8);
|
||||
c.source = cursor.getString(9);
|
||||
c.description = cursor.getString(10);
|
||||
|
|
@ -324,7 +321,7 @@ public class Contribution extends Media {
|
|||
+ "width INTEGER,"
|
||||
+ "height INTEGER,"
|
||||
+ "LICENSE STRING"
|
||||
+ ");";
|
||||
+ ");";
|
||||
|
||||
|
||||
public static void onCreate(SQLiteDatabase db) {
|
||||
|
|
@ -337,36 +334,36 @@ public class Contribution extends Media {
|
|||
}
|
||||
|
||||
public static void onUpdate(SQLiteDatabase db, int from, int to) {
|
||||
if(from == to) {
|
||||
if (from == to) {
|
||||
return;
|
||||
}
|
||||
if(from == 1) {
|
||||
if (from == 1) {
|
||||
db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN description STRING;");
|
||||
db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN creator STRING;");
|
||||
from++;
|
||||
onUpdate(db, from, to);
|
||||
return;
|
||||
}
|
||||
if(from == 2) {
|
||||
if (from == 2) {
|
||||
db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN multiple INTEGER;");
|
||||
db.execSQL("UPDATE " + TABLE_NAME + " SET multiple = 0");
|
||||
from++;
|
||||
onUpdate(db, from, to);
|
||||
return;
|
||||
}
|
||||
if(from == 3) {
|
||||
if (from == 3) {
|
||||
// Do nothing
|
||||
from++;
|
||||
onUpdate(db, from, to);
|
||||
return;
|
||||
}
|
||||
if(from == 4) {
|
||||
if (from == 4) {
|
||||
// Do nothing -- added Category
|
||||
from++;
|
||||
onUpdate(db, from, to);
|
||||
return;
|
||||
}
|
||||
if(from == 5) {
|
||||
if (from == 5) {
|
||||
// Added width and height fields
|
||||
db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN width INTEGER;");
|
||||
db.execSQL("UPDATE " + TABLE_NAME + " SET width = 0");
|
||||
|
|
|
|||
|
|
@ -3,15 +3,12 @@ package fr.free.nrw.commons.contributions;
|
|||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.content.FileProvider;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
import fr.free.nrw.commons.upload.ShareActivity;
|
||||
|
|
|
|||
|
|
@ -12,15 +12,15 @@ import android.os.Bundle;
|
|||
import android.os.RemoteException;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.mediawiki.api.ApiResult;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.MWApi;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.mwapi.LogEventResult;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
|
|
@ -61,29 +61,17 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||
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!
|
||||
String user = account.name;
|
||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
|
||||
SharedPreferences prefs = this.getContext().getSharedPreferences("prefs", Context.MODE_PRIVATE);
|
||||
String lastModified = prefs.getString("lastSyncTimestamp", "");
|
||||
Date curTime = new Date();
|
||||
ApiResult result;
|
||||
LogEventResult result;
|
||||
Boolean done = false;
|
||||
String queryContinue = null;
|
||||
while(!done) {
|
||||
|
||||
try {
|
||||
MWApi.RequestBuilder builder = api.action("query")
|
||||
.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();
|
||||
result = api.logEvents(user, lastModified, queryContinue, getLimit());
|
||||
} catch (IOException e) {
|
||||
// There isn't really much we can do, eh?
|
||||
// FIXME: Perhaps add EventLogging?
|
||||
|
|
@ -93,22 +81,21 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||
}
|
||||
Timber.d("Last modified at %s", lastModified);
|
||||
|
||||
ArrayList<ApiResult> uploads = result.getNodes("/api/query/logevents/item");
|
||||
Timber.d("%d results!", uploads.size());
|
||||
List<LogEventResult.LogEvent> logEvents = result.getLogEvents();
|
||||
Timber.d("%d results!", logEvents.size());
|
||||
ArrayList<ContentValues> imageValues = new ArrayList<>();
|
||||
for(ApiResult image: uploads) {
|
||||
String pageId = image.getString("@pageid");
|
||||
if (pageId.equals("0")) {
|
||||
for (LogEventResult.LogEvent image : logEvents) {
|
||||
if (image.isDeleted()) {
|
||||
// means that this upload was deleted.
|
||||
continue;
|
||||
}
|
||||
String filename = image.getString("@title");
|
||||
String filename = image.getFilename();
|
||||
if(fileExists(contentProviderClient, filename)) {
|
||||
Timber.d("Skipping %s", filename);
|
||||
continue;
|
||||
}
|
||||
String thumbUrl = Utils.makeThumbBaseUrl(filename);
|
||||
Date dateUpdated = Utils.parseMWDate(image.getString("@timestamp"));
|
||||
Date dateUpdated = image.getDateUpdated();
|
||||
Contribution contrib = new Contribution(null, thumbUrl, filename, "", -1, dateUpdated, dateUpdated, user, "", "");
|
||||
contrib.setState(Contribution.STATE_COMPLETED);
|
||||
imageValues.add(contrib.toContentValues());
|
||||
|
|
@ -130,7 +117,8 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
queryContinue = result.getString("/api/query-continue/logevents/@lestart");
|
||||
|
||||
queryContinue = result.getQueryContinue();
|
||||
if(TextUtils.isEmpty(queryContinue)) {
|
||||
done = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,11 +28,11 @@ import android.view.View;
|
|||
import android.view.ViewGroup;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.EventLog;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||
import fr.free.nrw.commons.mwapi.EventLog;
|
||||
|
||||
public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPageChangeListener {
|
||||
private ViewPager pager;
|
||||
|
|
|
|||
|
|
@ -12,15 +12,13 @@ import android.database.Cursor;
|
|||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import fr.free.nrw.commons.MWApi;
|
||||
import org.mediawiki.api.ApiResult;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
|
|
@ -41,14 +39,14 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||
}
|
||||
|
||||
// Exit early if nothing to do
|
||||
if(allModifications == null || allModifications.getCount() == 0) {
|
||||
if (allModifications == null || allModifications.getCount() == 0) {
|
||||
Timber.d("No modifications to perform");
|
||||
return;
|
||||
}
|
||||
|
||||
String authCookie;
|
||||
try {
|
||||
authCookie = AccountManager.get(getContext()).blockingGetAuthToken(account, "", false);
|
||||
authCookie = AccountManager.get(getContext()).blockingGetAuthToken(account, "", false);
|
||||
} catch (OperationCanceledException | AuthenticatorException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (IOException e) {
|
||||
|
|
@ -56,16 +54,15 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||
return;
|
||||
}
|
||||
|
||||
if(Utils.isNullOrWhiteSpace(authCookie)) {
|
||||
if (Utils.isNullOrWhiteSpace(authCookie)) {
|
||||
Timber.d("Could not authenticate :(");
|
||||
return;
|
||||
}
|
||||
|
||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
|
||||
api.setAuthCookie(authCookie);
|
||||
String editToken;
|
||||
|
||||
ApiResult requestResult, responseResult;
|
||||
try {
|
||||
editToken = api.getEditToken();
|
||||
} catch (IOException e) {
|
||||
|
|
@ -81,7 +78,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||
try {
|
||||
contributionsClient = getContext().getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY);
|
||||
|
||||
while(!allModifications.isAfterLast()) {
|
||||
while (!allModifications.isAfterLast()) {
|
||||
ModifierSequence sequence = ModifierSequence.fromCursor(allModifications);
|
||||
sequence.setContentProviderClient(contentProviderClient);
|
||||
Contribution contrib;
|
||||
|
|
@ -95,41 +92,31 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||
contributionCursor.moveToFirst();
|
||||
contrib = Contribution.fromCursor(contributionCursor);
|
||||
|
||||
if(contrib.getState() == Contribution.STATE_COMPLETED) {
|
||||
|
||||
if (contrib.getState() == Contribution.STATE_COMPLETED) {
|
||||
String pageContent;
|
||||
try {
|
||||
requestResult = api.action("query")
|
||||
.param("prop", "revisions")
|
||||
.param("rvprop", "timestamp|content")
|
||||
.param("titles", contrib.getFilename())
|
||||
.get();
|
||||
pageContent = api.revisionsByFilename(contrib.getFilename());
|
||||
} catch (IOException e) {
|
||||
Timber.d("Network fuckup on modifications sync!");
|
||||
continue;
|
||||
}
|
||||
|
||||
Timber.d("Page content is %s", Utils.getStringFromDOM(requestResult.getDocument()));
|
||||
String pageContent = requestResult.getString("/api/query/pages/page/revisions/rev");
|
||||
String processedPageContent = sequence.executeModifications(contrib.getFilename(), pageContent);
|
||||
Timber.d("Page content is %s", pageContent);
|
||||
String processedPageContent = sequence.executeModifications(contrib.getFilename(), pageContent);
|
||||
|
||||
String editResult;
|
||||
try {
|
||||
responseResult = api.action("edit")
|
||||
.param("title", contrib.getFilename())
|
||||
.param("token", editToken)
|
||||
.param("text", processedPageContent)
|
||||
.param("summary", sequence.getEditSummary())
|
||||
.post();
|
||||
editResult = api.edit(editToken, processedPageContent, contrib.getFilename(), sequence.getEditSummary());
|
||||
} catch (IOException e) {
|
||||
Timber.d("Network fuckup on modifications sync!");
|
||||
continue;
|
||||
}
|
||||
|
||||
Timber.d("Response is %s", Utils.getStringFromDOM(responseResult.getDocument()));
|
||||
Timber.d("Response is %s", editResult);
|
||||
|
||||
String result = responseResult.getString("/api/edit/@result");
|
||||
if(!result.equals("Success")) {
|
||||
if (!editResult.equals("Success")) {
|
||||
// FIXME: Log this somewhere else
|
||||
Timber.d("Non success result! %s", result);
|
||||
Timber.d("Non success result! %s", editResult);
|
||||
} else {
|
||||
sequence.delete();
|
||||
}
|
||||
|
|
@ -137,7 +124,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||
allModifications.moveToNext();
|
||||
}
|
||||
} finally {
|
||||
if(contributionsClient != null) {
|
||||
if (contributionsClient != null) {
|
||||
contributionsClient.release();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,374 @@
|
|||
package fr.free.nrw.commons.mwapi;
|
||||
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
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 org.mediawiki.api.MWApi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import in.yuvi.http.fluent.Http;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* @author Addshore
|
||||
*/
|
||||
public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||
private static final String THUMB_SIZE = "640";
|
||||
private AbstractHttpClient httpClient;
|
||||
private MWApi api;
|
||||
|
||||
public ApacheHttpClientMediaWikiApi(String apiURL) {
|
||||
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);
|
||||
httpClient = new DefaultHttpClient(cm, params);
|
||||
api = new MWApi(apiURL, httpClient);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(api.action("clientlogin")
|
||||
.param("rememberMe", "1")
|
||||
.param("username", username)
|
||||
.param("password", password)
|
||||
.param("logintoken", 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(api.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 api.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")) {
|
||||
api.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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthCookie() {
|
||||
return api.getAuthCookie();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAuthCookie(String authCookie) {
|
||||
api.setAuthCookie(authCookie);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateLogin() throws IOException {
|
||||
return api.validateLogin();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEditToken() throws IOException {
|
||||
return api.getEditToken();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean fileExistsWithName(String fileName) throws IOException {
|
||||
return api.action("query")
|
||||
.param("prop", "imageinfo")
|
||||
.param("titles", "File:" + fileName)
|
||||
.get()
|
||||
.getNodes("/api/query/pages/page/imageinfo").size() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String edit(String editToken, String processedPageContent, String filename, String summary) throws IOException {
|
||||
return api.action("edit")
|
||||
.param("title", filename)
|
||||
.param("token", editToken)
|
||||
.param("text", processedPageContent)
|
||||
.param("summary", summary)
|
||||
.post()
|
||||
.getString("/api/edit/@result");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String findThumbnailByFilename(String filename) throws IOException {
|
||||
return api.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
|
||||
@NonNull
|
||||
public MediaResult fetchMediaByFilename(String filename) throws IOException {
|
||||
ApiResult apiResult = api.action("query")
|
||||
.param("prop", "revisions")
|
||||
.param("titles", filename)
|
||||
.param("rvprop", "content")
|
||||
.param("rvlimit", 1)
|
||||
.param("rvgeneratexml", 1)
|
||||
.get();
|
||||
|
||||
return new MediaResult(
|
||||
apiResult.getString("/api/query/pages/page/revisions/rev"),
|
||||
apiResult.getString("/api/query/pages/page/revisions/rev/@parsetree"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public List<String> searchCategories(int searchCatsLimit, String filterValue) throws IOException {
|
||||
List<ApiResult> categoryNodes = api.action("query")
|
||||
.param("format", "xml")
|
||||
.param("list", "search")
|
||||
.param("srwhat", "text")
|
||||
.param("srnamespace", "14")
|
||||
.param("srlimit", searchCatsLimit)
|
||||
.param("srsearch", filterValue)
|
||||
.get()
|
||||
.getNodes("/api/query/search/p/@title");
|
||||
|
||||
if (categoryNodes == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<String> categories = new ArrayList<>();
|
||||
for (ApiResult categoryNode : categoryNodes) {
|
||||
String cat = categoryNode.getDocument().getTextContent();
|
||||
String catString = cat.replace("Category:", "");
|
||||
categories.add(catString);
|
||||
}
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public List<String> allCategories(int searchCatsLimit, String filterValue) throws IOException {
|
||||
ArrayList<ApiResult> categoryNodes = api.action("query")
|
||||
.param("list", "allcategories")
|
||||
.param("acprefix", filterValue)
|
||||
.param("aclimit", searchCatsLimit)
|
||||
.get()
|
||||
.getNodes("/api/query/allcategories/c");
|
||||
|
||||
if (categoryNodes == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<String> categories = new ArrayList<>();
|
||||
for (ApiResult categoryNode : categoryNodes) {
|
||||
categories.add(categoryNode.getDocument().getTextContent());
|
||||
}
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public List<String> searchTitles(int searchCatsLimit, String title) throws IOException {
|
||||
ArrayList<ApiResult> categoryNodes = api.action("query")
|
||||
.param("format", "xml")
|
||||
.param("list", "search")
|
||||
.param("srwhat", "text")
|
||||
.param("srnamespace", "14")
|
||||
.param("srlimit", searchCatsLimit)
|
||||
.param("srsearch", title)
|
||||
.get()
|
||||
.getNodes("/api/query/search/p/@title");
|
||||
|
||||
if (categoryNodes == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<String> titleCategories = new ArrayList<>();
|
||||
for (ApiResult categoryNode : categoryNodes) {
|
||||
String cat = categoryNode.getDocument().getTextContent();
|
||||
String catString = cat.replace("Category:", "");
|
||||
titleCategories.add(catString);
|
||||
}
|
||||
|
||||
return titleCategories;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public LogEventResult logEvents(String user, String lastModified, String queryContinue, int limit) throws IOException {
|
||||
org.mediawiki.api.MWApi.RequestBuilder builder = api.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);
|
||||
}
|
||||
ApiResult result = builder.get();
|
||||
|
||||
return new LogEventResult(
|
||||
getLogEventsFromResult(result),
|
||||
result.getString("/api/query-continue/logevents/@lestart"));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private ArrayList<LogEventResult.LogEvent> getLogEventsFromResult(ApiResult result) {
|
||||
ArrayList<ApiResult> uploads = result.getNodes("/api/query/logevents/item");
|
||||
Timber.d("%d results!", uploads.size());
|
||||
ArrayList<LogEventResult.LogEvent> logEvents = new ArrayList<>();
|
||||
for (ApiResult image : uploads) {
|
||||
logEvents.add(new LogEventResult.LogEvent(
|
||||
image.getString("@pageid"),
|
||||
image.getString("@title"),
|
||||
Utils.parseMWDate(image.getString("@timestamp")))
|
||||
);
|
||||
}
|
||||
return logEvents;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String revisionsByFilename(String filename) throws IOException {
|
||||
return api.action("query")
|
||||
.param("prop", "revisions")
|
||||
.param("rvprop", "timestamp|content")
|
||||
.param("titles", filename)
|
||||
.get()
|
||||
.getString("/api/query/pages/page/revisions/rev");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean existingFile(String fileSha1) throws IOException {
|
||||
return api.action("query")
|
||||
.param("format", "xml")
|
||||
.param("list", "allimages")
|
||||
.param("aisha1", fileSha1)
|
||||
.get()
|
||||
.getNodes("/api/query/allimages/img").size() > 0;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public UploadResult uploadFile(String filename, InputStream file, long dataLength, String pageContents, String editSummary, final ProgressListener progressListener) throws IOException {
|
||||
ApiResult result = api.upload(filename, file, dataLength, pageContents, editSummary, new in.yuvi.http.fluent.ProgressListener() {
|
||||
@Override
|
||||
public void onProgress(long transferred, long total) {
|
||||
progressListener.onProgress(transferred, total);
|
||||
}
|
||||
});
|
||||
|
||||
Log.e("WTF", "Result: "+result.toString());
|
||||
|
||||
String resultStatus = result.getString("/api/upload/@result");
|
||||
if (!resultStatus.equals("Success")) {
|
||||
String errorCode = result.getString("/api/error/@code");
|
||||
return new UploadResult(resultStatus, errorCode);
|
||||
} else {
|
||||
Date dateUploaded = Utils.parseMWDate(result.getString("/api/upload/imageinfo/@timestamp"));
|
||||
String canonicalFilename = "File:" + result.getString("/api/upload/@filename").replace("_", " "); // Title vs Filename
|
||||
String imageUrl = result.getString("/api/upload/imageinfo/@url");
|
||||
return new UploadResult(resultStatus, dateUploaded, canonicalFilename, imageUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package fr.free.nrw.commons.mwapi;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class LogEventResult {
|
||||
private final List<LogEvent> logEvents;
|
||||
private final String queryContinue;
|
||||
|
||||
LogEventResult(@NonNull List<LogEvent> logEvents, String queryContinue) {
|
||||
this.logEvents = logEvents;
|
||||
this.queryContinue = queryContinue;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public List<LogEvent> getLogEvents() {
|
||||
return logEvents;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getQueryContinue() {
|
||||
return queryContinue;
|
||||
}
|
||||
|
||||
public static class LogEvent {
|
||||
private final String pageId;
|
||||
private final String filename;
|
||||
private final Date dateUpdated;
|
||||
|
||||
LogEvent(String pageId, String filename, Date dateUpdated) {
|
||||
this.pageId = pageId;
|
||||
this.filename = filename;
|
||||
this.dateUpdated = dateUpdated;
|
||||
}
|
||||
|
||||
public boolean isDeleted() {
|
||||
return pageId.equals("0");
|
||||
}
|
||||
|
||||
public String getFilename() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
public Date getDateUpdated() {
|
||||
return dateUpdated;
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
19
app/src/main/java/fr/free/nrw/commons/mwapi/MediaResult.java
Normal file
19
app/src/main/java/fr/free/nrw/commons/mwapi/MediaResult.java
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package fr.free.nrw.commons.mwapi;
|
||||
|
||||
public class MediaResult {
|
||||
private final String wikiSource;
|
||||
private final String parseTreeXmlSource;
|
||||
|
||||
MediaResult(String wikiSource, String parseTreeXmlSource) {
|
||||
this.wikiSource = wikiSource;
|
||||
this.parseTreeXmlSource = parseTreeXmlSource;
|
||||
}
|
||||
|
||||
public String getWikiSource() {
|
||||
return wikiSource;
|
||||
}
|
||||
|
||||
public String getParseTreeXmlSource() {
|
||||
return parseTreeXmlSource;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package fr.free.nrw.commons.mwapi;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
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;
|
||||
|
||||
boolean fileExistsWithName(String fileName) throws IOException;
|
||||
|
||||
String findThumbnailByFilename(String filename) throws IOException;
|
||||
|
||||
boolean logEvents(LogBuilder[] logBuilders);
|
||||
|
||||
@NonNull
|
||||
UploadResult uploadFile(String filename, InputStream file, long dataLength, String pageContents, String editSummary, ProgressListener progressListener) throws IOException;
|
||||
|
||||
@Nullable
|
||||
String edit(String editToken, String processedPageContent, String filename, String summary) throws IOException;
|
||||
|
||||
@NonNull
|
||||
MediaResult fetchMediaByFilename(String filename) throws IOException;
|
||||
|
||||
@NonNull
|
||||
List<String> searchCategories(int searchCatsLimit, String filterValue) throws IOException;
|
||||
|
||||
@NonNull
|
||||
List<String> allCategories(int searchCatsLimit, String filter) throws IOException;
|
||||
|
||||
@NonNull
|
||||
List<String> searchTitles(int searchCatsLimit, String title) throws IOException;
|
||||
|
||||
@Nullable
|
||||
String revisionsByFilename(String filename) throws IOException;
|
||||
|
||||
boolean existingFile(String fileSha1) throws IOException;
|
||||
|
||||
@NonNull
|
||||
LogEventResult logEvents(String user, String lastModified, String queryContinue, int limit) throws IOException;
|
||||
|
||||
interface ProgressListener {
|
||||
void onProgress(long transferred, long total);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package fr.free.nrw.commons.mwapi;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class UploadResult {
|
||||
private String errorCode;
|
||||
private String resultStatus;
|
||||
private Date dateUploaded;
|
||||
private String imageUrl;
|
||||
private String canonicalFilename;
|
||||
|
||||
UploadResult(String resultStatus, String errorCode) {
|
||||
this.resultStatus = resultStatus;
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
UploadResult(String resultStatus, Date dateUploaded, String canonicalFilename, String imageUrl) {
|
||||
this.resultStatus = resultStatus;
|
||||
this.dateUploaded = dateUploaded;
|
||||
this.canonicalFilename = canonicalFilename;
|
||||
this.imageUrl = imageUrl;
|
||||
}
|
||||
|
||||
public Date getDateUploaded() {
|
||||
return dateUploaded;
|
||||
}
|
||||
|
||||
public String getImageUrl() {
|
||||
return imageUrl;
|
||||
}
|
||||
|
||||
public String getCanonicalFilename() {
|
||||
return canonicalFilename;
|
||||
}
|
||||
|
||||
public String getErrorCode() {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
public String getResultStatus() {
|
||||
return resultStatus;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,15 +6,12 @@ import android.content.Intent;
|
|||
import android.os.AsyncTask;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
|
||||
import org.mediawiki.api.ApiResult;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.MWApi;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
|
|
@ -49,27 +46,18 @@ public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> {
|
|||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||
ApiResult result;
|
||||
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
|
||||
|
||||
// https://commons.wikimedia.org/w/api.php?action=query&list=allimages&format=xml&aisha1=801957214aba50cb63bb6eb1b0effa50188900ba
|
||||
boolean fileExists;
|
||||
try {
|
||||
result = api.action("query")
|
||||
.param("format", "xml")
|
||||
.param("list", "allimages")
|
||||
.param("aisha1", fileSha1)
|
||||
.get();
|
||||
Timber.d("Searching Commons API for existing file: %s", result);
|
||||
String fileSha1 = this.fileSha1;
|
||||
fileExists = api.existingFile(fileSha1);
|
||||
} catch (IOException e) {
|
||||
Timber.e(e, "IO Exception: ");
|
||||
return false;
|
||||
}
|
||||
|
||||
ArrayList<ApiResult> resultNodes = result.getNodes("/api/query/allimages/img");
|
||||
Timber.d("Result nodes: %s", resultNodes);
|
||||
|
||||
boolean fileExists = !resultNodes.isEmpty();
|
||||
|
||||
Timber.d("File already exists in Commons: %s", fileExists);
|
||||
return fileExists;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import android.location.Location;
|
|||
import android.location.LocationListener;
|
||||
import android.location.LocationManager;
|
||||
import android.media.ExifInterface;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
|
|
|
|||
|
|
@ -20,12 +20,10 @@ import android.view.inputmethod.InputMethodManager;
|
|||
import android.widget.AdapterView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.EventLog;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
||||
|
|
@ -36,6 +34,7 @@ import fr.free.nrw.commons.modifications.CategoryModifier;
|
|||
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
||||
import fr.free.nrw.commons.modifications.ModifierSequence;
|
||||
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
|
||||
import fr.free.nrw.commons.mwapi.EventLog;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class MultipleShareActivity
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import android.net.Uri;
|
|||
import android.os.Bundle;
|
||||
import android.support.graphics.drawable.VectorDrawableCompat;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ import java.util.List;
|
|||
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.EventLog;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
||||
|
|
@ -42,6 +41,7 @@ import fr.free.nrw.commons.modifications.CategoryModifier;
|
|||
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
||||
import fr.free.nrw.commons.modifications.ModifierSequence;
|
||||
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
|
||||
import fr.free.nrw.commons.mwapi.EventLog;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -32,9 +32,9 @@ import butterknife.ButterKnife;
|
|||
import butterknife.OnClick;
|
||||
import butterknife.OnItemSelected;
|
||||
import butterknife.OnTouch;
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class SingleUploadFragment extends Fragment {
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ import java.util.Date;
|
|||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.HandlerService;
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class UploadController {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
|
|
@ -13,23 +14,25 @@ import android.support.v4.app.NotificationCompat;
|
|||
import android.webkit.MimeTypeMap;
|
||||
import android.widget.Toast;
|
||||
|
||||
import fr.free.nrw.commons.*;
|
||||
import org.mediawiki.api.ApiResult;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
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.ContributionsActivity;
|
||||
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
||||
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
||||
import in.yuvi.http.fluent.ProgressListener;
|
||||
import fr.free.nrw.commons.mwapi.EventLog;
|
||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||
import fr.free.nrw.commons.mwapi.UploadResult;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class UploadService extends HandlerService<Contribution> {
|
||||
|
|
@ -64,7 +67,7 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
super("UploadService");
|
||||
}
|
||||
|
||||
private class NotificationUpdateProgressListener implements ProgressListener {
|
||||
private class NotificationUpdateProgressListener implements MediaWikiApi.ProgressListener {
|
||||
|
||||
String notificationTag;
|
||||
boolean notificationTitleChanged;
|
||||
|
|
@ -175,10 +178,10 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
return START_REDELIVER_INTENT;
|
||||
}
|
||||
|
||||
@SuppressLint("StringFormatInvalid")
|
||||
private void uploadContribution(Contribution contribution) {
|
||||
MWApi api = app.getMWApi();
|
||||
MediaWikiApi api = app.getMWApi();
|
||||
|
||||
ApiResult result;
|
||||
InputStream file = null;
|
||||
|
||||
String notificationTag = contribution.getLocalUri().toString();
|
||||
|
|
@ -235,32 +238,27 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
getString(R.string.upload_progress_notification_title_finishing, contribution.getDisplayTitle()),
|
||||
contribution
|
||||
);
|
||||
result = api.upload(filename, file, contribution.getDataLength(), contribution.getPageContents(), contribution.getEditSummary(), notificationUpdater);
|
||||
UploadResult uploadResult = api.uploadFile(filename, file, contribution.getDataLength(), contribution.getPageContents(), contribution.getEditSummary(), notificationUpdater);
|
||||
|
||||
Timber.d("Response is %s", Utils.getStringFromDOM(result.getDocument()));
|
||||
Timber.d("Response is %s", uploadResult.toString());
|
||||
|
||||
curProgressNotification = null;
|
||||
|
||||
String resultStatus = result.getString("/api/upload/@result");
|
||||
String resultStatus = uploadResult.getResultStatus();
|
||||
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("multiple", contribution.getMultiple())
|
||||
.param("result", errorCode)
|
||||
.param("result", uploadResult.getErrorCode())
|
||||
.param("filename", contribution.getFilename())
|
||||
.log();
|
||||
} else {
|
||||
Date dateUploaded = null;
|
||||
dateUploaded = Utils.parseMWDate(result.getString("/api/upload/imageinfo/@timestamp"));
|
||||
String canonicalFilename = "File:" + result.getString("/api/upload/@filename").replace("_", " "); // Title vs Filename
|
||||
String imageUrl = result.getString("/api/upload/imageinfo/@url");
|
||||
contribution.setFilename(canonicalFilename);
|
||||
contribution.setImageUrl(imageUrl);
|
||||
contribution.setFilename(uploadResult.getCanonicalFilename());
|
||||
contribution.setImageUrl(uploadResult.getImageUrl());
|
||||
contribution.setState(Contribution.STATE_COMPLETED);
|
||||
contribution.setDateUploaded(dateUploaded);
|
||||
contribution.setDateUploaded(uploadResult.getDateUploaded());
|
||||
contribution.save();
|
||||
|
||||
EventLog.schema(CommonsApplication.EVENT_UPLOAD_ATTEMPT)
|
||||
|
|
@ -274,7 +272,6 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
} catch(IOException e) {
|
||||
Timber.d("I have a network fuckup");
|
||||
showFailedNotification(contribution);
|
||||
return;
|
||||
} finally {
|
||||
if ( filename != null ) {
|
||||
unfinishedUploads.remove(filename);
|
||||
|
|
@ -288,8 +285,9 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StringFormatInvalid")
|
||||
private void showFailedNotification(Contribution contribution) {
|
||||
Notification failureNotification = new NotificationCompat.Builder(this).setAutoCancel(true)
|
||||
Notification failureNotification = new NotificationCompat.Builder(this).setAutoCancel(true)
|
||||
.setSmallIcon(R.drawable.ic_launcher)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, ContributionsActivity.class), 0))
|
||||
|
|
@ -304,7 +302,7 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
}
|
||||
|
||||
private String findUniqueFilename(String fileName) throws IOException {
|
||||
MWApi api = app.getMWApi();
|
||||
MediaWikiApi api = app.getMWApi();
|
||||
String sequenceFileName;
|
||||
for ( int sequenceNumber = 1; true; sequenceNumber++ ) {
|
||||
if (sequenceNumber == 1) {
|
||||
|
|
@ -320,7 +318,7 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
sequenceFileName = regexMatcher.replaceAll("$1 " + sequenceNumber + "$2");
|
||||
}
|
||||
}
|
||||
if ( fileExistsWithName(api, sequenceFileName) || unfinishedUploads.contains(sequenceFileName) ) {
|
||||
if ( api.fileExistsWithName(sequenceFileName) || unfinishedUploads.contains(sequenceFileName) ) {
|
||||
continue;
|
||||
} else {
|
||||
break;
|
||||
|
|
@ -328,15 +326,4 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package fr.free.nrw.commons.utils;
|
||||
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
|
||||
import java.text.NumberFormat;
|
||||
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
|
||||
public class LengthUtils {
|
||||
/** Returns a formatted distance string between two points.
|
||||
* @param point1 LatLng type point1
|
||||
|
|
|
|||
|
|
@ -0,0 +1,227 @@
|
|||
package fr.free.nrw.commons.mwapi;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(constants = BuildConfig.class, sdk = 21)
|
||||
public class ApacheHttpClientMediaWikiApiTest {
|
||||
|
||||
private ApacheHttpClientMediaWikiApi testObject;
|
||||
private MockWebServer server;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
server = new MockWebServer();
|
||||
testObject = new ApacheHttpClientMediaWikiApi("http://" + server.getHostName() + ":" + server.getPort() + "/");
|
||||
}
|
||||
|
||||
@After
|
||||
public void teardown() throws IOException {
|
||||
server.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authCookiesAreHandled() {
|
||||
assertEquals("", testObject.getAuthCookie());
|
||||
|
||||
testObject.setAuthCookie("cookie=chocolate-chip");
|
||||
|
||||
assertEquals("cookie=chocolate-chip", testObject.getAuthCookie());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleLoginWithWrongPassword() throws Exception {
|
||||
server.enqueue(new MockResponse().setBody("<?xml version=\"1.0\"?><api batchcomplete=\"\"><query><tokens logintoken=\"baz\" /></query></api>"));
|
||||
server.enqueue(new MockResponse().setBody("<?xml version=\"1.0\"?><api><clientlogin status=\"FAIL\" message=\"Incorrect password entered. Please try again.\" messagecode=\"wrongpassword\" /></api>"));
|
||||
|
||||
String result = testObject.login("foo", "bar");
|
||||
|
||||
RecordedRequest loginTokenRequest = assertBasicRequestParameters(server, "POST");
|
||||
Map<String, String> body = parseBody(loginTokenRequest.getBody().readUtf8());
|
||||
assertEquals("xml", body.get("format"));
|
||||
assertEquals("query", body.get("action"));
|
||||
assertEquals("login", body.get("type"));
|
||||
assertEquals("tokens", body.get("meta"));
|
||||
|
||||
RecordedRequest loginRequest = assertBasicRequestParameters(server, "POST");
|
||||
body = parseBody(loginRequest.getBody().readUtf8());
|
||||
assertEquals("1", body.get("rememberMe"));
|
||||
assertEquals("foo", body.get("username"));
|
||||
assertEquals("bar", body.get("password"));
|
||||
assertEquals("baz", body.get("logintoken"));
|
||||
assertEquals("https://commons.wikimedia.org", body.get("loginreturnurl"));
|
||||
assertEquals("xml", body.get("format"));
|
||||
|
||||
assertEquals("wrongpassword", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleLogin() throws Exception {
|
||||
server.enqueue(new MockResponse().setBody("<?xml version=\"1.0\"?><api batchcomplete=\"\"><query><tokens logintoken=\"baz\" /></query></api>"));
|
||||
server.enqueue(new MockResponse().setBody("<?xml version=\"1.0\"?><api><clientlogin status=\"PASS\" username=\"foo\" /></api>"));
|
||||
|
||||
String result = testObject.login("foo", "bar");
|
||||
|
||||
RecordedRequest loginTokenRequest = assertBasicRequestParameters(server, "POST");
|
||||
Map<String, String> body = parseBody(loginTokenRequest.getBody().readUtf8());
|
||||
assertEquals("xml", body.get("format"));
|
||||
assertEquals("query", body.get("action"));
|
||||
assertEquals("login", body.get("type"));
|
||||
assertEquals("tokens", body.get("meta"));
|
||||
|
||||
RecordedRequest loginRequest = assertBasicRequestParameters(server, "POST");
|
||||
body = parseBody(loginRequest.getBody().readUtf8());
|
||||
assertEquals("1", body.get("rememberMe"));
|
||||
assertEquals("foo", body.get("username"));
|
||||
assertEquals("bar", body.get("password"));
|
||||
assertEquals("baz", body.get("logintoken"));
|
||||
assertEquals("https://commons.wikimedia.org", body.get("loginreturnurl"));
|
||||
assertEquals("xml", body.get("format"));
|
||||
|
||||
assertEquals("PASS", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void twoFactorLogin() throws Exception {
|
||||
server.enqueue(new MockResponse().setBody("<?xml version=\"1.0\"?><api batchcomplete=\"\"><query><tokens logintoken=\"baz\" /></query></api>"));
|
||||
server.enqueue(new MockResponse().setBody("<?xml version=\"1.0\"?><api><clientlogin status=\"PASS\" username=\"foo\" /></api>"));
|
||||
|
||||
String result = testObject.login("foo", "bar", "2fa");
|
||||
|
||||
RecordedRequest loginTokenRequest = assertBasicRequestParameters(server, "POST");
|
||||
Map<String, String> body = parseBody(loginTokenRequest.getBody().readUtf8());
|
||||
assertEquals("xml", body.get("format"));
|
||||
assertEquals("query", body.get("action"));
|
||||
assertEquals("login", body.get("type"));
|
||||
assertEquals("tokens", body.get("meta"));
|
||||
|
||||
RecordedRequest loginRequest = assertBasicRequestParameters(server, "POST");
|
||||
body = parseBody(loginRequest.getBody().readUtf8());
|
||||
assertEquals("1", body.get("rememberMe"));
|
||||
assertEquals("foo", body.get("username"));
|
||||
assertEquals("bar", body.get("password"));
|
||||
assertEquals("baz", body.get("logintoken"));
|
||||
assertEquals("1", body.get("logincontinue"));
|
||||
assertEquals("2fa", body.get("OATHToken"));
|
||||
assertEquals("xml", body.get("format"));
|
||||
|
||||
assertEquals("PASS", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateLoginForLoggedInUser() throws Exception {
|
||||
server.enqueue(new MockResponse().setBody("<?xml version=\"1.0\"?><api><query><userinfo id=\"10\" name=\"foo\"/></query></api>"));
|
||||
|
||||
boolean result = testObject.validateLogin();
|
||||
|
||||
RecordedRequest loginTokenRequest = assertBasicRequestParameters(server, "GET");
|
||||
Map<String, String> body = parseQueryParams(loginTokenRequest);
|
||||
assertEquals("xml", body.get("format"));
|
||||
assertEquals("query", body.get("action"));
|
||||
assertEquals("userinfo", body.get("meta"));
|
||||
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateLoginForLoggedOutUser() throws Exception {
|
||||
server.enqueue(new MockResponse().setBody("<?xml version=\"1.0\"?><api><query><userinfo id=\"0\" name=\"foo\"/></query></api>"));
|
||||
|
||||
boolean result = testObject.validateLogin();
|
||||
|
||||
RecordedRequest loginTokenRequest = assertBasicRequestParameters(server, "GET");
|
||||
Map<String, String> params = parseQueryParams(loginTokenRequest);
|
||||
assertEquals("xml", params.get("format"));
|
||||
assertEquals("query", params.get("action"));
|
||||
assertEquals("userinfo", params.get("meta"));
|
||||
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void editToken() throws Exception {
|
||||
server.enqueue(new MockResponse().setBody("<?xml version=\"1.0\"?><api><tokens edittoken=\"baz\" /></api>"));
|
||||
|
||||
String result = testObject.getEditToken();
|
||||
|
||||
RecordedRequest loginTokenRequest = assertBasicRequestParameters(server, "GET");
|
||||
Map<String, String> params = parseQueryParams(loginTokenRequest);
|
||||
assertEquals("xml", params.get("format"));
|
||||
assertEquals("tokens", params.get("action"));
|
||||
assertEquals("edit", params.get("type"));
|
||||
|
||||
assertEquals("baz", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileExistsWithName_FileNotFound() throws Exception {
|
||||
server.enqueue(new MockResponse().setBody("<?xml version=\"1.0\"?><api batchcomplete=\"\"><query> <normalized><n from=\"File:foo\" to=\"File:Foo\" /></normalized><pages><page _idx=\"-1\" ns=\"6\" title=\"File:Foo\" missing=\"\" imagerepository=\"\" /></pages></query></api>"));
|
||||
|
||||
boolean result = testObject.fileExistsWithName("foo");
|
||||
|
||||
RecordedRequest request = assertBasicRequestParameters(server, "GET");
|
||||
Map<String, String> params = parseQueryParams(request);
|
||||
assertEquals("xml", params.get("format"));
|
||||
assertEquals("query", params.get("action"));
|
||||
assertEquals("imageinfo", params.get("prop"));
|
||||
assertEquals("File:foo", params.get("titles"));
|
||||
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
|
||||
private RecordedRequest assertBasicRequestParameters(MockWebServer server, String method) throws InterruptedException {
|
||||
RecordedRequest request = server.takeRequest();
|
||||
assertEquals("/", request.getRequestUrl().encodedPath());
|
||||
assertEquals(method, request.getMethod());
|
||||
assertEquals("Commons/" + BuildConfig.VERSION_NAME + " (https://mediawiki.org/wiki/Apps/Commons) Android/" + Build.VERSION.RELEASE, request.getHeader("User-Agent"));
|
||||
if ("POST".equals(method)) {
|
||||
assertEquals("application/x-www-form-urlencoded", request.getHeader("Content-Type"));
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
private Map<String, String> parseQueryParams(RecordedRequest request) {
|
||||
Map<String, String> result = new HashMap<>();
|
||||
HttpUrl url = request.getRequestUrl();
|
||||
Set<String> params = url.queryParameterNames();
|
||||
for (String name : params) {
|
||||
result.put(name, url.queryParameter(name));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<String, String> parseBody(String body) throws UnsupportedEncodingException {
|
||||
String[] props = body.split("&");
|
||||
Map<String, String> result = new HashMap<>();
|
||||
for (String prop : props) {
|
||||
String[] pair = prop.split("=");
|
||||
result.put(pair[0], URLDecoder.decode(pair[1], "utf-8"));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ buildscript {
|
|||
dependencies {
|
||||
classpath "com.android.tools.build:gradle:${project.gradleVersion}"
|
||||
classpath 'com.dicedmelon.gradle:jacoco-android:0.1.1'
|
||||
classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.7.1'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
gradleVersion = 2.3.0
|
||||
|
||||
supportLibVersion = 25.2.0
|
||||
supportLibVersion = 25.3.1
|
||||
|
||||
compileSdkVersion = android-25
|
||||
buildToolsVersion = 25.0.1
|
||||
|
|
@ -11,6 +11,7 @@ targetSdkVersion = 25
|
|||
android.useDeprecatedNdk=true
|
||||
|
||||
# Library dependencies
|
||||
BUTTERKNIFE_VERSION=8.4.0
|
||||
BUTTERKNIFE_VERSION=8.6.0
|
||||
GUAVA_VERSION=19.0
|
||||
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
Loading…
Add table
Add a link
Reference in a new issue