mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-27 12:53:55 +01:00
Migrated logEvents to retrofit (#3087)
* Switched wikimedia-android-data-client for new features * Added UserCLient and UserInterface * Migrated ContributionsSyncAdapter to UserClient Fixed sync related bugs * Removed unused code * Removed unused code * Updated wikimedia-android-data-client to new version
This commit is contained in:
parent
386d08794e
commit
bb273eb22d
8 changed files with 90 additions and 132 deletions
|
|
@ -31,7 +31,7 @@ dependencies {
|
||||||
implementation 'com.facebook.fresco:fresco:1.13.0'
|
implementation 'com.facebook.fresco:fresco:1.13.0'
|
||||||
implementation 'com.drewnoakes:metadata-extractor:2.11.0'
|
implementation 'com.drewnoakes:metadata-extractor:2.11.0'
|
||||||
implementation 'org.apache.commons:commons-lang3:3.8.1'
|
implementation 'org.apache.commons:commons-lang3:3.8.1'
|
||||||
implementation 'com.dmitrybrant:wikimedia-android-data-client:0.0.25'
|
implementation 'com.github.ilgazer:wikimedia-android-data-client:f65809ee2c'
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
|
implementation 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,15 @@ public class Contribution extends Media {
|
||||||
this.dateCreatedSource = "";
|
this.dateCreatedSource = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Contribution(Uri localUri, String imageUrl, String filename, String description, long dataLength,
|
||||||
|
Date dateCreated, Date dateUploaded, String creator, String editSummary, String decimalCoords, int state) {
|
||||||
|
super(localUri, imageUrl, filename, description, dataLength, dateCreated, dateUploaded, creator);
|
||||||
|
this.decimalCoords = decimalCoords;
|
||||||
|
this.editSummary = editSummary;
|
||||||
|
this.dateCreatedSource = "";
|
||||||
|
this.state=state;
|
||||||
|
}
|
||||||
|
|
||||||
public Contribution(Parcel in) {
|
public Contribution(Parcel in) {
|
||||||
super(in);
|
super(in);
|
||||||
contentUri = in.readParcelable(Uri.class.getClassLoader());
|
contentUri = in.readParcelable(Uri.class.getClassLoader());
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package fr.free.nrw.commons.contributions;
|
package fr.free.nrw.commons.contributions;
|
||||||
|
|
||||||
import android.accounts.Account;
|
import android.accounts.Account;
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.content.AbstractThreadedSyncAdapter;
|
import android.content.AbstractThreadedSyncAdapter;
|
||||||
import android.content.ContentProviderClient;
|
import android.content.ContentProviderClient;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
|
|
@ -11,6 +12,8 @@ import android.os.Bundle;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwQueryLogEvent;
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwQueryResult;
|
||||||
import org.wikipedia.util.DateUtil;
|
import org.wikipedia.util.DateUtil;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -26,6 +29,7 @@ import fr.free.nrw.commons.di.ApplicationlessInjection;
|
||||||
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
import fr.free.nrw.commons.kvstore.JsonKvStore;
|
||||||
import fr.free.nrw.commons.mwapi.LogEventResult;
|
import fr.free.nrw.commons.mwapi.LogEventResult;
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
|
import fr.free.nrw.commons.mwapi.UserClient;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static fr.free.nrw.commons.contributions.Contribution.STATE_COMPLETED;
|
import static fr.free.nrw.commons.contributions.Contribution.STATE_COMPLETED;
|
||||||
|
|
@ -44,8 +48,8 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
// into the app, rather than the user's setting. Also see Github issue #52.
|
// into the app, rather than the user's setting. Also see Github issue #52.
|
||||||
public static final int ABSOLUTE_CONTRIBUTIONS_LOAD_LIMIT = 500;
|
public static final int ABSOLUTE_CONTRIBUTIONS_LOAD_LIMIT = 500;
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@Inject
|
||||||
@Inject MediaWikiApi mwApi;
|
UserClient userClient;
|
||||||
@Inject
|
@Inject
|
||||||
@Named("default_preferences")
|
@Named("default_preferences")
|
||||||
JsonKvStore defaultKvStore;
|
JsonKvStore defaultKvStore;
|
||||||
|
|
@ -58,24 +62,19 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
if (filename == null) {
|
if (filename == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Cursor cursor = null;
|
try (Cursor cursor = client.query(BASE_URI,
|
||||||
try {
|
existsQuery,
|
||||||
cursor = client.query(BASE_URI,
|
existsSelection,
|
||||||
existsQuery,
|
new String[]{filename},
|
||||||
existsSelection,
|
""
|
||||||
new String[]{filename},
|
)) {
|
||||||
""
|
|
||||||
);
|
|
||||||
return cursor != null && cursor.getCount() != 0;
|
return cursor != null && cursor.getCount() != 0;
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
} finally {
|
|
||||||
if (cursor != null) {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
@Override
|
@Override
|
||||||
public void onPerformSync(Account account, Bundle bundle, String authority,
|
public void onPerformSync(Account account, Bundle bundle, String authority,
|
||||||
ContentProviderClient contentProviderClient, SyncResult syncResult) {
|
ContentProviderClient contentProviderClient, SyncResult syncResult) {
|
||||||
|
|
@ -84,71 +83,20 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
.getApplicationContext())
|
.getApplicationContext())
|
||||||
.getCommonsApplicationComponent()
|
.getCommonsApplicationComponent()
|
||||||
.inject(this);
|
.inject(this);
|
||||||
// This code is fraught with possibilities of race conditions, but lalalalala I can't hear you!
|
// This code is(was?) fraught with possibilities of race conditions, but lalalalala I can't hear you!
|
||||||
String user = account.name;
|
String user = account.name;
|
||||||
String lastModified = defaultKvStore.getString("lastSyncTimestamp", "");
|
|
||||||
Date curTime = new Date();
|
|
||||||
LogEventResult result;
|
|
||||||
Boolean done = false;
|
|
||||||
String queryContinue = null;
|
|
||||||
ContributionDao contributionDao = new ContributionDao(() -> contentProviderClient);
|
ContributionDao contributionDao = new ContributionDao(() -> contentProviderClient);
|
||||||
while (!done) {
|
userClient.logEvents(user)
|
||||||
|
.doOnNext(mwQueryLogEvent->Timber.d("Received image %s", mwQueryLogEvent.title() ))
|
||||||
try {
|
.filter(mwQueryLogEvent -> !mwQueryLogEvent.isDeleted())
|
||||||
result = mwApi.logEvents(user, lastModified, queryContinue, ABSOLUTE_CONTRIBUTIONS_LOAD_LIMIT);
|
.filter(mwQueryLogEvent -> !fileExists(contentProviderClient, mwQueryLogEvent.title()))
|
||||||
} catch (IOException e) {
|
.doOnNext(mwQueryLogEvent->Timber.d("Image %s passed filters", mwQueryLogEvent.title() ))
|
||||||
// There isn't really much we can do, eh?
|
.map(image -> new Contribution(null, null, image.title(),
|
||||||
// FIXME: Perhaps add EventLogging?
|
"", -1, image.date(), image.date(), user,
|
||||||
syncResult.stats.numIoExceptions += 1; // Not sure if this does anything. Shitty docs
|
"", "", STATE_COMPLETED))
|
||||||
Timber.d("Syncing failed due to %s", e);
|
.map(contributionDao::toContentValues)
|
||||||
return;
|
.buffer(10)
|
||||||
}
|
.subscribe(imageValues->contentProviderClient.bulkInsert(BASE_URI, imageValues.toArray(EMPTY)));
|
||||||
Timber.d("Last modified at %s", lastModified);
|
|
||||||
|
|
||||||
List<LogEventResult.LogEvent> logEvents = result.getLogEvents();
|
|
||||||
Timber.d("%d results!", logEvents.size());
|
|
||||||
ArrayList<ContentValues> imageValues = new ArrayList<>();
|
|
||||||
for (LogEventResult.LogEvent image : logEvents) {
|
|
||||||
if (image.isDeleted()) {
|
|
||||||
// means that this upload was deleted.
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
String filename = image.getFilename();
|
|
||||||
if (fileExists(contentProviderClient, filename)) {
|
|
||||||
Timber.d("Skipping %s", filename);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Date dateUpdated = image.getDateUpdated();
|
|
||||||
Contribution contrib = new Contribution(null, null, filename,
|
|
||||||
"", -1, dateUpdated, dateUpdated, user,
|
|
||||||
"", "");
|
|
||||||
contrib.setState(STATE_COMPLETED);
|
|
||||||
imageValues.add(contributionDao.toContentValues(contrib));
|
|
||||||
|
|
||||||
if (imageValues.size() % COMMIT_THRESHOLD == 0) {
|
|
||||||
try {
|
|
||||||
contentProviderClient.bulkInsert(BASE_URI, imageValues.toArray(EMPTY));
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
imageValues.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imageValues.size() != 0) {
|
|
||||||
try {
|
|
||||||
contentProviderClient.bulkInsert(BASE_URI, imageValues.toArray(EMPTY));
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
queryContinue = result.getQueryContinue();
|
|
||||||
if (TextUtils.isEmpty(queryContinue)) {
|
|
||||||
done = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
defaultKvStore.putString("lastSyncTimestamp", DateUtil.iso8601DateFormat(curTime));
|
|
||||||
Timber.d("Oh hai, everyone! Look, a kitty!");
|
Timber.d("Oh hai, everyone! Look, a kitty!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ import fr.free.nrw.commons.media.MediaInterface;
|
||||||
import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi;
|
import fr.free.nrw.commons.mwapi.ApacheHttpClientMediaWikiApi;
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
|
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
|
||||||
|
import fr.free.nrw.commons.mwapi.UserInterface;
|
||||||
import fr.free.nrw.commons.review.ReviewInterface;
|
import fr.free.nrw.commons.review.ReviewInterface;
|
||||||
import fr.free.nrw.commons.upload.UploadInterface;
|
import fr.free.nrw.commons.upload.UploadInterface;
|
||||||
import okhttp3.Cache;
|
import okhttp3.Cache;
|
||||||
|
|
@ -229,4 +230,9 @@ public class NetworkingModule {
|
||||||
public CategoryInterface provideCategoryInterface(@Named(NAMED_COMMONS_WIKI_SITE) WikiSite commonsWikiSite) {
|
public CategoryInterface provideCategoryInterface(@Named(NAMED_COMMONS_WIKI_SITE) WikiSite commonsWikiSite) {
|
||||||
return ServiceFactory.get(commonsWikiSite, BuildConfig.COMMONS_URL, CategoryInterface.class);
|
return ServiceFactory.get(commonsWikiSite, BuildConfig.COMMONS_URL, CategoryInterface.class);
|
||||||
}
|
}
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
public UserInterface provideUserInterface(@Named(NAMED_COMMONS_WIKI_SITE) WikiSite commonsWikiSite) {
|
||||||
|
return ServiceFactory.get(commonsWikiSite, BuildConfig.COMMONS_URL, UserInterface.class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,54 +83,6 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
public LogEventResult logEvents(String user, String lastModified, String queryContinue, int limit) throws IOException {
|
|
||||||
CustomMwApi.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);
|
|
||||||
}
|
|
||||||
CustomApiResult result = builder.get();
|
|
||||||
|
|
||||||
return new LogEventResult(
|
|
||||||
getLogEventsFromResult(result),
|
|
||||||
result.getString("/api/query-continue/logevents/@lestart"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private ArrayList<LogEventResult.LogEvent> getLogEventsFromResult(CustomApiResult result) {
|
|
||||||
ArrayList<CustomApiResult> uploads = result.getNodes("/api/query/logevents/item");
|
|
||||||
Timber.d("%d results!", uploads.size());
|
|
||||||
ArrayList<LogEventResult.LogEvent> logEvents = new ArrayList<>();
|
|
||||||
for (CustomApiResult image : uploads) {
|
|
||||||
logEvents.add(new LogEventResult.LogEvent(
|
|
||||||
image.getString("@pageid"),
|
|
||||||
image.getString("@title"),
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks to see if a user is currently blocked from Commons
|
* Checks to see if a user is currently blocked from Commons
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,6 @@ public interface MediaWikiApi {
|
||||||
@NonNull
|
@NonNull
|
||||||
Single<MediaResult> fetchMediaByFilename(String filename);
|
Single<MediaResult> fetchMediaByFilename(String filename);
|
||||||
|
|
||||||
@Nullable
|
|
||||||
String revisionsByFilename(String filename) throws IOException;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
LogEventResult logEvents(String user, String lastModified, String queryContinue, int limit) throws IOException;
|
|
||||||
|
|
||||||
boolean isUserBlockedFromCommons();
|
boolean isUserBlockedFromCommons();
|
||||||
|
|
||||||
void logout();
|
void logout();
|
||||||
|
|
|
||||||
32
app/src/main/java/fr/free/nrw/commons/mwapi/UserClient.java
Normal file
32
app/src/main/java/fr/free/nrw/commons/mwapi/UserClient.java
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
package fr.free.nrw.commons.mwapi;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwQueryLogEvent;
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwQueryResult;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
|
||||||
|
public class UserClient {
|
||||||
|
private final UserInterface userInterface;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public UserClient(UserInterface userInterface) {
|
||||||
|
this.userInterface = userInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Observable<MwQueryLogEvent> logEvents(String user) {
|
||||||
|
try {
|
||||||
|
return userInterface.getUserLogEvents(user, Collections.emptyMap())
|
||||||
|
.map(MwQueryResponse::query)
|
||||||
|
.map(MwQueryResult::logevents)
|
||||||
|
.flatMap(Observable::fromIterable);
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
return Observable.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package fr.free.nrw.commons.mwapi;
|
||||||
|
|
||||||
|
import org.wikipedia.dataclient.mwapi.MwQueryResponse;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import io.reactivex.Observable;
|
||||||
|
import retrofit2.http.GET;
|
||||||
|
import retrofit2.http.Query;
|
||||||
|
import retrofit2.http.QueryMap;
|
||||||
|
|
||||||
|
import static org.wikipedia.dataclient.Service.MW_API_PREFIX;
|
||||||
|
|
||||||
|
public interface UserInterface {
|
||||||
|
@GET(MW_API_PREFIX+"action=query&list=logevents&letype=upload&leprop=title|timestamp|ids&lelimit=500")
|
||||||
|
Observable<MwQueryResponse> getUserLogEvents(@Query("leuser") String user, @QueryMap Map<String, String> continuation);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue