mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
commit
032c7873e5
67 changed files with 1239 additions and 676 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'
|
||||
|
||||
|
|
|
|||
|
|
@ -9,9 +9,8 @@ import android.support.test.espresso.matcher.ViewMatchers;
|
|||
import android.support.test.filters.LargeTest;
|
||||
import android.support.test.rule.ActivityTestRule;
|
||||
import android.support.test.runner.AndroidJUnit4;
|
||||
import android.view.View;
|
||||
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
|
@ -20,9 +19,6 @@ import java.util.Map;
|
|||
|
||||
import fr.free.nrw.commons.settings.SettingsActivity;
|
||||
|
||||
import static org.hamcrest.Matchers.allOf;
|
||||
import static org.hamcrest.Matchers.anything;
|
||||
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class SettingsActivityTest {
|
||||
|
|
@ -65,8 +61,8 @@ public class SettingsActivityTest {
|
|||
@Test
|
||||
public void oneLicenseIsChecked() {
|
||||
// click "License" (the first item)
|
||||
Espresso.onData(anything())
|
||||
.inAdapterView(findPreferenceList())
|
||||
Espresso.onData(Matchers.anything())
|
||||
.inAdapterView(ViewMatchers.withId(android.R.id.list))
|
||||
.atPosition(0)
|
||||
.perform(ViewActions.click());
|
||||
|
||||
|
|
@ -78,8 +74,8 @@ public class SettingsActivityTest {
|
|||
@Test
|
||||
public void afterClickingCcby4ItWillStay() {
|
||||
// click "License" (the first item)
|
||||
Espresso.onData(anything())
|
||||
.inAdapterView(findPreferenceList())
|
||||
Espresso.onData(Matchers.anything())
|
||||
.inAdapterView(ViewMatchers.withId(android.R.id.list))
|
||||
.atPosition(0)
|
||||
.perform(ViewActions.click());
|
||||
|
||||
|
|
@ -89,8 +85,8 @@ public class SettingsActivityTest {
|
|||
).perform(ViewActions.click());
|
||||
|
||||
// click "License" (the first item)
|
||||
Espresso.onData(anything())
|
||||
.inAdapterView(findPreferenceList())
|
||||
Espresso.onData(Matchers.anything())
|
||||
.inAdapterView(ViewMatchers.withId(android.R.id.list))
|
||||
.atPosition(0)
|
||||
.perform(ViewActions.click());
|
||||
|
||||
|
|
@ -100,12 +96,4 @@ public class SettingsActivityTest {
|
|||
ViewMatchers.withText(R.string.license_name_cc_by_four)
|
||||
));
|
||||
}
|
||||
|
||||
private static Matcher<View> findPreferenceList() {
|
||||
return allOf(
|
||||
ViewMatchers.isDescendantOfA(ViewMatchers.withId(R.id.settingsFragment)),
|
||||
ViewMatchers.withResourceName("list"),
|
||||
ViewMatchers.hasFocus()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,9 +8,11 @@ import android.widget.TextView;
|
|||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||
import fr.free.nrw.commons.ui.widget.HtmlTextView;
|
||||
|
||||
public class AboutActivity extends NavigationBaseActivity {
|
||||
@BindView(R.id.about_version) TextView versionText;
|
||||
@BindView(R.id.about_license) HtmlTextView aboutLicenseText;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
|
|
@ -19,6 +21,9 @@ public class AboutActivity extends NavigationBaseActivity {
|
|||
|
||||
ButterKnife.bind(this);
|
||||
|
||||
String aboutText = getString(R.string.about_license, getString(R.string.trademarked_name));
|
||||
aboutLicenseText.setHtmlText(aboutText);
|
||||
|
||||
versionText.setText(BuildConfig.VERSION_NAME);
|
||||
initDrawer();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import java.io.IOException;
|
|||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||
import timber.log.Timber;
|
||||
|
||||
public abstract class AuthenticatedActivity extends NavigationBaseActivity {
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
@ -60,7 +61,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
|||
usernameEdit.addTextChangedListener(textWatcher);
|
||||
passwordEdit.addTextChangedListener(textWatcher);
|
||||
twoFactorEdit.addTextChangedListener(textWatcher);
|
||||
passwordEdit.setOnEditorActionListener( newLoginInputActionListener() );
|
||||
passwordEdit.setOnEditorActionListener(newLoginInputActionListener());
|
||||
|
||||
loginButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
|
|
@ -150,7 +151,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
|||
private LoginTask getLoginTask() {
|
||||
return new LoginTask(
|
||||
this,
|
||||
canonicializeUsername( usernameEdit.getText().toString() ),
|
||||
canonicializeUsername(usernameEdit.getText().toString()),
|
||||
passwordEdit.getText().toString(),
|
||||
twoFactorEdit.getText().toString()
|
||||
);
|
||||
|
|
@ -161,16 +162,16 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
|||
* @param username String
|
||||
* @return String canonicial username
|
||||
*/
|
||||
private String canonicializeUsername( String username ) {
|
||||
private String canonicializeUsername(String username) {
|
||||
return new PageTitle(username).getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
NavUtils.navigateUpFromSameTask(this);
|
||||
return true;
|
||||
case android.R.id.home:
|
||||
NavUtils.navigateUpFromSameTask(this);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
|
@ -185,20 +186,20 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
|||
}
|
||||
|
||||
public void askUserForTwoFactorAuth() {
|
||||
if(BuildConfig.DEBUG) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
twoFactorEdit.setVisibility(View.VISIBLE);
|
||||
showUserToastAndCancelDialog( R.string.login_failed_2fa_needed );
|
||||
}else{
|
||||
showUserToastAndCancelDialog( R.string.login_failed_2fa_not_supported );
|
||||
showUserToastAndCancelDialog(R.string.login_failed_2fa_needed);
|
||||
} else {
|
||||
showUserToastAndCancelDialog(R.string.login_failed_2fa_not_supported);
|
||||
}
|
||||
}
|
||||
|
||||
public void showUserToastAndCancelDialog( int resId ) {
|
||||
showUserToast( resId );
|
||||
public void showUserToastAndCancelDialog(int resId) {
|
||||
showUserToast(resId);
|
||||
progressDialog.cancel();
|
||||
}
|
||||
|
||||
private void showUserToast( int resId ) {
|
||||
private void showUserToast(int resId) {
|
||||
Toast.makeText(this, resId, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
@ -67,7 +67,7 @@ class LoginTask extends AsyncTask<String, String, String> {
|
|||
if (result.equals("PASS")) {
|
||||
handlePassResult();
|
||||
} else {
|
||||
handleOtherResults( result );
|
||||
handleOtherResults(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -88,38 +88,38 @@ class LoginTask extends AsyncTask<String, String, String> {
|
|||
}
|
||||
}
|
||||
|
||||
AccountUtil.createAccount( response, username, password );
|
||||
AccountUtil.createAccount(response, username, password);
|
||||
loginActivity.startMainActivity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Match known failure message codes and provide messages
|
||||
* Match known failure message codes and provide messages.
|
||||
* @param result String
|
||||
*/
|
||||
private void handleOtherResults( String result ) {
|
||||
private void handleOtherResults(String result) {
|
||||
if (result.equals("NetworkFailure")) {
|
||||
// Matches NetworkFailure which is created by the doInBackground method
|
||||
loginActivity.showUserToastAndCancelDialog( R.string.login_failed_network );
|
||||
loginActivity.showUserToastAndCancelDialog(R.string.login_failed_network);
|
||||
} else if (result.toLowerCase().contains("nosuchuser".toLowerCase()) || result.toLowerCase().contains("noname".toLowerCase())) {
|
||||
// Matches nosuchuser, nosuchusershort, noname
|
||||
loginActivity.showUserToastAndCancelDialog( R.string.login_failed_username );
|
||||
loginActivity.showUserToastAndCancelDialog(R.string.login_failed_username);
|
||||
loginActivity.emptySensitiveEditFields();
|
||||
} else if (result.toLowerCase().contains("wrongpassword".toLowerCase())) {
|
||||
// Matches wrongpassword, wrongpasswordempty
|
||||
loginActivity.showUserToastAndCancelDialog( R.string.login_failed_password );
|
||||
loginActivity.showUserToastAndCancelDialog(R.string.login_failed_password);
|
||||
loginActivity.emptySensitiveEditFields();
|
||||
} else if (result.toLowerCase().contains("throttle".toLowerCase())) {
|
||||
// Matches unknown throttle error codes
|
||||
loginActivity.showUserToastAndCancelDialog( R.string.login_failed_throttled );
|
||||
loginActivity.showUserToastAndCancelDialog(R.string.login_failed_throttled);
|
||||
} else if (result.toLowerCase().contains("userblocked".toLowerCase())) {
|
||||
// Matches login-userblocked
|
||||
loginActivity.showUserToastAndCancelDialog( R.string.login_failed_blocked );
|
||||
loginActivity.showUserToastAndCancelDialog(R.string.login_failed_blocked);
|
||||
} else if (result.equals("2FA")) {
|
||||
loginActivity.askUserForTwoFactorAuth();
|
||||
} else {
|
||||
// Occurs with unhandled login failure codes
|
||||
Timber.d("Login failed with reason: %s", result);
|
||||
loginActivity.showUserToastAndCancelDialog( R.string.login_failed_generic );
|
||||
loginActivity.showUserToastAndCancelDialog(R.string.login_failed_generic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")) {
|
||||
|
|
|
|||
|
|
@ -5,17 +5,17 @@ import android.app.Service;
|
|||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
|
||||
public class WikiAccountAuthenticatorService extends Service{
|
||||
public class WikiAccountAuthenticatorService extends Service {
|
||||
|
||||
private static WikiAccountAuthenticator wikiAccountAuthenticator = null;
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
if (!intent.getAction().equals(AccountManager.ACTION_AUTHENTICATOR_INTENT)) {
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
|
||||
if(wikiAccountAuthenticator == null) {
|
||||
if (wikiAccountAuthenticator == null) {
|
||||
wikiAccountAuthenticator = new WikiAccountAuthenticator(this);
|
||||
}
|
||||
return wikiAccountAuthenticator.getIBinder();
|
||||
|
|
|
|||
|
|
@ -74,14 +74,14 @@ public class CacheController {
|
|||
double offset = 100;
|
||||
|
||||
//Coordinate offsets in radians
|
||||
double dLat = offset/EARTH_RADIUS;
|
||||
double dLon = offset/(EARTH_RADIUS*Math.cos(Math.PI*lat/180));
|
||||
double dLat = offset / EARTH_RADIUS;
|
||||
double dLon = offset / (EARTH_RADIUS * Math.cos(Math.PI * lat / 180));
|
||||
|
||||
//OffsetPosition, decimal degrees
|
||||
yPlus = lat + dLat * 180/Math.PI;
|
||||
yMinus = lat - dLat * 180/Math.PI;
|
||||
xPlus = lon + dLon * 180/Math.PI;
|
||||
xMinus = lon - dLon * 180/Math.PI;
|
||||
yPlus = lat + dLat * 180 / Math.PI;
|
||||
yMinus = lat - dLat * 180 / Math.PI;
|
||||
xPlus = lon + dLon * 180 / Math.PI;
|
||||
xMinus = lon - dLon * 180 / Math.PI;
|
||||
Timber.d("Search within: xMinus=%s, yMinus=%s, xPlus=%s, yPlus=%s",
|
||||
xMinus, yMinus, xPlus, yPlus);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -60,12 +60,12 @@ public class Category {
|
|||
|
||||
public void save() {
|
||||
try {
|
||||
if(contentUri == null) {
|
||||
if (contentUri == null) {
|
||||
contentUri = client.insert(CategoryContentProvider.BASE_URI, this.toContentValues());
|
||||
} else {
|
||||
client.update(contentUri, toContentValues(), null, null);
|
||||
}
|
||||
} catch(RemoteException e) {
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
|
@ -121,23 +121,23 @@ public class Category {
|
|||
}
|
||||
|
||||
public static void onUpdate(SQLiteDatabase db, int from, int to) {
|
||||
if(from == to) {
|
||||
if (from == to) {
|
||||
return;
|
||||
}
|
||||
if(from < 4) {
|
||||
if (from < 4) {
|
||||
// doesn't exist yet
|
||||
from++;
|
||||
onUpdate(db, from, to);
|
||||
return;
|
||||
}
|
||||
if(from == 4) {
|
||||
if (from == 4) {
|
||||
// table added in version 5
|
||||
onCreate(db);
|
||||
from++;
|
||||
onUpdate(db, from, to);
|
||||
return;
|
||||
}
|
||||
if(from == 5) {
|
||||
if (from == 5) {
|
||||
from++;
|
||||
onUpdate(db, from, to);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -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> filterIrrelevantResults(List<String> items) {
|
||||
|
||||
Iterator<String> iterator;
|
||||
|
||||
|
|
@ -62,15 +62,18 @@ 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)) {
|
||||
Timber.d("Filtering out year %s", s);
|
||||
//And if it is an irrelevant category such as Media_needing_categories_as_of_16_June_2017(Issue #750)
|
||||
if ((s.matches(".*(19|20)\\d{2}.*") && !s.contains(yearInString) && !s.contains(prevYearInString))
|
||||
|| s.matches("(.*)needing(.*)")||s.matches("(.*)taken on(.*)")) {
|
||||
Timber.d("Filtering out irrelevant result: %s", s);
|
||||
iterator.remove();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Timber.d("Items: %s", items);
|
||||
|
|
@ -78,45 +81,35 @@ 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));
|
||||
return new ArrayList<>(filterIrrelevantResults(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));
|
||||
return new ArrayList<>(filterIrrelevantResults(cachedItems));
|
||||
}
|
||||
|
||||
//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));
|
||||
return new ArrayList<>(filterIrrelevantResults(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,13 +28,26 @@ 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 {
|
||||
|
||||
public interface MediaDetailProvider {
|
||||
Media getMediaAtPosition(int i);
|
||||
|
||||
int getTotalMediaCount();
|
||||
|
||||
void notifyDatasetChanged();
|
||||
|
||||
void registerDataSetObserver(DataSetObserver observer);
|
||||
|
||||
void unregisterDataSetObserver(DataSetObserver observer);
|
||||
}
|
||||
|
||||
private ViewPager pager;
|
||||
private Boolean editable;
|
||||
private CommonsApplication app;
|
||||
|
|
@ -48,14 +61,6 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
|
|||
this.editable = editable;
|
||||
}
|
||||
|
||||
public interface MediaDetailProvider {
|
||||
Media getMediaAtPosition(int i);
|
||||
int getTotalMediaCount();
|
||||
void notifyDatasetChanged();
|
||||
void registerDataSetObserver(DataSetObserver observer);
|
||||
void unregisterDataSetObserver(DataSetObserver observer);
|
||||
}
|
||||
|
||||
//FragmentStatePagerAdapter allows user to swipe across collection of images (no. of images undetermined)
|
||||
private class MediaDetailAdapter extends FragmentStatePagerAdapter {
|
||||
|
||||
|
|
@ -65,7 +70,7 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
|
|||
|
||||
@Override
|
||||
public Fragment getItem(int i) {
|
||||
if(i == 0) {
|
||||
if (i == 0) {
|
||||
// See bug https://code.google.com/p/android/issues/detail?id=27526
|
||||
pager.postDelayed(new Runnable() {
|
||||
@Override
|
||||
|
|
@ -120,7 +125,7 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
|
|||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if(savedInstanceState != null) {
|
||||
if (savedInstanceState != null) {
|
||||
editable = savedInstanceState.getBoolean("editable");
|
||||
}
|
||||
app = CommonsApplication.getInstance();
|
||||
|
|
@ -206,13 +211,13 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
|
|||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
if(!editable) { // Disable menu options for editable views
|
||||
if (!editable) { // Disable menu options for editable views
|
||||
menu.clear(); // see http://stackoverflow.com/a/8495697/17865
|
||||
inflater.inflate(R.menu.fragment_image_detail, menu);
|
||||
if(pager != null) {
|
||||
if (pager != null) {
|
||||
MediaDetailProvider provider = (MediaDetailProvider)getActivity();
|
||||
Media m = provider.getMediaAtPosition(pager.getCurrentItem());
|
||||
if(m != null) {
|
||||
if (m != null) {
|
||||
// Enable default set of actions, then re-enable different set of actions only if it is a failed contrib
|
||||
menu.findItem(R.id.menu_retry_current_image).setEnabled(false).setVisible(false);
|
||||
menu.findItem(R.id.menu_cancel_current_image).setEnabled(false).setVisible(false);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,17 @@ import fr.free.nrw.commons.utils.UriDeserializer;
|
|||
import fr.free.nrw.commons.utils.UriSerializer;
|
||||
|
||||
public class NearbyBaseMarker extends BaseMarkerOptions<NearbyMarker, NearbyBaseMarker> {
|
||||
|
||||
public static final Parcelable.Creator<NearbyBaseMarker> CREATOR = new Parcelable.Creator<NearbyBaseMarker>() {
|
||||
public NearbyBaseMarker createFromParcel(Parcel in) {
|
||||
return new NearbyBaseMarker(in);
|
||||
}
|
||||
|
||||
public NearbyBaseMarker[] newArray(int size) {
|
||||
return new NearbyBaseMarker[size];
|
||||
}
|
||||
};
|
||||
|
||||
private Place place;
|
||||
|
||||
NearbyBaseMarker() {
|
||||
|
|
@ -74,15 +85,4 @@ public class NearbyBaseMarker extends BaseMarkerOptions<NearbyMarker, NearbyBase
|
|||
dest.writeString(title);
|
||||
dest.writeString(gson.toJson(place));
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<NearbyBaseMarker> CREATOR
|
||||
= new Parcelable.Creator<NearbyBaseMarker>() {
|
||||
public NearbyBaseMarker createFromParcel(Parcel in) {
|
||||
return new NearbyBaseMarker(in);
|
||||
}
|
||||
|
||||
public NearbyBaseMarker[] newArray(int size) {
|
||||
return new NearbyBaseMarker[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,4 +19,8 @@ public class HtmlTextView extends AppCompatTextView {
|
|||
setMovementMethod(LinkMovementMethod.getInstance());
|
||||
setText(Utils.fromHtml(getText().toString()));
|
||||
}
|
||||
|
||||
public void setHtmlText(String newText) {
|
||||
setText(Utils.fromHtml(newText));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
@ -209,16 +209,6 @@ public class ShareActivity
|
|||
protected void onAuthCookieAcquired(String authCookie) {
|
||||
app.getMWApi().setAuthCookie(authCookie);
|
||||
|
||||
SingleUploadFragment shareView = (SingleUploadFragment) getSupportFragmentManager().findFragmentByTag("shareView");
|
||||
categorizationFragment = (CategorizationFragment) getSupportFragmentManager().findFragmentByTag("categorization");
|
||||
if(shareView == null && categorizationFragment == null) {
|
||||
shareView = new SingleUploadFragment();
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(R.id.single_upload_fragment_container, shareView, "shareView")
|
||||
.commitAllowingStateLoss();
|
||||
}
|
||||
uploadController.prepareService();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -311,6 +301,18 @@ public class ShareActivity
|
|||
}
|
||||
}
|
||||
performPreuploadProcessingOfFile();
|
||||
|
||||
|
||||
SingleUploadFragment shareView = (SingleUploadFragment) getSupportFragmentManager().findFragmentByTag("shareView");
|
||||
categorizationFragment = (CategorizationFragment) getSupportFragmentManager().findFragmentByTag("categorization");
|
||||
if(shareView == null && categorizationFragment == null) {
|
||||
shareView = new SingleUploadFragment();
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.add(R.id.single_upload_fragment_container, shareView, "shareView")
|
||||
.commitAllowingStateLoss();
|
||||
}
|
||||
uploadController.prepareService();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -83,12 +86,12 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
@Override
|
||||
public void onProgress(long transferred, long total) {
|
||||
Timber.d("Uploaded %d of %d", transferred, total);
|
||||
if(!notificationTitleChanged) {
|
||||
if (!notificationTitleChanged) {
|
||||
curProgressNotification.setContentTitle(notificationProgressTitle);
|
||||
notificationTitleChanged = true;
|
||||
contribution.setState(Contribution.STATE_IN_PROGRESS);
|
||||
}
|
||||
if(transferred == total) {
|
||||
if (transferred == total) {
|
||||
// Completed!
|
||||
curProgressNotification.setContentTitle(notificationFinishingTitle);
|
||||
curProgressNotification.setProgress(0, 100, true);
|
||||
|
|
@ -121,7 +124,7 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
|
||||
@Override
|
||||
protected void handle(int what, Contribution contribution) {
|
||||
switch(what) {
|
||||
switch (what) {
|
||||
case ACTION_UPLOAD_FILE:
|
||||
//FIXME: Google Photos bug
|
||||
uploadContribution(contribution);
|
||||
|
|
@ -159,7 +162,7 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if(intent.getAction().equals(ACTION_START_SERVICE) && freshStart) {
|
||||
if (intent.getAction().equals(ACTION_START_SERVICE) && freshStart) {
|
||||
ContentValues failedValues = new ContentValues();
|
||||
failedValues.put(Contribution.Table.COLUMN_STATE, Contribution.STATE_FAILED);
|
||||
|
||||
|
|
@ -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();
|
||||
|
|
@ -186,7 +189,7 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
try {
|
||||
//FIXME: Google Photos bug
|
||||
file = this.getContentResolver().openInputStream(contribution.getLocalUri());
|
||||
} catch(FileNotFoundException e) {
|
||||
} catch (FileNotFoundException e) {
|
||||
Timber.d("File not found");
|
||||
Toast fileNotFound = Toast.makeText(this, R.string.upload_failed, Toast.LENGTH_LONG);
|
||||
fileNotFound.show();
|
||||
|
|
@ -217,9 +220,9 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
filename = findUniqueFilename(filename);
|
||||
unfinishedUploads.add(filename);
|
||||
}
|
||||
if(!api.validateLogin()) {
|
||||
if (!api.validateLogin()) {
|
||||
// Need to revalidate!
|
||||
if(app.revalidateAuthToken()) {
|
||||
if (app.revalidateAuthToken()) {
|
||||
Timber.d("Successfully revalidated token!");
|
||||
} else {
|
||||
Timber.d("Unable to revalidate :(");
|
||||
|
|
@ -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");
|
||||
if(!resultStatus.equals("Success")) {
|
||||
String errorCode = result.getString("/api/error/@code");
|
||||
String resultStatus = uploadResult.getResultStatus();
|
||||
if (!resultStatus.equals("Success")) {
|
||||
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)
|
||||
|
|
@ -271,16 +269,15 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
.param("result", "success")
|
||||
.log();
|
||||
}
|
||||
} catch(IOException e) {
|
||||
} catch (IOException e) {
|
||||
Timber.d("I have a network fuckup");
|
||||
showFailedNotification(contribution);
|
||||
return;
|
||||
} finally {
|
||||
if ( filename != null ) {
|
||||
if (filename != null) {
|
||||
unfinishedUploads.remove(filename);
|
||||
}
|
||||
toUpload--;
|
||||
if(toUpload == 0) {
|
||||
if (toUpload == 0) {
|
||||
// Sync modifications right after all uplaods are processed
|
||||
ContentResolver.requestSync((CommonsApplication.getInstance()).getCurrentAccount(), ModificationsContentProvider.AUTHORITY, new Bundle());
|
||||
stopForeground(true);
|
||||
|
|
@ -288,6 +285,7 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StringFormatInvalid")
|
||||
private void showFailedNotification(Contribution contribution) {
|
||||
Notification failureNotification = new NotificationCompat.Builder(this).setAutoCancel(true)
|
||||
.setSmallIcon(R.drawable.ic_launcher)
|
||||
|
|
@ -304,9 +302,9 @@ 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++ ) {
|
||||
for (int sequenceNumber = 1; true; sequenceNumber++) {
|
||||
if (sequenceNumber == 1) {
|
||||
sequenceFileName = fileName;
|
||||
} else {
|
||||
|
|
@ -320,23 +318,11 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
sequenceFileName = regexMatcher.replaceAll("$1 " + sequenceNumber + "$2");
|
||||
}
|
||||
}
|
||||
if ( fileExistsWithName(api, sequenceFileName) || unfinishedUploads.contains(sequenceFileName) ) {
|
||||
continue;
|
||||
} else {
|
||||
if (!api.fileExistsWithName(sequenceFileName)
|
||||
&& !unfinishedUploads.contains(sequenceFileName)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
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
|
||||
|
|
|
|||
|
|
@ -189,4 +189,5 @@
|
|||
<string name="no_description_found">nun s\'atoparon descripciones</string>
|
||||
<string name="nearby_info_menu_commons_article">Artículu en Commons</string>
|
||||
<string name="nearby_info_menu_wikidata_article">Elementu de WikiData</string>
|
||||
<string name="error_while_cache">Error al poner les fotos na caché</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@
|
|||
<string name="login_failed_password">Ne c\'haller ket kevreañ - Gwiriit ho ker tremen, mar plij</string>
|
||||
<string name="login_failed_throttled">Re a daolioù-esae. Klaskit en-dro a-benn ur pennadig amzer.</string>
|
||||
<string name="login_failed_blocked">Hon digarezit, prennet eo bet an implijer-mañ e Commons</string>
|
||||
<string name="login_failed_2fa_needed">Rankout a rit reiñ ho kod dilesa gant daou faktor.</string>
|
||||
<string name="login_failed_generic">C\'hwitet eo ar c\'hevreañ</string>
|
||||
<string name="share_upload_button">Pellgargañ</string>
|
||||
<string name="multiple_share_base_title">Envel ar rikoù-mañ</string>
|
||||
|
|
@ -129,7 +130,7 @@
|
|||
<string name="welcome_final_text">Ha soñjal a rit eo mat ?</string>
|
||||
<string name="welcome_final_button_text">Ya !</string>
|
||||
<string name="detail_panel_cats_label">Rummadoù</string>
|
||||
<string name="detail_panel_cats_loading" fuzzy="true">O kargañ…</string>
|
||||
<string name="detail_panel_cats_loading">O kargañ…</string>
|
||||
<string name="detail_panel_cats_none">Hini ebet diuzet</string>
|
||||
<string name="detail_description_empty">Deskrivadur ebet</string>
|
||||
<string name="detail_license_empty">Aotre-implijout dizanv</string>
|
||||
|
|
@ -155,7 +156,20 @@
|
|||
<string name="_2fa_code">Kod 2FA</string>
|
||||
<string name="maximum_limit">Bevenn uc\'hek</string>
|
||||
<string name="maximum_limit_alert">Ne c\'haller ket diskwel ouzhpenn 500</string>
|
||||
<string name="login_failed_2fa_not_supported">Ne vez ket embreget an dilesadur gant daou faktor evit ar mare.</string>
|
||||
<string name="logout_verification">Sur oc\'h e fell deoc\'h digevreañ ?</string>
|
||||
<string name="commons_logo">Logo Commons</string>
|
||||
<string name="no_image_found">N\'eus bet kavet skeudenn ebet</string>
|
||||
<string name="upload_image">Kargañ ur skeudenn</string>
|
||||
<string name="welcome_image_mount_zao">Menez Zao</string>
|
||||
<string name="welcome_image_llamas">Lamaed</string>
|
||||
<string name="welcome_image_rainbow_bridge">Pont Gwareg-ar-glav</string>
|
||||
<string name="welcome_image_tulip">Tulipez</string>
|
||||
<string name="welcome_image_no_selfies">Emboltred ebet</string>
|
||||
<string name="welcome_image_proprietary">Skeudenn brevez</string>
|
||||
<string name="welcome_image_welcome_wikipedia">Donemat e Wikipedia</string>
|
||||
<string name="welcome_image_welcome_copyright">Gwirioù oberour donemat</string>
|
||||
<string name="welcome_image_sydney_opera_house">Ti Opera Sydney</string>
|
||||
<string name="cancel">Nullañ</string>
|
||||
<string name="navigation_drawer_open">Digeriñ</string>
|
||||
<string name="navigation_drawer_close">Serriñ</string>
|
||||
|
|
@ -166,4 +180,9 @@
|
|||
<string name="navigation_item_settings">Arventennoù</string>
|
||||
<string name="navigation_item_feedback">Evezhiadennoù</string>
|
||||
<string name="navigation_item_logout">Digevreañ</string>
|
||||
<string name="navigation_item_info">Tutorial</string>
|
||||
<string name="nearby_needs_permissions">Ne c\'haller ket diskwel al lec\'hioù tost ma ne rannit ket ho lec\'hiadur</string>
|
||||
<string name="no_description_found">N\'eus bet kavet deskrivadur ebet</string>
|
||||
<string name="nearby_info_menu_commons_article">Pennad Commons</string>
|
||||
<string name="nearby_info_menu_wikidata_article">Elfenn Wikidata</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -189,4 +189,5 @@
|
|||
<string name="no_description_found">Keine Beschreibung gefunden</string>
|
||||
<string name="nearby_info_menu_commons_article">Commons-Artikel</string>
|
||||
<string name="nearby_info_menu_wikidata_article">Wikidata-Objekt</string>
|
||||
<string name="error_while_cache">Fehler beim Zwischenspeichern der Bilder</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -184,4 +184,5 @@
|
|||
<string name="nearby_needs_permissions">אי־אפשר להציג מקומות בסביבה ללא הרשאות מיקום</string>
|
||||
<string name="no_description_found">לא נמצא תיאור</string>
|
||||
<string name="nearby_info_menu_wikidata_article">פריט ויקינתונים</string>
|
||||
<string name="error_while_cache">שגיאה במשירת תמונות במטמון</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -180,4 +180,5 @@
|
|||
<string name="no_description_found">설명이 없습니다</string>
|
||||
<string name="nearby_info_menu_commons_article">공용 문서</string>
|
||||
<string name="nearby_info_menu_wikidata_article">위키데이터 항목</string>
|
||||
<string name="error_while_cache">그림 캐시 처리 오류</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -189,4 +189,5 @@
|
|||
<string name="no_description_found">не најдов описи</string>
|
||||
<string name="nearby_info_menu_commons_article">Статија на Ризницата</string>
|
||||
<string name="nearby_info_menu_wikidata_article">Предмет на Википодатоците</string>
|
||||
<string name="error_while_cache">Грешка при меѓускладирање на сликите</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -189,4 +189,5 @@
|
|||
<string name="no_description_found">gnun-a descrission trovà</string>
|
||||
<string name="nearby_info_menu_commons_article">Artìcol ëd Comun</string>
|
||||
<string name="nearby_info_menu_wikidata_article">Element ëd WikiData</string>
|
||||
<string name="error_while_cache">Eror antramentre ch\'as butavo le plance an memòria local</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -193,4 +193,5 @@
|
|||
<string name="no_description_found">описание не найдено</string>
|
||||
<string name="nearby_info_menu_commons_article">Статья на Викискладе</string>
|
||||
<string name="nearby_info_menu_wikidata_article">Элемент Викиданных</string>
|
||||
<string name="error_while_cache">Ошибка при кэшировании картинок</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -189,4 +189,5 @@
|
|||
<string name="no_description_found">teu manggihan pedaran</string>
|
||||
<string name="nearby_info_menu_commons_article">Artikel Common</string>
|
||||
<string name="nearby_info_menu_wikidata_article">item Wikidata</string>
|
||||
<string name="error_while_cache">Kasalahan nalika muat gambar</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -189,4 +189,5 @@
|
|||
<string name="no_description_found">ingen beskrivning hittades</string>
|
||||
<string name="nearby_info_menu_commons_article">Commons-artikel</string>
|
||||
<string name="nearby_info_menu_wikidata_article">Wikidata-objekt</string>
|
||||
<string name="error_while_cache">Fel uppstod när bilder cachelagras</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -4,21 +4,36 @@
|
|||
<string name="username">ಸದಸ್ಯೆರ್ನ ಪುದರ್</string>
|
||||
<string name="password">ಪ್ರವೇಸೊ ಪದೊ</string>
|
||||
<string name="login">ಲಾಗ್ ಇನ್</string>
|
||||
<string name="signup">ಖಾತೆ ಸುರು ಮಲ್ಪುಲೆ</string>
|
||||
<string name="logging_in_title">ಇರ್ ಲಾಗ್-ಇನ್ ಅವೊಂತುಲ್ಲಾರ್</string>
|
||||
<string name="logging_in_message">ದಯಾದಿತ್ ವಂತೆ ಕಾಪುಲೆ…</string>
|
||||
<string name="login_success">ಲಾಗಿನ್ ಅಂಡ್!</string>
|
||||
<string name="login_failed">ಲಾಗಿನ್ ಅಯಿಜಾತ್ತ!</string>
|
||||
<string name="upload_failed">ಈ ಕಡತ ತಿಕ್ಕಿಜಿ. ದಯಮಲ್ತ್ ಕುಡೊಂಜಿ ಕಡತೊನು ಪ್ರಯತ್ನ ಮಲ್ಪುಲೆ.</string>
|
||||
<string name="authentication_failed">ದೃಢೀಕರಣ ಸರಿ ಆಯಿಜಿ!</string>
|
||||
<string name="uploading_started">ದಿಂಜಪುನಾ ಸುರು ಅಂಡ್!</string>
|
||||
<string name="upload_completed_notification_title">%1$s ಅಪ್ಲೋಡ್ ಆಂಡ್!</string>
|
||||
<string name="upload_completed_notification_text">ಇರೆನ ಅಪ್ಲೋಡ್ ತೂಯೆರೆ ಒತ್ತುಲೆ</string>
|
||||
<string name="upload_progress_notification_title_start">%1$s ಅಪ್ಲೋಡ್ ಸುರು ಆವೊಂದುಂಡು</string>
|
||||
<string name="upload_progress_notification_title_in_progress">%1$s ಅಪ್ಲೋಡ್ ಆವೊಂದುಂಡು</string>
|
||||
<string name="upload_progress_notification_title_finishing">%1$s ಅಪ್ಲೋಡ್ ಕೈದ್ ಆವೊಂದುಂಡು.</string>
|
||||
<string name="upload_failed_notification_title">%1$s ಅಪ್ಲೋಡ್ ಸರಿ ಆತಿಜಿ</string>
|
||||
<string name="upload_failed_notification_subtitle">ತುಯಾರ ಮೆಲ್ಲ ಒತ್ತುಲೆ</string>
|
||||
<plurals name="uploads_pending_notification_indicator">
|
||||
<item quantity="one">%d ಕಡತ ಅಪ್ಲೊಡ್ ಆವೊಂದುಂಡು</item>
|
||||
<item quantity="other">%d ಕಡತೊಲು ಅಪ್ಲೋಡ್ ಆವೊಂದುಂಡು</item>
|
||||
</plurals>
|
||||
<string name="title_activity_contributions" fuzzy="true">ಎನ್ನ ದಿಂಜಯೀನಾ ವಿಚಾರೊಳು</string>
|
||||
<string name="contribution_state_queued">ದಿಂಜೊಂತುಂಡು</string>
|
||||
<string name="contribution_state_failed">ದಿಂಜಿಜಿ</string>
|
||||
<string name="contribution_state_in_progress">%1$d%% ಮುಗಿಂಡ್</string>
|
||||
<string name="contribution_state_starting">ದಿಂಜೊಂತುಂಡು…</string>
|
||||
<string name="menu_from_gallery">ಛಾಯಾಂಕಣತ್</string>
|
||||
<string name="menu_from_camera">ಪಟ ದೆಪ್ಪುಲೇ</string>
|
||||
<string name="menu_nearby">ಕೈತಲ್ದ</string>
|
||||
<string name="provider_contributions">ಎನ್ನ ದಿಂಜಯೀನಾ ವಿಚಾರೊಲು</string>
|
||||
<string name="menu_share">ಪಟ್ಟುಲೆ</string>
|
||||
<string name="menu_open_in_browser">ಬ್ರೌಸರ್ಡ್ ತೂಲೆ</string>
|
||||
<string name="share_title_hint">ತರೆಬರವು</string>
|
||||
<string name="share_description_hint">ವಿವರಣೆ</string>
|
||||
<string name="login_failed_generic">ಲಾಗಿನ್ ಆಯಾರಾ ಅಯಿಜಿ</string>
|
||||
|
|
|
|||
|
|
@ -189,4 +189,5 @@
|
|||
<string name="no_description_found">找不到描述</string>
|
||||
<string name="nearby_info_menu_commons_article">共享資源條目</string>
|
||||
<string name="nearby_info_menu_wikidata_article">維基數據項目</string>
|
||||
<string name="error_while_cache">在快取圖片時發生錯誤</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -189,4 +189,5 @@
|
|||
<string name="no_description_found">找不到描述</string>
|
||||
<string name="nearby_info_menu_commons_article">共享资源条目</string>
|
||||
<string name="nearby_info_menu_wikidata_article">维基数据项</string>
|
||||
<string name="error_while_cache">缓存图片时出错</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -79,7 +79,8 @@ Tap this message (or hit back) to skip this step.</string>
|
|||
<string name="title_activity_settings">Settings</string>
|
||||
<string name="title_activity_signup">Sign Up</string>
|
||||
<string name="menu_about">About</string>
|
||||
<string name="about_license">Open Source software released under the <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache License v2</a>. Wikimedia Commons and its logo are trademarks of the Wikimedia Foundation and are used with the permission of the Wikimedia Foundation. We are not endorsed by or affiliated with the Wikimedia Foundation.</string>
|
||||
<string name="about_license">Open Source software released under the <a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\">Apache License v2</a>. %1$s and its logo are trademarks of the Wikimedia Foundation and are used with the permission of the Wikimedia Foundation. We are not endorsed by or affiliated with the Wikimedia Foundation.</string>
|
||||
<string name="trademarked_name" translatable="false">Wikimedia Commons</string>
|
||||
<string name="about_improve"><a href=\"https://github.com/commons-app/apps-android-commons\">Source</a> and <a href=\"https://commons-app.github.io/\">website</a> on GitHub. Create a new <a href=\"https://github.com/commons-app/apps-android-commons/issues\">GitHub issue</a> for bug reports and suggestions.</string>
|
||||
<string name="about_privacy_policy"><a href=\"https://wikimediafoundation.org/wiki/Privacy_policy\">Privacy policy</a></string>
|
||||
<string name="about_credits"><a href=\"https://github.com/commons-app/apps-android-commons/blob/master/CREDITS\">Credits</a></string>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
package fr.free.nrw.commons;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
|
||||
public class LatLngTests {
|
||||
@Test public void testZeroZero() {
|
||||
LatLng place = new LatLng(0, 0, 0);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
package fr.free.nrw.commons;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.utils.LengthUtils;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
|
||||
public class LengthUtilsTest {
|
||||
@Test public void testZeroDistance() {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package fr.free.nrw.commons;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
|
||||
public class UtilsFixExtensionTest {
|
||||
|
||||
@Test public void jpegResultsInJpg() {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -22,10 +22,11 @@ import fr.free.nrw.commons.BuildConfig;
|
|||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(constants = BuildConfig.class)
|
||||
@Config(constants = BuildConfig.class, sdk = 21)
|
||||
public class NearbyAdapterFactoryTest {
|
||||
|
||||
private static final Place PLACE = new Place("name", Place.Description.AIRPORT,
|
||||
|
|
|
|||
|
|
@ -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,17 +1,17 @@
|
|||
gradleVersion = 2.3.0
|
||||
|
||||
supportLibVersion = 25.2.0
|
||||
supportLibVersion = 25.3.1
|
||||
|
||||
compileSdkVersion = android-25
|
||||
buildToolsVersion = 25.0.1
|
||||
|
||||
minSdkVersion = 15
|
||||
|
||||
# Cannot target API 24+ until https://github.com/commons-app/apps-android-commons/issues/457 fixed
|
||||
targetSdkVersion = 23
|
||||
|
||||
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