Move JobManager to external project and dependency.

// FREEBIE
This commit is contained in:
Moxie Marlinspike 2014-11-28 10:10:47 -08:00
parent 0d01feebc3
commit de75a98093
35 changed files with 6 additions and 2074 deletions

View file

@ -45,12 +45,13 @@ dependencies {
compile 'com.squareup.dagger:dagger:1.2.2'
provided 'com.squareup.dagger:dagger-compiler:1.2.2'
compile 'org.whispersystems:jobmanager:0.9.0'
androidTestCompile 'com.squareup:fest-android:1.0.8'
androidTestCompile 'com.google.dexmaker:dexmaker:1.1'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.1'
compile project(':libtextsecure')
compile project(':jobqueue')
}
dependencyVerification {
@ -72,6 +73,7 @@ dependencyVerification {
'com.google.protobuf:protobuf-java:e0c1c64575c005601725e7c6a02cebf9e1285e888f756b2a1d73ffa8d725cc74',
'com.googlecode.libphonenumber:libphonenumber:eba17eae81dd622ea89a00a3a8c025b2f25d342e0d9644c5b62e16f15687c3ab',
'org.whispersystems:gson:08f4f7498455d1539c9233e5aac18e9b1805815ef29221572996508eb512fe51',
'org.whispersystems:jobmanager:adb4329b69035053a7b6a48e22a3280235ac405b225ed8679b994612a8e6f5b6',
'com.android.support:support-annotations:1aa96ef0cc4a445bfc2f93ccf762305bc57fa107b12afe9d11f3863ae8a11036',
]
}

View file

@ -1,6 +1,6 @@
#Fri Nov 14 10:44:11 MSK 2014
#Fri Nov 28 10:03:17 PST 2014
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip

1
jobqueue/.gitignore vendored
View file

@ -1 +0,0 @@
/build

View file

@ -1,238 +0,0 @@
# JobManager
An Android library that facilitates scheduling persistent jobs which are executed when their
prerequisites have been met. Similar to Path's android-priority-queue.
## The JobManager Way
Android apps often need to perform blocking operations. A messaging app might need to make REST
API calls over a network, send SMS messages, download attachments, and interact with a database.
The standard Android way to do these things are with Services, AsyncTasks, or a dedicated Thread.
However, some of an app's operations might need to wait until certain dependencies are available
(such as a network connection), and some of the operations might need to be durable (complete even if the
app restarts before they have a chance to run). The standard Android way can result in
a lot of retry logic, timers for monitoring dependencies, and one-off code for making operations
durable.
By contrast, the JobManager way allows operations to be broken up into Jobs. A Job represents a
unit of work to be done, the prerequisites that need to be met (such as network access) before the
work can execute, and the characteristics of the job (such as durable persistence).
Applications construct a `JobManager` at initialization time:
```
public class ApplicationContext extends Application {
private JobManager jobManager;
@Override
public void onCreate() {
initializeJobManager();
}
private void initializeJobManager() {
this.jobManager = JobManager.newBuilder(this)
.withName("SampleJobManager")
.withConsumerThreads(5)
.build();
}
...
}
```
This constructs a new `JobManager` with 5 consumer threads dedicated to executing Jobs. A
Job looks like this:
```
public class SampleJob extends Job {
public SampleJob() {
super(JobParameters.newBuilder().create());
}
@Override
public onAdded() {
// Called after the Job has been added to the queue.
}
@Override
public void onRun() {
// Here's where we execute our work.
Log.w("SampleJob", "Hello, world!");
}
@Override
public void onCanceled() {
// This would be called if the job had failed.
}
@Override
public boolean onShouldRetry(Exception exception) {
// Called if onRun() had thrown an exception to determine whether
// onRun() should be called again.
return false;
}
}
```
A Job is scheduled simply by adding it to the JobManager:
```
this.jobManager.add(new SampleJob());
```
## Persistence
To create durable Jobs, the JobManager needs to be given an interface responsible for serializing
and deserializing Job objects. A `JavaJobSerializer` is included with JobManager that uses Java
Serialization, but you can specify your own serializer if you wish:
```
public class ApplicationContext extends Application {
private JobManager jobManager;
@Override
public void onCreate() {
initializeJobManager();
}
private void initializeJobManager() {
this.jobManager = JobManager.newBuilder(this)
.withName("SampleJobManager")
.withConsumerThreads(5)
.withJobSerializer(new JavaJobSerializer())
.build();
}
...
}
```
The Job simply needs to declare itself as durable when constructed:
```
public class SampleJob extends Job {
public SampleJob() {
super(JobParameters.newBuilder()
.withPersistence()
.create());
}
...
```
Persistent jobs that are enqueued will be serialized to disk to ensure that they run even if
the App restarts first. A Job's onAdded() method is called after the commit to disk is complete.
## Requirements
A Job might have certain requirements that need to be met before it can run. A requirement is
represented by the `Requirement` interface. Each `Requirement` must also have a corresponding
`RequirementProvider` that is registered with the JobManager.
A `Requirement` tells you whether it is present when queried, while a `RequirementProvider`
broadcasts to a listener when a Requirement's status might have changed. `Requirement` is attached
to Job, while `RequirementProvider` is attached to JobManager.
One common `Requirement` a `Job` might depend on is the presence of network connectivity.
A `NetworkRequirement` is bundled with JobManager:
```
public class ApplicationContext extends Application {
private JobManager jobManager;
@Override
public void onCreate() {
initializeJobManager();
}
private void initializeJobManager() {
this.jobManager = JobManager.newBuilder(this)
.withName("SampleJobManager")
.withConsumerThreads(5)
.withJobSerializer(new JavaJobSerializer())
.withRequirementProviders(new NetworkRequirementProvider(this))
.build();
}
...
}
```
The Job declares itself as having a `Requirement` when constructed:
```
public class SampleJob extends Job {
public SampleJob(Context context) {
super(JobParameters.newBuilder()
.withPersistence()
.withRequirement(new NetworkRequirement(context))
.create());
}
...
```
## Dependency Injection
It is possible that Jobs (and Requirements) might require dependency injection. A simple example
is `Context`, which many Jobs might require, but can't be persisted to disk for durable Jobs. Or
maybe Jobs require more complex DI through libraries such as Dagger.
JobManager has an extremely primitive DI mechanism strictly for injecting `Context` objects into
Jobs and Requirements after they're deserialized, and includes support for plugging in more complex
DI systems such as Dagger.
The JobManager `Context` injection works by having your `Job` and/or `Requirement` implement the
`ContextDependent` interface. `Job`s and `Requirement`s implementing that interface will get a
`setContext(Context context)` call immediately after the persistent `Job` or `Requirement` is
deserialized.
To plugin a more complex DI mechanism, simply pass an instance of the `DependencyInjector` interface
to the `JobManager`:
```
public class ApplicationContext extends Application implements DependencyInjector {
private JobManager jobManager;
@Override
public void onCreate() {
initializeJobManager();
}
private void initializeJobManager() {
this.jobManager = JobManager.newBuilder(this)
.withName("SampleJobManager")
.withConsumerThreads(5)
.withJobSerializer(new JavaJobSerializer())
.withRequirementProviders(new NetworkRequirementProvider(this))
.withDependencyInjector(this)
.build();
}
@Override
public void injectDependencies(Object object) {
// And here we do our DI magic.
}
...
}
```
`injectDependencies(Object object)` will be called for a `Job` before the job's `onAdded()` method
is called, or after a persistent job is deserialized.

