Add free space precondition check for copy job.

Bug: 24948755
Change-Id: I210395bdf339d630604e90e867ffddbbd3cf4bea
(cherry picked from commit 2a530ab3be585b366d562ea24940dcdd239af6d0)
This commit is contained in:
Garfield, Tan
2016-07-06 17:24:20 -07:00
committed by Garfield Tan
parent 8f4731db63
commit 71ac8a003e
3 changed files with 95 additions and 11 deletions

View File

@@ -36,6 +36,7 @@ import android.os.Handler;
import android.os.SystemClock;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Root;
import android.provider.DocumentsProvider;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
@@ -350,11 +351,20 @@ public class RootsCache {
* waiting for all the other roots to come back.
*/
public RootInfo getRootOneshot(String authority, String rootId) {
return getRootOneshot(authority, rootId, false);
}
/**
* Return the requested {@link RootInfo}, but only loading the roots of the requested authority.
* It always fetches from {@link DocumentsProvider} if forceRefresh is true, which is used to
* get the most up-to-date free space before starting copy operations.
*/
public RootInfo getRootOneshot(String authority, String rootId, boolean forceRefresh) {
synchronized (mLock) {
RootInfo root = getRootLocked(authority, rootId);
RootInfo root = forceRefresh ? null : getRootLocked(authority, rootId);
if (root == null) {
mRoots.putAll(authority,
loadRootsForAuthority(mContext.getContentResolver(), authority, false));
mRoots.putAll(authority, loadRootsForAuthority(
mContext.getContentResolver(), authority, forceRefresh));
root = getRootLocked(authority, rootId);
}
return root;

View File

@@ -51,9 +51,11 @@ import android.text.format.DateUtils;
import android.util.Log;
import android.webkit.MimeTypeMap;
import com.android.documentsui.UrisSupplier;
import com.android.documentsui.DocumentsApplication;
import com.android.documentsui.Metrics;
import com.android.documentsui.R;
import com.android.documentsui.RootsCache;
import com.android.documentsui.UrisSupplier;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.RootInfo;
@@ -210,7 +212,6 @@ class CopyJob extends Job {
@Override
boolean setUp() {
try {
buildDocumentList();
} catch (ResourceException e) {
@@ -218,6 +219,7 @@ class CopyJob extends Job {
return false;
}
// Check if user has canceled this task.
if (isCanceled()) {
return false;
}
@@ -229,7 +231,15 @@ class CopyJob extends Job {
mBatchSize = -1;
}
return true;
// Check if user has canceled this task. We should check it again here as user cancels
// tasks in main thread, but this is running in a worker thread. calculateSize() may
// take a long time during which user can cancel this task, and we don't want to waste
// resources doing useless large chunk of work.
if (isCanceled()) {
return false;
}
return checkSpace();
}
@Override
@@ -286,6 +296,44 @@ class CopyJob extends Job {
return !root.isDownloads() || !doc.isDirectory();
}
/**
* Checks whether the destination folder has enough space to take all source files.
* @return true if the root has enough space or doesn't provide free space info; otherwise false
*/
boolean checkSpace() {
return checkSpace(mBatchSize);
}
/**
* Checks whether the destination folder has enough space to take files of batchSize
* @param batchSize the total size of files
* @return true if the root has enough space or doesn't provide free space info; otherwise false
*/
final boolean checkSpace(long batchSize) {
// Default to be true because if batchSize or available space is invalid, we still let the
// copy start anyway.
boolean result = true;
if (batchSize >= 0) {
RootsCache cache = DocumentsApplication.getRootsCache(appContext);
// Query root info here instead of using stack.root because the number there may be
// stale.
RootInfo root = cache.getRootOneshot(stack.root.authority, stack.root.rootId, true);
if (root.availableBytes >= 0) {
result = (batchSize <= root.availableBytes);
} else {
Log.w(TAG, root.toString() + " doesn't provide available bytes.");
}
}
if (!result) {
failedFileCount += mSrcs.size();
failedFiles.addAll(mSrcs);
}
return result;
}
@Override
boolean hasWarnings() {
return !convertedFiles.isEmpty();
@@ -585,7 +633,7 @@ class CopyJob extends Job {
result += calculateFileSizesRecursively(getClient(src), src.derivedUri);
} catch (RemoteException e) {
throw new ResourceException("Failed to obtain the client for %s.",
src.derivedUri);
src.derivedUri, e);
}
} else {
result += src.size;
@@ -603,7 +651,7 @@ class CopyJob extends Job {
*
* @throws ResourceException
*/
private long calculateFileSizesRecursively(
long calculateFileSizesRecursively(
ContentProviderClient client, Uri uri) throws ResourceException {
final String authority = uri.getAuthority();
final Uri queryUri = buildChildDocumentsUri(authority, getDocumentId(uri));

View File

@@ -29,8 +29,8 @@ import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.util.Log;
import com.android.documentsui.UrisSupplier;
import com.android.documentsui.R;
import com.android.documentsui.UrisSupplier;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
@@ -96,9 +96,35 @@ final class MoveJob extends CopyJob {
return super.setUp();
}
/**
* {@inheritDoc}
*
* Only check space for moves across authorities. For now we don't know if the doc in
* {@link #mSrcs} is in the same root of destination, and if it's optimized move in the same
* root it should succeed regardless of free space, but it's for sure a failure if there is no
* enough free space if docs are moved from another authority.
*/
@Override
public void start() {
super.start();
boolean checkSpace() {
long size = 0;
for (DocumentInfo src : mSrcs) {
if (!src.authority.equals(stack.root.authority)) {
if (src.isDirectory()) {
try {
size += calculateFileSizesRecursively(getClient(src), src.derivedUri);
} catch (RemoteException|ResourceException e) {
Log.w(TAG, "Failed to obtain client for %s" + src.derivedUri + ".", e);
// Failed to calculate size, but move may still succeed.
return true;
}
} else {
size += src.size;
}
}
}
return checkSpace(size);
}
void processDocument(DocumentInfo src, DocumentInfo srcParent, DocumentInfo dest)