Browse Source

Refactoring FileContentProvider and FileDataStorageManager: deletion of subfolders in provider & transactions on database connection

David A. Velasco 11 năm trước cách đây
mục cha
commit
af62f9857d

+ 174 - 88
src/com/owncloud/android/datamodel/FileDataStorageManager.java

@@ -20,9 +20,9 @@ package com.owncloud.android.datamodel;
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Vector;
 
 import com.owncloud.android.Log_OC;
@@ -35,6 +35,7 @@ import android.content.ContentProviderClient;
 import android.content.ContentProviderOperation;
 import android.content.ContentProviderResult;
 import android.content.ContentResolver;
+import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.OperationApplicationException;
 import android.database.Cursor;
@@ -249,15 +250,21 @@ public class FileDataStorageManager {
     }
 
 
-    public void saveFiles(List<OCFile> files) {
+    /**
+     * Inserts or updates the list of files contained in a given folder.
+     * 
+     * @param folder
+     * @param files
+     * @param removeNotUpdated
+     */
+    public void saveFolder(OCFile folder, Collection<OCFile> updatedFiles, Collection<OCFile> filesToRemove) {
+        
+        Log_OC.d(TAG,  "Saving folder " + folder.getRemotePath() + " with " + updatedFiles.size() + " children and " + filesToRemove.size() + " files to remove");
 
-        Iterator<OCFile> filesIt = files.iterator();
-        ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(files.size());
-        OCFile file = null;
+        ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(updatedFiles.size());
 
-        // prepare operations to perform
-        while (filesIt.hasNext()) {
-            file = filesIt.next();
+        // prepare operations to insert or update files to save in the given folder
+        for (OCFile file : updatedFiles) {
             ContentValues cv = new ContentValues();
             cv.put(ProviderTableMeta.FILE_MODIFIED, file.getModificationTimestamp());
             cv.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, file.getModificationTimestampAtLastSyncForData());
@@ -265,43 +272,47 @@ public class FileDataStorageManager {
             cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, file.getFileLength());
             cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimetype());
             cv.put(ProviderTableMeta.FILE_NAME, file.getFileName());
-            if (file.getParentId() != FileDataStorageManager.ROOT_PARENT_ID)
-                cv.put(ProviderTableMeta.FILE_PARENT, file.getParentId());
+            //cv.put(ProviderTableMeta.FILE_PARENT, file.getParentId());
+            cv.put(ProviderTableMeta.FILE_PARENT, folder.getFileId());
             cv.put(ProviderTableMeta.FILE_PATH, file.getRemotePath());
-            if (!file.isFolder())
+            if (!file.isFolder()) {
                 cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
+            }
             cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name);
             cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, file.getLastSyncDateForProperties());
             cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, file.getLastSyncDateForData());
             cv.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, file.keepInSync() ? 1 : 0);
             cv.put(ProviderTableMeta.FILE_ETAG, file.getEtag());
 
-            if (fileExists(file.getRemotePath())) {
-                OCFile oldFile = getFileByPath(file.getRemotePath());
-                file.setFileId(oldFile.getFileId());
-               
-                if (file.isFolder()) {
-                    cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, oldFile.getFileLength());
-                    file.setFileLength(oldFile.getFileLength());
-                }
+            boolean existsByPath = fileExists(file.getRemotePath());
+            if (existsByPath || fileExists(file.getFileId())) {
+                // updating an existing file
                 
-                operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI).
-                        withValues(cv).
-                        withSelection(  ProviderTableMeta._ID + "=?", 
-                                new String[] { String.valueOf(file.getFileId()) })
-                                .build());
-
-            } else if (fileExists(file.getFileId())) {
-                OCFile oldFile = getFileById(file.getFileId());
-                if (file.getStoragePath() == null && oldFile.getStoragePath() != null)
-                    file.setStoragePath(oldFile.getStoragePath());
+                /* CALLER IS THE RESPONSIBLE FOR GRANTING RIGHT UPDATE OF INFORMATION, NOT THIS METHOD.
+                 * 
+                 * HERE ONLY DATA CONSISTENCY SHOULD BE GRANTED 
+                 */
+                /*
+                OCFile oldFile = null;
+                if (existsByPath) {
+                    // grant same id
+                    oldFile = getFileByPath(file.getRemotePath());
+                    file.setFileId(oldFile.getFileId());
+                } else {
+                    oldFile = getFileById(file.getFileId());
+                }
                 
-                if (!file.isFolder())
-                    cv.put(ProviderTableMeta.FILE_STORAGE_PATH, file.getStoragePath());
-                else {
-                    cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, oldFile.getFileLength());
+                if (file.isFolder()) {
+                    // folders keep size information, since it's calculated
                     file.setFileLength(oldFile.getFileLength());
+                    cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, oldFile.getFileLength());
+                    
+                } else if (file.getStoragePath() == null && oldFile.getStoragePath() != null) {
+                    // regular files keep access to local contents, although it's lost in the new OCFile
+                    file.setStoragePath(oldFile.getStoragePath());
+                    cv.put(ProviderTableMeta.FILE_STORAGE_PATH, oldFile.getStoragePath());
                 }
+                */
                 
                 operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI).
                         withValues(cv).