View file

@ -1,19 +0,0 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 21
buildToolsVersion '21.1.1'
defaultConfig {
applicationId "org.whispersystems.jobqueue"
minSdkVersion 9
targetSdkVersion 19
versionCode 1
versionName "1.0"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
}

View file

@ -1,225 +0,0 @@
package org.whispersystems.jobqueue;
import android.test.AndroidTestCase;
import org.whispersystems.jobqueue.jobs.PersistentTestJob;
import org.whispersystems.jobqueue.jobs.RequirementDeferringTestJob;
import org.whispersystems.jobqueue.jobs.RequirementTestJob;
import org.whispersystems.jobqueue.jobs.TestJob;
import org.whispersystems.jobqueue.persistence.JavaJobSerializer;
import org.whispersystems.jobqueue.requirements.RequirementProvider;
import org.whispersystems.jobqueue.util.MockRequirement;
import org.whispersystems.jobqueue.util.MockRequirementProvider;
import org.whispersystems.jobqueue.util.PersistentMockRequirement;
import org.whispersystems.jobqueue.util.PersistentRequirement;
import org.whispersystems.jobqueue.util.PersistentResult;
import org.whispersystems.jobqueue.util.RunnableThrowable;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
public class JobManagerTest extends AndroidTestCase {
public void testTransientJobExecution() throws InterruptedException {
TestJob testJob = new TestJob();
JobManager jobManager = JobManager.newBuilder(getContext())
.withName("transient-test")
.withConsumerThreads(1)
.build();
jobManager.add(testJob);
assertTrue(testJob.isAdded());
assertTrue(testJob.isRan());
}
public void testTransientRequirementJobExecution() throws InterruptedException {
MockRequirementProvider provider = new MockRequirementProvider();
MockRequirement requirement = new MockRequirement(false);
TestJob testJob = new RequirementTestJob(requirement);
JobManager jobManager = JobManager.newBuilder(getContext())
.withName("transient-requirement-test")
.withRequirementProviders(provider)
.withConsumerThreads(1)
.build();
jobManager.add(testJob);
assertTrue(testJob.isAdded());
assertTrue(!testJob.isRan());
requirement.setPresent(true);
provider.fireChange();
assertTrue(testJob.isRan());
}
public void testTransientRequirementDeferringJobExecution() throws InterruptedException {
final Object lock = new Object();
RunnableThrowable waitRunnable = new RunnableThrowable() {
public Boolean shouldThrow = false;
@Override
public void run() throws Exception {
try {
synchronized (lock) {
lock.wait();
if (shouldThrow) {
throw new Exception();
}
}
} catch (InterruptedException e) {
throw new AssertionError(e);
}
}
@Override
public void shouldThrow(Boolean value) {
shouldThrow = value;
}
};
MockRequirementProvider provider = new MockRequirementProvider();
MockRequirement requirement = new MockRequirement(false);
RequirementDeferringTestJob testJob = new RequirementDeferringTestJob(requirement, 5, waitRunnable);
JobManager jobManager = JobManager.newBuilder(getContext())
.withName("transient-requirement-test")
.withRequirementProviders(provider)
.withConsumerThreads(1)
.build();
jobManager.add(testJob);
waitRunnable.shouldThrow(true);
requirement.setPresent(true);
provider.fireChange();
assertTrue(testJob.isRan());
assertTrue(!testJob.isFinished());
synchronized (lock) { lock.notifyAll(); }
assertTrue(!testJob.isFinished());
requirement.setPresent(false);
provider.fireChange();
assertTrue(!testJob.isFinished());
synchronized (lock) { lock.notifyAll(); }
assertTrue(!testJob.isFinished());
waitRunnable.shouldThrow(false);
requirement.setPresent(true);
provider.fireChange();
assertTrue(!testJob.isFinished());
synchronized (lock) { lock.notifyAll(); }
assertTrue(testJob.isFinished());
}
public void testPersistentJobExecuton() throws InterruptedException {
PersistentMockRequirement requirement = new PersistentMockRequirement();
PersistentTestJob testJob = new PersistentTestJob(requirement);
JobManager jobManager = JobManager.newBuilder(getContext())
.withName("persistent-requirement-test3")
.withJobSerializer(new JavaJobSerializer())
.withConsumerThreads(1)
.build();
PersistentResult.getInstance().reset();
PersistentRequirement.getInstance().setPresent(false);
jobManager.add(testJob);
assertTrue(PersistentResult.getInstance().isAdded());
assertTrue(!PersistentResult.getInstance().isRan());
PersistentRequirement.getInstance().setPresent(true);
jobManager = JobManager.newBuilder(getContext())
.withName("persistent-requirement-test3")
.withJobSerializer(new JavaJobSerializer())
.withConsumerThreads(1)
.build();
assertTrue(PersistentResult.getInstance().isRan());
}
public void testEncryptedJobExecuton() throws InterruptedException {
EncryptionKeys keys = new EncryptionKeys(new byte[30]);
PersistentMockRequirement requirement = new PersistentMockRequirement();
PersistentTestJob testJob = new PersistentTestJob(requirement, keys);
JobManager jobManager = JobManager.newBuilder(getContext())
.withName("persistent-requirement-test4")
.withJobSerializer(new JavaJobSerializer())
.withConsumerThreads(1)
.build();
jobManager.setEncryptionKeys(keys);
PersistentResult.getInstance().reset();
PersistentRequirement.getInstance().setPresent(false);
jobManager.add(testJob);
assertTrue(PersistentResult.getInstance().isAdded());
assertTrue(!PersistentResult.getInstance().isRan());
PersistentRequirement.getInstance().setPresent(true);
jobManager = JobManager.newBuilder(getContext())
.withName("persistent-requirement-test4")
.withJobSerializer(new JavaJobSerializer())
.withConsumerThreads(1)
.build();
assertTrue(!PersistentResult.getInstance().isRan());
jobManager.setEncryptionKeys(keys);
assertTrue(PersistentResult.getInstance().isRan());
}
public void testGroupIdExecution() throws InterruptedException {
final Object lock = new Object();
Runnable waitRunnable = new Runnable() {
@Override
public void run() {
try {
synchronized (lock) {
lock.wait();
}
} catch (InterruptedException e) {
throw new AssertionError(e);
}
}
};
TestJob testJobOne = new TestJob(JobParameters.newBuilder().withGroupId("foo").create(), waitRunnable);
TestJob testJobTwo = new TestJob(JobParameters.newBuilder().withGroupId("foo").create());
TestJob testJobThree = new TestJob(JobParameters.newBuilder().withGroupId("bar").create());
JobManager jobManager = JobManager.newBuilder(getContext())
.withName("transient-test")
.withConsumerThreads(3)
.build();
jobManager.add(testJobOne);
jobManager.add(testJobTwo);
jobManager.add(testJobThree);
assertTrue(testJobOne.isAdded());
assertTrue(testJobTwo.isAdded());
assertTrue(testJobThree.isAdded());
assertTrue(testJobOne.isRan());
assertTrue(!testJobTwo.isRan());
assertTrue(testJobThree.isRan());
synchronized (lock) {
lock.notifyAll();
}
assertTrue(testJobTwo.isRan());
}
}

View file

@ -1,39 +0,0 @@
package org.whispersystems.jobqueue.jobs;
import org.whispersystems.jobqueue.EncryptionKeys;
import org.whispersystems.jobqueue.Job;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.Requirement;
import org.whispersystems.jobqueue.util.PersistentResult;
public class PersistentTestJob extends Job {
public PersistentTestJob(Requirement requirement) {
super(JobParameters.newBuilder().withRequirement(requirement).withPersistence().create());
}
public PersistentTestJob(Requirement requirement, EncryptionKeys keys) {
super(JobParameters.newBuilder().withRequirement(requirement).withPersistence().withEncryption(keys).create());
}
@Override
public void onAdded() {
PersistentResult.getInstance().onAdded();
}
@Override
public void onRun() throws Exception {
PersistentResult.getInstance().onRun();
}
@Override
public void onCanceled() {
PersistentResult.getInstance().onCanceled();
}
@Override
public boolean onShouldRetry(Exception exception) {
return false;
}
}

View file

@ -1,51 +0,0 @@
package org.whispersystems.jobqueue.jobs;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.Requirement;
import org.whispersystems.jobqueue.util.RunnableThrowable;
import java.io.IOException;
public class RequirementDeferringTestJob extends TestJob {
private final Object FINISHED_LOCK = new Object();
private boolean finished = false;
private RunnableThrowable runnable;
public RequirementDeferringTestJob(Requirement requirement, int retryCount, RunnableThrowable runnable) {
super(JobParameters.newBuilder().withRequirement(requirement).withRetryCount(retryCount).create());
this.runnable = runnable;
}
@Override
public void onRun() throws Exception {
synchronized (RAN_LOCK) {
this.ran = true;
}
if (runnable != null)
runnable.run();
synchronized (FINISHED_LOCK) {
this.finished = true;
}
}
@Override
public boolean onShouldRetry(Exception exception) {
if (exception instanceof Exception) {
return true;
}
return false;
}
public boolean isFinished() throws InterruptedException {
synchronized (FINISHED_LOCK) {
if (!finished) FINISHED_LOCK.wait(1000);
return finished;
}
}
}

View file

@ -1,12 +0,0 @@
package org.whispersystems.jobqueue.jobs;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.Requirement;
public class RequirementTestJob extends TestJob {
public RequirementTestJob(Requirement requirement) {
super(JobParameters.newBuilder().withRequirement(requirement).create());
}
}

View file

