mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
Merge pull request #818 from akaita/rx_upload_count
Use RxJava2 for getting upload count
This commit is contained in:
commit
28a0a15616
13 changed files with 87 additions and 275 deletions
|
|
@ -25,14 +25,18 @@ dependencies {
|
|||
compile ('com.mapbox.mapboxsdk:mapbox-android-sdk:5.1.0@aar'){
|
||||
transitive=true
|
||||
}
|
||||
|
||||
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
|
||||
// Because RxAndroid releases are few and far between, it is recommended you also
|
||||
// explicitly depend on RxJava's latest version for bug fixes and new features.
|
||||
compile 'io.reactivex.rxjava2:rxjava:2.1.2'
|
||||
|
||||
|
||||
compile 'com.facebook.fresco:fresco:1.3.0'
|
||||
compile 'com.facebook.stetho:stetho:1.5.0'
|
||||
compile "com.google.guava:guava:${GUAVA_VERSION}"
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile ('org.robolectric:robolectric:3.3.2') {
|
||||
exclude module: 'guava'
|
||||
}
|
||||
testCompile 'org.robolectric:robolectric:3.3.2'
|
||||
|
||||
testCompile 'com.squareup.okhttp3:mockwebserver:3.8.1'
|
||||
androidTestCompile 'com.squareup.okhttp3:mockwebserver:3.8.1'
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
package fr.free.nrw.commons.concurrency;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
|
||||
public class BackgroundPoolExceptionHandler implements ExceptionHandler {
|
||||
@Override
|
||||
public void onException(@NonNull final Throwable t) {
|
||||
//Crash for debug build
|
||||
if (BuildConfig.DEBUG) {
|
||||
Thread thread = new Thread(() -> {
|
||||
throw new RuntimeException(t);
|
||||
});
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
package fr.free.nrw.commons.concurrency;
|
||||
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
|
||||
class ExceptionAwareThreadPoolExecutor extends ScheduledThreadPoolExecutor {
|
||||
|
||||
private final ExceptionHandler exceptionHandler;
|
||||
|
||||
public ExceptionAwareThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory,
|
||||
ExceptionHandler exceptionHandler) {
|
||||
super(corePoolSize, threadFactory);
|
||||
this.exceptionHandler = exceptionHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterExecute(Runnable r, Throwable t) {
|
||||
super.afterExecute(r, t);
|
||||
if (t == null && r instanceof Future<?>) {
|
||||
try {
|
||||
Future<?> future = (Future<?>) r;
|
||||
if (future.isDone()) future.get();
|
||||
} catch (CancellationException | InterruptedException e) {
|
||||
//ignore
|
||||
} catch (ExecutionException e) {
|
||||
t = e.getCause() != null ? e.getCause() : e;
|
||||
} catch (Exception e) {
|
||||
t = e;
|
||||
}
|
||||
}
|
||||
|
||||
if (t != null) {
|
||||
exceptionHandler.onException(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package fr.free.nrw.commons.concurrency;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
public interface ExceptionHandler {
|
||||
void onException(@NonNull Throwable t);
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
package fr.free.nrw.commons.concurrency;
|
||||
|
||||
import android.os.Process;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
class ThreadFactoryMaker {
|
||||
public static ThreadFactory get(@NonNull final String name, final int priority) {
|
||||
return new ThreadFactory() {
|
||||
private int count = 0;
|
||||
|
||||
@Override
|
||||
public Thread newThread(@NonNull final Runnable runnable) {
|
||||
count++;
|
||||
Runnable wrapperRunnable = () -> {
|
||||
Process.setThreadPriority(priority);
|
||||
runnable.run();
|
||||
};
|
||||
return new Thread(wrapperRunnable, String.format("%s-%s", name, count));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
package fr.free.nrw.commons.concurrency;
|
||||
|
||||
import android.os.Process;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class ThreadPoolExecutorService implements Executor {
|
||||
private final ScheduledThreadPoolExecutor backgroundPool;
|
||||
|
||||
private ThreadPoolExecutorService(Builder b) {
|
||||
backgroundPool = new ExceptionAwareThreadPoolExecutor(b.poolSize,
|
||||
ThreadFactoryMaker.get(b.name, b.priority), b.exceptionHandler);
|
||||
}
|
||||
|
||||
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long time, TimeUnit timeUnit) {
|
||||
return backgroundPool.schedule(callable, time, timeUnit);
|
||||
}
|
||||
|
||||
public ScheduledFuture<?> schedule(Runnable runnable) {
|
||||
return schedule(runnable, 0, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public ScheduledFuture<?> schedule(Runnable runnable, long time, TimeUnit timeUnit) {
|
||||
return backgroundPool.schedule(runnable, time, timeUnit);
|
||||
}
|
||||
|
||||
public ScheduledFuture<?> scheduleAtFixedRate(final Runnable task, long initialDelay,
|
||||
long period, final TimeUnit timeUnit) {
|
||||
return backgroundPool.scheduleAtFixedRate(task, initialDelay, period, timeUnit);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
backgroundPool.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@NonNull Runnable command) {
|
||||
backgroundPool.execute(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder class for {@link ThreadPoolExecutorService}
|
||||
*/
|
||||
public static class Builder {
|
||||
//Required
|
||||
private final String name;
|
||||
|
||||
//Optional
|
||||
private int poolSize = 1;
|
||||
private int priority = Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_MORE_FAVORABLE;
|
||||
private ExceptionHandler exceptionHandler = null;
|
||||
|
||||
/**
|
||||
* @param name the name of the threads in the service. if there are N threads,
|
||||
* the thread names will be like name-1, name-2, name-3,...,name-N
|
||||
*/
|
||||
public Builder(@NonNull String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param poolSize the number of threads to keep in the pool
|
||||
* @throws IllegalArgumentException if size of pool <=0
|
||||
*/
|
||||
public Builder setPoolSize(int poolSize) throws IllegalArgumentException {
|
||||
if (poolSize <= 0) {
|
||||
throw new IllegalArgumentException("Pool size must be grater than 0");
|
||||
}
|
||||
this.poolSize = poolSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param priority Priority of the threads in the service. You can supply a constant from
|
||||
* {@link android.os.Process}
|
||||
* By default, the priority is set to a value slightly higher than the normal
|
||||
* background priority
|
||||
*/
|
||||
public Builder setPriority(int priority) {
|
||||
this.priority = priority;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param handler The handler to use to handle exceptions in the service
|
||||
*/
|
||||
public Builder setExceptionHandler(ExceptionHandler handler) {
|
||||
this.exceptionHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ThreadPoolExecutorService build() {
|
||||
return new ThreadPoolExecutorService(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,6 @@ import android.net.Uri;
|
|||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
|
|
@ -26,10 +25,6 @@ import android.view.View;
|
|||
import android.widget.Adapter;
|
||||
import android.widget.AdapterView;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -44,7 +39,9 @@ import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
|||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import fr.free.nrw.commons.upload.UploadService;
|
||||
import fr.free.nrw.commons.utils.ExecutorUtils;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ContributionsActivity
|
||||
|
|
@ -74,6 +71,8 @@ public class ContributionsActivity
|
|||
*/
|
||||
private String CONTRIBUTION_SORT = Contribution.Table.COLUMN_STATE + " DESC, " + Contribution.Table.COLUMN_UPLOADED + " DESC , (" + Contribution.Table.COLUMN_TIMESTAMP + " * " + Contribution.Table.COLUMN_STATE + ")";
|
||||
|
||||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
|
||||
private ServiceConnection uploadServiceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName componentName, IBinder binder) {
|
||||
|
|
@ -90,6 +89,7 @@ public class ContributionsActivity
|
|||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
compositeDisposable.clear();
|
||||
getSupportFragmentManager().removeOnBackStackChangedListener(this);
|
||||
super.onDestroy();
|
||||
if(isUploadServiceConnected) {
|
||||
|
|
@ -116,6 +116,8 @@ public class ContributionsActivity
|
|||
super.onPause();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
|
|
@ -280,24 +282,22 @@ public class ContributionsActivity
|
|||
}
|
||||
|
||||
private void setUploadCount() {
|
||||
UploadCountClient uploadCountClient = new UploadCountClient();
|
||||
CommonsApplication application = CommonsApplication.getInstance();
|
||||
ListenableFuture<Integer> future = uploadCountClient
|
||||
.getUploadCount(application.getCurrentAccount().name);
|
||||
Futures.addCallback(future, new FutureCallback<Integer>() {
|
||||
@Override
|
||||
public void onSuccess(Integer uploadCount) {
|
||||
|
||||
compositeDisposable.add(
|
||||
CommonsApplication.getInstance().getMWApi()
|
||||
.getUploadCount(application.getCurrentAccount().name)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
uploadCount ->
|
||||
getSupportActionBar().setSubtitle(getResources()
|
||||
.getQuantityString(R.plurals.contributions_subtitle,
|
||||
uploadCount,
|
||||
uploadCount));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Throwable t) {
|
||||
Timber.e(t, "Fetching upload count failed");
|
||||
}
|
||||
}, ExecutorUtils.uiExecutor());
|
||||
uploadCount)),
|
||||
throwable -> Timber.e(throwable, "Fetching upload count failed")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -1,54 +0,0 @@
|
|||
package fr.free.nrw.commons.contributions;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.Locale;
|
||||
|
||||
import fr.free.nrw.commons.PageTitle;
|
||||
import fr.free.nrw.commons.concurrency.BackgroundPoolExceptionHandler;
|
||||
import fr.free.nrw.commons.concurrency.ThreadPoolExecutorService;
|
||||
import timber.log.Timber;
|
||||
|
||||
class UploadCountClient {
|
||||
private ThreadPoolExecutorService threadPoolExecutor;
|
||||
|
||||
UploadCountClient() {
|
||||
threadPoolExecutor = new ThreadPoolExecutorService.Builder("bg-pool")
|
||||
.setPoolSize(Runtime.getRuntime().availableProcessors())
|
||||
.setExceptionHandler(new BackgroundPoolExceptionHandler())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static final String UPLOAD_COUNT_URL_TEMPLATE =
|
||||
"https://tools.wmflabs.org/urbanecmbot/uploadsbyuser/uploadsbyuser.py?user=%s";
|
||||
|
||||
ListenableFuture<Integer> getUploadCount(final String userName) {
|
||||
final SettableFuture<Integer> future = SettableFuture.create();
|
||||
threadPoolExecutor.schedule(() -> {
|
||||
URL url;
|
||||
try {
|
||||
url = new URL(String.format(Locale.ENGLISH, UPLOAD_COUNT_URL_TEMPLATE,
|
||||
new PageTitle(userName).getText()));
|
||||
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
|
||||
try {
|
||||
BufferedReader bufferedReader = new BufferedReader(new
|
||||
InputStreamReader(urlConnection.getInputStream()));
|
||||
String uploadCount = bufferedReader.readLine();
|
||||
bufferedReader.close();
|
||||
future.set(Integer.parseInt(uploadCount));
|
||||
} finally {
|
||||
urlConnection.disconnect();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Timber.e("Error getting upload count Error", e);
|
||||
future.setException(e);
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package fr.free.nrw.commons.mwapi;
|
|||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
|
|
@ -17,6 +18,7 @@ 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.apache.http.util.EntityUtils;
|
||||
import org.mediawiki.api.ApiResult;
|
||||
import org.mediawiki.api.MWApi;
|
||||
|
||||
|
|
@ -27,16 +29,21 @@ import java.util.ArrayList;
|
|||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.PageTitle;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import in.yuvi.http.fluent.Http;
|
||||
import io.reactivex.Single;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* @author Addshore
|
||||
*/
|
||||
public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||
private String wikiMediaToolforgeUrl = "https://tools.wmflabs.org/";
|
||||
|
||||
private static final String THUMB_SIZE = "640";
|
||||
private AbstractHttpClient httpClient;
|
||||
private MWApi api;
|
||||
|
|
@ -53,6 +60,11 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
api = new MWApi(apiURL, httpClient);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setWikiMediaToolforgeUrl(String wikiMediaToolforgeUrl) {
|
||||
this.wikiMediaToolforgeUrl = wikiMediaToolforgeUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param username String
|
||||
* @param password String
|
||||
|
|
@ -366,4 +378,24 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
|||
return new UploadResult(resultStatus, dateUploaded, canonicalFilename, imageUrl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Single<Integer> getUploadCount(String userName) {
|
||||
final String uploadCountUrlTemplate =
|
||||
wikiMediaToolforgeUrl + "urbanecmbot/uploadsbyuser/uploadsbyuser.py";
|
||||
|
||||
return Single.fromCallable(() -> {
|
||||
String url = String.format(
|
||||
Locale.ENGLISH,
|
||||
uploadCountUrlTemplate,
|
||||
new PageTitle(userName).getText());
|
||||
HttpResponse response = Http.get(url).use(httpClient)
|
||||
.data("user", userName)
|
||||
.asResponse();
|
||||
String uploadCount = EntityUtils.toString(response.getEntity()).trim();
|
||||
return Integer.parseInt(uploadCount);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.Single;
|
||||
|
||||
public interface MediaWikiApi {
|
||||
String getAuthCookie();
|
||||
|
||||
|
|
@ -52,6 +54,9 @@ public interface MediaWikiApi {
|
|||
@NonNull
|
||||
LogEventResult logEvents(String user, String lastModified, String queryContinue, int limit) throws IOException;
|
||||
|
||||
@NonNull
|
||||
Single<Integer> getUploadCount(String userName);
|
||||
|
||||
interface ProgressListener {
|
||||
void onProgress(long transferred, long total);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import android.text.TextUtils;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.HandlerService;
|
||||
|
|
@ -162,6 +163,6 @@ public class UploadController {
|
|||
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, contribution);
|
||||
onComplete.onUploadStarted(contribution);
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}.executeOnExecutor(Executors.newFixedThreadPool(1)); // TODO remove this by using a sensible thread handling strategy
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,13 @@ import java.io.UnsupportedEncodingException;
|
|||
import java.net.URLDecoder;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Observer;
|
||||
import java.util.Set;
|
||||
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.observers.TestObserver;
|
||||
import io.reactivex.subscribers.TestSubscriber;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
|
|
@ -37,6 +41,7 @@ public class ApacheHttpClientMediaWikiApiTest {
|
|||
public void setUp() throws Exception {
|
||||
server = new MockWebServer();
|
||||
testObject = new ApacheHttpClientMediaWikiApi("http://" + server.getHostName() + ":" + server.getPort() + "/");
|
||||
testObject.setWikiMediaToolforgeUrl("http://" + server.getHostName() + ":" + server.getPort() + "/");
|
||||
}
|
||||
|
||||
@After
|
||||
|
|
@ -193,6 +198,19 @@ public class ApacheHttpClientMediaWikiApiTest {
|
|||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUploadCount() throws InterruptedException {
|
||||
server.enqueue(new MockResponse().setBody("23\n"));
|
||||
|
||||
TestObserver<Integer> testObserver = testObject.getUploadCount("testUsername").test();
|
||||
|
||||
RecordedRequest request = server.takeRequest();
|
||||
Map<String, String> params = parseQueryParams(request);
|
||||
assertEquals("testUsername", params.get("user"));
|
||||
|
||||
assertEquals(1, testObserver.valueCount());
|
||||
assertEquals(23, (int)testObserver.values().get(0));
|
||||
}
|
||||
|
||||
private RecordedRequest assertBasicRequestParameters(MockWebServer server, String method) throws InterruptedException {
|
||||
RecordedRequest request = server.takeRequest();
|
||||
|
|
|
|||
|
|
@ -12,6 +12,5 @@ android.useDeprecatedNdk=true
|
|||
|
||||
# Library dependencies
|
||||
BUTTERKNIFE_VERSION=8.6.0
|
||||
GUAVA_VERSION=19.0
|
||||
|
||||
org.gradle.jvmargs=-Xmx1536M
|
||||
Loading…
Add table
Add a link
Reference in a new issue