@@ -310,12 +321,62 @@ public class FileDataStorageManager {
                                 .build());
 
             } else {
+                // adding a new file
                 operations.add(ContentProviderOperation.newInsert(ProviderTableMeta.CONTENT_URI).withValues(cv).build());
             }
         }
+        
+        // prepare operations to remove files in the given folder
+        String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?" + " AND " + ProviderTableMeta.FILE_PATH + "=?";
+        String [] whereArgs = null;
+        for (OCFile file : filesToRemove) {
+            if (file.getParentId() == folder.getFileId()) {
+                whereArgs = new String[]{mAccount.name, file.getRemotePath()};
+                //Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, "" + file.getFileId());
+                if (file.isFolder()) {
+                    operations.add(ContentProviderOperation
+                                    .newDelete(ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_DIR, file.getFileId())).withSelection(where, whereArgs)
+                                        .build());
+                    // TODO remove local folder
+                } else {
+                    operations.add(ContentProviderOperation
+                                    .newDelete(ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, file.getFileId())).withSelection(where, whereArgs)
+                                        .build());
+                    if (file.isDown()) {
+                        new File(file.getStoragePath()).delete();
+                        // TODO move the deletion of local contents after success of deletions
+                    }
+                }
+            }
+        }
+        
+        // update metadata of folder --> TODO  MOVE UPDATE OF SIZE TO CONTENTPROVIDER
+        ContentValues cv = new ContentValues();
+        cv.put(ProviderTableMeta.FILE_MODIFIED, folder.getModificationTimestamp());
+        cv.put(ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA, folder.getModificationTimestampAtLastSyncForData());
+        cv.put(ProviderTableMeta.FILE_CREATION, folder.getCreationTimestamp());
+        cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, folder.getFileLength());
+        cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, folder.getMimetype());
+        cv.put(ProviderTableMeta.FILE_NAME, folder.getFileName());
+        cv.put(ProviderTableMeta.FILE_PARENT, folder.getParentId());
+        cv.put(ProviderTableMeta.FILE_PATH, folder.getRemotePath());
+        if (!folder.isFolder()) {
+            cv.put(ProviderTableMeta.FILE_STORAGE_PATH, folder.getStoragePath());
+        }
+        cv.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, mAccount.name);
+        cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE, folder.getLastSyncDateForProperties());
+        cv.put(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA, folder.getLastSyncDateForData());
+        cv.put(ProviderTableMeta.FILE_KEEP_IN_SYNC, folder.keepInSync() ? 1 : 0);
+        cv.put(ProviderTableMeta.FILE_ETAG, folder.getEtag());
+        operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI).
+                withValues(cv).
+                withSelection(  ProviderTableMeta._ID + "=?", 
+                        new String[] { String.valueOf(folder.getFileId()) })
+                        .build());
 
         // apply operations in batch
         ContentProviderResult[] results = null;
