mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
Show the upload count for contributions by the user
This commit is contained in:
parent
a6ea218149
commit
82c0d24f37
8 changed files with 306 additions and 13 deletions
|
|
@ -0,0 +1,21 @@
|
||||||
|
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(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
throw new RuntimeException(t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package fr.free.nrw.commons.concurrency;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
public interface ExceptionHandler {
|
||||||
|
void onException(@NonNull Throwable t);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
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(final Runnable runnable) {
|
||||||
|
count++;
|
||||||
|
Runnable wrapperRunnable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Process.setThreadPriority(priority);
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Thread t = new Thread(wrapperRunnable, String.format("%s-%s", name, count));
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
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(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,7 @@ import android.database.DataSetObserver;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v4.app.LoaderManager;
|
import android.support.v4.app.LoaderManager;
|
||||||
import android.support.v4.content.CursorLoader;
|
import android.support.v4.content.CursorLoader;
|
||||||
|
|
@ -22,8 +23,11 @@ import android.view.View;
|
||||||
import android.widget.Adapter;
|
import android.widget.Adapter;
|
||||||
import android.widget.AdapterView;
|
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.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
|
|
@ -35,6 +39,7 @@ import fr.free.nrw.commons.hamburger.HamburgerMenuContainer;
|
||||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||||
import fr.free.nrw.commons.settings.Prefs;
|
import fr.free.nrw.commons.settings.Prefs;
|
||||||
import fr.free.nrw.commons.upload.UploadService;
|
import fr.free.nrw.commons.upload.UploadService;
|
||||||
|
import fr.free.nrw.commons.utils.ExecutorUtils;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class ContributionsActivity
|
public class ContributionsActivity
|
||||||
|
|
@ -234,18 +239,7 @@ public class ContributionsActivity
|
||||||
((CursorAdapter)contributionsList.getAdapter()).swapCursor(cursor);
|
((CursorAdapter)contributionsList.getAdapter()).swapCursor(cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cursor.getCount() == 0
|
setUploadCount();
|
||||||
&& Locale.getDefault().getISO3Language().equals(Locale.ENGLISH.getISO3Language())) {
|
|
||||||
//cursor count is zero and language is english -
|
|
||||||
// we need to set the message for 0 case explicitly.
|
|
||||||
getSupportActionBar().setSubtitle(getResources()
|
|
||||||
.getString(R.string.contributions_subtitle_zero));
|
|
||||||
} else {
|
|
||||||
getSupportActionBar().setSubtitle(getResources()
|
|
||||||
.getQuantityString(R.plurals.contributions_subtitle,
|
|
||||||
cursor.getCount(),
|
|
||||||
cursor.getCount()));
|
|
||||||
}
|
|
||||||
|
|
||||||
contributionsList.clearSyncMessage();
|
contributionsList.clearSyncMessage();
|
||||||
notifyAndMigrateDataSetObservers();
|
notifyAndMigrateDataSetObservers();
|
||||||
|
|
@ -275,6 +269,27 @@ public class ContributionsActivity
|
||||||
return contributionsList.getAdapter().getCount();
|
return contributionsList.getAdapter().getCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void notifyDatasetChanged() {
|
public void notifyDatasetChanged() {
|
||||||
// Do nothing for now
|
// Do nothing for now
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
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.CommonsApplication;
|
||||||
|
import fr.free.nrw.commons.concurrency.BackgroundPoolExceptionHandler;
|
||||||
|
import fr.free.nrw.commons.concurrency.ThreadPoolExecutorService;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public class UploadCountClient {
|
||||||
|
private ThreadPoolExecutorService threadPoolExecutor;
|
||||||
|
|
||||||
|
public 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";
|
||||||
|
|
||||||
|
public ListenableFuture<Integer> getUploadCount(final String userName) {
|
||||||
|
final SettableFuture<Integer> future = SettableFuture.create();
|
||||||
|
threadPoolExecutor.schedule(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
URL url;
|
||||||
|
try {
|
||||||
|
url = new URL(String.format(Locale.ENGLISH, UPLOAD_COUNT_URL_TEMPLATE, userName));
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package fr.free.nrw.commons.utils;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
public class ExecutorUtils {
|
||||||
|
|
||||||
|
private static final Executor uiExecutor = new Executor() {
|
||||||
|
@Override
|
||||||
|
public void execute(Runnable command) {
|
||||||
|
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||||
|
command.run();
|
||||||
|
} else {
|
||||||
|
new Handler(Looper.getMainLooper()).post(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Executor uiExecutor () { return uiExecutor;}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue