Эх сурвалжийг харах

enhance documents storage provider

Signed-off-by: Jens Mueller <tschenser@gmx.de>
Jens Mueller 6 жил өмнө
parent
commit
7f157a9ff1

+ 372 - 178
src/main/java/com/owncloud/android/providers/DocumentsStorageProvider.java

@@ -34,17 +34,18 @@ import android.content.res.AssetFileDescriptor;
 import android.database.Cursor;
 import android.database.Cursor;
 import android.graphics.Point;
 import android.graphics.Point;
 import android.net.Uri;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Looper;
 import android.os.ParcelFileDescriptor;
 import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract;
 import android.provider.DocumentsProvider;
 import android.provider.DocumentsProvider;
 import android.util.Log;
 import android.util.Log;
 import android.widget.Toast;
 import android.widget.Toast;
 
 
-import com.evernote.android.job.JobRequest;
-import com.evernote.android.job.util.Device;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.account.UserAccountManager;
 import com.nextcloud.client.account.UserAccountManagerImpl;
 import com.nextcloud.client.account.UserAccountManagerImpl;
 import com.nextcloud.client.preferences.AppPreferences;
 import com.nextcloud.client.preferences.AppPreferences;
@@ -80,10 +81,20 @@ import org.nextcloud.providers.cursors.RootCursor;
 import java.io.File;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import javax.inject.Inject;
+
+import dagger.android.AndroidInjection;
 
 
 import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
 import static com.owncloud.android.datamodel.OCFile.PATH_SEPARATOR;
 import static com.owncloud.android.datamodel.OCFile.ROOT_PATH;
 import static com.owncloud.android.datamodel.OCFile.ROOT_PATH;
@@ -91,16 +102,24 @@ import static com.owncloud.android.datamodel.OCFile.ROOT_PATH;
 @TargetApi(Build.VERSION_CODES.KITKAT)
 @TargetApi(Build.VERSION_CODES.KITKAT)
 public class DocumentsStorageProvider extends DocumentsProvider {
 public class DocumentsStorageProvider extends DocumentsProvider {
 
 
-    private static final String TAG = "DocumentsStorageProvider";
+    private static final String TAG = DocumentsStorageProvider.class.getSimpleName();
 
 
-    private FileDataStorageManager currentStorageManager;
-    private Map<Long, FileDataStorageManager> rootIdToStorageManager;
-    private OwnCloudClient client;
+    private static final long CACHE_EXPIRATION = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES);
 
 
     UserAccountManager accountManager;
     UserAccountManager accountManager;
 
 
+    private static final String ROOT_SEPARATOR = "/";
+    private final Map<Integer, FileDataStorageManager> rootIdToStorageManager = new HashMap<>();
+
+    private final Executor executor = Executors.newCachedThreadPool();
+
+    private final OnTaskFinishedCallback<Document> loadChildrenCallback =
+        (status, document, exception) -> getContext().getContentResolver().notifyChange(toNotifyUri(document), null, false);
+
     @Override
     @Override
-    public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+    public Cursor queryRoots(String[] projection) {
+        Log.d(TAG, "queryRoots()");
+
         Context context = MainApp.getAppContext();
         Context context = MainApp.getAppContext();
         AppPreferences preferences = AppPreferencesImpl.fromContext(context);
         AppPreferences preferences = AppPreferencesImpl.fromContext(context);
         if (SettingsActivity.LOCK_PASSCODE.equals(preferences.getLockPreference()) ||
         if (SettingsActivity.LOCK_PASSCODE.equals(preferences.getLockPreference()) ||
@@ -108,12 +127,9 @@ public class DocumentsStorageProvider extends DocumentsProvider {
             return new FileCursor();
             return new FileCursor();
         }
         }
 
 
-        initiateStorageMap();
-
         final RootCursor result = new RootCursor(projection);
         final RootCursor result = new RootCursor(projection);
-
-        for (Account account : accountManager.getAccounts()) {
-            result.addRoot(account, getContext());
+        for (Map.Entry<Integer, FileDataStorageManager> entry : rootIdToStorageManager.entrySet()) {
+            result.addRoot(new Document(entry.getValue(), "/"), getContext());
         }
         }
 
 
         return result;
         return result;
@@ -121,28 +137,16 @@ public class DocumentsStorageProvider extends DocumentsProvider {
 
 
     @Override
     @Override
     public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
     public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
-        final long docId = Long.parseLong(documentId);
-        updateCurrentStorageManagerIfNeeded(docId);
+        Log.d(TAG, "queryDocument(), id=" + documentId);
 
 
-        if (currentStorageManager == null) {
+        Document document = toDocument(documentId);
 
 
-            for (Map.Entry<Long, FileDataStorageManager> entry : rootIdToStorageManager.entrySet()) {
-                if (entry.getValue().getFileById(docId) != null) {
-                    currentStorageManager = entry.getValue();
-                    break;
-                }
-            }
-        }
-
-        if (currentStorageManager == null) {
-            throw new FileNotFoundException("File with id " + documentId + " not found");
+        if (document == null) {
+            throw new FileNotFoundException("File " + documentId + " not found!");
         }
         }
 
 
         final FileCursor result = new FileCursor(projection);
         final FileCursor result = new FileCursor(projection);
-        OCFile file = currentStorageManager.getFileById(docId);
-        if (file != null) {
-            result.addFile(file);
-        }
+        result.addFile(document);
 
 
         return result;
         return result;
     }
     }
@@ -151,34 +155,38 @@ public class DocumentsStorageProvider extends DocumentsProvider {
     @Override
     @Override
     public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder)
     public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder)
         throws FileNotFoundException {
         throws FileNotFoundException {
+        Log.d(TAG, "queryChildDocuments(), id=" + parentDocumentId);
 
 
-        final long folderId = Long.parseLong(parentDocumentId);
-        updateCurrentStorageManagerIfNeeded(folderId);
+        Document parentFolder = toDocument(parentDocumentId);
+        if (parentFolder == null) {
+            throw new FileNotFoundException("File " + parentDocumentId + " not found!");
+        }
 
 
         Context context = getContext();
         Context context = getContext();
-
         if (context == null) {
         if (context == null) {
             throw new FileNotFoundException("Context may not be null");
             throw new FileNotFoundException("Context may not be null");
         }
         }
 
 
-        Account account = currentStorageManager.getAccount();
-        final OCFile browsedDir = currentStorageManager.getFileById(folderId);
-        if (Device.getNetworkType(context).equals(JobRequest.NetworkType.UNMETERED)) {
-            RemoteOperationResult result = new RefreshFolderOperation(browsedDir, System.currentTimeMillis(), false,
-                                                                      false, true, currentStorageManager, account,
-                                                                      getContext()).execute(client);
-
-            if (!result.isSuccess()) {
-                throw new FileNotFoundException("Failed to update document " + parentDocumentId);
-            }
-        }
+        FileDataStorageManager storageManager = parentFolder.getStorageManager();
 
 
         final FileCursor resultCursor = new FileCursor(projection);
         final FileCursor resultCursor = new FileCursor(projection);
 
 
-        for (OCFile file : currentStorageManager.getFolderContent(browsedDir, false)) {
-            resultCursor.addFile(file);
+        for (OCFile file : storageManager.getFolderContent(parentFolder.getFile(), false)) {
+            resultCursor.addFile(new Document(storageManager, file));
+        }
+
+        boolean isLoading = false;
+        if (parentFolder.isExpired()) {
+            final LoadChildrenTask task = new LoadChildrenTask(parentFolder, getContext(), loadChildrenCallback);
+            task.executeOnExecutor(executor);
+            resultCursor.setLoadingTask(task);
+            isLoading = true;
         }
         }
 
 
+        final Bundle extra = new Bundle();
+        extra.putBoolean(DocumentsContract.EXTRA_LOADING, isLoading);
+        resultCursor.setExtras(extra);
+        resultCursor.setNotificationUri(getContext().getContentResolver(), toNotifyUri(parentFolder));
         return resultCursor;
         return resultCursor;
     }
     }
 
 
@@ -186,12 +194,10 @@ public class DocumentsStorageProvider extends DocumentsProvider {
     @Override
     @Override
     public ParcelFileDescriptor openDocument(String documentId, String mode, CancellationSignal cancellationSignal)
     public ParcelFileDescriptor openDocument(String documentId, String mode, CancellationSignal cancellationSignal)
             throws FileNotFoundException {
             throws FileNotFoundException {
-        final long docId = Long.parseLong(documentId);
-        updateCurrentStorageManagerIfNeeded(docId);
+        Log.d(TAG, "openDocument(), id=" + documentId);
 
 
-        OCFile ocFile = currentStorageManager.getFileById(docId);
-
-        if (ocFile == null) {
+        Document document = toDocument(documentId);
+        if (document == null) {
             throw new FileNotFoundException("File not found: " + documentId);
             throw new FileNotFoundException("File not found: " + documentId);
         }
         }
 
 
@@ -201,7 +207,9 @@ public class DocumentsStorageProvider extends DocumentsProvider {
             throw new FileNotFoundException("Context may not be null!");
             throw new FileNotFoundException("Context may not be null!");
         }
         }
 
 
-        Account account = currentStorageManager.getAccount();
+        OCFile ocFile = document.getFile();
+        Account account = document.getAccount();
+
         if (!ocFile.isDown()) {
         if (!ocFile.isDown()) {
             Intent i = new Intent(getContext(), FileDownloader.class);
             Intent i = new Intent(getContext(), FileDownloader.class);
             i.putExtra(FileDownloader.EXTRA_ACCOUNT, account);
             i.putExtra(FileDownloader.EXTRA_ACCOUNT, account);
@@ -216,7 +224,7 @@ public class DocumentsStorageProvider extends DocumentsProvider {
                 if (!waitOrGetCancelled(cancellationSignal)) {
                 if (!waitOrGetCancelled(cancellationSignal)) {
                     throw new FileNotFoundException("File with id " + documentId + " not found!");
                     throw new FileNotFoundException("File with id " + documentId + " not found!");
                 }
                 }
-                ocFile = currentStorageManager.getFileById(docId);
+                ocFile = document.getFile();
 
 
                 if (ocFile == null) {
                 if (ocFile == null) {
                     throw new FileNotFoundException("File with id " + documentId + " not found!");
                     throw new FileNotFoundException("File with id " + documentId + " not found!");
@@ -226,11 +234,11 @@ public class DocumentsStorageProvider extends DocumentsProvider {
             OCFile finalFile = ocFile;
             OCFile finalFile = ocFile;
             Thread syncThread = new Thread(() -> {
             Thread syncThread = new Thread(() -> {
                 try {
                 try {
-                    FileDataStorageManager storageManager =
+                    FileDataStorageManager sm =
                             new FileDataStorageManager(account, context.getContentResolver());
                             new FileDataStorageManager(account, context.getContentResolver());
                     SynchronizeFileOperation sfo =
                     SynchronizeFileOperation sfo =
                             new SynchronizeFileOperation(finalFile, null, account, true, context);
                             new SynchronizeFileOperation(finalFile, null, account, true, context);
-                    RemoteOperationResult result = sfo.execute(storageManager, context);
+                    RemoteOperationResult result = sfo.execute(sm, context);
                     if (result.getCode() == RemoteOperationResult.ResultCode.SYNC_CONFLICT) {
                     if (result.getCode() == RemoteOperationResult.ResultCode.SYNC_CONFLICT) {
                         // ISSUE 5: if the user is not running the app (this is a service!),
                         // ISSUE 5: if the user is not running the app (this is a service!),
                         // this can be very intrusive; a notification should be preferred
                         // this can be very intrusive; a notification should be preferred
@@ -271,7 +279,7 @@ public class DocumentsStorageProvider extends DocumentsProvider {
                 return ParcelFileDescriptor.open(file, accessMode, handler, l -> {
                 return ParcelFileDescriptor.open(file, accessMode, handler, l -> {
                     RemoteOperationResult result = new SynchronizeFileOperation(newFile, oldFile, account, true,
                     RemoteOperationResult result = new SynchronizeFileOperation(newFile, oldFile, account, true,
                                                                                 context)
                                                                                 context)
-                        .execute(client, currentStorageManager);
+                        .execute(document.getClient(), document.getStorageManager());
 
 
                     boolean success = result.isSuccess();
                     boolean success = result.isSuccess();
 
 
@@ -297,6 +305,7 @@ public class DocumentsStorageProvider extends DocumentsProvider {
     @Override
     @Override
     public boolean onCreate() {
     public boolean onCreate() {
         accountManager = UserAccountManagerImpl.fromContext(getContext());
         accountManager = UserAccountManagerImpl.fromContext(getContext());
+        initiateStorageMap();
         return true;
         return true;
     }
     }
 
 
@@ -305,13 +314,11 @@ public class DocumentsStorageProvider extends DocumentsProvider {
                                                      Point sizeHint,
                                                      Point sizeHint,
                                                      CancellationSignal signal)
                                                      CancellationSignal signal)
             throws FileNotFoundException {
             throws FileNotFoundException {
-        long docId = Long.parseLong(documentId);
-        updateCurrentStorageManagerIfNeeded(docId);
-
-        OCFile file = currentStorageManager.getFileById(docId);
+        Log.d(TAG, "openDocumentThumbnail(), id=" + documentId);
 
 
-        if (file == null) {
-            throw new FileNotFoundException("File with id " + documentId + " not found!");
+        Document document = toDocument(documentId);
+        if (document == null) {
+            throw new FileNotFoundException("File " + documentId + " not found!");
         }
         }
 
 
         Context context = getContext();
         Context context = getContext();
@@ -321,125 +328,152 @@ public class DocumentsStorageProvider extends DocumentsProvider {
         }
         }
 
 
         boolean exists = ThumbnailsCacheManager.containsBitmap(ThumbnailsCacheManager.PREFIX_THUMBNAIL
         boolean exists = ThumbnailsCacheManager.containsBitmap(ThumbnailsCacheManager.PREFIX_THUMBNAIL
-                                                                   + file.getRemoteId());
+                                                                   + document.getFile().getRemoteId());
 
 
         if (!exists) {
         if (!exists) {
-            ThumbnailsCacheManager.generateThumbnailFromOCFile(file);
+            ThumbnailsCacheManager.generateThumbnailFromOCFile(document.getFile());
         }
         }
 
 
         Uri uri = Uri.parse(UriUtils.URI_CONTENT_SCHEME + context.getResources().getString(
         Uri uri = Uri.parse(UriUtils.URI_CONTENT_SCHEME + context.getResources().getString(
-            R.string.image_cache_provider_authority) + file.getRemotePath());
+            R.string.image_cache_provider_authority) + document.getFile().getRemotePath());
+        Log.d(TAG, "open thumbnail, uri=" + uri);
         return context.getContentResolver().openAssetFileDescriptor(uri, "r");
         return context.getContentResolver().openAssetFileDescriptor(uri, "r");
     }
     }
 
 
     @Override
     @Override
     public String renameDocument(String documentId, String displayName) throws FileNotFoundException {
     public String renameDocument(String documentId, String displayName) throws FileNotFoundException {
-        long docId = Long.parseLong(documentId);
-        updateCurrentStorageManagerIfNeeded(docId);
+        Log.d(TAG, "renameDocument(), id=" + documentId);
 
 
-        OCFile file = currentStorageManager.getFileById(docId);
-
-        if (file == null) {
+        Document document = toDocument(documentId);
+        if (document == null) {
             throw new FileNotFoundException("File " + documentId + " not found!");
             throw new FileNotFoundException("File " + documentId + " not found!");
         }
         }
 
 
-        RemoteOperationResult result = new RenameFileOperation(file.getRemotePath(), displayName)
-            .execute(client, currentStorageManager);
+        Context context = getContext();
+
+        if (context == null) {
+            throw new FileNotFoundException("Context may not be null!");
+        }
+
+        RemoteOperationResult result = new RenameFileOperation(document.getFile().getRemotePath(), displayName)
+            .execute(document.getClient(), document.getStorageManager());
 
 
         if (!result.isSuccess()) {
         if (!result.isSuccess()) {
             throw new FileNotFoundException("Failed to rename document with documentId " + documentId + ": " +
             throw new FileNotFoundException("Failed to rename document with documentId " + documentId + ": " +
                                                 result.getException());
                                                 result.getException());
         }
         }
 
 
+        context.getContentResolver().notifyChange(toNotifyUri(document.getParent()), null, false);
+
         return null;
         return null;
     }
     }
 
 
     @Override
     @Override
     public String copyDocument(String sourceDocumentId, String targetParentDocumentId) throws FileNotFoundException {
     public String copyDocument(String sourceDocumentId, String targetParentDocumentId) throws FileNotFoundException {
-        long sourceId = Long.parseLong(sourceDocumentId);
-
-        updateCurrentStorageManagerIfNeeded(sourceId);
+        Log.d(TAG, "copyDocument(), id=" + sourceDocumentId);
 
 
-        OCFile file = currentStorageManager.getFileById(sourceId);
-        if (file == null) {
+        Document document = toDocument(sourceDocumentId);
+        if (document == null) {
             throw new FileNotFoundException("File " + sourceDocumentId + " not found!");
             throw new FileNotFoundException("File " + sourceDocumentId + " not found!");
         }
         }
 
 
-        long targetId = Long.parseLong(targetParentDocumentId);
-        OCFile targetFolder = currentStorageManager.getFileById(targetId);
+        Document targetFolder = toDocument(targetParentDocumentId);
         if (targetFolder == null) {
         if (targetFolder == null) {
             throw new FileNotFoundException("File " + targetParentDocumentId + " not found!");
             throw new FileNotFoundException("File " + targetParentDocumentId + " not found!");
         }
         }
 
 
-        RemoteOperationResult result = new CopyFileOperation(file.getRemotePath(), targetFolder.getRemotePath())
-            .execute(client, currentStorageManager);
+        Context context = getContext();
+
+        if (context == null) {
+            throw new FileNotFoundException("Context may not be null!");
+        }
+
+        FileDataStorageManager storageManager = document.getStorageManager();
+
+        RemoteOperationResult result = new CopyFileOperation(document.getFile().getRemotePath(), targetFolder.getFile().getRemotePath())
+            .execute(document.getClient(), storageManager);
 
 
         if (!result.isSuccess()) {
         if (!result.isSuccess()) {
             throw new FileNotFoundException("Failed to copy document with documentId " + sourceDocumentId
             throw new FileNotFoundException("Failed to copy document with documentId " + sourceDocumentId
                                                 + " to " + targetParentDocumentId);
                                                 + " to " + targetParentDocumentId);
         }
         }
 
 
-        Account account = currentStorageManager.getAccount();
+        Account account = document.getAccount();
 
 
-        RemoteOperationResult updateParent = new RefreshFolderOperation(targetFolder, System.currentTimeMillis(),
-                                                                        false, false, true, currentStorageManager,
-                                                                        account, getContext()).execute(client);
+        RemoteOperationResult updateParent = new RefreshFolderOperation(targetFolder.getFile(), System.currentTimeMillis(),
+                                                                        false, false, true, storageManager,
+                                                                        account, context).execute(targetFolder.getClient());
 
 
         if (!updateParent.isSuccess()) {
         if (!updateParent.isSuccess()) {
             throw new FileNotFoundException("Failed to copy document with documentId " + sourceDocumentId
             throw new FileNotFoundException("Failed to copy document with documentId " + sourceDocumentId
                                                 + " to " + targetParentDocumentId);
                                                 + " to " + targetParentDocumentId);
         }
         }
 
 
-        String newPath = targetFolder.getRemotePath() + file.getFileName();
+        String newPath = targetFolder.getFile().getRemotePath() + document.getFile().getFileName();
 
 
-        if (file.isFolder()) {
-            newPath = newPath + PATH_SEPARATOR;
+        if (document.getFile().isFolder()) {
+            newPath = newPath + "/";
         }
         }
-        OCFile newFile = currentStorageManager.getFileByPath(newPath);
+        Document newFile = new Document(storageManager, newPath);
+
+        context.getContentResolver().notifyChange(toNotifyUri(newFile.getParent()), null, false);
 
 
-        return String.valueOf(newFile.getFileId());
+        return newFile.getDocumentId();
     }
     }
 
 
     @Override
     @Override
     public String moveDocument(String sourceDocumentId, String sourceParentDocumentId, String targetParentDocumentId)
     public String moveDocument(String sourceDocumentId, String sourceParentDocumentId, String targetParentDocumentId)
         throws FileNotFoundException {
         throws FileNotFoundException {
-        long sourceId = Long.parseLong(sourceDocumentId);
-
-        updateCurrentStorageManagerIfNeeded(sourceId);
+        Log.d(TAG, "moveDocument(), id=" + sourceDocumentId);
 
 
-        OCFile file = currentStorageManager.getFileById(sourceId);
-
-        if (file == null) {
+        Document document = toDocument(sourceDocumentId);
+        if (document == null) {
             throw new FileNotFoundException("File " + sourceDocumentId + " not found!");
             throw new FileNotFoundException("File " + sourceDocumentId + " not found!");
         }
         }
 
 
-        long targetId = Long.parseLong(targetParentDocumentId);
-        OCFile targetFolder = currentStorageManager.getFileById(targetId);
+        Document sourceFolder = toDocument(sourceParentDocumentId);
+        if (sourceFolder == null) {
+            throw new FileNotFoundException("File " + sourceParentDocumentId + " not found!");
+        }
 
 
+        Document targetFolder = toDocument(targetParentDocumentId);
         if (targetFolder == null) {
         if (targetFolder == null) {
             throw new FileNotFoundException("File " + targetParentDocumentId + " not found!");
             throw new FileNotFoundException("File " + targetParentDocumentId + " not found!");
         }
         }
 
 
-        RemoteOperationResult result = new MoveFileOperation(file.getRemotePath(), targetFolder.getRemotePath())
-            .execute(client, currentStorageManager);
+        Context context = getContext();
+
+        if (context == null) {
+            throw new FileNotFoundException("Context may not be null!");
+        }
+
+        RemoteOperationResult result = new MoveFileOperation(document.getFile().getRemotePath(), targetFolder.getFile().getRemotePath())
+            .execute(document.getClient(), document.getStorageManager());
 
 
         if (!result.isSuccess()) {
         if (!result.isSuccess()) {
             throw new FileNotFoundException("Failed to move document with documentId " + sourceDocumentId
             throw new FileNotFoundException("Failed to move document with documentId " + sourceDocumentId
                                                 + " to " + targetParentDocumentId);
                                                 + " to " + targetParentDocumentId);
         }
         }
 
 
-        return String.valueOf(file.getFileId());
+        getContext().getContentResolver().notifyChange(toNotifyUri(sourceFolder), null, false);
+        getContext().getContentResolver().notifyChange(toNotifyUri(targetFolder), null, false);
+
+        return sourceDocumentId;
     }
     }
 
 
     @Override
     @Override
     public Cursor querySearchDocuments(String rootId, String query, String[] projection) {
     public Cursor querySearchDocuments(String rootId, String query, String[] projection) {
-        updateCurrentStorageManagerIfNeeded(rootId);
+        Log.d(TAG, "querySearchDocuments(), rootId=" + rootId);
 
 
-        OCFile root = currentStorageManager.getFileByPath(ROOT_PATH);
         FileCursor result = new FileCursor(projection);
         FileCursor result = new FileCursor(projection);
 
 
-        for (OCFile f : findFiles(root, query)) {
-            result.addFile(f);
+        FileDataStorageManager storageManager = getStorageManager(rootId);
+        if (storageManager == null) {
+            return result;
+        }
+
+        for (Document d : findFiles(new Document(storageManager, "/"), query)) {
+            result.addFile(d);
         }
         }
 
 
         return result;
         return result;
@@ -447,49 +481,66 @@ public class DocumentsStorageProvider extends DocumentsProvider {
 
 
     @Override
     @Override
     public String createDocument(String documentId, String mimeType, String displayName) throws FileNotFoundException {
     public String createDocument(String documentId, String mimeType, String displayName) throws FileNotFoundException {
-        long docId = Long.parseLong(documentId);
-        updateCurrentStorageManagerIfNeeded(docId);
-
-        OCFile parent = currentStorageManager.getFileById(docId);
+        Log.d(TAG, "createDocument(), id=" + documentId);
 
 
-        if (parent == null) {
+        Document folderDocument = toDocument(documentId);
+        if (folderDocument == null) {
             throw new FileNotFoundException("Parent file not found");
             throw new FileNotFoundException("Parent file not found");
         }
         }
 
 
-        if ("vnd.android.document/directory".equalsIgnoreCase(mimeType)) {
-            return createFolder(parent, displayName, documentId);
+        if (DocumentsContract.Document.MIME_TYPE_DIR.equalsIgnoreCase(mimeType)) {
+            return createFolder(folderDocument, displayName);
         } else {
         } else {
-            return createFile(parent, displayName, documentId);
+            return createFile(folderDocument, displayName);
         }
         }
     }
     }
 
 
-    private String createFolder(OCFile parent, String displayName, String documentId) throws FileNotFoundException {
+    private String createFolder(Document targetFolder, String displayName) throws FileNotFoundException {
 
 
-        CreateFolderOperation createFolderOperation = new CreateFolderOperation(parent.getRemotePath() + displayName
-                                                                                    + PATH_SEPARATOR, true);
-        RemoteOperationResult result = createFolderOperation.execute(client, currentStorageManager);
+        Context context = getContext();
+
+        if (context == null) {
+            throw new FileNotFoundException("Context may not be null!");
+        }
+
+        FileDataStorageManager storageManager = targetFolder.getStorageManager();
+
+        CreateFolderOperation createFolderOperation = new CreateFolderOperation(targetFolder.getFile().getRemotePath() + displayName
+                                                                                    + "/", true);
+        RemoteOperationResult result = createFolderOperation.execute(targetFolder.getClient(), storageManager);
 
 
 
 
         if (!result.isSuccess()) {
         if (!result.isSuccess()) {
             throw new FileNotFoundException("Failed to create document with name " +
             throw new FileNotFoundException("Failed to create document with name " +
-                                                displayName + " and documentId " + documentId);
+                                                displayName + " and documentId " + targetFolder.getDocumentId());
         }
         }
 
 
 
 
-        String newDirPath = parent.getRemotePath() + displayName + PATH_SEPARATOR;
-        OCFile newFolder = currentStorageManager.getFileByPath(newDirPath);
+        Account account = targetFolder.getAccount();
+        OwnCloudClient client = targetFolder.getClient();
+        RemoteOperationResult updateParent = new RefreshFolderOperation(targetFolder.getFile(), System.currentTimeMillis(),
+                                                                        false, false, true, storageManager,
+                                                                        account, context).execute(client);
+
+        if (!updateParent.isSuccess()) {
+            throw new FileNotFoundException("Failed to create document with documentId " + targetFolder.getDocumentId());
+        }
 
 
-        return String.valueOf(newFolder.getFileId());
+        String newDirPath = targetFolder.getFile().getRemotePath() + displayName + "/";
+        Document newFolder = new Document(storageManager, newDirPath);
+
+        context.getContentResolver().notifyChange(toNotifyUri(targetFolder), null, false);
+
+        return newFolder.getDocumentId();
     }
     }
 
 
-    private String createFile(OCFile parent, String displayName, String documentId) throws FileNotFoundException {
+    private String createFile(Document targetFolder, String displayName) throws FileNotFoundException {
         Context context = getContext();
         Context context = getContext();
-
         if (context == null) {
         if (context == null) {
             throw new FileNotFoundException("Context may not be null!");
             throw new FileNotFoundException("Context may not be null!");
         }
         }
 
 
-        Account account = currentStorageManager.getAccount();
+        Account account = targetFolder.getAccount();
 
 
         // create dummy file
         // create dummy file
         File tempDir = new File(FileStorageUtils.getTemporalPath(account.name));
         File tempDir = new File(FileStorageUtils.getTemporalPath(account.name));
@@ -503,8 +554,8 @@ public class DocumentsStorageProvider extends DocumentsProvider {
         }
         }
 
 
         FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
         FileUploader.UploadRequester requester = new FileUploader.UploadRequester();
-        requester.uploadNewFile(getContext(), account, new String[]{emptyFile.getAbsolutePath()},
-                                new String[]{parent.getRemotePath() + displayName}, null,
+        requester.uploadNewFile(context, account, new String[]{emptyFile.getAbsolutePath()},
+                                new String[]{targetFolder.getFile().getRemotePath() + displayName}, null,
                                 FileUploader.LOCAL_BEHAVIOUR_MOVE, true, UploadFileOperation.CREATED_BY_USER, false,
                                 FileUploader.LOCAL_BEHAVIOUR_MOVE, true, UploadFileOperation.CREATED_BY_USER, false,
                                 false);
                                 false);
 
 
@@ -514,18 +565,23 @@ public class DocumentsStorageProvider extends DocumentsProvider {
             Log_OC.e(TAG, "Thread interruption error");
             Log_OC.e(TAG, "Thread interruption error");
         }
         }
 
 
-        RemoteOperationResult updateParent = new RefreshFolderOperation(parent, System.currentTimeMillis(),
-                                                                        false, false, true, currentStorageManager,
-                                                                        account, getContext()).execute(client);
+        OwnCloudClient client = targetFolder.getClient();
+        FileDataStorageManager storageManager = targetFolder.getStorageManager();
+
+        RemoteOperationResult updateParent = new RefreshFolderOperation(targetFolder.getFile(), System.currentTimeMillis(),
+                                                                        false, false, true, storageManager,
+                                                                        account, context).execute(client);
 
 
         if (!updateParent.isSuccess()) {
         if (!updateParent.isSuccess()) {
-            throw new FileNotFoundException("Failed to create document with documentId " + documentId);
+            throw new FileNotFoundException("Failed to create document with documentId " + targetFolder.getDocumentId());
         }
         }
 
 
-        String newFilePath = parent.getRemotePath() + displayName;
-        OCFile newFile = currentStorageManager.getFileByPath(newFilePath);
+        String newFilePath = targetFolder.getFile().getRemotePath() + displayName;
+        Document newFile = new Document(storageManager, newFilePath);
+
+        context.getContentResolver().notifyChange(toNotifyUri(targetFolder), null, false);
 
 
-        return String.valueOf(newFile.getFileId());
+        return newFile.getDocumentId();
     }
     }
 
 
     @Override
     @Override
@@ -535,76 +591,86 @@ public class DocumentsStorageProvider extends DocumentsProvider {
 
 
     @Override
     @Override
     public void deleteDocument(String documentId) throws FileNotFoundException {
     public void deleteDocument(String documentId) throws FileNotFoundException {
-        long docId = Long.parseLong(documentId);
-        updateCurrentStorageManagerIfNeeded(docId);
+        Log.d(TAG, "deleteDocument(), id=" + documentId);
 
 
-        OCFile file = currentStorageManager.getFileById(docId);
-
-        if (file == null) {
+        Document document = toDocument(documentId);
+        if (document == null) {
             throw new FileNotFoundException("File " + documentId + " not found!");
             throw new FileNotFoundException("File " + documentId + " not found!");
         }
         }
-        Account account = currentStorageManager.getAccount();
 
 
-        RemoveFileOperation removeFileOperation = new RemoveFileOperation(file.getRemotePath(), false, account, true,
-                                                                          getContext());
+        Context context = getContext();
+        if (context == null) {
+            throw new FileNotFoundException("Context may not be null!");
+        }
+
+        recursiveRevokePermission(document);
+
+        Document parentFolder = document.getParent();
 
 
-        RemoteOperationResult result = removeFileOperation.execute(client, currentStorageManager);
+        RemoveFileOperation removeFileOperation = new RemoveFileOperation(document.getFile().getRemotePath(), false, document.getAccount(), true,
+                                                                          context);
+
+        RemoteOperationResult result = removeFileOperation.execute(document.getClient(), document.getStorageManager());
 
 
         if (!result.isSuccess()) {
         if (!result.isSuccess()) {
             throw new FileNotFoundException("Failed to delete document with documentId " + documentId);
             throw new FileNotFoundException("Failed to delete document with documentId " + documentId);
         }
         }
+
+        context.getContentResolver().notifyChange(toNotifyUri(parentFolder), null, false);
     }
     }
 
 
-    @SuppressLint("LongLogTag")
-    private void updateCurrentStorageManagerIfNeeded(long docId) {
-        if (rootIdToStorageManager == null) {
-            try {
-                queryRoots(FileCursor.DEFAULT_DOCUMENT_PROJECTION);
-            } catch (FileNotFoundException e) {
-                Log.e(TAG, "Failed to query roots");
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    private void recursiveRevokePermission(Document document) {
+        FileDataStorageManager storageManager = document.getStorageManager();
+        OCFile file = document.getFile();
+        if (file.isFolder()) {
+            for (OCFile child : storageManager.getFolderContent(file, false)) {
+                recursiveRevokePermission(new Document(storageManager, child));
             }
             }
         }
         }
 
 
-        if (currentStorageManager == null ||
-            rootIdToStorageManager.containsKey(docId) && currentStorageManager != rootIdToStorageManager.get(docId)) {
-            currentStorageManager = rootIdToStorageManager.get(docId);
-        }
+        revokeDocumentPermission(document.getDocumentId());
+    }
 
 
-        try {
-            Account account = currentStorageManager.getAccount();
-            OwnCloudAccount ocAccount = new OwnCloudAccount(account, MainApp.getAppContext());
-            client = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, getContext());
-        } catch (OperationCanceledException | IOException | AuthenticatorException |
-            com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException e) {
-            Log_OC.e(TAG, "Failed to set client", e);
+    @Override
+    public void removeDocument(String documentId, String parentDocumentId) throws FileNotFoundException {
+        Log.d(TAG, "removeDocument(), id=" + documentId);
+        deleteDocument(documentId);
+    }
+
+    @Override
+    public boolean isChildDocument(String parentDocumentId, String documentId) {
+        Log.d(TAG, "isChildDocument(), parent=" + parentDocumentId + ", id=" + documentId);
+        Document document = toDocument(documentId);
+        Document folderDocument = toDocument(parentDocumentId);
+
+        if (null == document || null == folderDocument) {
+            return false;
         }
         }
+
+        return document.getFile().getParentId() == folderDocument.getFile().getFileId();
     }
     }
 
 
-    private void updateCurrentStorageManagerIfNeeded(String rootId) {
+    private FileDataStorageManager getStorageManager(String rootId) {
         for (FileDataStorageManager data : rootIdToStorageManager.values()) {
         for (FileDataStorageManager data : rootIdToStorageManager.values()) {
             if (data.getAccount().name.equals(rootId)) {
             if (data.getAccount().name.equals(rootId)) {
-                currentStorageManager = data;
+                return data;
             }
             }
         }
         }
+
+        return null;
     }
     }
 
 
     @SuppressLint("UseSparseArrays")
     @SuppressLint("UseSparseArrays")
-    private void initiateStorageMap() throws FileNotFoundException {
+    private void initiateStorageMap() {
 
 
-        Context context = getContext();
+        rootIdToStorageManager.clear();
 
 
-        final Account[] allAccounts = accountManager.getAccounts();
-        rootIdToStorageManager = new HashMap<>(allAccounts.length);
-
-        if (context == null) {
-            throw new FileNotFoundException("Context may not be null!");
-        }
+        ContentResolver contentResolver = getContext().getContentResolver();
 
 
-        final ContentResolver contentResolver = context.getContentResolver();
-        for (Account account : allAccounts) {
+        for (Account account : accountManager.getAccounts()) {
             final FileDataStorageManager storageManager = new FileDataStorageManager(account, contentResolver);
             final FileDataStorageManager storageManager = new FileDataStorageManager(account, contentResolver);
-            final OCFile rootDir = storageManager.getFileByPath(ROOT_PATH);
-            rootIdToStorageManager.put(rootDir.getFileId(), storageManager);
+            rootIdToStorageManager.put(account.hashCode(), storageManager);
         }
         }
     }
     }
 
 
@@ -618,15 +684,143 @@ public class DocumentsStorageProvider extends DocumentsProvider {
         return !(cancellationSignal != null && cancellationSignal.isCanceled());
         return !(cancellationSignal != null && cancellationSignal.isCanceled());
     }
     }
 
 
-    List<OCFile> findFiles(OCFile root, String query) {
-        List<OCFile> result = new ArrayList<>();
-        for (OCFile f : currentStorageManager.getFolderContent(root, false)) {
+    private List<Document> findFiles(Document root, String query) {
+        FileDataStorageManager storageManager = root.getStorageManager();
+        List<Document> result = new ArrayList<>();
+        for (OCFile f : storageManager.getFolderContent(root.getFile(), false)) {
             if (f.isFolder()) {
             if (f.isFolder()) {
-                result.addAll(findFiles(f, query));
+                result.addAll(findFiles(new Document(storageManager, f), query));
             } else if (f.getFileName().contains(query)) {
             } else if (f.getFileName().contains(query)) {
-                result.add(f);
+                result.add(new Document(storageManager, f));
             }
             }
         }
         }
         return result;
         return result;
     }
     }
+
+    private Uri toNotifyUri(Document document) {
+        return DocumentsContract.buildDocumentUri(
+            getContext().getString(R.string.document_provider_authority),
+            document.getDocumentId());
+    }
+
+    private Document toDocument(String documentId) {
+        String[] separated = documentId.split(ROOT_SEPARATOR);
+        if (separated.length != 2) {
+            return null;
+        }
+
+        FileDataStorageManager storageManager = rootIdToStorageManager.get(Integer.parseInt(separated[0]));
+        if (storageManager == null) {
+            return null;
+        }
+
+        return new Document(storageManager, Long.parseLong(separated[1]));
+    }
+
+    public interface OnTaskFinishedCallback<T> {
+
+        int SUCCEEDED = 0;
+        int FAILED = 1;
+        int CANCELLED = 2;
+
+        @IntDef({ SUCCEEDED, FAILED, CANCELLED })
+        @Retention(RetentionPolicy.SOURCE)
+        @interface Status {}
+
+        void onTaskFinished(@Status int status, @Nullable T item, @Nullable Exception exception);
+    }
+
+    static class LoadChildrenTask extends AsyncTask<Void, Void, RemoteOperationResult> {
+
+        private final Document document;
+        private final Context context;
+        private final OnTaskFinishedCallback<Document> callback;
+
+        LoadChildrenTask(Document document, Context context, OnTaskFinishedCallback<Document> callback) {
+            this.document = document;
+            this.context = context;
+            this.callback = callback;
+        }
+
+        @Override
+        public final RemoteOperationResult doInBackground(Void... params) {
+            Log.d(TAG, "run RefreshDocumentTask(), id=" + document.getDocumentId());
+            return new RefreshFolderOperation(document.getFile(), System.currentTimeMillis(), false,
+                                              false, true, document.getStorageManager(), document.getAccount(),
+                                              context).execute(document.getClient());
+        }
+
+        @Override
+        public final void onPostExecute(RemoteOperationResult result) {
+            if (callback != null) {
+                if (result.isSuccess()) {
+                    callback.onTaskFinished(OnTaskFinishedCallback.SUCCEEDED, document, null);
+                } else if (result.isCancelled()) {
+                    callback.onTaskFinished(OnTaskFinishedCallback.CANCELLED, document, null);
+                } else {
+                    callback.onTaskFinished(OnTaskFinishedCallback.FAILED, document, result.getException());
+                }
+            }
+        }
+    }
+
+    public class Document {
+        private final FileDataStorageManager storageManager;
+        private final Long fileId;
+
+        Document(FileDataStorageManager storageManager, Long fileId) {
+            this.storageManager = storageManager;
+            this.fileId = fileId;
+        }
+
+        Document(FileDataStorageManager storageManager, OCFile file) {
+            this.storageManager = storageManager;
+            this.fileId = file.getFileId();
+        }
+
+        Document(FileDataStorageManager storageManager, String filePath) {
+            this.storageManager = storageManager;
+            this.fileId = storageManager.getFileByPath(filePath).getFileId();
+        }
+
+        public String getDocumentId() {
+            for (Map.Entry<Integer, FileDataStorageManager> entry : rootIdToStorageManager.entrySet()) {
+                if (Objects.equals(storageManager, entry.getValue())) {
+                    return entry.getKey() + ROOT_SEPARATOR + fileId;
+                }
+            }
+            return null;
+        }
+
+        FileDataStorageManager getStorageManager() {
+            return storageManager;
+        }
+
+        public Account getAccount() {
+            return getStorageManager().getAccount();
+        }
+
+        public OCFile getFile() {
+            return getStorageManager().getFileById(fileId);
+        }
+
+        OwnCloudClient getClient() {
+            try {
+                OwnCloudAccount ocAccount = new OwnCloudAccount(getAccount(), MainApp.getAppContext());
+                return OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, getContext());
+            } catch (OperationCanceledException | IOException | AuthenticatorException |
+                com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException e) {
+                Log_OC.e(TAG, "Failed to set client", e);
+            }
+            return null;
+        }
+
+        boolean isExpired() {
+            return getFile().getLastSyncDateForData() + CACHE_EXPIRATION < System.currentTimeMillis();
+        }
+
+        Document getParent() {
+            return new Document(getStorageManager(), getFile().getParentId());
+        }
+    }
 }
 }

+ 33 - 3
src/main/java/org/nextcloud/providers/cursors/FileCursor.java

@@ -22,10 +22,13 @@ package org.nextcloud.providers.cursors;
 
 
 import android.annotation.TargetApi;
 import android.annotation.TargetApi;
 import android.database.MatrixCursor;
 import android.database.MatrixCursor;
+import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Build;
+import android.os.Bundle;
 import android.provider.DocumentsContract.Document;
 import android.provider.DocumentsContract.Document;
 
 
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.providers.DocumentsStorageProvider;
 import com.owncloud.android.utils.MimeTypeUtil;
 import com.owncloud.android.utils.MimeTypeUtil;
 
 
 @TargetApi(Build.VERSION_CODES.KITKAT)
 @TargetApi(Build.VERSION_CODES.KITKAT)
@@ -37,15 +40,42 @@ public class FileCursor extends MatrixCursor {
             Document.COLUMN_FLAGS, Document.COLUMN_LAST_MODIFIED
             Document.COLUMN_FLAGS, Document.COLUMN_LAST_MODIFIED
     };
     };
 
 
+    private Bundle extra;
+    private AsyncTask<?, ?, ?> loadingTask;
+
     public FileCursor(String... projection) {
     public FileCursor(String... projection) {
         super(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
         super(projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION);
     }
     }
 
 
-    public void addFile(OCFile file) {
-        if (file == null) {
+    public void setLoadingTask(AsyncTask<?, ?, ?> task) {
+        this.loadingTask = task;
+    }
+
+    @Override
+    public void setExtras(Bundle extras) {
+        this.extra = extras;
+    }
+
+    @Override
+    public Bundle getExtras() {
+        return extra;
+    }
+
+    @Override
+    public void close() {
+        super.close();
+        if (loadingTask != null && loadingTask.getStatus() != AsyncTask.Status.FINISHED) {
+            loadingTask.cancel(false);
+        }
+    }
+
+    public void addFile(DocumentsStorageProvider.Document document) {
+        if (document == null) {
             return;
             return;
         }
         }
 
 
+        OCFile file = document.getFile();
+
         final int iconRes = MimeTypeUtil.getFileTypeIconId(file.getMimeType(), file.getFileName());
         final int iconRes = MimeTypeUtil.getFileTypeIconId(file.getMimeType(), file.getFileName());
         final String mimeType = file.isFolder() ? Document.MIME_TYPE_DIR : file.getMimeType();
         final String mimeType = file.isFolder() ? Document.MIME_TYPE_DIR : file.getMimeType();
         int flags = Document.FLAG_SUPPORTS_DELETE |
         int flags = Document.FLAG_SUPPORTS_DELETE |
@@ -64,7 +94,7 @@ public class FileCursor extends MatrixCursor {
             flags = Document.FLAG_SUPPORTS_RENAME | flags;
             flags = Document.FLAG_SUPPORTS_RENAME | flags;
         }
         }
 
 
-        newRow().add(Document.COLUMN_DOCUMENT_ID, Long.toString(file.getFileId()))
+        newRow().add(Document.COLUMN_DOCUMENT_ID, document.getDocumentId())
                 .add(Document.COLUMN_DISPLAY_NAME, file.getFileName())
                 .add(Document.COLUMN_DISPLAY_NAME, file.getFileName())
                 .add(Document.COLUMN_LAST_MODIFIED, file.getModificationTimestamp())
                 .add(Document.COLUMN_LAST_MODIFIED, file.getModificationTimestamp())
                 .add(Document.COLUMN_SIZE, file.getFileLength())
                 .add(Document.COLUMN_SIZE, file.getFileLength())

+ 6 - 7
src/main/java/org/nextcloud/providers/cursors/RootCursor.java

@@ -30,6 +30,7 @@ import android.provider.DocumentsContract.Root;
 import com.owncloud.android.R;
 import com.owncloud.android.R;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.providers.DocumentsStorageProvider;
 
 
 import static com.owncloud.android.datamodel.OCFile.ROOT_PATH;
 import static com.owncloud.android.datamodel.OCFile.ROOT_PATH;
 
 
@@ -47,16 +48,14 @@ public class RootCursor extends MatrixCursor {
         super(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
         super(projection != null ? projection : DEFAULT_ROOT_PROJECTION);
     }
     }
 
 
-    public void addRoot(Account account, Context context) {
-        final FileDataStorageManager manager = new FileDataStorageManager(account, context.getContentResolver());
-        final OCFile mainDir = manager.getFileByPath(ROOT_PATH);
-
+    public void addRoot(DocumentsStorageProvider.Document document, Context context) {
+        Account account = document.getAccount();
         newRow().add(Root.COLUMN_ROOT_ID, account.name)
         newRow().add(Root.COLUMN_ROOT_ID, account.name)
-            .add(Root.COLUMN_DOCUMENT_ID, mainDir.getFileId())
+            .add(Root.COLUMN_DOCUMENT_ID, document.getDocumentId())
             .add(Root.COLUMN_SUMMARY, account.name)
             .add(Root.COLUMN_SUMMARY, account.name)
             .add(Root.COLUMN_TITLE, context.getString(R.string.app_name))
             .add(Root.COLUMN_TITLE, context.getString(R.string.app_name))
             .add(Root.COLUMN_ICON, R.mipmap.ic_launcher)
             .add(Root.COLUMN_ICON, R.mipmap.ic_launcher)
-            .add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_RECENTS |
-                Root.FLAG_SUPPORTS_SEARCH);
+            .add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_IS_CHILD |
+                Root.FLAG_SUPPORTS_RECENTS | Root.FLAG_SUPPORTS_SEARCH);
     }
     }
 }
 }