+        Log_OC.d(TAG, "Sending " + operations.size() + " operations to FileContentProvider");
         try {
             if (getContentResolver() != null) {
                 results = getContentResolver().applyBatch(ProviderMeta.AUTHORITY_FILES, operations);
@@ -325,29 +386,33 @@ public class FileDataStorageManager {
             }
 
         } catch (OperationApplicationException e) {
-            Log_OC.e(TAG, "Fail to update/insert list of files to database " + e.getMessage());
+            Log_OC.e(TAG, "Exception in batch of operations " + e.getMessage());
 
         } catch (RemoteException e) {
-            Log_OC.e(TAG, "Fail to update/insert list of files to database " + e.getMessage());
+            Log_OC.e(TAG, "Exception in batch of operations  " + e.getMessage());
         }
 
         // update new id in file objects for insertions
         if (results != null) {
             long newId;
+            Iterator<OCFile> filesIt = updatedFiles.iterator();
+            OCFile file = null;
             for (int i=0; i<results.length; i++) {
+                if (filesIt.hasNext()) {
+                    file = filesIt.next();
+                } else {
+                    file = null;
+                }
                 if (results[i].uri != null) {
                     newId = Long.parseLong(results[i].uri.getPathSegments().get(1));
-                    files.get(i).setFileId(newId);
-                    //Log_OC.v(TAG, "Found and added id in insertion for " + files.get(i).getRemotePath());
+                    //updatedFiles.get(i).setFileId(newId);
+                    if (file != null) {
+                        file.setFileId(newId);
+                    }
                 }
             }
         }
-
-        for (OCFile aFile : files) {
-            if (aFile.isFolder() && aFile.needsUpdatingWhileSaving())
-                saveFiles(getFolderContent(aFile));
-        }
-
+        
     }
 
 
@@ -369,68 +434,89 @@ public class FileDataStorageManager {
     }
     
     
-    public void removeFile(OCFile file, boolean removeLocalCopy) {
-        Uri file_uri = Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, ""+file.getFileId());
+    public void removeFile(OCFile file, boolean removeDBData, boolean removeLocalCopy) {
+        if (file != null) {
+            if (file.isFolder()) {
+                removeFolder(file, removeDBData, removeLocalCopy);
+                
+            } else {
+                if (removeDBData) {
+                    //Uri file_uri = Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, ""+file.getFileId());
+                    Uri file_uri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, file.getFileId());
+                    String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?" + " AND " + ProviderTableMeta.FILE_PATH + "=?";
+                    String [] whereArgs = new String[]{mAccount.name, file.getRemotePath()};
+                    if (getContentProviderClient() != null) {
+                        try {
+                            getContentProviderClient().delete(file_uri, where, whereArgs);
+                        } catch (RemoteException e) {
+                            e.printStackTrace();
+                        }
+                    } else {
+                        getContentResolver().delete(file_uri, where, whereArgs);
+                    }
+                    if (file.getFileLength() > 0) {
+                        updateSizesToTheRoot(file.getParentId());   // TODO move to content provider
+                    }
+                }
+                if (removeLocalCopy && file.isDown()) {
+                    boolean success = new File(file.getStoragePath()).delete();
+                    if (!removeDBData && success) {
+                        // maybe unnecessary, but should be checked TODO remove if unnecessary
+                        file.setStoragePath(null);
+                        saveFile(file);
+                    }
+                }
+            }
+        }
+    }
+    
+
+    public void removeFolder(OCFile folder, boolean removeDBData, boolean removeLocalContent) {
+        if (folder != null && folder.isFolder()) {
+            if (removeDBData &&  folder.getFileId() != -1) {
+                removeFolderInDb(folder);
+            }
+            if (removeLocalContent) {
+                File localFolder = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, folder));
+                removeLocalFolder(localFolder);
+            }
+        }
+    }
+
+    private void removeFolderInDb(OCFile folder) {
+        Uri folder_uri = Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_DIR, ""+ folder.getFileId());   // URI for recursive deletion
+        String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?" + " AND " + ProviderTableMeta.FILE_PATH + "=?";
+        String [] whereArgs = new String[]{mAccount.name, folder.getRemotePath()};
         if (getContentProviderClient() != null) {
             try {
-                getContentProviderClient().delete(file_uri,
-                        ProviderTableMeta.FILE_ACCOUNT_OWNER+"=?",
-                        new String[]{mAccount.name});
+                getContentProviderClient().delete(folder_uri, where, whereArgs);
             } catch (RemoteException e) {
                 e.printStackTrace();
             }
         } else {
-            getContentResolver().delete(file_uri,
-                    ProviderTableMeta.FILE_ACCOUNT_OWNER+"=?",
-                    new String[]{mAccount.name});
-        }
-        if (file.isDown() && removeLocalCopy) {
-            new File(file.getStoragePath()).delete();
-        }
-        if (file.isFolder() && removeLocalCopy) {
-            File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file));
-            if (f.exists() && f.isDirectory() && (f.list() == null || f.list().length == 0)) {
-                f.delete();
-            }
+            getContentResolver().delete(folder_uri, where, whereArgs); 
         }
