mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-24 02:25:19 +00:00
Add support for Job chains.
This commit is contained in:
parent
1a50910910
commit
96c641c2a0
@ -0,0 +1,45 @@
|
|||||||
|
package org.thoughtcrime.securesms.jobmanager;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
|
public class ChainParameters {
|
||||||
|
|
||||||
|
private final String groupId;
|
||||||
|
private final boolean ignoreDuplicates;
|
||||||
|
|
||||||
|
private ChainParameters(@NonNull String groupId, boolean ignoreDuplicates) {
|
||||||
|
this.groupId = groupId;
|
||||||
|
this.ignoreDuplicates = ignoreDuplicates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<String> getGroupId() {
|
||||||
|
return Optional.fromNullable(groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean shouldIgnoreDuplicates() {
|
||||||
|
return ignoreDuplicates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
|
||||||
|
private String groupId;
|
||||||
|
private boolean ignoreDuplicates;
|
||||||
|
|
||||||
|
public Builder setGroupId(@Nullable String groupId) {
|
||||||
|
this.groupId = groupId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder ignoreDuplicates(boolean ignore) {
|
||||||
|
this.ignoreDuplicates = ignore;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChainParameters build() {
|
||||||
|
return new ChainParameters(groupId, ignoreDuplicates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@ import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirement;
|
|||||||
import org.thoughtcrime.securesms.jobs.requirements.SqlCipherMigrationRequirement;
|
import org.thoughtcrime.securesms.jobs.requirements.SqlCipherMigrationRequirement;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.service.GenericForegroundService;
|
import org.thoughtcrime.securesms.service.GenericForegroundService;
|
||||||
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -33,6 +34,7 @@ public abstract class Job extends Worker implements Serializable {
|
|||||||
static final String KEY_RETRY_COUNT = "Job_retry_count";
|
static final String KEY_RETRY_COUNT = "Job_retry_count";
|
||||||
static final String KEY_RETRY_UNTIL = "Job_retry_until";
|
static final String KEY_RETRY_UNTIL = "Job_retry_until";
|
||||||
static final String KEY_SUBMIT_TIME = "Job_submit_time";
|
static final String KEY_SUBMIT_TIME = "Job_submit_time";
|
||||||
|
static final String KEY_FAILED = "Job_failed";
|
||||||
static final String KEY_REQUIRES_NETWORK = "Job_requires_network";
|
static final String KEY_REQUIRES_NETWORK = "Job_requires_network";
|
||||||
static final String KEY_REQUIRES_SQLCIPHER = "Job_requires_sqlcipher";
|
static final String KEY_REQUIRES_SQLCIPHER = "Job_requires_sqlcipher";
|
||||||
|
|
||||||
@ -86,26 +88,31 @@ public abstract class Job extends Worker implements Serializable {
|
|||||||
try {
|
try {
|
||||||
initialize(new SafeData(data));
|
initialize(new SafeData(data));
|
||||||
|
|
||||||
if (withinRetryLimits(data)) {
|
if (data.getBoolean(KEY_FAILED, false)) {
|
||||||
if (requirementsMet(data)) {
|
warn("Failing due to a failure earlier in the chain." + logSuffix());
|
||||||
if (needsForegroundService(data)) {
|
return cancel();
|
||||||
Log.i(TAG, "Running a foreground service with description '" + getDescription() + "' to aid in job execution." + logSuffix());
|
}
|
||||||
GenericForegroundService.startForegroundTask(getApplicationContext(), getDescription());
|
|
||||||
foregroundRunning = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
onRun();
|
if (!withinRetryLimits(data)) {
|
||||||
|
|
||||||
log("Successfully completed." + logSuffix());
|
|
||||||
return Result.success();
|
|
||||||
} else {
|
|
||||||
log("Retrying due to unmet requirements." + logSuffix());
|
|
||||||
return retry();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
warn("Failing after hitting the retry limit." + logSuffix());
|
warn("Failing after hitting the retry limit." + logSuffix());
|
||||||
return cancel();
|
return cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!requirementsMet(data)) {
|
||||||
|
log("Retrying due to unmet requirements." + logSuffix());
|
||||||
|
return retry();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsForegroundService(data)) {
|
||||||
|
Log.i(TAG, "Running a foreground service with description '" + getDescription() + "' to aid in job execution." + logSuffix());
|
||||||
|
GenericForegroundService.startForegroundTask(getApplicationContext(), getDescription());
|
||||||
|
foregroundRunning = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onRun();
|
||||||
|
|
||||||
|
log("Successfully completed." + logSuffix());
|
||||||
|
return success();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (onShouldRetry(e)) {
|
if (onShouldRetry(e)) {
|
||||||
log("Retrying after a retryable exception." + logSuffix(), e);
|
log("Retrying after a retryable exception." + logSuffix(), e);
|
||||||
@ -197,6 +204,10 @@ public abstract class Job extends Worker implements Serializable {
|
|||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Result success() {
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
private Result retry() {
|
private Result retry() {
|
||||||
onRetry();
|
onRetry();
|
||||||
return Result.retry();
|
return Result.retry();
|
||||||
@ -204,7 +215,7 @@ public abstract class Job extends Worker implements Serializable {
|
|||||||
|
|
||||||
private Result cancel() {
|
private Result cancel() {
|
||||||
onCanceled();
|
onCanceled();
|
||||||
return Result.success();
|
return Result.success(new Data.Builder().putBoolean(KEY_FAILED, true).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean requirementsMet(@NonNull Data data) {
|
private boolean requirementsMet(@NonNull Data data) {
|
||||||
|
@ -3,8 +3,14 @@ package org.thoughtcrime.securesms.jobmanager;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
@ -16,6 +22,7 @@ import androidx.work.Data;
|
|||||||
import androidx.work.ExistingWorkPolicy;
|
import androidx.work.ExistingWorkPolicy;
|
||||||
import androidx.work.NetworkType;
|
import androidx.work.NetworkType;
|
||||||
import androidx.work.OneTimeWorkRequest;
|
import androidx.work.OneTimeWorkRequest;
|
||||||
|
import androidx.work.WorkContinuation;
|
||||||
import androidx.work.WorkManager;
|
import androidx.work.WorkManager;
|
||||||
|
|
||||||
public class JobManager {
|
public class JobManager {
|
||||||
@ -36,7 +43,25 @@ public class JobManager {
|
|||||||
this.workManager = workManager;
|
this.workManager = workManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Chain startChain(@NonNull Job job) {
|
||||||
|
return startChain(Collections.singletonList(job));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chain startChain(@NonNull List<? extends Job> jobs) {
|
||||||
|
return new Chain(jobs);
|
||||||
|
}
|
||||||
|
|
||||||
public void add(Job job) {
|
public void add(Job job) {
|
||||||
|
JobParameters jobParameters = job.getJobParameters();
|
||||||
|
|
||||||
|
if (jobParameters == null) {
|
||||||
|
throw new IllegalStateException("Jobs must have JobParameters at this stage. (" + job.getClass().getSimpleName() + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
startChain(job).enqueue(jobParameters.getSoloChainParameters());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enqueueChain(@NonNull Chain chain, @NonNull ChainParameters chainParameters) {
|
||||||
executor.execute(() -> {
|
executor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
workManager.pruneWork().getResult().get();
|
workManager.pruneWork().getResult().get();
|
||||||
@ -44,38 +69,85 @@ public class JobManager {
|
|||||||
Log.w(TAG, "Failed to prune work.", e);
|
Log.w(TAG, "Failed to prune work.", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
JobParameters jobParameters = job.getJobParameters();
|
List<List<Job>> jobListChain = chain.getJobListChain();
|
||||||
|
List<List<OneTimeWorkRequest>> requestListChain = Stream.of(jobListChain).map(jl -> Stream.of(jl).map(this::toWorkRequest).toList()).toList();
|
||||||
|
|
||||||
if (jobParameters == null) {
|
if (jobListChain.isEmpty()) {
|
||||||
throw new IllegalStateException("Jobs must have JobParameters at this stage. (" + job.getClass().getSimpleName() + ")");
|
throw new IllegalStateException("Enqueued an empty chain.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Data.Builder dataBuilder = new Data.Builder().putInt(Job.KEY_RETRY_COUNT, jobParameters.getRetryCount())
|
for (int i = 0; i < jobListChain.size(); i++) {
|
||||||
.putLong(Job.KEY_RETRY_UNTIL, jobParameters.getRetryUntil())
|
for (int j = 0; j < jobListChain.get(i).size(); j++) {
|
||||||
.putLong(Job.KEY_SUBMIT_TIME, System.currentTimeMillis())
|
jobListChain.get(i).get(j).onSubmit(context, requestListChain.get(i).get(j).getId());
|
||||||
.putBoolean(Job.KEY_REQUIRES_NETWORK, jobParameters.requiresNetwork())
|
}
|
||||||
.putBoolean(Job.KEY_REQUIRES_SQLCIPHER, jobParameters.requiresSqlCipher());
|
|
||||||
Data data = job.serialize(dataBuilder);
|
|
||||||
|
|
||||||
OneTimeWorkRequest.Builder requestBuilder = new OneTimeWorkRequest.Builder(job.getClass())
|
|
||||||
.setInputData(data)
|
|
||||||
.setBackoffCriteria(BackoffPolicy.LINEAR, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS);
|
|
||||||
|
|
||||||
if (jobParameters.requiresNetwork()) {
|
|
||||||
requestBuilder.setConstraints(NETWORK_CONSTRAINT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OneTimeWorkRequest request = requestBuilder.build();
|
WorkContinuation continuation;
|
||||||
|
|
||||||
job.onSubmit(context, request.getId());
|
if (chainParameters.getGroupId().isPresent()) {
|
||||||
|
ExistingWorkPolicy policy = chainParameters.shouldIgnoreDuplicates() ? ExistingWorkPolicy.KEEP : ExistingWorkPolicy.APPEND;
|
||||||
String groupId = jobParameters.getGroupId();
|
continuation = workManager.beginUniqueWork(chainParameters.getGroupId().get(), policy, requestListChain.get(0));
|
||||||
if (groupId != null) {
|
|
||||||
ExistingWorkPolicy policy = jobParameters.shouldIgnoreDuplicates() ? ExistingWorkPolicy.KEEP : ExistingWorkPolicy.APPEND;
|
|
||||||
workManager.beginUniqueWork(groupId, policy, request).enqueue();
|
|
||||||
} else {
|
} else {
|
||||||
workManager.beginWith(request).enqueue();
|
continuation = workManager.beginWith(requestListChain.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i < requestListChain.size(); i++) {
|
||||||
|
continuation = continuation.then(requestListChain.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
continuation.enqueue();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private OneTimeWorkRequest toWorkRequest(@NonNull Job job) {
|
||||||
|
JobParameters jobParameters = job.getJobParameters();
|
||||||
|
|
||||||
|
if (jobParameters == null) {
|
||||||
|
throw new IllegalStateException("Jobs must have JobParameters at this stage. (" + job.getClass().getSimpleName() + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
Data.Builder dataBuilder = new Data.Builder().putInt(Job.KEY_RETRY_COUNT, jobParameters.getRetryCount())
|
||||||
|
.putLong(Job.KEY_RETRY_UNTIL, jobParameters.getRetryUntil())
|
||||||
|
.putLong(Job.KEY_SUBMIT_TIME, System.currentTimeMillis())
|
||||||
|
.putBoolean(Job.KEY_REQUIRES_NETWORK, jobParameters.requiresNetwork())
|
||||||
|
.putBoolean(Job.KEY_REQUIRES_SQLCIPHER, jobParameters.requiresSqlCipher());
|
||||||
|
Data data = job.serialize(dataBuilder);
|
||||||
|
|
||||||
|
OneTimeWorkRequest.Builder requestBuilder = new OneTimeWorkRequest.Builder(job.getClass())
|
||||||
|
.setInputData(data)
|
||||||
|
.setBackoffCriteria(BackoffPolicy.LINEAR, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
if (jobParameters.requiresNetwork()) {
|
||||||
|
requestBuilder.setConstraints(NETWORK_CONSTRAINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Chain {
|
||||||
|
|
||||||
|
private final List<List<Job>> jobs = new LinkedList<>();
|
||||||
|
|
||||||
|
private Chain(@NonNull List<? extends Job> jobs) {
|
||||||
|
this.jobs.add(new ArrayList<>(jobs));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chain then(@NonNull Job job) {
|
||||||
|
return then(Collections.singletonList(job));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Chain then(@NonNull List<Job> jobs) {
|
||||||
|
this.jobs.add(new ArrayList<>(jobs));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enqueue(@NonNull ChainParameters chainParameters) {
|
||||||
|
enqueueChain(this, chainParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<List<Job>> getJobListChain() {
|
||||||
|
return jobs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,6 +104,13 @@ public class JobParameters implements Serializable {
|
|||||||
return retryUntil;
|
return retryUntil;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ChainParameters getSoloChainParameters() {
|
||||||
|
return new ChainParameters.Builder()
|
||||||
|
.setGroupId(groupId)
|
||||||
|
.ignoreDuplicates(ignoreDuplicates)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return a builder used to construct JobParameters.
|
* @return a builder used to construct JobParameters.
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user