@ -1,81 +0,0 @@
package org.whispersystems.jobqueue.jobs;
import org.whispersystems.jobqueue.Job;
import org.whispersystems.jobqueue.JobParameters;
public class TestJob extends Job {
private final Object ADDED_LOCK = new Object();
protected final Object RAN_LOCK = new Object();
private final Object CANCELED_LOCK = new Object();
private boolean added = false;
protected boolean ran = false;
private boolean canceled = false;
private Runnable runnable;
public TestJob() {
this(JobParameters.newBuilder().create());
}
public TestJob(JobParameters parameters) {
super(parameters);
}
public TestJob(JobParameters parameters, Runnable runnable) {
super(parameters);
this.runnable = runnable;
}
@Override
public void onAdded() {
synchronized (ADDED_LOCK) {
this.added = true;
this.ADDED_LOCK.notifyAll();
}
}
@Override
public void onRun() throws Exception {
synchronized (RAN_LOCK) {
this.ran = true;
}
if (runnable != null)
runnable.run();
}
@Override
public void onCanceled() {
synchronized (CANCELED_LOCK) {
this.canceled = true;
}
}
@Override
public boolean onShouldRetry(Exception exception) {
return false;
}
public boolean isAdded() throws InterruptedException {
synchronized (ADDED_LOCK) {
if (!added) ADDED_LOCK.wait(1000);
return added;
}
}
public boolean isRan() throws InterruptedException {
synchronized (RAN_LOCK) {
if (!ran) RAN_LOCK.wait(1000);
return ran;
}
}
public boolean isCanceled() throws InterruptedException {
synchronized (CANCELED_LOCK) {
if (!canceled) CANCELED_LOCK.wait(1000);
return canceled;
}
}
}

View file

@ -1,23 +0,0 @@
package org.whispersystems.jobqueue.util;
import org.whispersystems.jobqueue.requirements.Requirement;
import java.util.concurrent.atomic.AtomicBoolean;
public class MockRequirement implements Requirement {
private AtomicBoolean present;
public MockRequirement(boolean present) {
this.present = new AtomicBoolean(present);
}
public void setPresent(boolean present) {
this.present.set(present);
}
@Override
public boolean isPresent() {
return present.get();
}
}

View file

@ -1,23 +0,0 @@
package org.whispersystems.jobqueue.util;
import org.whispersystems.jobqueue.requirements.RequirementListener;
import org.whispersystems.jobqueue.requirements.RequirementProvider;
public class MockRequirementProvider implements RequirementProvider {
private RequirementListener listener;
public void fireChange() {
listener.onRequirementStatusChanged();
}
@Override
public void setListener(RequirementListener listener) {
this.listener = listener;
}
@Override
public String getName() {
return "mock-requirement-provider";
}
}

View file

@ -1,10 +0,0 @@
package org.whispersystems.jobqueue.util;
import org.whispersystems.jobqueue.requirements.Requirement;
public class PersistentMockRequirement implements Requirement {
@Override
public boolean isPresent() {
return PersistentRequirement.getInstance().isPresent();
}
}

View file

@ -1,22 +0,0 @@
package org.whispersystems.jobqueue.util;
import java.util.concurrent.atomic.AtomicBoolean;
public class PersistentRequirement {
private AtomicBoolean present = new AtomicBoolean(false);
private static final PersistentRequirement instance = new PersistentRequirement();
public static PersistentRequirement getInstance() {
return instance;
}
public void setPresent(boolean present) {
this.present.set(present);
}
public boolean isPresent() {
return present.get();
}
}

View file

@ -1,73 +0,0 @@
package org.whispersystems.jobqueue.util;
public class PersistentResult {
private final Object ADDED_LOCK = new Object();
private final Object RAN_LOCK = new Object();
private final Object CANCELED_LOCK = new Object();
private boolean added = false;
private boolean ran = false;
private boolean canceled = false;
private static final PersistentResult instance = new PersistentResult();
public static PersistentResult getInstance() {
return instance;
}
public void onAdded() {
synchronized (ADDED_LOCK) {
this.added = true;
this.ADDED_LOCK.notifyAll();
}
}
public void onRun() throws Exception {
synchronized (RAN_LOCK) {
this.ran = true;
}
}
public void onCanceled() {
synchronized (CANCELED_LOCK) {
this.canceled = true;
}
}
public boolean isAdded() throws InterruptedException {
synchronized (ADDED_LOCK) {
if (!added) ADDED_LOCK.wait(1000);
return added;
}
}
public boolean isRan() throws InterruptedException {
synchronized (RAN_LOCK) {
if (!ran) RAN_LOCK.wait(1000);
return ran;
}
}
public boolean isCanceled() throws InterruptedException {
synchronized (CANCELED_LOCK) {
if (!canceled) CANCELED_LOCK.wait(1000);
return canceled;
}
}
public void reset() {
synchronized (ADDED_LOCK) {
this.added = false;
}
synchronized (RAN_LOCK) {
this.ran = false;
}
synchronized (CANCELED_LOCK) {
this.canceled = false;
}
}
}

View file

@ -1,8 +0,0 @@
package org.whispersystems.jobqueue.util;
public interface RunnableThrowable {
public void run() throws Exception;
public void shouldThrow(Boolean value);
}

View file

@ -1,6 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.whispersystems.jobqueue">
<application />
</manifest>

View file

