diff --git a/clients/android/uploader/res/values/strings.xml b/clients/android/uploader/res/values/strings.xml
index 21e5c7a72..c0916cf93 100644
--- a/clients/android/uploader/res/values/strings.xml
+++ b/clients/android/uploader/res/values/strings.xml
@@ -7,4 +7,6 @@
Pause / Resume
Pause
Resume
+Uploading...
+Digesting...
diff --git a/clients/android/uploader/src/com/danga/camli/CamliActivity.java b/clients/android/uploader/src/com/danga/camli/CamliActivity.java
index dd24eb3ce..2a7ee265b 100644
--- a/clients/android/uploader/src/com/danga/camli/CamliActivity.java
+++ b/clients/android/uploader/src/com/danga/camli/CamliActivity.java
@@ -27,6 +27,7 @@ import android.widget.TextView;
public class CamliActivity extends Activity {
private static final String TAG = "CamliActivity";
private static final int MENU_SETTINGS = 1;
+ private static final int MENU_STOP = 2;
private IUploadService mServiceStub = null;
private IStatusCallback mCallback = null;
@@ -90,7 +91,8 @@ public class CamliActivity extends Activity {
});
mCallback = new IStatusCallback.Stub() {
- private volatile int mLastBlobsRemain = 0;
+ private volatile int mLastBlobsUploadRemain = 0;
+ private volatile int mLastBlobsDigestRemain = 0;
public void logToClient(String stuff) throws RemoteException {
// TODO Auto-generated method stub
@@ -102,24 +104,29 @@ public class CamliActivity extends Activity {
public void run() {
if (uploading) {
buttonToggle.setText(R.string.pause);
- textStatus.setText("Uploading...");
+ textStatus.setText(R.string.uploading);
+ } else if (mLastBlobsDigestRemain > 0) {
+ buttonToggle.setText(R.string.pause);
+ textStatus.setText(R.string.digesting);
} else {
buttonToggle.setText(R.string.resume);
- textStatus.setText(mLastBlobsRemain > 0 ? "Paused." : "Idle.");
+ int stepsRemain = mLastBlobsUploadRemain + mLastBlobsDigestRemain;
+ textStatus.setText(stepsRemain > 0 ? "Paused." : "Idle.");
}
}
});
}
- public void setBlobStatus(final int done, final int inFlight, final int total)
+ public void setBlobStatus(final int blobsDone, final int inFlight, final int total)
throws RemoteException {
mHandler.post(new Runnable() {
public void run() {
- buttonToggle.setEnabled(done != total);
+ boolean finished = (blobsDone == total && mLastBlobsDigestRemain == 0);
+ buttonToggle.setEnabled(!finished);
progressBlob.setMax(total);
- progressBlob.setProgress(done);
- progressBlob.setSecondaryProgress(done + inFlight);
- if (done == total) {
+ progressBlob.setProgress(blobsDone);
+ progressBlob.setSecondaryProgress(blobsDone + inFlight);
+ if (finished) {
buttonToggle.setText(getString(R.string.pause_resume));
}
}
@@ -140,12 +147,20 @@ public class CamliActivity extends Activity {
});
}
- public void setBlobsRemain(final int num) throws RemoteException {
+ public void setBlobsRemain(final int toUpload, final int toDigest)
+ throws RemoteException {
mHandler.post(new Runnable() {
public void run() {
- mLastBlobsRemain = num;
- buttonToggle.setEnabled(num != 0);
- textBlobsRemain.setText("Blobs remain: " + num);
+ mLastBlobsUploadRemain = toUpload;
+ mLastBlobsDigestRemain = toDigest;
+
+ buttonToggle.setEnabled((toUpload + toDigest) != 0);
+ StringBuilder sb = new StringBuilder(40);
+ sb.append("Blobs to upload: ").append(toUpload);
+ if (toDigest > 0) {
+ sb.append("; to digest: ").append(toDigest);
+ }
+ textBlobsRemain.setText(sb.toString());
}
});
}
@@ -177,6 +192,7 @@ public class CamliActivity extends Activity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
+ menu.add(Menu.NONE, MENU_STOP, 0, "Stop");
menu.add(Menu.NONE, MENU_SETTINGS, 0, "Settings");
return true;
}
@@ -184,6 +200,15 @@ public class CamliActivity extends Activity {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
+ case MENU_STOP:
+ try {
+ if (mServiceStub != null) {
+ mServiceStub.stopEverything();
+ }
+ } catch (RemoteException e) {
+ // Ignore.
+ }
+ break;
case MENU_SETTINGS:
SettingsActivity.show(this);
break;
diff --git a/clients/android/uploader/src/com/danga/camli/DummyNullCallback.java b/clients/android/uploader/src/com/danga/camli/DummyNullCallback.java
index 842c2e718..2a0bb83e8 100644
--- a/clients/android/uploader/src/com/danga/camli/DummyNullCallback.java
+++ b/clients/android/uploader/src/com/danga/camli/DummyNullCallback.java
@@ -24,7 +24,7 @@ public class DummyNullCallback extends IStatusCallback.Stub {
}
- public void setBlobsRemain(int num) throws RemoteException {
+ public void setBlobsRemain(int toUpload, int toDigest) throws RemoteException {
// TODO Auto-generated method stub
}
diff --git a/clients/android/uploader/src/com/danga/camli/IStatusCallback.aidl b/clients/android/uploader/src/com/danga/camli/IStatusCallback.aidl
index 85f3a8b12..9463cdba9 100644
--- a/clients/android/uploader/src/com/danga/camli/IStatusCallback.aidl
+++ b/clients/android/uploader/src/com/danga/camli/IStatusCallback.aidl
@@ -5,7 +5,7 @@ oneway interface IStatusCallback {
void setUploadStatusText(String text);
void setUploading(boolean uploading);
- void setBlobsRemain(int num);
+ void setBlobsRemain(int toUpload, int toDigest);
// done: acknowledged by server
// inFlight: written to the server, but no reply yet (i.e. large HTTP POST body)
diff --git a/clients/android/uploader/src/com/danga/camli/IUploadService.aidl b/clients/android/uploader/src/com/danga/camli/IUploadService.aidl
index 7400fcaaf..a9af221fb 100644
--- a/clients/android/uploader/src/com/danga/camli/IUploadService.aidl
+++ b/clients/android/uploader/src/com/danga/camli/IUploadService.aidl
@@ -24,4 +24,7 @@ interface IUploadService {
// Returns false if server not configured.
boolean enqueueUpload(in Uri uri);
int enqueueUploadList(in List uri);
+
+ // Stop digesting, stop uploads, clear queues.
+ void stopEverything();
}
diff --git a/clients/android/uploader/src/com/danga/camli/UploadService.java b/clients/android/uploader/src/com/danga/camli/UploadService.java
index fcd1563c4..920123783 100644
--- a/clients/android/uploader/src/com/danga/camli/UploadService.java
+++ b/clients/android/uploader/src/com/danga/camli/UploadService.java
@@ -5,6 +5,7 @@ import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
import android.app.Service;
import android.content.ContentResolver;
@@ -33,6 +34,7 @@ public class UploadService extends Service {
private String mLastUploadStatusText = null;
private int mBytesInFlight = 0;
private int mBlobsInFlight = 0;
+ private int mBlobsToDigest = 0;
// Stats, all guarded by 'this', and all reset to 0 on queue size transition from 0 -> 1.
private long mBytesTotal = 0;
@@ -54,7 +56,11 @@ public class UploadService extends Service {
@Override
public void onDestroy() {
super.onDestroy();
- Log.d(TAG, "UPLOAD SERVICE onDestroy !!!");
+ if (mUploadThread != null) {
+ Log.e(TAG, "Unexpected onDestroy with active upload thread. Killing it.");
+ mUploadThread.interrupt();
+ mUploadThread = null;
+ }
}
@Override
@@ -109,6 +115,19 @@ public class UploadService extends Service {
}
}
+ void broadcastAllState() {
+ synchronized (this) {
+ try {
+ mCallback.setUploading(mUploading);
+ mCallback.setUploadStatusText(mLastUploadStatusText);
+ mCallback.setBlobsRemain(mQueueSet.size(), mBlobsToDigest);
+ } catch (RemoteException e) {
+ }
+ }
+ broadcastBlobStatus();
+ broadcastByteStatus();
+ }
+
void setInFlightBlobs(int v) {
synchronized (this) {
mBlobsInFlight = v;
@@ -127,17 +146,32 @@ public class UploadService extends Service {
}
}
- void onUploadComplete(QueuedFile qf, boolean wasAlreadyExisting) {
+ /**
+ * Callback from the UploadThread to the service.
+ *
+ * @param qf
+ * the queued file
+ * @param wasUploaded
+ * not a dupe that the server already had. the bytes were
+ * actually uploaded.
+ */
+ void onUploadComplete(QueuedFile qf, boolean wasUploaded) {
+ Log.d(TAG, "onUploadComplete of " + qf + ", uploaded=" + wasUploaded);
synchronized (this) {
if (!mQueueSet.remove(qf)) {
return;
}
mQueueList.remove(qf); // TODO: ghetto, linear scan
- mBytesUploaded += qf.getSize();
- mBlobsUploaded += 1;
+ if (wasUploaded) {
+ mBytesUploaded += qf.getSize();
+ mBlobsUploaded += 1;
+ } else {
+ mBytesTotal -= qf.getSize();
+ mBlobsTotal -= 1;
+ }
try {
- mCallback.setBlobsRemain(mQueueSet.size());
+ mCallback.setBlobsRemain(mQueueSet.size(), mBlobsToDigest);
} catch (RemoteException e) {
}
broadcastByteStatus();
@@ -148,10 +182,11 @@ public class UploadService extends Service {
private void stopServiceIfEmpty() {
synchronized (this) {
- if (mQueueSet.isEmpty()) {
+ if (mQueueSet.isEmpty() && mBlobsToDigest == 0) {
stopService(new Intent(UploadService.this, UploadService.class));
}
}
+
}
ParcelFileDescriptor getFileDescriptor(Uri uri) {
@@ -166,21 +201,42 @@ public class UploadService extends Service {
private final IUploadService.Stub service = new IUploadService.Stub() {
+ // Incremented whenever "stop" is pressed:
+ private final AtomicInteger mStopDigestingCounter = new AtomicInteger(0);
+
public int enqueueUploadList(List uriList) throws RemoteException {
+ startService(new Intent(UploadService.this, UploadService.class));
Log.d(TAG, "enqueuing list of " + uriList.size() + " URIs");
+ synchronized (UploadService.this) {
+ mBlobsToDigest += uriList.size();
+ mCallback.setBlobsRemain(mQueueSet.size(), mBlobsToDigest);
+ }
int goodCount = 0;
+ int startGen = mStopDigestingCounter.get();
for (Uri uri : uriList) {
- goodCount += enqueueUpload(uri) ? 1 : 0;
+ goodCount += enqueueSingleUri(uri) ? 1 : 0;
+ if (startGen != mStopDigestingCounter.get()) {
+ return goodCount;
+ }
}
Log.d(TAG, "...goodCount = " + goodCount);
return goodCount;
}
+ /*
+ * Note: blocks while sha1'ing the file. Should be called from an
+ * AsyncTask from the Activity. TODO: make the activity pass this info
+ * via a startService(Intent) to us.
+ */
public boolean enqueueUpload(Uri uri) throws RemoteException {
startService(new Intent(UploadService.this, UploadService.class));
+ return enqueueSingleUri(uri);
+ }
+ private boolean enqueueSingleUri(Uri uri) throws RemoteException {
ParcelFileDescriptor pfd = getFileDescriptor(uri);
String sha1 = Util.getSha1(pfd.getFileDescriptor());
+ mBlobsToDigest--;
QueuedFile qf = new QueuedFile(sha1, uri, pfd.getStatSize());
boolean needResume = false;
@@ -204,7 +260,7 @@ public class UploadService extends Service {
mBlobsTotal += 1;
needResume = !mUploading;
- mCallback.setBlobsRemain(mQueueSet.size());
+ mCallback.setBlobsRemain(mQueueSet.size(), mBlobsToDigest);
}
broadcastBlobStatus();
broadcastByteStatus();
@@ -227,13 +283,8 @@ public class UploadService extends Service {
cb = DummyNullCallback.instance();
}
mCallback = cb;
- // Init the new connection.
- cb.setUploading(mUploading);
- cb.setUploadStatusText(mLastUploadStatusText);
- cb.setBlobsRemain(mQueueSet.size());
}
- broadcastBlobStatus();
- broadcastByteStatus();
+ broadcastAllState();
}
public void unregisterCallback(IStatusCallback cb) throws RemoteException {
@@ -305,5 +356,27 @@ public class UploadService extends Service {
return mQueueList.size();
}
}
+
+ public void stopEverything() throws RemoteException {
+ synchronized (UploadService.this) {
+ mQueueSet.clear();
+ mQueueList.clear();
+ mLastUploadStatusText = "Stopped";
+ mUploading = false;
+ mBytesInFlight = 0;
+ mBlobsInFlight = 0;
+ mBlobsToDigest = 0;
+ mBytesTotal = 0;
+ mBytesUploaded = 0;
+ mBlobsTotal = 0;
+ mBlobsUploaded = 0;
+ mStopDigestingCounter.incrementAndGet();
+ if (mUploadThread != null) {
+ mUploadThread.stopPlease();
+ mUploadThread = null;
+ }
+ }
+ broadcastAllState();
+ }
};
}