-        
-        if (file.getFileLength() > 0) {
-            updateSizesToTheRoot(file.getParentId());
+        if (folder.getFileLength() > 0) {
+            updateSizesToTheRoot(folder.getParentId()); // TODO move to FileContentProvider
         }
     }
 
-    public void removeFolder(OCFile folder, boolean removeDBData, boolean removeLocalContent) {
-        // TODO consider possible failures
-        if (folder != null && folder.isFolder() && folder.getFileId() != -1) {
-            Vector<OCFile> children = getFolderContent(folder);
-            if (children.size() > 0) {
-                OCFile child = null;
-                for (int i=0; i<children.size(); i++) {
-                    child = children.get(i);
-                    if (child.isFolder()) {
-                        removeFolder(child, removeDBData, removeLocalContent);
+    private void removeLocalFolder(File folder) {
+        if (folder.exists()) {
+            File[] files = folder.listFiles();
+            if (files != null) {
+                for (File file : files) {
+                    if (file.isDirectory()) {
+                        removeLocalFolder(file);
                     } else {
-                        if (removeDBData) {
-                            removeFile(child, removeLocalContent);
-                        } else if (removeLocalContent) {
-                            if (child.isDown()) {
-                                new File(child.getStoragePath()).delete();
-                            }
-                        }
+                        file.delete();
                     }
                 }
             }
-            if (removeDBData) {
-                removeFile(folder, true);
-            }
-            
-            if (folder.getFileLength() > 0) {
-                updateSizesToTheRoot(folder.getParentId());
-            }
+            folder.delete();
         }
     }
 
-
     /**
      * Updates database for a folder that was moved to a different location.
      * 

+ 1 - 5
src/com/owncloud/android/operations/RemoveFileOperation.java

@@ -81,11 +81,7 @@ public class RemoveFileOperation extends RemoteOperation {
             delete = new DeleteMethod(client.getBaseUri() + WebdavUtils.encodePath(mFileToRemove.getRemotePath()));
             int status = client.executeMethod(delete, REMOVE_READ_TIMEOUT, REMOVE_CONNECTION_TIMEOUT);
             if (delete.succeeded() || status == HttpStatus.SC_NOT_FOUND) {
-                if (mFileToRemove.isFolder()) {
-                    mDataStorageManager.removeFolder(mFileToRemove, true, mDeleteLocalCopy);
-                } else {
-                    mDataStorageManager.removeFile(mFileToRemove, mDeleteLocalCopy);
-                }
+                mDataStorageManager.removeFile(mFileToRemove, true, mDeleteLocalCopy);
             }
             delete.getResponseBodyAsString();   // exhaust the response, although not interesting
             result = new RemoteOperationResult((delete.succeeded() || status == HttpStatus.SC_NOT_FOUND), status, delete.getResponseHeaders());

+ 19 - 32
src/com/owncloud/android/operations/SynchronizeFolderOperation.java

@@ -251,12 +251,21 @@ public class SynchronizeFolderOperation extends RemoteOperation {
                 mStorageManager.saveFile(remoteFolder);
             }
             
+            Log_OC.d(TAG, "Remote folder " + mLocalFolder.getRemotePath() + " didn't change");
             mChildren = mStorageManager.getFolderContent(mLocalFolder);
             
         } else {
-            // read info of folder contents
+            Log_OC.d(TAG, "Remote folder " + mLocalFolder.getRemotePath() + " changed - starting update of local data ");
+            
             List<OCFile> updatedFiles = new Vector<OCFile>(dataInServer.getResponses().length - 1);
             List<SynchronizeFileOperation> filesToSyncContents = new Vector<SynchronizeFileOperation>();
+
+            // get current data about local contents of the folder to synchronize
+            List<OCFile> localFiles = mStorageManager.getFolderContent(mLocalFolder);
+            Map<String, OCFile> localFilesMap = new HashMap<String, OCFile>(localFiles.size());
+            for (OCFile file : localFiles) {
+                localFilesMap.put(file.getRemotePath(), file);
+            }
             
             // loop to update every child
             OCFile remoteFile = null, localFile = null;
@@ -267,12 +276,14 @@ public class SynchronizeFolderOperation extends RemoteOperation {
                 remoteFile.setParentId(mLocalFolder.getFileId());
 
                 /// retrieve local data for the read file 
-                localFile = mStorageManager.getFileByPath(remoteFile.getRemotePath());
+                //localFile = mStorageManager.getFileByPath(remoteFile.getRemotePath());
+                localFile = localFilesMap.remove(remoteFile.getRemotePath());
                 
                 /// add to the remoteFile (the new one) data about LOCAL STATE (not existing in the server side)
                 remoteFile.setLastSyncDateForProperties(mCurrentSyncTime);
                 if (localFile != null) {
-                    // properties of local state are kept unmodified 
+                    // some properties of local state are kept unmodified
+                    remoteFile.setFileId(localFile.getFileId());
                     remoteFile.setKeepInSync(localFile.keepInSync());
                     remoteFile.setLastSyncDateForData(localFile.getLastSyncDateForData());
                     remoteFile.setModificationTimestampAtLastSyncForData(localFile.getModificationTimestampAtLastSyncForData());
@@ -282,7 +293,7 @@ public class SynchronizeFolderOperation extends RemoteOperation {
                     remoteFile.setEtag(""); // remote eTag will not be updated unless contents are synchronized (Synchronize[File|Folder]Operation with remoteFile as parameter)
                 }
 
-                /// check and fix, if need, local storage path
+                /// check and fix, if needed, local storage path
                 checkAndFixForeignStoragePath(remoteFile);      // fixing old policy - now local files must be copied into the ownCloud local folder 
                 searchForLocalFileInDefaultPath(remoteFile);    // legacy   
 
@@ -303,16 +314,17 @@ public class SynchronizeFolderOperation extends RemoteOperation {
             }
 
             // save updated contents in local database; all at once, trying to get a best performance in database update (not a big deal, indeed)
-            mStorageManager.saveFiles(updatedFiles);
+            mStorageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values());
 
             // request for the synchronization of file contents AFTER saving current remote properties
             startContentSynchronizations(filesToSyncContents, client);
 
             // removal of obsolete files
-            removeObsoleteFiles();
+            //removeObsoleteFiles();
            
             // must be done AFTER saving all the children information, so that eTag is not updated in the database in case of unexpected exceptions
-            mStorageManager.saveFile(remoteFolder);
+            //mStorageManager.saveFile(remoteFolder);
+            mChildren = updatedFiles;
             
         }
         
@@ -320,31 +332,6 @@ public class SynchronizeFolderOperation extends RemoteOperation {
         
     }
 
-
-    /**
-     *  Removes obsolete children in the folder after saving all the new data.
-     */
-    private void removeObsoleteFiles() {
-        mChildren = mStorageManager.getFolderContent(mLocalFolder);
-        OCFile file;
-        for (int i=0; i < mChildren.size(); ) {
-            file = mChildren.get(i);
-            if (file.getLastSyncDateForProperties() != mCurrentSyncTime) {
-                if (file.isFolder()) {
-                    Log_OC.d(TAG, "removing folder: " + file);
-                    mStorageManager.removeFolder(file, true, true);
-                } else {
-                    Log_OC.d(TAG, "removing file: " + file);
-                    mStorageManager.removeFile(file, true);
-                }
-                mChildren.remove(i);
-            } else {
-                i++;
-            }
-        }
-    }
-
-
     /**
      * Performs a list of synchronization operations, determining if a download or upload is needed or
      * if exists conflict due to changes both in local and remote contents of the each file.

+ 161 - 19
src/com/owncloud/android/providers/FileContentProvider.java

@@ -18,6 +18,7 @@
 
 package com.owncloud.android.providers;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 
 import com.owncloud.android.Log_OC;
@@ -26,9 +27,12 @@ import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
 
 
 import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.OperationApplicationException;
 import android.content.UriMatcher;
 import android.database.Cursor;
 import android.database.SQLException;
@@ -37,11 +41,13 @@ import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
 import android.text.TextUtils;
+import android.util.Log;
 
 /**
  * The ContentProvider for the ownCloud App.
  * 
  * @author Bartek Przybylski
+ * @author David A. Velasco
  * 
  */
 public class FileContentProvider extends ContentProvider {
@@ -82,6 +88,7 @@ public class FileContentProvider extends ContentProvider {
                 ProviderTableMeta.FILE_ETAG);
     }
 
+    private static final String TAG = FileContentProvider.class.getSimpleName(); 
     private static final int SINGLE_FILE = 1;
     private static final int DIRECTORY = 2;
     private static final int ROOT_DIRECTORY = 3;
@@ -96,26 +103,98 @@ public class FileContentProvider extends ContentProvider {
 
     @Override
     public int delete(Uri uri, String where, String[] whereArgs) {
+        //Log_OC.d(TAG, "Deleting " + uri + " at provider " + this);
+        int count = 0;
         SQLiteDatabase db = mDbHelper.getWritableDatabase();
+        db.beginTransaction();
+        try {
+            count = delete(db, uri, where, whereArgs);
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+        getContext().getContentResolver().notifyChange(uri, null);
+        return count;
+    }
+    
+    
+    private int delete(SQLiteDatabase db, Uri uri, String where, String[] whereArgs) {
         int count = 0;
         switch (mUriMatcher.match(uri)) {
         case SINGLE_FILE:
+            /*Cursor c = query(db, uri, null, where, whereArgs, null);
+            String remotePath = "(unexisting)";
+            if (c != null && c.moveToFirst()) {
+                remotePath = c.getString(c.getColumnIndex(ProviderTableMeta.FILE_PATH));
+            }
+            Log_OC.d(TAG, "Removing FILE " + remotePath);
+            */
             count = db.delete(ProviderTableMeta.DB_NAME,
                     ProviderTableMeta._ID
                             + "="
                             + uri.getPathSegments().get(1)
                             + (!TextUtils.isEmpty(where) ? " AND (" + where
                                     + ")" : ""), whereArgs);
+            /* just for log
+            if (c!=null) {
+                c.close();
+            }
+            */
+            break;
+        case DIRECTORY:
+            // deletion of folder is recursive
+            /*
+            Uri folderUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, Long.parseLong(uri.getPathSegments().get(1)));
+            Cursor folder = query(db, folderUri, null, null, null, null);
+            String folderName = "(unknown)";
+            if (folder != null && folder.moveToFirst()) {
+                folderName = folder.getString(folder.getColumnIndex(ProviderTableMeta.FILE_PATH));
+            }
+            */
+            Cursor children = query(uri, null, null, null, null);
+            if (children != null && children.moveToFirst())  {
+                long childId;
+                boolean isDir; 
+                String remotePath; 
+                while (!children.isAfterLast()) {
+                    childId = children.getLong(children.getColumnIndex(ProviderTableMeta._ID));
+                    isDir = "DIR".equals(children.getString(children.getColumnIndex(ProviderTableMeta.FILE_CONTENT_TYPE)));
+                    remotePath = children.getString(children.getColumnIndex(ProviderTableMeta.FILE_PATH));
+                    if (isDir) {
+                        count += delete(db, ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_DIR, childId), null, null);
+                    } else {
+                        count += delete(db, ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, childId), null, null);
+                    }
+                    children.moveToNext();
+                }
+                children.close();
+            } /*else {
+                Log_OC.d(TAG, "No child to remove in DIRECTORY " + folderName);
+            }
+            Log_OC.d(TAG, "Removing DIRECTORY " + folderName + " (or maybe not) ");
+            */
+            count += db.delete(ProviderTableMeta.DB_NAME,
+                    ProviderTableMeta._ID
+                    + "="
+                    + uri.getPathSegments().get(1)
+                    + (!TextUtils.isEmpty(where) ? " AND (" + where
+                            + ")" : ""), whereArgs);
+            /* Just for log
+             if (folder != null) {
+                folder.close();
+            }*/
             break;
         case ROOT_DIRECTORY:
+            //Log_OC.d(TAG, "Removing ROOT!");
             count = db.delete(ProviderTableMeta.DB_NAME, where, whereArgs);
             break;
         default:
+            //Log_OC.e(TAG, "Unknown uri " + uri);
             throw new IllegalArgumentException("Unknown uri: " + uri.toString());
         }
-        getContext().getContentResolver().notifyChange(uri, null);
         return count;
     }
+    
 
     @Override
     public String getType(Uri uri) {
@@ -132,33 +211,61 @@ public class FileContentProvider extends ContentProvider {
 
     @Override
     public Uri insert(Uri uri, ContentValues values) {
+        //Log_OC.d(TAG, "Inserting " + values.getAsString(ProviderTableMeta.FILE_PATH) + " at provider " + this);
+        Uri newUri = null;
+        SQLiteDatabase db = mDbHelper.getWritableDatabase();
+        db.beginTransaction();
+        try {
+            newUri = insert(db, uri, values);
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+        getContext().getContentResolver().notifyChange(newUri, null);
+        return newUri;
+    }
+    
+    private Uri insert(SQLiteDatabase db, Uri uri, ContentValues values) {
         if (mUriMatcher.match(uri) != SINGLE_FILE &&
-            mUriMatcher.match(uri) != ROOT_DIRECTORY) {
-            
+                mUriMatcher.match(uri) != ROOT_DIRECTORY) {
+            //Log_OC.e(TAG, "Inserting invalid URI: " + uri);
             throw new IllegalArgumentException("Unknown uri id: " + uri);
         }
 
-        SQLiteDatabase db = mDbHelper.getWritableDatabase();
         long rowId = db.insert(ProviderTableMeta.DB_NAME, null, values);
         if (rowId > 0) {
-            Uri insertedFileUri = ContentUris.withAppendedId(
-                    ProviderTableMeta.CONTENT_URI_FILE, rowId);
-            getContext().getContentResolver().notifyChange(insertedFileUri,
-                    null);
+            Uri insertedFileUri = ContentUris.withAppendedId(ProviderTableMeta.CONTENT_URI_FILE, rowId);
+            //Log_OC.d(TAG, "Inserted " + values.getAsString(ProviderTableMeta.FILE_PATH) + " at provider " + this);
             return insertedFileUri;
+        } else {
+            //Log_OC.d(TAG, "Error while inserting " + values.getAsString(ProviderTableMeta.FILE_PATH)  + " at provider " + this);
+            throw new SQLException("ERROR " + uri);
         }
-        throw new SQLException("ERROR " + uri);
     }
 
+    
     @Override
     public boolean onCreate() {
         mDbHelper = new DataBaseHelper(getContext());
         return true;
     }
 
+    
     @Override
-    public Cursor query(Uri uri, String[] projection, String selection,
-            String[] selectionArgs, String sortOrder) {
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+        Cursor result = null;
+        SQLiteDatabase db = mDbHelper.getReadableDatabase();
+        db.beginTransaction();
+        try {
+            result = query(db, uri, projection, selection, selectionArgs, sortOrder);
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+        return result;
+    }
+    
+    private Cursor query(SQLiteDatabase db, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
         SQLiteQueryBuilder sqlQuery = new SQLiteQueryBuilder();
 
         sqlQuery.setTables(ProviderTableMeta.DB_NAME);
@@ -188,24 +295,59 @@ public class FileContentProvider extends ContentProvider {
             order = sortOrder;
         }
 
-        SQLiteDatabase db = mDbHelper.getReadableDatabase();
         // DB case_sensitive
         db.execSQL("PRAGMA case_sensitive_like = true");
-        Cursor c = sqlQuery.query(db, projection, selection, selectionArgs,
-                null, null, order);
+        Cursor c = sqlQuery.query(db, projection, selection, selectionArgs, null, null, order);
 
         c.setNotificationUri(getContext().getContentResolver(), uri);
-
         return c;
     }
 
     @Override
-    public int update(Uri uri, ContentValues values, String selection,
-            String[] selectionArgs) {
-        return mDbHelper.getWritableDatabase().update(
-                ProviderTableMeta.DB_NAME, values, selection, selectionArgs);
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        
+        //Log_OC.d(TAG, "Updating " + values.getAsString(ProviderTableMeta.FILE_PATH) + " at provider " + this);
+        int count = 0;
+        SQLiteDatabase db = mDbHelper.getWritableDatabase();
+        db.beginTransaction();
+        try {
+            count = update(db, uri, values, selection, selectionArgs);
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+        getContext().getContentResolver().notifyChange(uri, null);
+        return count;
+    }
+    
+    
+    private int update(SQLiteDatabase db, Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        return db.update(ProviderTableMeta.DB_NAME, values, selection, selectionArgs);
+    }    
+
+    
+    @Override
+    public ContentProviderResult[] applyBatch (ArrayList<ContentProviderOperation> operations) throws OperationApplicationException {
+        //Log.d(TAG, "applying batch in provider " + this + " (temporary: " + isTemporary() + ")" );
+        ContentProviderResult[] results = new ContentProviderResult[operations.size()];
+        int i=0;
+        
+        SQLiteDatabase db = mDbHelper.getWritableDatabase();
+        db.beginTransaction();  // it's supposed that transactions can be nested
+        try {
+            for (ContentProviderOperation operation : operations) {
+                results[i] = operation.apply(this, results, i);
+                i++;
+            }
+            db.setTransactionSuccessful();
+        } finally {
+            db.endTransaction();
+        }
+        //Log.d(TAG, "applied batch in provider " + this);
+        return results;
     }
 
+    
     class DataBaseHelper extends SQLiteOpenHelper {
 
         public DataBaseHelper(Context context) {

+ 0 - 2
src/com/owncloud/android/ui/activity/FileDisplayActivity.java

@@ -877,8 +877,6 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa
             String accountName = intent.getStringExtra(FileSyncService.ACCOUNT_NAME);
             RemoteOperationResult synchResult = (RemoteOperationResult)intent.getSerializableExtra(FileSyncService.SYNC_RESULT);
 
-            Log_OC.d(TAG, "sync of account " + accountName + " is in_progress: " + inProgress);
-            
             if (getAccount() != null && accountName.equals(getAccount().name)) {  
 
                 String synchFolderRemotePath = intent.getStringExtra(FileSyncService.SYNC_FOLDER_REMOTE_PATH); 

+ 1 - 10
src/com/owncloud/android/ui/fragment/OCFileListFragment.java

@@ -483,16 +483,7 @@ public class OCFileListFragment extends ExtendedListFragment implements EditName
     
     @Override
     public void onNeutral(String callerTag) {
-        File f = null;
-        if (mTargetFile.isFolder()) {
-            // TODO run in a secondary thread?
-            mContainerActivity.getStorageManager().removeFolder(mTargetFile, false, true);
-            
-        } else if (mTargetFile.isDown() && (f = new File(mTargetFile.getStoragePath())).exists()) {
-            f.delete();
-            mTargetFile.setStoragePath(null);
-            mContainerActivity.getStorageManager().saveFile(mTargetFile);
-        }
+        mContainerActivity.getStorageManager().removeFile(mTargetFile, false, true);    // TODO perform in background task / new thread
         listDirectory();
         mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
     }

+ 1 - 1
src/eu/alefzero/webdav/WebdavClient.java

@@ -168,7 +168,7 @@ public class WebdavClient extends HttpClient {
         try {
             method.setFollowRedirects(mFollowRedirects);
         } catch (Exception e) {
-            if (mFollowRedirects) Log_OC.d(TAG, "setFollowRedirects failed for " + method.getName() + " method, custom redirection will be used");
+            //if (mFollowRedirects) Log_OC.d(TAG, "setFollowRedirects failed for " + method.getName() + " method, custom redirection will be used if needed");
             customRedirectionNeeded = mFollowRedirects;
         }
         if (mSsoSessionCookie != null && mSsoSessionCookie.length() > 0) {