@ -1,30 +0,0 @@
/**
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.jobqueue;
public class EncryptionKeys {
private transient final byte[] encoded;
public EncryptionKeys(byte[] encoded) {
this.encoded = encoded;
}
public byte[] getEncoded() {
return encoded;
}
}

View file

@ -1,116 +0,0 @@
/**
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.jobqueue;
import org.whispersystems.jobqueue.requirements.Requirement;
import java.io.Serializable;
import java.util.List;
/**
* An abstract class representing a unit of work that can be scheduled with
* the JobManager. This should be extended to implement tasks.
*/
public abstract class Job implements Serializable {
private final JobParameters parameters;
private transient long persistentId;
private transient int runIteration;
public Job(JobParameters parameters) {
this.parameters = parameters;
}
public List<Requirement> getRequirements() {
return parameters.getRequirements();
}
public boolean isRequirementsMet() {
for (Requirement requirement : parameters.getRequirements()) {
if (!requirement.isPresent()) return false;
}
return true;
}
public String getGroupId() {
return parameters.getGroupId();
}
public boolean isPersistent() {
return parameters.isPersistent();
}
public EncryptionKeys getEncryptionKeys() {
return parameters.getEncryptionKeys();
}
public void setEncryptionKeys(EncryptionKeys keys) {
parameters.setEncryptionKeys(keys);
}
public int getRetryCount() {
return parameters.getRetryCount();
}
public void setPersistentId(long persistentId) {
this.persistentId = persistentId;
}
public long getPersistentId() {
return persistentId;
}
public int getRunIteration() {
return runIteration;
}
public void setRunIteration(int runIteration) {
this.runIteration = runIteration;
}
/**
* Called after a job has been added to the JobManager queue. If it's a persistent job,
* the state has been persisted to disk before this method is called.
*/
public abstract void onAdded();
/**
* Called to actually execute the job.
* @throws Exception
*/
public abstract void onRun() throws Exception;
/**
* If onRun() throws an exception, this method will be called to determine whether the
* job should be retried.
*
* @param exception The exception onRun() threw.
* @return true if onRun() should be called again, false otherwise.
*/
public abstract boolean onShouldRetry(Exception exception);
/**
* Called if a job fails to run (onShouldRetry returned false, or the number of retries exceeded
* the job's configured retry count.
*/
public abstract void onCanceled();
}

View file

