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(); + } }; }