@ -1,90 +0,0 @@
/**
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.jobqueue;
import android.util.Log;
import org.whispersystems.jobqueue.persistence.PersistentStorage;
class JobConsumer extends Thread {
private static final String TAG = JobConsumer.class.getSimpleName();
enum JobResult {
SUCCESS,
FAILURE,
DEFERRED
}
private final JobQueue jobQueue;
private final PersistentStorage persistentStorage;
public JobConsumer(String name, JobQueue jobQueue, PersistentStorage persistentStorage) {
super(name);
this.jobQueue = jobQueue;
this.persistentStorage = persistentStorage;
}
@Override
public void run() {
while (true) {
Job job = jobQueue.getNext();
JobResult result = runJob(job);
if (result == JobResult.DEFERRED) {
jobQueue.push(job);
} else {
if (result == JobResult.FAILURE) {
job.onCanceled();
}
if (job.isPersistent()) {
persistentStorage.remove(job.getPersistentId());
}
}
if (job.getGroupId() != null) {
jobQueue.setGroupIdAvailable(job.getGroupId());
}
}
}
private JobResult runJob(Job job) {
int retryCount = job.getRetryCount();
int runIteration = job.getRunIteration();
for (;runIteration<retryCount;runIteration++) {
try {
job.onRun();
return JobResult.SUCCESS;
} catch (Exception exception) {
Log.w(TAG, exception);
if (exception instanceof RuntimeException) {
throw (RuntimeException)exception;
} else if (!job.onShouldRetry(exception)) {
return JobResult.FAILURE;
} else if (!job.isRequirementsMet()) {
job.setRunIteration(runIteration+1);
return JobResult.DEFERRED;
}
}
}
return JobResult.FAILURE;
}
}

View file

@ -1,253 +0,0 @@
/**
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.jobqueue;
import android.content.Context;
import android.util.Log;
import org.whispersystems.jobqueue.dependencies.DependencyInjector;
import org.whispersystems.jobqueue.persistence.JobSerializer;
import org.whispersystems.jobqueue.persistence.PersistentStorage;
import org.whispersystems.jobqueue.requirements.RequirementListener;
import org.whispersystems.jobqueue.requirements.RequirementProvider;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* A JobManager allows you to enqueue {@link org.whispersystems.jobqueue.Job} tasks
* that are executed once a Job's {@link org.whispersystems.jobqueue.requirements.Requirement}s
* are met.
*/
public class JobManager implements RequirementListener {
private final JobQueue jobQueue = new JobQueue();
private final Executor eventExecutor = Executors.newSingleThreadExecutor();
private final AtomicBoolean hasLoadedEncrypted = new AtomicBoolean(false);
private final PersistentStorage persistentStorage;
private final List<RequirementProvider> requirementProviders;
private final DependencyInjector dependencyInjector;
private JobManager(Context context, String name,
List<RequirementProvider> requirementProviders,
DependencyInjector dependencyInjector,
JobSerializer jobSerializer, int consumers)
{
this.persistentStorage = new PersistentStorage(context, name, jobSerializer, dependencyInjector);
this.requirementProviders = requirementProviders;
this.dependencyInjector = dependencyInjector;
eventExecutor.execute(new LoadTask(null));
if (requirementProviders != null && !requirementProviders.isEmpty()) {
for (RequirementProvider provider : requirementProviders) {
provider.setListener(this);
}
}
for (int i=0;i<consumers;i++) {
new JobConsumer("JobConsumer-" + i, jobQueue, persistentStorage).start();
}
}
/**
* @param context An Android {@link android.content.Context}.
* @return a {@link org.whispersystems.jobqueue.JobManager.Builder} used to construct a JobManager.
*/
public static Builder newBuilder(Context context) {
return new Builder(context);
}
/**
* Returns a {@link org.whispersystems.jobqueue.requirements.RequirementProvider} registered with
* the JobManager by name.
*
* @param name The name of the registered {@link org.whispersystems.jobqueue.requirements.RequirementProvider}
* @return The RequirementProvider, or null if no provider is registered with that name.
*/
public RequirementProvider getRequirementProvider(String name) {
for (RequirementProvider provider : requirementProviders) {
if (provider.getName().equals(name)) {
return provider;
}
}
return null;
}
public void setEncryptionKeys(EncryptionKeys keys) {
if (hasLoadedEncrypted.compareAndSet(false, true)) {
eventExecutor.execute(new LoadTask(keys));
}
}
/**
* Queue a {@link org.whispersystems.jobqueue.Job} to be executed.
*
* @param job The Job to be executed.
*/
public void add(final Job job) {
eventExecutor.execute(new Runnable() {
@Override
public void run() {
try {
if (job.isPersistent()) {
persistentStorage.store(job);
}
if (dependencyInjector != null) {
dependencyInjector.injectDependencies(job);
}
job.onAdded();
jobQueue.add(job);
} catch (IOException e) {
Log.w("JobManager", e);
job.onCanceled();
}
}
});
}
@Override
public void onRequirementStatusChanged() {
eventExecutor.execute(new Runnable() {
@Override
public void run() {
jobQueue.onRequirementStatusChanged();
}
});
}
private class LoadTask implements Runnable {
private final EncryptionKeys keys;
public LoadTask(EncryptionKeys keys) {
this.keys = keys;
}
@Override
public void run() {
List<Job> pendingJobs;
if (keys == null) pendingJobs = persistentStorage.getAllUnencrypted();
else pendingJobs = persistentStorage.getAllEncrypted(keys);
jobQueue.addAll(pendingJobs);
}
}
public static class Builder {
private final Context context;
private String name;
private List<RequirementProvider> requirementProviders;
private DependencyInjector dependencyInjector;
private JobSerializer jobSerializer;
private int consumerThreads;
Builder(Context context) {
this.context = context;
this.consumerThreads = 5;
}
/**
* A name for the {@link org.whispersystems.jobqueue.JobManager}. This is a required parameter,
* and is linked to the durable queue used by persistent jobs.
*
* @param name The name for the JobManager to build.
* @return The builder.
*/
public Builder withName(String name) {
this.name = name;
return this;
}
/**
* The {@link org.whispersystems.jobqueue.requirements.RequirementProvider}s to register with this
* JobManager. Optional. Each {@link org.whispersystems.jobqueue.requirements.Requirement} an
* enqueued Job depends on should have a matching RequirementProvider registered here.
*
* @param requirementProviders The RequirementProviders
* @return The builder.
*/
public Builder withRequirementProviders(RequirementProvider... requirementProviders) {
this.requirementProviders = Arrays.asList(requirementProviders);
return this;
}
/**
* The {@link org.whispersystems.jobqueue.dependencies.DependencyInjector} to use for injecting
* dependencies into {@link Job}s. Optional. Injection occurs just before a Job's onAdded() callback, or
* after deserializing a persistent job.
*
* @param dependencyInjector The injector to use.
* @return The builder.
*/
public Builder withDependencyInjector(DependencyInjector dependencyInjector) {
this.dependencyInjector = dependencyInjector;
return this;
}
/**
* The {@link org.whispersystems.jobqueue.persistence.JobSerializer} to use for persistent Jobs.
* Required if persistent Jobs are used.
*
* @param jobSerializer The serializer to use.
* @return The builder.
*/
public Builder withJobSerializer(JobSerializer jobSerializer) {
this.jobSerializer = jobSerializer;
return this;
}
/**
* Set the number of threads dedicated to consuming Jobs from the queue and executing them.
*
* @param consumerThreads The number of threads.
* @return The builder.
*/
public Builder withConsumerThreads(int consumerThreads) {
this.consumerThreads = consumerThreads;
return this;
}
/**
* @return A constructed JobManager.
*/
public JobManager build() {
if (name == null) {
throw new IllegalArgumentException("You must specify a name!");
}
if (requirementProviders == null) {
requirementProviders = new LinkedList<>();
}
return new JobManager(context, name, requirementProviders,
dependencyInjector, jobSerializer,
consumerThreads);
}
}
}

View file

@ -1,149 +0,0 @@
/**
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.jobqueue;
import org.whispersystems.jobqueue.requirements.Requirement;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
/**
* The set of parameters that describe a {@link org.whispersystems.jobqueue.Job}.
*/
public class JobParameters implements Serializable {
private transient EncryptionKeys encryptionKeys;
private final List<Requirement> requirements;
private final boolean isPersistent;
private final int retryCount;
private final String groupId;
private JobParameters(List<Requirement> requirements,
boolean isPersistent, String groupId,
EncryptionKeys encryptionKeys,
int retryCount)
{
this.requirements = requirements;
this.isPersistent = isPersistent;
this.groupId = groupId;
this.encryptionKeys = encryptionKeys;
this.retryCount = retryCount;
}
public List<Requirement> getRequirements() {
return requirements;
}
public boolean isPersistent() {
return isPersistent;
}
public EncryptionKeys getEncryptionKeys() {
return encryptionKeys;
}
public void setEncryptionKeys(EncryptionKeys encryptionKeys) {
this.encryptionKeys = encryptionKeys;
}
public int getRetryCount() {
return retryCount;
}
/**
* @return a builder used to construct JobParameters.
*/
public static Builder newBuilder() {
return new Builder();
}
public String getGroupId() {
return groupId;
}
public static class Builder {
private List<Requirement> requirements = new LinkedList<>();
private boolean isPersistent = false;
private EncryptionKeys encryptionKeys = null;
private int retryCount = 100;
private String groupId = null;
/**
* Specify a {@link org.whispersystems.jobqueue.requirements.Requirement }that must be met
* before the Job is executed. May be called multiple times to register multiple requirements.
* @param requirement The Requirement that must be met.
* @return the builder.
*/
public Builder withRequirement(Requirement requirement) {
this.requirements.add(requirement);
return this;
}
/**
* Specify that the Job should be durably persisted to disk, so that it remains in the queue
* across application restarts.
* @return The builder.
*/
public Builder withPersistence() {
this.isPersistent = true;
return this;
}
/**
* Specify that the job should use encryption when durably persisted to disk.
* @param encryptionKeys The keys to encrypt the serialized job with before persisting.
* @return the builder.
*/
public Builder withEncryption(EncryptionKeys encryptionKeys) {
this.encryptionKeys = encryptionKeys;
return this;
}
/**
* Specify how many times the job should be retried if execution fails but onShouldRetry() returns
* true.
*
* @param retryCount The number of times the job should be retried.
* @return the builder.
*/
public Builder withRetryCount(int retryCount) {
this.retryCount = retryCount;
return this;
}
/**
* Specify a groupId the job should belong to. Jobs with the same groupId are guaranteed to be
* executed serially.
*
* @param groupId The job's groupId.
* @return the builder.
*/
public Builder withGroupId(String groupId) {
this.groupId = groupId;
return this;
}
/**
* @return the JobParameters instance that describes a Job.
*/
public JobParameters create() {
return new JobParameters(requirements, isPersistent, groupId, encryptionKeys, retryCount);
}
}
}

View file

@ -1,95 +0,0 @@
/**
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.jobqueue;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
class JobQueue {
private final Set<String> activeGroupIds = new HashSet<>();
private final LinkedList<Job> jobQueue = new LinkedList<>();
synchronized void onRequirementStatusChanged() {
notifyAll();
}
synchronized void add(Job job) {
jobQueue.add(job);
notifyAll();
}
synchronized void addAll(List<Job> jobs) {
jobQueue.addAll(jobs);
notifyAll();
}
synchronized void push(Job job) {
jobQueue.push(job);
}
synchronized Job getNext() {
try {
Job nextAvailableJob;
while ((nextAvailableJob = getNextAvailableJob()) == null) {
wait();
}
return nextAvailableJob;
} catch (InterruptedException e) {
throw new AssertionError(e);
}
}
synchronized void setGroupIdAvailable(String groupId) {
if (groupId != null) {
activeGroupIds.remove(groupId);
notifyAll();
}
}
private Job getNextAvailableJob() {
if (jobQueue.isEmpty()) return null;
ListIterator<Job> iterator = jobQueue.listIterator();
while (iterator.hasNext()) {
Job job = iterator.next();
if (job.isRequirementsMet() && isGroupIdAvailable(job.getGroupId())) {
iterator.remove();
setGroupIdUnavailable(job.getGroupId());
return job;
}
}
return null;
}
private boolean isGroupIdAvailable(String groupId) {
return groupId == null || !activeGroupIds.contains(groupId);
}
private void setGroupIdUnavailable(String groupId) {
if (groupId != null) {
activeGroupIds.add(groupId);
}
}
}

View file

@ -1,27 +0,0 @@
/**
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.jobqueue.dependencies;
import android.content.Context;
/**
* Any Job or Requirement that depends on {@link android.content.Context} can implement this
* interface to receive a Context after being deserialized.
*/
public interface ContextDependent {
public void setContext(Context context);
}

View file

@ -1,24 +0,0 @@
/**
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.jobqueue.dependencies;
/**
* Interface responsible for injecting dependencies into Jobs.
*/
public interface DependencyInjector {
public void injectDependencies(Object object);
}

View file

@ -1,58 +0,0 @@
/**
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.jobqueue.persistence;
import android.util.Base64;
import org.whispersystems.jobqueue.EncryptionKeys;
import org.whispersystems.jobqueue.Job;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* An implementation of {@link org.whispersystems.jobqueue.persistence.JobSerializer} that uses
* Java Serialization.
*/
public class JavaJobSerializer implements JobSerializer {
public JavaJobSerializer() {}
@Override
public String serialize(Job job) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(job);
return Base64.encodeToString(baos.toByteArray(), Base64.NO_WRAP);
}
@Override
public Job deserialize(EncryptionKeys keys, boolean encrypted, String serialized) throws IOException {
try {
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(serialized, Base64.NO_WRAP));
ObjectInputStream ois = new ObjectInputStream(bais);
return (Job)ois.readObject();
} catch (ClassNotFoundException e) {
throw new IOException(e);
}
}
}

View file

@ -1,47 +0,0 @@
/**
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.jobqueue.persistence;
import org.whispersystems.jobqueue.EncryptionKeys;
import org.whispersystems.jobqueue.Job;
import java.io.IOException;
/**
* A JobSerializer is responsible for serializing and deserializing persistent jobs.
*/
public interface JobSerializer {
/**
* Serialize a job object into a string.
* @param job The Job to serialize.
* @return The serialized Job.
* @throws IOException if serialization fails.
*/
public String serialize(Job job) throws IOException;
/**
* Deserialize a String into a Job.
* @param keys Optional encryption keys that could have been used.
* @param encrypted True if the job was encrypted using the encryption keys.
* @param serialized The serialized Job.
* @return The deserialized Job.
* @throws IOException If the Job deserialization fails.
*/
public Job deserialize(EncryptionKeys keys, boolean encrypted, String serialized) throws IOException;
}

View file

@ -1,152 +0,0 @@
/**
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.jobqueue.persistence;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import org.whispersystems.jobqueue.EncryptionKeys;
import org.whispersystems.jobqueue.Job;
import org.whispersystems.jobqueue.dependencies.ContextDependent;
import org.whispersystems.jobqueue.dependencies.DependencyInjector;
import org.whispersystems.jobqueue.requirements.Requirement;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
public class PersistentStorage {
private static final int DATABASE_VERSION = 1;
private static final String TABLE_NAME = "queue";
private static final String ID = "_id";
private static final String ITEM = "item";
private static final String ENCRYPTED = "encrypted";
private static final String DATABASE_CREATE = String.format("CREATE TABLE %s (%s INTEGER PRIMARY KEY, %s TEXT NOT NULL, %s INTEGER DEFAULT 0);",
TABLE_NAME, ID, ITEM, ENCRYPTED);
private final Context context;
private final DatabaseHelper databaseHelper;
private final JobSerializer jobSerializer;
private final DependencyInjector dependencyInjector;
public PersistentStorage(Context context, String name,
JobSerializer serializer,
DependencyInjector dependencyInjector)
{
this.databaseHelper = new DatabaseHelper(context, "_jobqueue-" + name);
this.context = context;
this.jobSerializer = serializer;
this.dependencyInjector = dependencyInjector;
}
public void store(Job job) throws IOException {
ContentValues contentValues = new ContentValues();
contentValues.put(ITEM, jobSerializer.serialize(job));
contentValues.put(ENCRYPTED, job.getEncryptionKeys() != null);
long id = databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, contentValues);
job.setPersistentId(id);
}
public List<Job> getAllUnencrypted() {
return getJobs(null, ENCRYPTED + " = 0");
}
public List<Job> getAllEncrypted(EncryptionKeys keys) {
return getJobs(keys, ENCRYPTED + " = 1");
}
private List<Job> getJobs(EncryptionKeys keys, String where) {
List<Job> results = new LinkedList<>();
SQLiteDatabase database = databaseHelper.getReadableDatabase();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, null, where, null, null, null, ID + " ASC", null);
while (cursor.moveToNext()) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
String item = cursor.getString(cursor.getColumnIndexOrThrow(ITEM));
boolean encrypted = cursor.getInt(cursor.getColumnIndexOrThrow(ENCRYPTED)) == 1;
try{
Job job = jobSerializer.deserialize(keys, encrypted, item);
job.setPersistentId(id);
job.setEncryptionKeys(keys);
injectDependencies(job);
results.add(job);
} catch (IOException e) {
Log.w("PersistentStore", e);
remove(id);
}
}
} finally {
if (cursor != null)
cursor.close();
}
return results;
}
public void remove(long id) {
databaseHelper.getWritableDatabase()
.delete(TABLE_NAME, ID + " = ?", new String[] {String.valueOf(id)});
}
private void injectDependencies(Job job) {
if (job instanceof ContextDependent) {
((ContextDependent)job).setContext(context);
}
for (Requirement requirement : job.getRequirements()) {
if (requirement instanceof ContextDependent) {
((ContextDependent)requirement).setContext(context);
}
}
if (dependencyInjector != null) {
dependencyInjector.injectDependencies(job);
}
}
private static class DatabaseHelper extends SQLiteOpenHelper {
public DatabaseHelper(Context context, String name) {
super(context, name, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
}

View file

@ -1,50 +0,0 @@
/**
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.jobqueue.requirements;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import org.whispersystems.jobqueue.dependencies.ContextDependent;
/**
* A requirement that is satisfied when a network connection is present.
*/
public class NetworkRequirement implements Requirement, ContextDependent {
private transient Context context;
public NetworkRequirement(Context context) {
this.context = context;
}
public NetworkRequirement() {}
@Override
public boolean isPresent() {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo();
return netInfo != null && netInfo.isConnectedOrConnecting();
}
@Override
public void setContext(Context context) {
this.context = context;
}
}

View file

@ -1,59 +0,0 @@
/**
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.jobqueue.requirements;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
public class NetworkRequirementProvider implements RequirementProvider {
private RequirementListener listener;
private final NetworkRequirement requirement;
public NetworkRequirementProvider(Context context) {
this.requirement = new NetworkRequirement(context);
context.getApplicationContext().registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (listener == null) {
return;
}
if (requirement.isPresent()) {
listener.onRequirementStatusChanged();
}
}
}, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}
@Override
public String getName() {
return "network";
}
@Override
public void setListener(RequirementListener listener) {
this.listener = listener;
}
}

View file

@ -1,29 +0,0 @@
/**
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.jobqueue.requirements;
import java.io.Serializable;
/**
* A Requirement that must be satisfied before a Job can run.
*/
public interface Requirement extends Serializable {
/**
* @return true if the requirement is satisfied, false otherwise.
*/
public boolean isPresent();
}

View file

@ -1,21 +0,0 @@
/**
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.jobqueue.requirements;
public interface RequirementListener {
public void onRequirementStatusChanged();
}

View file

@ -1,37 +0,0 @@
/**
* Copyright (C) 2014 Open Whisper Systems
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.whispersystems.jobqueue.requirements;
/**
* Notifies listeners when a {@link org.whispersystems.jobqueue.requirements.Requirement}'s
* state is likely to have changed.
*/
public interface RequirementProvider {
/**
* @return The name of the provider.
*/
public String getName();
/**
* The {@link org.whispersystems.jobqueue.requirements.RequirementListener} to call when
* a {@link org.whispersystems.jobqueue.requirements.Requirement}'s status is likely to
* have changed.
*
* @param listener The listener to call.
*/
public void setListener(RequirementListener listener);
}

View file

@ -1,2 +0,0 @@
<resources>
</resources>

View file

@ -1 +1 @@
include ':libtextsecure', ':libaxolotl', ':jobqueue'
include ':libtextsecure', ':libaxolotl'