From: David A. Velasco Date: Tue, 22 Oct 2013 06:26:40 +0000 (+0200) Subject: Merge branch 'develop' into refresh_folder_contents_when_browsed_into X-Git-Tag: oc-android-1.5.5~155^2~6 X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/commitdiff_plain/9060663f66a4c518bd428d4d68e035b64e24c26c?hp=194a7360f88e309608b990e52674ca2f884e6d0a Merge branch 'develop' into refresh_folder_contents_when_browsed_into --- diff --git a/res/values/strings.xml b/res/values/strings.xml index 0872084a..14f0e1a0 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -129,6 +129,7 @@ Some local files were forgotten %1$d files out of the %2$s directory could not be copied into As of version 1.3.16, files uploaded from this device are copied into the local %1$s folder to prevent data loss when a single file is synced with multiple accounts.\n\nDue to this change, all files uploaded in previous versions of this app were copied into the %2$s folder. However, an error prevented the completion of this operation during account synchronization. You may either leave the file(s) as is and remove the link to %3$s, or move the file(s) into the %1$s directory and retain the link to %4$s.\n\nListed below are the local file(s), and the the remote file(s) in %5$s they were linked to. + Folder %1$s does not exist anymore "Move all" "All files were moved" "Some files could not be moved" diff --git a/src/com/owncloud/android/Uploader.java b/src/com/owncloud/android/Uploader.java index 0ff09079..fe1527a0 100644 --- a/src/com/owncloud/android/Uploader.java +++ b/src/com/owncloud/android/Uploader.java @@ -55,7 +55,6 @@ import android.widget.SimpleAdapter; import android.widget.Toast; import com.owncloud.android.authentication.AccountAuthenticator; -import com.owncloud.android.datamodel.DataStorageManager; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileUploader; @@ -77,7 +76,7 @@ public class Uploader extends ListActivity implements OnItemClickListener, andro private ArrayList mStreamsToUpload; private boolean mCreateDir; private String mUploadPath; - private DataStorageManager mStorageManager; + private FileDataStorageManager mStorageManager; private OCFile mFile; private final static int DIALOG_NO_ACCOUNT = 0; @@ -230,12 +229,12 @@ public class Uploader extends ListActivity implements OnItemClickListener, andro public void onItemClick(AdapterView parent, View view, int position, long id) { // click on folder in the list Log_OC.d(TAG, "on item click"); - Vector tmpfiles = mStorageManager.getDirectoryContent(mFile); + Vector tmpfiles = mStorageManager.getFolderContent(mFile); if (tmpfiles.size() <= 0) return; // filter on dirtype Vector files = new Vector(); for (OCFile f : tmpfiles) - if (f.isDirectory()) + if (f.isFolder()) files.add(f); if (files.size() < position) { throw new IndexOutOfBoundsException("Incorrect item selected"); @@ -295,11 +294,11 @@ public class Uploader extends ListActivity implements OnItemClickListener, andro mFile = mStorageManager.getFileByPath(full_path); if (mFile != null) { - Vector files = mStorageManager.getDirectoryContent(mFile); + Vector files = mStorageManager.getFolderContent(mFile); List> data = new LinkedList>(); for (OCFile f : files) { HashMap h = new HashMap(); - if (f.isDirectory()) { + if (f.isFolder()) { h.put("dirname", f.getFileName()); data.add(h); } diff --git a/src/com/owncloud/android/datamodel/DataStorageManager.java b/src/com/owncloud/android/datamodel/DataStorageManager.java deleted file mode 100644 index 133ab8de..00000000 --- a/src/com/owncloud/android/datamodel/DataStorageManager.java +++ /dev/null @@ -1,52 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2012 Bartek Przybylski - * Copyright (C) 2012-2013 ownCloud Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package com.owncloud.android.datamodel; - -import java.util.List; -import java.util.Vector; - -public interface DataStorageManager { - - public static final int ROOT_PARENT_ID = 0; - - public OCFile getFileByPath(String path); - - public OCFile getFileById(long id); - - public boolean fileExists(String path); - - public boolean fileExists(long id); - - public boolean saveFile(OCFile file); - - public void saveFiles(List files); - - public Vector getDirectoryContent(OCFile f); - - public void removeFile(OCFile file, boolean removeLocalCopy); - - public void removeDirectory(OCFile dir, boolean removeDBData, boolean removeLocalContent); - - public void moveDirectory(OCFile dir, String newPath); - - public Vector getDirectoryImages(OCFile mParentFolder); - - public void calculateFolderSize(long id); - -} diff --git a/src/com/owncloud/android/datamodel/FileDataStorageManager.java b/src/com/owncloud/android/datamodel/FileDataStorageManager.java index 5f6321d8..7e5e935a 100644 --- a/src/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/src/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -20,12 +20,11 @@ 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.DisplayUtils; import com.owncloud.android.Log_OC; import com.owncloud.android.db.ProviderMeta; import com.owncloud.android.db.ProviderMeta.ProviderTableMeta; @@ -36,33 +35,62 @@ 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; import android.net.Uri; import android.os.RemoteException; -public class FileDataStorageManager implements DataStorageManager { +public class FileDataStorageManager { + + public static final int ROOT_PARENT_ID = 0; private ContentResolver mContentResolver; - private ContentProviderClient mContentProvider; + private ContentProviderClient mContentProviderClient; private Account mAccount; - private static String TAG = "FileDataStorageManager"; + private static String TAG = FileDataStorageManager.class.getSimpleName(); + public FileDataStorageManager(Account account, ContentResolver cr) { - mContentProvider = null; + mContentProviderClient = null; mContentResolver = cr; mAccount = account; } public FileDataStorageManager(Account account, ContentProviderClient cp) { - mContentProvider = cp; + mContentProviderClient = cp; mContentResolver = null; mAccount = account; } - @Override + + public void setAccount(Account account) { + mAccount = account; + } + + public Account getAccount() { + return mAccount; + } + + public void setContentResolver(ContentResolver cr) { + mContentResolver = cr; + } + + public ContentResolver getContentResolver() { + return mContentResolver; + } + + public void setContentProviderClient(ContentProviderClient cp) { + mContentProviderClient = cp; + } + + public ContentProviderClient getContentProviderClient() { + return mContentProviderClient; + } + + public OCFile getFileByPath(String path) { Cursor c = getCursorForValue(ProviderTableMeta.FILE_PATH, path); OCFile file = null; @@ -70,22 +98,13 @@ public class FileDataStorageManager implements DataStorageManager { file = createFileInstance(c); } c.close(); - if (file == null && OCFile.PATH_SEPARATOR.equals(path)) { + if (file == null && OCFile.ROOT_PATH.equals(path)) { return createRootDir(); // root should always exist } return file; } - private OCFile createRootDir() { - OCFile file = new OCFile(OCFile.PATH_SEPARATOR); - file.setMimetype("DIR"); - file.setParentId(DataStorageManager.ROOT_PARENT_ID); - saveFile(file); - return file; - } - - @Override public OCFile getFileById(long id) { Cursor c = getCursorForValue(ProviderTableMeta._ID, String.valueOf(id)); OCFile file = null; @@ -106,17 +125,42 @@ public class FileDataStorageManager implements DataStorageManager { return file; } - @Override public boolean fileExists(long id) { return fileExists(ProviderTableMeta._ID, String.valueOf(id)); } - @Override public boolean fileExists(String path) { return fileExists(ProviderTableMeta.FILE_PATH, path); } - @Override + + public Vector getFolderContent(OCFile f) { + if (f != null && f.isFolder() && f.getFileId() != -1) { + return getFolderContent(f.getFileId()); + + } else { + return new Vector(); + } + } + + + public Vector getFolderImages(OCFile folder) { + Vector ret = new Vector(); + if (folder != null) { + // TODO better implementation, filtering in the access to database (if possible) instead of here + Vector tmp = getFolderContent(folder); + OCFile current = null; + for (int i=0; i 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 updatedFiles, Collection filesToRemove) { + + Log_OC.d(TAG, "Saving folder " + folder.getRemotePath() + " with " + updatedFiles.size() + " children and " + filesToRemove.size() + " files to remove"); - Iterator filesIt = files.iterator(); - ArrayList operations = new ArrayList(files.size()); - OCFile file = null; + ArrayList operations = new ArrayList(updatedFiles.size()); + long folderSize = 0; - // 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()); cv.put(ProviderTableMeta.FILE_CREATION, file.getCreationTimestamp()); cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, file.getFileLength()); + folderSize += file.getFileLength(); cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimetype()); cv.put(ProviderTableMeta.FILE_NAME, file.getFileName()); - if (file.getParentId() != 0) - 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.isDirectory()) + 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.isDirectory()) { - 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.isDirectory()) - 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). @@ -266,81 +316,268 @@ public class FileDataStorageManager implements DataStorageManager { .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 + 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, folderSize); + 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()); + 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); } else { - results = getContentProvider().applyBatch(operations); + results = getContentProviderClient().applyBatch(operations); } } 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 filesIt = updatedFiles.iterator(); + OCFile file = null; for (int i=0; i getDirectoryContent(OCFile f) { - if (f != null && f.isDirectory() && f.getFileId() != -1) { - return getDirectoryContent(f.getFileId()); + if (folder != null && folder.isFolder() && folder.fileExists() && !OCFile.ROOT_PATH.equals(folder.getFileName())) { + /// 1. get all the descendants of 'dir' in a single QUERY (including 'dir') + Cursor c = null; + if (getContentProviderClient() != null) { + try { + c = getContentProviderClient().query(ProviderTableMeta.CONTENT_URI, + null, + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + ProviderTableMeta.FILE_PATH + " LIKE ? ", + new String[] { mAccount.name, folder.getRemotePath() + "%" }, ProviderTableMeta.FILE_PATH + " ASC "); + } catch (RemoteException e) { + Log_OC.e(TAG, e.getMessage()); + } + } else { + c = getContentResolver().query(ProviderTableMeta.CONTENT_URI, + null, + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + ProviderTableMeta.FILE_PATH + " LIKE ? ", + new String[] { mAccount.name, folder.getRemotePath() + "%" }, ProviderTableMeta.FILE_PATH + " ASC "); + } + + /// 2. prepare a batch of update operations to change all the descendants + ArrayList operations = new ArrayList(c.getCount()); + int lengthOfOldPath = folder.getRemotePath().length(); + String defaultSavePath = FileStorageUtils.getSavePath(mAccount.name); + int lengthOfOldStoragePath = defaultSavePath.length() + lengthOfOldPath; + if (c.moveToFirst()) { + do { + ContentValues cv = new ContentValues(); // don't take the constructor out of the loop and clear the object + OCFile child = createFileInstance(c); + cv.put(ProviderTableMeta.FILE_PATH, newPath + child.getRemotePath().substring(lengthOfOldPath)); + if (child.getStoragePath() != null && child.getStoragePath().startsWith(defaultSavePath)) { + cv.put(ProviderTableMeta.FILE_STORAGE_PATH, defaultSavePath + newPath + child.getStoragePath().substring(lengthOfOldStoragePath)); + } + operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI). + withValues(cv). + withSelection( ProviderTableMeta._ID + "=?", + new String[] { String.valueOf(child.getFileId()) }) + .build()); + } while (c.moveToNext()); + } + c.close(); + + /// 3. apply updates in batch + try { + if (getContentResolver() != null) { + getContentResolver().applyBatch(ProviderMeta.AUTHORITY_FILES, operations); + + } else { + getContentProviderClient().applyBatch(operations); + } + + } catch (OperationApplicationException e) { + Log_OC.e(TAG, "Fail to update descendants of " + folder.getFileId() + " in database", e); + + } catch (RemoteException e) { + Log_OC.e(TAG, "Fail to update desendants of " + folder.getFileId() + " in database", e); + } - } else { - return new Vector(); } } - private Vector getDirectoryContent(long parentId) { + + private Vector getFolderContent(long parentId) { Vector ret = new Vector(); @@ -349,9 +586,9 @@ public class FileDataStorageManager implements DataStorageManager { String.valueOf(parentId)); Cursor c = null; - if (getContentProvider() != null) { + if (getContentProviderClient() != null) { try { - c = getContentProvider().query(req_uri, null, + c = getContentProviderClient().query(req_uri, null, ProviderTableMeta.FILE_PARENT + "=?" , new String[] { String.valueOf(parentId)}, null); } catch (RemoteException e) { @@ -379,6 +616,13 @@ public class FileDataStorageManager implements DataStorageManager { } + private OCFile createRootDir() { + OCFile file = new OCFile(OCFile.ROOT_PATH); + file.setMimetype("DIR"); + file.setParentId(FileDataStorageManager.ROOT_PARENT_ID); + saveFile(file); + return file; + } private boolean fileExists(String cmp_key, String value) { Cursor c; @@ -392,7 +636,7 @@ public class FileDataStorageManager implements DataStorageManager { new String[] { value, mAccount.name }, null); } else { try { - c = getContentProvider().query( + c = getContentProviderClient().query( ProviderTableMeta.CONTENT_URI, null, cmp_key + "=? AND " @@ -422,7 +666,7 @@ public class FileDataStorageManager implements DataStorageManager { new String[] { value, mAccount.name }, null); } else { try { - c = getContentProvider().query( + c = getContentProviderClient().query( ProviderTableMeta.CONTENT_URI, null, key + "=? AND " + ProviderTableMeta.FILE_ACCOUNT_OWNER @@ -446,7 +690,7 @@ public class FileDataStorageManager implements DataStorageManager { .getColumnIndex(ProviderTableMeta.FILE_PARENT))); file.setMimetype(c.getString(c .getColumnIndex(ProviderTableMeta.FILE_CONTENT_TYPE))); - if (!file.isDirectory()) { + if (!file.isFolder()) { file.setStoragePath(c.getString(c .getColumnIndex(ProviderTableMeta.FILE_STORAGE_PATH))); if (file.getStoragePath() == null) { @@ -472,182 +716,15 @@ public class FileDataStorageManager implements DataStorageManager { getColumnIndex(ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA))); file.setKeepInSync(c.getInt( c.getColumnIndex(ProviderTableMeta.FILE_KEEP_IN_SYNC)) == 1 ? true : false); + file.setEtag(c.getString(c.getColumnIndex(ProviderTableMeta.FILE_ETAG))); + } return file; } - @Override - public void removeFile(OCFile file, boolean removeLocalCopy) { - Uri file_uri = Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_FILE, ""+file.getFileId()); - if (getContentProvider() != null) { - try { - getContentProvider().delete(file_uri, - ProviderTableMeta.FILE_ACCOUNT_OWNER+"=?", - new String[]{mAccount.name}); - } 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.isDirectory() && removeLocalCopy) { - File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file)); - if (f.exists() && f.isDirectory() && (f.list() == null || f.list().length == 0)) { - f.delete(); - } - } - - if (file.getFileLength() > 0) { - updateSizesToTheRoot(file.getParentId()); - } - } - - @Override - public void removeDirectory(OCFile dir, boolean removeDBData, boolean removeLocalContent) { - // TODO consider possible failures - if (dir != null && dir.isDirectory() && dir.getFileId() != -1) { - Vector children = getDirectoryContent(dir); - if (children.size() > 0) { - OCFile child = null; - for (int i=0; i 0) { - updateSizesToTheRoot(dir.getParentId()); - } - } - } - - - /** - * Updates database for a folder that was moved to a different location. - * - * TODO explore better (faster) implementations - * TODO throw exceptions up ! - */ - @Override - public void moveDirectory(OCFile dir, String newPath) { - // TODO check newPath - - if (dir != null && dir.isDirectory() && dir.fileExists() && !dir.getFileName().equals(OCFile.PATH_SEPARATOR)) { - /// 1. get all the descendants of 'dir' in a single QUERY (including 'dir') - Cursor c = null; - if (getContentProvider() != null) { - try { - c = getContentProvider().query(ProviderTableMeta.CONTENT_URI, - null, - ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + ProviderTableMeta.FILE_PATH + " LIKE ? ", - new String[] { mAccount.name, dir.getRemotePath() + "%" }, ProviderTableMeta.FILE_PATH + " ASC "); - } catch (RemoteException e) { - Log_OC.e(TAG, e.getMessage()); - } - } else { - c = getContentResolver().query(ProviderTableMeta.CONTENT_URI, - null, - ProviderTableMeta.FILE_ACCOUNT_OWNER + "=? AND " + ProviderTableMeta.FILE_PATH + " LIKE ? ", - new String[] { mAccount.name, dir.getRemotePath() + "%" }, ProviderTableMeta.FILE_PATH + " ASC "); - } - - /// 2. prepare a batch of update operations to change all the descendants - ArrayList operations = new ArrayList(c.getCount()); - int lengthOfOldPath = dir.getRemotePath().length(); - String defaultSavePath = FileStorageUtils.getSavePath(mAccount.name); - int lengthOfOldStoragePath = defaultSavePath.length() + lengthOfOldPath; - if (c.moveToFirst()) { - do { - ContentValues cv = new ContentValues(); // don't take the constructor out of the loop and clear the object - OCFile child = createFileInstance(c); - cv.put(ProviderTableMeta.FILE_PATH, newPath + child.getRemotePath().substring(lengthOfOldPath)); - if (child.getStoragePath() != null && child.getStoragePath().startsWith(defaultSavePath)) { - cv.put(ProviderTableMeta.FILE_STORAGE_PATH, defaultSavePath + newPath + child.getStoragePath().substring(lengthOfOldStoragePath)); - } - operations.add(ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI). - withValues(cv). - withSelection( ProviderTableMeta._ID + "=?", - new String[] { String.valueOf(child.getFileId()) }) - .build()); - } while (c.moveToNext()); - } - c.close(); - - /// 3. apply updates in batch - try { - if (getContentResolver() != null) { - getContentResolver().applyBatch(ProviderMeta.AUTHORITY_FILES, operations); - - } else { - getContentProvider().applyBatch(operations); - } - - } catch (OperationApplicationException e) { - Log_OC.e(TAG, "Fail to update descendants of " + dir.getFileId() + " in database", e); - - } catch (RemoteException e) { - Log_OC.e(TAG, "Fail to update desendants of " + dir.getFileId() + " in database", e); - } - - } - } - - @Override - public Vector getDirectoryImages(OCFile directory) { - Vector ret = new Vector(); - if (directory != null) { - // TODO better implementation, filtering in the access to database (if possible) instead of here - Vector tmp = getDirectoryContent(directory); - OCFile current = null; - for (int i=0; i files = getDirectoryContent(id); - - for (OCFile f: files) - { - folderSize = folderSize + f.getFileLength(); - } - - updateSize(id, folderSize); - } - - /** + /* * Update the size value of an OCFile in DB - */ + * private int updateSize(long id, long size) { ContentValues cv = new ContentValues(); cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, size); @@ -657,7 +734,7 @@ public class FileDataStorageManager implements DataStorageManager { new String[] { String.valueOf(id) }); } else { try { - result = getContentProvider().update(ProviderTableMeta.CONTENT_URI, cv, ProviderTableMeta._ID + "=?", + result = getContentProviderClient().update(ProviderTableMeta.CONTENT_URI, cv, ProviderTableMeta._ID + "=?", new String[] { String.valueOf(id) }); } catch (RemoteException e) { Log_OC.e(TAG,"Fail to update size column into database " + e.getMessage()); @@ -665,26 +742,27 @@ public class FileDataStorageManager implements DataStorageManager { } return result; } + */ - /** + /* * Update the size of a subtree of folder from a file to the root * @param parentId: parent of the file - */ + * private void updateSizesToTheRoot(long parentId) { OCFile file; - while (parentId != 0) { + while (parentId != FileDataStorageManager.ROOT_PARENT_ID) { // Update the size of the parent - calculateFolderSize(parentId); + updateFolderSize(parentId); // search the next parent file = getFileById(parentId); parentId = file.getParentId(); } - } + */ } diff --git a/src/com/owncloud/android/datamodel/OCFile.java b/src/com/owncloud/android/datamodel/OCFile.java index 6b8e1785..9bd84abd 100644 --- a/src/com/owncloud/android/datamodel/OCFile.java +++ b/src/com/owncloud/android/datamodel/OCFile.java @@ -41,6 +41,7 @@ public class OCFile implements Parcelable, Comparable { }; public static final String PATH_SEPARATOR = "/"; + public static final String ROOT_PATH = PATH_SEPARATOR; private static final String TAG = OCFile.class.getSimpleName(); @@ -60,6 +61,7 @@ public class OCFile implements Parcelable, Comparable { private String mEtag; + /** * Create new {@link OCFile} with given path. * @@ -95,6 +97,7 @@ public class OCFile implements Parcelable, Comparable { mKeepInSync = source.readInt() == 1; mLastSyncDateForProperties = source.readLong(); mLastSyncDateForData = source.readLong(); + mEtag = source.readString(); } @Override @@ -112,6 +115,7 @@ public class OCFile implements Parcelable, Comparable { dest.writeInt(mKeepInSync ? 1 : 0); dest.writeLong(mLastSyncDateForProperties); dest.writeLong(mLastSyncDateForData); + dest.writeString(mEtag); } /** @@ -143,11 +147,11 @@ public class OCFile implements Parcelable, Comparable { } /** - * Use this to find out if this file is a Directory + * Use this to find out if this file is a folder. * - * @return true if it is a directory + * @return true if it is a folder */ - public boolean isDirectory() { + public boolean isFolder() { return mMimeType != null && mMimeType.equals("DIR"); } @@ -254,7 +258,7 @@ public class OCFile implements Parcelable, Comparable { */ public String getFileName() { File f = new File(getRemotePath()); - return f.getName().length() == 0 ? PATH_SEPARATOR : f.getName(); + return f.getName().length() == 0 ? ROOT_PATH : f.getName(); } /** @@ -264,11 +268,11 @@ public class OCFile implements Parcelable, Comparable { */ public void setFileName(String name) { Log_OC.d(TAG, "OCFile name changin from " + mRemotePath); - if (name != null && name.length() > 0 && !name.contains(PATH_SEPARATOR) && !mRemotePath.equals(PATH_SEPARATOR)) { + if (name != null && name.length() > 0 && !name.contains(PATH_SEPARATOR) && !mRemotePath.equals(ROOT_PATH)) { String parent = (new File(getRemotePath())).getParent(); parent = (parent.endsWith(PATH_SEPARATOR)) ? parent : parent + PATH_SEPARATOR; mRemotePath = parent + name; - if (isDirectory()) { + if (isFolder()) { mRemotePath += PATH_SEPARATOR; } Log_OC.d(TAG, "OCFile name changed to " + mRemotePath); @@ -293,7 +297,7 @@ public class OCFile implements Parcelable, Comparable { * not a directory */ public void addFile(OCFile file) throws IllegalStateException { - if (isDirectory()) { + if (isFolder()) { file.mParentId = mId; mNeedsUpdating = true; return; @@ -319,6 +323,7 @@ public class OCFile implements Parcelable, Comparable { mLastSyncDateForData = 0; mKeepInSync = false; mNeedsUpdating = false; + mEtag = null; } /** @@ -415,11 +420,11 @@ public class OCFile implements Parcelable, Comparable { @Override public int compareTo(OCFile another) { - if (isDirectory() && another.isDirectory()) { + if (isFolder() && another.isFolder()) { return getRemotePath().toLowerCase().compareTo(another.getRemotePath().toLowerCase()); - } else if (isDirectory()) { + } else if (isFolder()) { return -1; - } else if (another.isDirectory()) { + } else if (another.isFolder()) { return 1; } return getRemotePath().toLowerCase().compareTo(another.getRemotePath().toLowerCase()); @@ -439,8 +444,8 @@ public class OCFile implements Parcelable, Comparable { @Override public String toString() { - String asString = "[id=%s, name=%s, mime=%s, downloaded=%s, local=%s, remote=%s, parentId=%s, keepInSinc=%s]"; - asString = String.format(asString, Long.valueOf(mId), getFileName(), mMimeType, isDown(), mLocalPath, mRemotePath, Long.valueOf(mParentId), Boolean.valueOf(mKeepInSync)); + String asString = "[id=%s, name=%s, mime=%s, downloaded=%s, local=%s, remote=%s, parentId=%s, keepInSinc=%s etag=%s]"; + asString = String.format(asString, Long.valueOf(mId), getFileName(), mMimeType, isDown(), mLocalPath, mRemotePath, Long.valueOf(mParentId), Boolean.valueOf(mKeepInSync), mEtag); return asString; } @@ -448,6 +453,10 @@ public class OCFile implements Parcelable, Comparable { return mEtag; } + public void setEtag(String etag) { + this.mEtag = etag; + } + public long getLocalModificationTimestamp() { if (mLocalPath != null && mLocalPath.length() > 0) { File f = new File(mLocalPath); diff --git a/src/com/owncloud/android/db/ProviderMeta.java b/src/com/owncloud/android/db/ProviderMeta.java index b8058b89..521d4bbd 100644 --- a/src/com/owncloud/android/db/ProviderMeta.java +++ b/src/com/owncloud/android/db/ProviderMeta.java @@ -31,7 +31,7 @@ public class ProviderMeta { public static final String AUTHORITY_FILES = "org.owncloud"; public static final String DB_FILE = "owncloud.db"; public static final String DB_NAME = "filelist"; - public static final int DB_VERSION = 4; + public static final int DB_VERSION = 5; private ProviderMeta() { } @@ -61,6 +61,7 @@ public class ProviderMeta { public static final String FILE_LAST_SYNC_DATE = "last_sync_date"; // _for_properties, but let's keep it as it is public static final String FILE_LAST_SYNC_DATE_FOR_DATA = "last_sync_date_for_data"; public static final String FILE_KEEP_IN_SYNC = "keep_in_sync"; + public static final String FILE_ETAG = "etag"; public static final String DEFAULT_SORT_ORDER = FILE_NAME + " collate nocase asc"; diff --git a/src/com/owncloud/android/files/OwnCloudFileObserver.java b/src/com/owncloud/android/files/OwnCloudFileObserver.java index 2ea90c9a..5383520e 100644 --- a/src/com/owncloud/android/files/OwnCloudFileObserver.java +++ b/src/com/owncloud/android/files/OwnCloudFileObserver.java @@ -83,7 +83,6 @@ public class OwnCloudFileObserver extends FileObserver { storageManager, mOCAccount, true, - true, mContext); RemoteOperationResult result = sfo.execute(mOCAccount, mContext); if (result.getCode() == ResultCode.SYNC_CONFLICT) { diff --git a/src/com/owncloud/android/files/services/FileDownloader.java b/src/com/owncloud/android/files/services/FileDownloader.java index e07a17b5..a37a86a9 100644 --- a/src/com/owncloud/android/files/services/FileDownloader.java +++ b/src/com/owncloud/android/files/services/FileDownloader.java @@ -231,7 +231,7 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis if (account == null || file == null) return false; String targetKey = buildRemoteName(account, file); synchronized (mPendingDownloads) { - if (file.isDirectory()) { + if (file.isFolder()) { // this can be slow if there are many downloads :( Iterator it = mPendingDownloads.keySet().iterator(); boolean found = false; diff --git a/src/com/owncloud/android/files/services/FileUploader.java b/src/com/owncloud/android/files/services/FileUploader.java index 4a7a83d0..a10d2179 100644 --- a/src/com/owncloud/android/files/services/FileUploader.java +++ b/src/com/owncloud/android/files/services/FileUploader.java @@ -377,7 +377,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe return false; String targetKey = buildRemoteName(account, file); synchronized (mPendingUploads) { - if (file.isDirectory()) { + if (file.isFolder()) { // this can be slow if there are many uploads :( Iterator it = mPendingUploads.keySet().iterator(); boolean found = false; diff --git a/src/com/owncloud/android/operations/CreateFolderOperation.java b/src/com/owncloud/android/operations/CreateFolderOperation.java index 5745ec21..c86d2fc3 100644 --- a/src/com/owncloud/android/operations/CreateFolderOperation.java +++ b/src/com/owncloud/android/operations/CreateFolderOperation.java @@ -23,7 +23,7 @@ import org.apache.commons.httpclient.HttpStatus; import org.apache.jackrabbit.webdav.client.methods.MkColMethod; import com.owncloud.android.Log_OC; -import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import eu.alefzero.webdav.WebdavClient; @@ -43,7 +43,7 @@ public class CreateFolderOperation extends RemoteOperation { protected String mRemotePath; protected boolean mCreateFullPath; - protected DataStorageManager mStorageManager; + protected FileDataStorageManager mStorageManager; /** * Constructor @@ -52,7 +52,7 @@ public class CreateFolderOperation extends RemoteOperation { * @param createFullPath 'True' means that all the ancestor folders should be created if don't exist yet. * @param storageManager Reference to the local database corresponding to the account where the file is contained. */ - public CreateFolderOperation(String remotePath, boolean createFullPath, DataStorageManager storageManager) { + public CreateFolderOperation(String remotePath, boolean createFullPath, FileDataStorageManager storageManager) { mRemotePath = remotePath; mCreateFullPath = createFullPath; mStorageManager = storageManager; diff --git a/src/com/owncloud/android/operations/RemoteOperation.java b/src/com/owncloud/android/operations/RemoteOperation.java index 4d7bceef..e05dadff 100644 --- a/src/com/owncloud/android/operations/RemoteOperation.java +++ b/src/com/owncloud/android/operations/RemoteOperation.java @@ -136,14 +136,8 @@ public abstract class RemoteOperation implements Runnable { mCallerActivity = callerActivity; mClient = null; // the client instance will be created from mAccount and mContext in the runnerThread to create below - if (listener == null) { - throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a listener to notiy the result"); - } mListener = listener; - if (listenerHandler == null) { - throw new IllegalArgumentException("Trying to execute a remote operation asynchronously without a handler to the listener's thread"); - } mListenerHandler = listenerHandler; Thread runnerThread = new Thread(this); diff --git a/src/com/owncloud/android/operations/RemoveFileOperation.java b/src/com/owncloud/android/operations/RemoveFileOperation.java index 9dabfafd..ffb3a91a 100644 --- a/src/com/owncloud/android/operations/RemoveFileOperation.java +++ b/src/com/owncloud/android/operations/RemoveFileOperation.java @@ -21,7 +21,7 @@ import org.apache.commons.httpclient.HttpStatus; import org.apache.jackrabbit.webdav.client.methods.DeleteMethod; import com.owncloud.android.Log_OC; -import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import eu.alefzero.webdav.WebdavClient; @@ -41,7 +41,7 @@ public class RemoveFileOperation extends RemoteOperation { OCFile mFileToRemove; boolean mDeleteLocalCopy; - DataStorageManager mDataStorageManager; + FileDataStorageManager mDataStorageManager; /** @@ -51,7 +51,7 @@ public class RemoveFileOperation extends RemoteOperation { * @param deleteLocalCopy When 'true', and a local copy of the file exists, it is also removed. * @param storageManager Reference to the local database corresponding to the account where the file is contained. */ - public RemoveFileOperation(OCFile fileToRemove, boolean deleteLocalCopy, DataStorageManager storageManager) { + public RemoveFileOperation(OCFile fileToRemove, boolean deleteLocalCopy, FileDataStorageManager storageManager) { mFileToRemove = fileToRemove; mDeleteLocalCopy = deleteLocalCopy; mDataStorageManager = storageManager; @@ -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.isDirectory()) { - mDataStorageManager.removeDirectory(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()); diff --git a/src/com/owncloud/android/operations/RenameFileOperation.java b/src/com/owncloud/android/operations/RenameFileOperation.java index fddb8499..f9bb4242 100644 --- a/src/com/owncloud/android/operations/RenameFileOperation.java +++ b/src/com/owncloud/android/operations/RenameFileOperation.java @@ -26,7 +26,7 @@ import org.apache.jackrabbit.webdav.client.methods.DavMethodBase; import android.accounts.Account; import com.owncloud.android.Log_OC; -import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.utils.FileStorageUtils; @@ -51,7 +51,7 @@ public class RenameFileOperation extends RemoteOperation { private Account mAccount; private String mNewName; private String mNewRemotePath; - private DataStorageManager mStorageManager; + private FileDataStorageManager mStorageManager; /** @@ -62,7 +62,7 @@ public class RenameFileOperation extends RemoteOperation { * @param newName New name to set as the name of file. * @param storageManager Reference to the local database corresponding to the account where the file is contained. */ - public RenameFileOperation(OCFile file, Account account, String newName, DataStorageManager storageManager) { + public RenameFileOperation(OCFile file, Account account, String newName, FileDataStorageManager storageManager) { mFile = file; mAccount = account; mNewName = newName; @@ -94,7 +94,7 @@ public class RenameFileOperation extends RemoteOperation { String parent = (new File(mFile.getRemotePath())).getParent(); parent = (parent.endsWith(OCFile.PATH_SEPARATOR)) ? parent : parent + OCFile.PATH_SEPARATOR; mNewRemotePath = parent + mNewName; - if (mFile.isDirectory()) { + if (mFile.isFolder()) { mNewRemotePath += OCFile.PATH_SEPARATOR; } @@ -113,7 +113,7 @@ public class RenameFileOperation extends RemoteOperation { int status = client.executeMethod(move, RENAME_READ_TIMEOUT, RENAME_CONNECTION_TIMEOUT); if (move.succeeded()) { - if (mFile.isDirectory()) { + if (mFile.isFolder()) { saveLocalDirectory(); } else { @@ -152,7 +152,7 @@ public class RenameFileOperation extends RemoteOperation { private void saveLocalDirectory() { - mStorageManager.moveDirectory(mFile, mNewRemotePath); + mStorageManager.moveFolder(mFile, mNewRemotePath); String localPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile); File localDir = new File(localPath); if (localDir.exists()) { diff --git a/src/com/owncloud/android/operations/SynchronizeFileOperation.java b/src/com/owncloud/android/operations/SynchronizeFileOperation.java index 7a81b84e..b5f6575a 100644 --- a/src/com/owncloud/android/operations/SynchronizeFileOperation.java +++ b/src/com/owncloud/android/operations/SynchronizeFileOperation.java @@ -28,7 +28,7 @@ import android.content.Context; import android.content.Intent; import com.owncloud.android.Log_OC; -import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileDownloader; import com.owncloud.android.files.services.FileUploader; @@ -46,10 +46,9 @@ public class SynchronizeFileOperation extends RemoteOperation { private OCFile mLocalFile; private OCFile mServerFile; - private DataStorageManager mStorageManager; + private FileDataStorageManager mStorageManager; private Account mAccount; private boolean mSyncFileContents; - private boolean mLocalChangeAlreadyKnown; private Context mContext; private boolean mTransferWasRequested = false; @@ -57,10 +56,9 @@ public class SynchronizeFileOperation extends RemoteOperation { public SynchronizeFileOperation( OCFile localFile, OCFile serverFile, // make this null to let the operation checks the server; added to reuse info from SynchronizeFolderOperation - DataStorageManager storageManager, + FileDataStorageManager storageManager, Account account, boolean syncFileContents, - boolean localChangeAlreadyKnown, Context context) { mLocalFile = localFile; @@ -68,7 +66,6 @@ public class SynchronizeFileOperation extends RemoteOperation { mStorageManager = storageManager; mAccount = account; mSyncFileContents = syncFileContents; - mLocalChangeAlreadyKnown = localChangeAlreadyKnown; mContext = context; } @@ -112,16 +109,18 @@ public class SynchronizeFileOperation extends RemoteOperation { /// check changes in server and local file boolean serverChanged = false; + /* time for eTag is coming, but not yet if (mServerFile.getEtag() != null) { serverChanged = (!mServerFile.getEtag().equals(mLocalFile.getEtag())); // TODO could this be dangerous when the user upgrades the server from non-tagged to tagged? - } else { + } else { */ // server without etags serverChanged = (mServerFile.getModificationTimestamp() > mLocalFile.getModificationTimestampAtLastSyncForData()); - } - boolean localChanged = (mLocalChangeAlreadyKnown || mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData()); + //} + boolean localChanged = (mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData()); // TODO this will be always true after the app is upgraded to database version 2; will result in unnecessary uploads /// decide action to perform depending upon changes + //if (!mLocalFile.getEtag().isEmpty() && localChanged && serverChanged) { if (localChanged && serverChanged) { result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT); @@ -218,6 +217,8 @@ public class SynchronizeFileOperation extends RemoteOperation { file.setFileLength(we.contentLength()); file.setMimetype(we.contentType()); file.setModificationTimestamp(we.modifiedTimestamp()); + file.setEtag(we.etag()); + return file; } diff --git a/src/com/owncloud/android/operations/SynchronizeFolderOperation.java b/src/com/owncloud/android/operations/SynchronizeFolderOperation.java index f4065477..32c27bec 100644 --- a/src/com/owncloud/android/operations/SynchronizeFolderOperation.java +++ b/src/com/owncloud/android/operations/SynchronizeFolderOperation.java @@ -28,7 +28,6 @@ import java.util.List; import java.util.Map; import java.util.Vector; -import org.apache.commons.httpclient.Header; import org.apache.http.HttpStatus; import org.apache.jackrabbit.webdav.DavConstants; import org.apache.jackrabbit.webdav.MultiStatus; @@ -36,11 +35,13 @@ import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; import android.accounts.Account; import android.content.Context; +import android.content.Intent; import com.owncloud.android.Log_OC; -import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.syncadapter.FileSyncService; import com.owncloud.android.utils.FileStorageUtils; import eu.alefzero.webdav.WebdavClient; @@ -49,55 +50,81 @@ import eu.alefzero.webdav.WebdavUtils; /** - * Remote operation performing the synchronization a the contents of a remote folder with the local database + * Remote operation performing the synchronization of the list of files contained + * in a folder identified with its remote path. + * + * Fetches the list and properties of the files contained in the given folder, including their + * properties, and updates the local database with them. + * + * Does NOT enter in the child folders to synchronize their contents also. * - * @author David A. Velasco + * @author David A. Velasco */ public class SynchronizeFolderOperation extends RemoteOperation { private static final String TAG = SynchronizeFolderOperation.class.getSimpleName(); - /** Remote folder to synchronize */ - private String mRemotePath; - /** Timestamp for the synchronization in progress */ + /** Time stamp for the synchronization process in progress */ private long mCurrentSyncTime; - /** Id of the folder to synchronize in the local database */ - private long mParentId; + /** Remote folder to synchronize */ + private OCFile mLocalFolder; /** Access to the local database */ - private DataStorageManager mStorageManager; + private FileDataStorageManager mStorageManager; /** Account where the file to synchronize belongs */ private Account mAccount; - /** Android context; necessary to send requests to the download service; maybe something to refactor */ + /** Android context; necessary to send requests to the download service */ private Context mContext; - /** Files and folders contained in the synchronized folder */ + /** Files and folders contained in the synchronized folder after a successful operation */ private List mChildren; + /** Counter of conflicts found between local and remote files */ private int mConflictsFound; + /** Counter of failed operations in synchronization of kept-in-sync files */ private int mFailsInFavouritesFound; + /** Map of remote and local paths to files that where locally stored in a location out of the ownCloud folder and couldn't be copied automatically into it */ private Map mForgottenLocalFiles; + + /** 'True' means that this operation is part of a full account synchronization */ + private boolean mSyncFullAccount; + + /** 'True' means that the remote folder changed from last synchronization and should be fetched */ + private boolean mRemoteFolderChanged; - public SynchronizeFolderOperation( String remotePath, + /** + * Creates a new instance of {@link SynchronizeFolderOperation}. + * + * @param remoteFolderPath Remote folder to synchronize. + * @param currentSyncTime Time stamp for the synchronization process in progress. + * @param localFolderId Identifier in the local database of the folder to synchronize. + * @param updateFolderProperties 'True' means that the properties of the folder should be updated also, not just its content. + * @param syncFullAccount 'True' means that this operation is part of a full account synchronization. + * @param dataStorageManager Interface with the local database. + * @param account ownCloud account where the folder is located. + * @param context Application context. + */ + public SynchronizeFolderOperation( OCFile folder, long currentSyncTime, - long parentId, - DataStorageManager dataStorageManager, + boolean syncFullAccount, + FileDataStorageManager dataStorageManager, Account account, Context context ) { - mRemotePath = remotePath; + mLocalFolder = folder; mCurrentSyncTime = currentSyncTime; - mParentId = parentId; + mSyncFullAccount = syncFullAccount; mStorageManager = dataStorageManager; mAccount = account; mContext = context; mForgottenLocalFiles = new HashMap(); + mRemoteFolderChanged = false; } @@ -116,13 +143,17 @@ public class SynchronizeFolderOperation extends RemoteOperation { /** * Returns the list of files and folders contained in the synchronized folder, if called after synchronization is complete. * - * @return List of files and folders contained in the synchronized folder. + * @return List of files and folders contained in the synchronized folder. */ public List getChildren() { return mChildren; } - + /** + * Performs the synchronization. + * + * {@inheritDoc} + */ @Override protected RemoteOperationResult run(WebdavClient client) { RemoteOperationResult result = null; @@ -130,130 +161,115 @@ public class SynchronizeFolderOperation extends RemoteOperation { mConflictsFound = 0; mForgottenLocalFiles.clear(); - // code before in FileSyncAdapter.fetchData + result = checkForChanges(client); + + if (result.isSuccess()) { + if (mRemoteFolderChanged) { + result = fetchAndSyncRemoteFolder(client); + } else { + mChildren = mStorageManager.getFolderContent(mLocalFolder); + } + } + + if (!mSyncFullAccount) { + sendStickyBroadcast(false, mLocalFolder.getRemotePath(), result); + } + + return result; + + } + + + private RemoteOperationResult checkForChanges(WebdavClient client) { + mRemoteFolderChanged = false; + RemoteOperationResult result = null; + String remotePath = null; PropFindMethod query = null; + try { - Log_OC.d(TAG, "Synchronizing " + mAccount.name + ", fetching files in " + mRemotePath); - + remotePath = mLocalFolder.getRemotePath(); + Log_OC.d(TAG, "Checking changes in " + mAccount.name + remotePath); + // remote request - query = new PropFindMethod(client.getBaseUri() + WebdavUtils.encodePath(mRemotePath), + query = new PropFindMethod(client.getBaseUri() + WebdavUtils.encodePath(remotePath), DavConstants.PROPFIND_ALL_PROP, - DavConstants.DEPTH_1); + DavConstants.DEPTH_0); int status = client.executeMethod(query); - - // check and process response - /// TODO take into account all the possible status per child-resource - if (isMultiStatus(status)) { - MultiStatus resp = query.getResponseBodyAsMultiStatus(); - - // synchronize properties of the parent folder, if necessary - if (mParentId == DataStorageManager.ROOT_PARENT_ID) { - WebdavEntry we = new WebdavEntry(resp.getResponses()[0], client.getBaseUri().getPath()); - OCFile parent = fillOCFile(we); - mStorageManager.saveFile(parent); - mParentId = parent.getFileId(); - } - - // read contents in folder - List updatedFiles = new Vector(resp.getResponses().length - 1); - List filesToSyncContents = new Vector(); - for (int i = 1; i < resp.getResponses().length; ++i) { - /// new OCFile instance with the data from the server - WebdavEntry we = new WebdavEntry(resp.getResponses()[i], client.getBaseUri().getPath()); - OCFile file = fillOCFile(we); - - /// set data about local state, keeping unchanged former data if existing - file.setLastSyncDateForProperties(mCurrentSyncTime); - OCFile oldFile = mStorageManager.getFileByPath(file.getRemotePath()); - if (oldFile != null) { - file.setKeepInSync(oldFile.keepInSync()); - file.setLastSyncDateForData(oldFile.getLastSyncDateForData()); - file.setModificationTimestampAtLastSyncForData(oldFile.getModificationTimestampAtLastSyncForData()); // must be kept unchanged when the file contents are not updated - checkAndFixForeignStoragePath(oldFile); - file.setStoragePath(oldFile.getStoragePath()); - } - /// scan default location if local copy of file is not linked in OCFile instance - if (file.getStoragePath() == null && !file.isDirectory()) { - File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file)); - if (f.exists()) { - file.setStoragePath(f.getAbsolutePath()); - file.setLastSyncDateForData(f.lastModified()); - } - } - - /// prepare content synchronization for kept-in-sync files - if (file.keepInSync()) { - SynchronizeFileOperation operation = new SynchronizeFileOperation( oldFile, - file, - mStorageManager, - mAccount, - true, - false, - mContext - ); - filesToSyncContents.add(operation); - } + // check and process response + if (isMultiStatus(status)) { + // parse data from remote folder + WebdavEntry we = new WebdavEntry(query.getResponseBodyAsMultiStatus().getResponses()[0], client.getBaseUri().getPath()); + OCFile remoteFolder = fillOCFile(we); - updatedFiles.add(file); - } - - // 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); + // check if remote and local folder are different + mRemoteFolderChanged = !(remoteFolder.getEtag().equalsIgnoreCase(mLocalFolder.getEtag())); - // request for the synchronization of files AFTER saving last properties - SynchronizeFileOperation op = null; - RemoteOperationResult contentsResult = null; - for (int i=0; i < filesToSyncContents.size(); i++) { - op = filesToSyncContents.get(i); - contentsResult = op.execute(client); // returns without waiting for upload or download finishes - if (!contentsResult.isSuccess()) { - if (contentsResult.getCode() == ResultCode.SYNC_CONFLICT) { - mConflictsFound++; - } else { - mFailsInFavouritesFound++; - if (contentsResult.getException() != null) { - Log_OC.e(TAG, "Error while synchronizing favourites : " + contentsResult.getLogMessage(), contentsResult.getException()); - } else { - Log_OC.e(TAG, "Error while synchronizing favourites : " + contentsResult.getLogMessage()); - } - } - } // won't let these fails break the synchronization process - } - - - // removal of obsolete files - mChildren = mStorageManager.getDirectoryContent(mStorageManager.getFileById(mParentId)); - OCFile file; - String currentSavePath = FileStorageUtils.getSavePath(mAccount.name); - for (int i=0; i < mChildren.size(); ) { - file = mChildren.get(i); - if (file.getLastSyncDateForProperties() != mCurrentSyncTime) { - Log_OC.d(TAG, "removing file: " + file); - mStorageManager.removeFile(file, (file.isDown() && file.getStoragePath().startsWith(currentSavePath))); - mChildren.remove(i); - } else { - i++; - } - } + result = new RemoteOperationResult(ResultCode.OK); } else { + // check failed client.exhaustResponse(query.getResponseBodyAsStream()); + if (status == HttpStatus.SC_NOT_FOUND) { + removeLocalFolder(); + } + result = new RemoteOperationResult(false, status, query.getResponseHeaders()); + } + + } catch (Exception e) { + result = new RemoteOperationResult(e); + + + } finally { + if (query != null) + query.releaseConnection(); // let the connection available for other methods + if (result.isSuccess()) { + Log_OC.i(TAG, "Checked " + mAccount.name + remotePath + " : " + (mRemoteFolderChanged ? "changed" : "not changed")); + } else { + if (result.isException()) { + Log_OC.e(TAG, "Checked " + mAccount.name + remotePath + " : " + result.getLogMessage(), result.getException()); + } else { + Log_OC.e(TAG, "Checked " + mAccount.name + remotePath + " : " + result.getLogMessage()); + } } - // prepare result object + } + return result; + } + + + private RemoteOperationResult fetchAndSyncRemoteFolder(WebdavClient client) { + RemoteOperationResult result = null; + String remotePath = null; + PropFindMethod query = null; + try { + remotePath = mLocalFolder.getRemotePath(); + Log_OC.d(TAG, "Synchronizing " + mAccount.name + remotePath); + + // remote request + query = new PropFindMethod(client.getBaseUri() + WebdavUtils.encodePath(remotePath), + DavConstants.PROPFIND_ALL_PROP, + DavConstants.DEPTH_1); + int status = client.executeMethod(query); + + // check and process response if (isMultiStatus(status)) { + synchronizeData(query.getResponseBodyAsMultiStatus(), client); if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) { result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT); // should be different result, but will do the job - } else { result = new RemoteOperationResult(true, status, query.getResponseHeaders()); } + } else { + // synchronization failed + client.exhaustResponse(query.getResponseBodyAsStream()); + if (status == HttpStatus.SC_NOT_FOUND) { + removeLocalFolder(); + } result = new RemoteOperationResult(false, status, query.getResponseHeaders()); } - - - + } catch (Exception e) { result = new RemoteOperationResult(e); @@ -262,19 +278,153 @@ public class SynchronizeFolderOperation extends RemoteOperation { if (query != null) query.releaseConnection(); // let the connection available for other methods if (result.isSuccess()) { - Log_OC.i(TAG, "Synchronizing " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage()); + Log_OC.i(TAG, "Synchronized " + mAccount.name + remotePath + ": " + result.getLogMessage()); } else { if (result.isException()) { - Log_OC.e(TAG, "Synchronizing " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage(), result.getException()); + Log_OC.e(TAG, "Synchronized " + mAccount.name + remotePath + ": " + result.getLogMessage(), result.getException()); } else { - Log_OC.e(TAG, "Synchronizing " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage()); + Log_OC.e(TAG, "Synchronized " + mAccount.name + remotePath + ": " + result.getLogMessage()); } } + } - return result; } - + + + private void removeLocalFolder() { + if (mStorageManager.fileExists(mLocalFolder.getFileId())) { + String currentSavePath = FileStorageUtils.getSavePath(mAccount.name); + mStorageManager.removeFolder(mLocalFolder, true, (mLocalFolder.isDown() && mLocalFolder.getStoragePath().startsWith(currentSavePath))); + } + } + + + /** + * Synchronizes the data retrieved from the server about the contents of the target folder + * with the current data in the local database. + * + * Grants that mChildren is updated with fresh data after execution. + * + * @param dataInServer Full response got from the server with the data of the target + * folder and its direct children. + * @param client Client instance to the remote server where the data were + * retrieved. + * @return 'True' when any change was made in the local data, 'false' otherwise. + */ + private void synchronizeData(MultiStatus dataInServer, WebdavClient client) { + // get 'fresh data' from the database + mLocalFolder = mStorageManager.getFileById(mLocalFolder.getFileId()); + + // parse data from remote folder + WebdavEntry we = new WebdavEntry(dataInServer.getResponses()[0], client.getBaseUri().getPath()); + OCFile remoteFolder = fillOCFile(we); + remoteFolder.setParentId(mLocalFolder.getParentId()); + remoteFolder.setFileId(mLocalFolder.getFileId()); + + Log_OC.d(TAG, "Remote folder " + mLocalFolder.getRemotePath() + " changed - starting update of local data "); + + List updatedFiles = new Vector(dataInServer.getResponses().length - 1); + List filesToSyncContents = new Vector(); + + // get current data about local contents of the folder to synchronize + List localFiles = mStorageManager.getFolderContent(mLocalFolder); + Map localFilesMap = new HashMap(localFiles.size()); + for (OCFile file : localFiles) { + localFilesMap.put(file.getRemotePath(), file); + } + + // loop to update every child + OCFile remoteFile = null, localFile = null; + for (int i = 1; i < dataInServer.getResponses().length; ++i) { + /// new OCFile instance with the data from the server + we = new WebdavEntry(dataInServer.getResponses()[i], client.getBaseUri().getPath()); + remoteFile = fillOCFile(we); + remoteFile.setParentId(mLocalFolder.getFileId()); + + /// retrieve local data for the read file + //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) { + // some properties of local state are kept unmodified + remoteFile.setFileId(localFile.getFileId()); + remoteFile.setKeepInSync(localFile.keepInSync()); + remoteFile.setLastSyncDateForData(localFile.getLastSyncDateForData()); + remoteFile.setModificationTimestampAtLastSyncForData(localFile.getModificationTimestampAtLastSyncForData()); + remoteFile.setStoragePath(localFile.getStoragePath()); + remoteFile.setEtag(localFile.getEtag()); // eTag will not be updated unless contents are synchronized (Synchronize[File|Folder]Operation with remoteFile as parameter) + if (remoteFile.isFolder()) { + remoteFile.setFileLength(localFile.getFileLength()); // TODO move operations about size of folders to FileContentProvider + } + } else { + remoteFile.setEtag(""); // remote eTag will not be updated unless contents are synchronized (Synchronize[File|Folder]Operation with remoteFile as parameter) + } + + /// 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 + + /// prepare content synchronization for kept-in-sync files + if (remoteFile.keepInSync()) { + SynchronizeFileOperation operation = new SynchronizeFileOperation( localFile, + remoteFile, + mStorageManager, + mAccount, + true, + mContext + ); + filesToSyncContents.add(operation); + } + + updatedFiles.add(remoteFile); + } + + // save updated contents in local database; all at once, trying to get a best performance in database update (not a big deal, indeed) + 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(); + + // 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); + mChildren = updatedFiles; + + } + + /** + * 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. + * + * If download or upload is needed, request the operation to the corresponding service and goes on. + * + * @param filesToSyncContents Synchronization operations to execute. + * @param client Interface to the remote ownCloud server. + */ + private void startContentSynchronizations(List filesToSyncContents, WebdavClient client) { + RemoteOperationResult contentsResult = null; + for (SynchronizeFileOperation op: filesToSyncContents) { + contentsResult = op.execute(client); // returns without waiting for upload or download finishes + if (!contentsResult.isSuccess()) { + if (contentsResult.getCode() == ResultCode.SYNC_CONFLICT) { + mConflictsFound++; + } else { + mFailsInFavouritesFound++; + if (contentsResult.getException() != null) { + Log_OC.e(TAG, "Error while synchronizing favourites : " + contentsResult.getLogMessage(), contentsResult.getException()); + } else { + Log_OC.e(TAG, "Error while synchronizing favourites : " + contentsResult.getLogMessage()); + } + } + } // won't let these fails break the synchronization process + } + } + public boolean isMultiStatus(int status) { return (status == HttpStatus.SC_MULTI_STATUS); @@ -293,7 +443,7 @@ public class SynchronizeFolderOperation extends RemoteOperation { file.setFileLength(we.contentLength()); file.setMimetype(we.contentType()); file.setModificationTimestamp(we.modifiedTimestamp()); - file.setParentId(mParentId); + file.setEtag(we.etag()); return file; } @@ -304,7 +454,7 @@ public class SynchronizeFolderOperation extends RemoteOperation { * * If the copy fails, the link to the local file is nullified. The account of forgotten files is kept in * {@link #mForgottenLocalFiles} - * + *) * @param file File to check and fix. */ private void checkAndFixForeignStoragePath(OCFile file) { @@ -362,4 +512,46 @@ public class SynchronizeFolderOperation extends RemoteOperation { } + /** + * Scans the default location for saving local copies of files searching for + * a 'lost' file with the same full name as the {@link OCFile} received as + * parameter. + * + * @param file File to associate a possible 'lost' local file. + */ + private void searchForLocalFileInDefaultPath(OCFile file) { + if (file.getStoragePath() == null && !file.isFolder()) { + File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file)); + if (f.exists()) { + file.setStoragePath(f.getAbsolutePath()); + file.setLastSyncDateForData(f.lastModified()); + } + } + } + + + /** + * Sends a message to any application component interested in the progress of the synchronization. + * + * @param inProgress 'True' when the synchronization progress is not finished. + * @param dirRemotePath Remote path of a folder that was just synchronized (with or without success) + */ + private void sendStickyBroadcast(boolean inProgress, String dirRemotePath, RemoteOperationResult result) { + Intent i = new Intent(FileSyncService.SYNC_MESSAGE); + i.putExtra(FileSyncService.IN_PROGRESS, inProgress); + i.putExtra(FileSyncService.ACCOUNT_NAME, mAccount.name); + if (dirRemotePath != null) { + i.putExtra(FileSyncService.SYNC_FOLDER_REMOTE_PATH, dirRemotePath); + } + if (result != null) { + i.putExtra(FileSyncService.SYNC_RESULT, result); + } + mContext.sendStickyBroadcast(i); + } + + + public boolean getRemoteFolderChanged() { + return mRemoteFolderChanged; + } + } diff --git a/src/com/owncloud/android/providers/FileContentProvider.java b/src/com/owncloud/android/providers/FileContentProvider.java index bd2b8072..8673d4f3 100644 --- a/src/com/owncloud/android/providers/FileContentProvider.java +++ b/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; @@ -42,6 +46,7 @@ import android.text.TextUtils; * The ContentProvider for the ownCloud App. * * @author Bartek Przybylski + * @author David A. Velasco * */ public class FileContentProvider extends ContentProvider { @@ -78,42 +83,120 @@ public class FileContentProvider extends ContentProvider { ProviderTableMeta.FILE_KEEP_IN_SYNC); mProjectionMap.put(ProviderTableMeta.FILE_ACCOUNT_OWNER, ProviderTableMeta.FILE_ACCOUNT_OWNER); + mProjectionMap.put(ProviderTableMeta.FILE_ETAG, + ProviderTableMeta.FILE_ETAG); } private static final int SINGLE_FILE = 1; private static final int DIRECTORY = 2; private static final int ROOT_DIRECTORY = 3; private static final UriMatcher mUriMatcher; + + public static final String METHOD_UPDATE_FOLDER_SIZE = "updateFolderSize"; + static { mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); mUriMatcher.addURI(ProviderMeta.AUTHORITY_FILES, null, ROOT_DIRECTORY); mUriMatcher.addURI(ProviderMeta.AUTHORITY_FILES, "file/", SINGLE_FILE); mUriMatcher.addURI(ProviderMeta.AUTHORITY_FILES, "file/#", SINGLE_FILE); + mUriMatcher.addURI(ProviderMeta.AUTHORITY_FILES, "dir/", DIRECTORY); mUriMatcher.addURI(ProviderMeta.AUTHORITY_FILES, "dir/#", DIRECTORY); } @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) { @@ -130,33 +213,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); @@ -166,8 +277,9 @@ public class FileContentProvider extends ContentProvider { case ROOT_DIRECTORY: break; case DIRECTORY: + String folderId = uri.getPathSegments().get(1); sqlQuery.appendWhere(ProviderTableMeta.FILE_PARENT + "=" - + uri.getPathSegments().get(1)); + + folderId); break; case SINGLE_FILE: if (uri.getPathSegments().size() > 1) { @@ -186,24 +298,96 @@ 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); + if (mUriMatcher.match(uri) == DIRECTORY && c != null && c.moveToFirst()) { + long size = 0; + while (!c.isAfterLast()) { + size += c.getLong(c.getColumnIndex(ProviderTableMeta.FILE_CONTENT_LENGTH)); + c.moveToNext(); + } + ContentValues cv = new ContentValues(); + cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, size); + db.update(ProviderTableMeta.DB_NAME, cv, ProviderTableMeta._ID + "=?", new String[] {uri.getPathSegments().get(1)}); + } 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) { + switch (mUriMatcher.match(uri)) { + case DIRECTORY: + return updateFolderSize(db, selectionArgs[0]); + default: + return db.update(ProviderTableMeta.DB_NAME, values, selection, selectionArgs); + } + } + + + private int updateFolderSize(SQLiteDatabase db, String folderId) { + int count = 0; + Uri selectUri = Uri.withAppendedPath(ProviderTableMeta.CONTENT_URI_DIR, folderId); + String[] selectProjection = new String[] { ProviderTableMeta.FILE_CONTENT_LENGTH }; + String selectWhere = ProviderTableMeta.FILE_PARENT + "=?"; + String updateWhere = ProviderTableMeta._ID + "=?"; + String [] whereArgs = new String[] { folderId }; + Cursor childrenLengths = query(db, selectUri, selectProjection, selectWhere, whereArgs, null); + if (childrenLengths != null && childrenLengths.moveToFirst()) { + long size = 0; + while (!childrenLengths.isAfterLast()) { + size += childrenLengths.getLong(childrenLengths.getColumnIndex(ProviderTableMeta.FILE_CONTENT_LENGTH)); + childrenLengths.moveToNext(); + } + ContentValues cv = new ContentValues(); + cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, size); + count = db.update(ProviderTableMeta.DB_NAME, cv, updateWhere, whereArgs); + } + return count; + } + + + @Override + public ContentProviderResult[] applyBatch (ArrayList 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) { @@ -229,7 +413,8 @@ public class FileContentProvider extends ContentProvider { + ProviderTableMeta.FILE_LAST_SYNC_DATE + " INTEGER, " + ProviderTableMeta.FILE_KEEP_IN_SYNC + " INTEGER, " + ProviderTableMeta.FILE_LAST_SYNC_DATE_FOR_DATA + " INTEGER, " - + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " INTEGER );" + + ProviderTableMeta.FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA + " INTEGER, " + + ProviderTableMeta.FILE_ETAG + " TEXT );" ); } @@ -283,8 +468,24 @@ public class FileContentProvider extends ContentProvider { } if (!upgraded) Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion); + + if (oldVersion < 5 && newVersion >= 5) { + Log_OC.i("SQL", "Entering in the #4 ADD in onUpgrade"); + db.beginTransaction(); + try { + db .execSQL("ALTER TABLE " + ProviderTableMeta.DB_NAME + + " ADD COLUMN " + ProviderTableMeta.FILE_ETAG + " TEXT " + + " DEFAULT NULL"); + + upgraded = true; + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + if (!upgraded) + Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion); } - } } diff --git a/src/com/owncloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java b/src/com/owncloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java index e9f7a9b6..43c0f2f2 100644 --- a/src/com/owncloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java +++ b/src/com/owncloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java @@ -19,18 +19,18 @@ package com.owncloud.android.syncadapter; import java.io.IOException; -import java.net.UnknownHostException; -import java.util.Date; +//import java.net.UnknownHostException; +//import java.util.Date; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; -import org.apache.http.conn.ConnectionKeepAliveStrategy; -import org.apache.http.protocol.HttpContext; +//import org.apache.http.conn.ConnectionKeepAliveStrategy; +//import org.apache.http.protocol.HttpContext; import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.authentication.AccountUtils.AccountNotFoundException; -import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.network.OwnCloudClientUtils; import android.accounts.Account; @@ -43,11 +43,13 @@ import android.content.Context; import eu.alefzero.webdav.WebdavClient; /** - * Base SyncAdapter for OwnCloud Designed to be subclassed for the concrete - * SyncAdapter, like ConcatsSync, CalendarSync, FileSync etc.. + * Base synchronization adapter for ownCloud designed to be subclassed for different + * resource types, like FileSync, ConcatsSync, CalendarSync, etc.. * - * @author sassman + * Implements the standard {@link AbstractThreadedSyncAdapter}. * + * @author sassman + * @author David A. Velasco */ public abstract class AbstractOwnCloudSyncAdapter extends AbstractThreadedSyncAdapter { @@ -55,8 +57,8 @@ public abstract class AbstractOwnCloudSyncAdapter extends private AccountManager accountManager; private Account account; private ContentProviderClient contentProvider; - private Date lastUpdated; - private DataStorageManager mStoreManager; + //private Date lastUpdated; + private FileDataStorageManager mStoreManager; private WebdavClient mClient = null; @@ -89,20 +91,47 @@ public abstract class AbstractOwnCloudSyncAdapter extends this.contentProvider = contentProvider; } - public Date getLastUpdated() { - return lastUpdated; + public void setStorageManager(FileDataStorageManager storage_manager) { + mStoreManager = storage_manager; } - public void setLastUpdated(Date lastUpdated) { - this.lastUpdated = lastUpdated; + public FileDataStorageManager getStorageManager() { + return mStoreManager; } - public void setStorageManager(DataStorageManager storage_manager) { - mStoreManager = storage_manager; + protected void initClientForCurrentAccount() throws OperationCanceledException, AuthenticatorException, IOException, AccountNotFoundException { + AccountUtils.constructFullURLForAccount(getContext(), account); + mClient = OwnCloudClientUtils.createOwnCloudClient(account, getContext()); + } + + protected WebdavClient getClient() { + return mClient; + } + + + /* method called by ContactSyncAdapter, that is never used */ + protected HttpResponse fireRawRequest(HttpRequest query) + throws ClientProtocolException, OperationCanceledException, + AuthenticatorException, IOException { + /* + * BasicHttpContext httpContext = new BasicHttpContext(); BasicScheme + * basicAuth = new BasicScheme(); + * httpContext.setAttribute("preemptive-auth", basicAuth); + * + * HttpResponse response = getClient().execute(mHost, query, + * httpContext); + */ + return null; } - public DataStorageManager getStorageManager() { - return mStoreManager; + /* methods never used below */ + /* + public Date getLastUpdated() { + return lastUpdated; + } + + public void setLastUpdated(Date lastUpdated) { + this.lastUpdated = lastUpdated; } protected ConnectionKeepAliveStrategy getKeepAliveStrategy() { @@ -128,27 +157,5 @@ public abstract class AbstractOwnCloudSyncAdapter extends } }; } - - protected HttpResponse fireRawRequest(HttpRequest query) - throws ClientProtocolException, OperationCanceledException, - AuthenticatorException, IOException { - /* - * BasicHttpContext httpContext = new BasicHttpContext(); BasicScheme - * basicAuth = new BasicScheme(); - * httpContext.setAttribute("preemptive-auth", basicAuth); - * - * HttpResponse response = getClient().execute(mHost, query, - * httpContext); - */ - return null; - } - - protected void initClientForCurrentAccount() throws OperationCanceledException, AuthenticatorException, IOException, AccountNotFoundException { - AccountUtils.constructFullURLForAccount(getContext(), account); - mClient = OwnCloudClientUtils.createOwnCloudClient(account, getContext()); - } - - protected WebdavClient getClient() { - return mClient; - } + */ } \ No newline at end of file diff --git a/src/com/owncloud/android/syncadapter/FileSyncAdapter.java b/src/com/owncloud/android/syncadapter/FileSyncAdapter.java index 49f88f97..2aab9222 100644 --- a/src/com/owncloud/android/syncadapter/FileSyncAdapter.java +++ b/src/com/owncloud/android/syncadapter/FileSyncAdapter.java @@ -30,7 +30,6 @@ import com.owncloud.android.Log_OC; import com.owncloud.android.R; import com.owncloud.android.authentication.AccountAuthenticator; import com.owncloud.android.authentication.AuthenticatorActivity; -import com.owncloud.android.datamodel.DataStorageManager; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.operations.RemoteOperationResult; @@ -38,13 +37,13 @@ import com.owncloud.android.operations.SynchronizeFolderOperation; import com.owncloud.android.operations.UpdateOCVersionOperation; import com.owncloud.android.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.ui.activity.ErrorsWhileCopyingHandlerActivity; -import com.owncloud.android.utils.FileStorageUtils; import android.accounts.Account; import android.accounts.AccountsException; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.content.AbstractThreadedSyncAdapter; import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; @@ -53,36 +52,60 @@ import android.content.SyncResult; import android.os.Bundle; /** - * SyncAdapter implementation for syncing sample SyncAdapter contacts to the - * platform ContactOperations provider. + * Implementation of {@link AbstractThreadedSyncAdapter} responsible for synchronizing + * ownCloud files. + * + * Performs a full synchronization of the account recieved in {@link #onPerformSync(Account, Bundle, String, ContentProviderClient, SyncResult)}. * * @author Bartek Przybylski * @author David A. Velasco */ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { - private final static String TAG = "FileSyncAdapter"; + private final static String TAG = FileSyncAdapter.class.getSimpleName(); - /** - * Maximum number of failed folder synchronizations that are supported before finishing the synchronization operation - */ + /** Maximum number of failed folder synchronizations that are supported before finishing the synchronization operation */ private static final int MAX_FAILED_RESULTS = 3; + + /** Time stamp for the current synchronization process, used to distinguish fresh data */ private long mCurrentSyncTime; + + /** Flag made 'true' when a request to cancel the synchronization is received */ private boolean mCancellation; + + /** When 'true' the process was requested by the user through the user interface; when 'false', it was requested automatically by the system */ private boolean mIsManualSync; - private int mFailedResultsCounter; + + /** Counter for failed operations in the synchronization process */ + private int mFailedResultsCounter; + + /** Result of the last failed operation */ private RemoteOperationResult mLastFailedResult; - private SyncResult mSyncResult; + + /** Counter of conflicts found between local and remote files */ private int mConflictsFound; + + /** Counter of failed operations in synchronization of kept-in-sync files */ private int mFailsInFavouritesFound; + + /** Map of remote and local paths to files that where locally stored in a location out of the ownCloud folder and couldn't be copied automatically into it */ private Map mForgottenLocalFiles; + /** {@link SyncResult} instance to return to the system when the synchronization finish */ + private SyncResult mSyncResult; + + /** + * Creates an {@link FileSyncAdapter} + * + * {@inheritDoc} + */ public FileSyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize); } + /** * {@inheritDoc} */ @@ -100,20 +123,20 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { mForgottenLocalFiles = new HashMap(); mSyncResult = syncResult; mSyncResult.fullSyncRequested = false; - mSyncResult.delayUntil = 60*60*24; // sync after 24h + mSyncResult.delayUntil = 60*60*24; // avoid too many automatic synchronizations this.setAccount(account); this.setContentProvider(provider); - this.setStorageManager(new FileDataStorageManager(account, getContentProvider())); + this.setStorageManager(new FileDataStorageManager(account, provider)); try { this.initClientForCurrentAccount(); } catch (IOException e) { - /// the account is unknown for the Synchronization Manager, or unreachable for this context; don't try this again + /// the account is unknown for the Synchronization Manager, unreachable this context, or can not be authenticated; don't try this again mSyncResult.tooManyRetries = true; notifyFailedSynchronization(); return; } catch (AccountsException e) { - /// the account is unknown for the Synchronization Manager, or unreachable for this context; don't try this again + /// the account is unknown for the Synchronization Manager, unreachable this context, or can not be authenticated; don't try this again mSyncResult.tooManyRetries = true; notifyFailedSynchronization(); return; @@ -126,10 +149,10 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { updateOCVersion(); mCurrentSyncTime = System.currentTimeMillis(); if (!mCancellation) { - fetchData(OCFile.PATH_SEPARATOR, DataStorageManager.ROOT_PARENT_ID); + synchronizeFolder(getStorageManager().getFileByPath(OCFile.ROOT_PATH)); } else { - Log_OC.d(TAG, "Leaving synchronization before any remote request due to cancellation was requested"); + Log_OC.d(TAG, "Leaving synchronization before synchronizing the root folder because cancelation request"); } @@ -143,14 +166,12 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { /// notify the user about the failure of MANUAL synchronization notifyFailedSynchronization(); - } if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) { notifyFailsInFavourites(); } if (mForgottenLocalFiles.size() > 0) { notifyForgottenLocalFiles(); - } sendStickyBroadcast(false, null, mLastFailedResult); // message to signal the end to the UI } @@ -160,8 +181,12 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { /** * Called by system SyncManager when a synchronization is required to be cancelled. * - * Sets the mCancellation flag to 'true'. THe synchronization will be stopped when before a new folder is fetched. Data of the last folder - * fetched will be still saved in the database. See onPerformSync implementation. + * Sets the mCancellation flag to 'true'. THe synchronization will be stopped later, + * before a new folder is fetched. Data of the last folder synchronized will be still + * locally saved. + * + * See {@link #onPerformSync(Account, Bundle, String, ContentProviderClient, SyncResult)} + * and {@link #synchronizeFolder(String, long)}. */ @Override public void onSyncCanceled() { @@ -184,20 +209,36 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { /** - * Synchronize the properties of files and folders contained in a remote folder given by remotePath. + * Synchronizes the list of files contained in a folder identified with its remote path. + * + * Fetches the list and properties of the files contained in the given folder, including their + * properties, and updates the local database with them. + * + * Enters in the child folders to synchronize their contents also, following a recursive + * depth first strategy. * - * @param remotePath Remote path to the folder to synchronize. - * @param parentId Database Id of the folder to synchronize. + * @param folder Folder to synchronize. */ - private void fetchData(String remotePath, long parentId) { + private void synchronizeFolder(OCFile folder) { if (mFailedResultsCounter > MAX_FAILED_RESULTS || isFinisher(mLastFailedResult)) return; - // perform folder synchronization - SynchronizeFolderOperation synchFolderOp = new SynchronizeFolderOperation( remotePath, + /* + OCFile folder, + long currentSyncTime, + boolean updateFolderProperties, + boolean syncFullAccount, + DataStorageManager dataStorageManager, + Account account, + Context context ) { + + } + */ + // folder synchronization + SynchronizeFolderOperation synchFolderOp = new SynchronizeFolderOperation( folder, mCurrentSyncTime, - parentId, + true, getStorageManager(), getAccount(), getContext() @@ -206,8 +247,9 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { // synchronized folder -> notice to UI - ALWAYS, although !result.isSuccess - sendStickyBroadcast(true, remotePath, null); + sendStickyBroadcast(true, folder.getRemotePath(), null); + // check the result of synchronizing the folder if (result.isSuccess() || result.getCode() == ResultCode.SYNC_CONFLICT) { if (result.getCode() == ResultCode.SYNC_CONFLICT) { @@ -217,15 +259,15 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { if (synchFolderOp.getForgottenLocalFiles().size() > 0) { mForgottenLocalFiles.putAll(synchFolderOp.getForgottenLocalFiles()); } - // synchronize children folders - List children = synchFolderOp.getChildren(); - fetchChildren(children); // beware of the 'hidden' recursion here! - - sendStickyBroadcast(true, remotePath, null); + if (result.isSuccess()) { + // synchronize children folders + List children = synchFolderOp.getChildren(); + fetchChildren(folder, children, synchFolderOp.getRemoteFolderChanged()); // beware of the 'hidden' recursion here! + } } else { + // in failures, the statistics for the global result are updated if (result.getCode() == RemoteOperationResult.ResultCode.UNAUTHORIZED || - // (result.isTemporalRedirection() && result.isIdPRedirection() && ( result.isIdPRedirection() && AccountAuthenticator.AUTH_TOKEN_TYPE_SAML_WEB_SSO_SESSION_COOKIE.equals(getClient().getAuthTokenType()))) { mSyncResult.stats.numAuthExceptions++; @@ -261,23 +303,30 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { } /** - * Synchronize data of folders in the list of received files + * Triggers the synchronization of any folder contained in the list of received files. * - * @param files Files to recursively fetch + * @param files Files to recursively synchronize. */ - private void fetchChildren(List files) { + private void fetchChildren(OCFile parent, List files, boolean parentEtagChanged) { int i; + OCFile newFile = null; + String etag = null; + boolean syncDown = false; for (i=0; i < files.size() && !mCancellation; i++) { - OCFile newFile = files.get(i); - if (newFile.isDirectory()) { - fetchData(newFile.getRemotePath(), newFile.getFileId()); - - // Update folder size on DB - getStorageManager().calculateFolderSize(newFile.getFileId()); + newFile = files.get(i); + if (newFile.isFolder()) { + etag = newFile.getEtag(); + syncDown = (parentEtagChanged || etag == null || etag.length() == 0); + if(syncDown) { + synchronizeFolder(newFile); + // update the size of the parent folder again after recursive synchronization + getStorageManager().updateFolderSize(parent.getFileId()); + sendStickyBroadcast(true, parent.getRemotePath(), null); // notify again to refresh size in UI + } } } - if (mCancellation && i mDirectories; /** Access point to the cached database for the current ownCloud {@link Account} */ - private DataStorageManager mStorageManager = null; + private FileDataStorageManager mStorageManager = null; private SyncBroadcastReceiver mSyncBroadcastReceiver; private UploadFinishReceiver mUploadFinishReceiver; @@ -118,6 +118,7 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa private View mRightFragmentContainer; private static final String KEY_WAITING_TO_PREVIEW = "WAITING_TO_PREVIEW"; + private static final String KEY_SYNC_IN_PROGRESS = "SYNC_IN_PROGRESS"; public static final int DIALOG_SHORT_WAIT = 0; private static final int DIALOG_CHOOSE_UPLOAD_SOURCE = 1; @@ -138,6 +139,8 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa private OCFile mWaitingToPreview; private Handler mHandler; + + private boolean mSyncInProgress = false; @Override protected void onCreate(Bundle savedInstanceState) { @@ -167,10 +170,12 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa /// Load of saved instance state if(savedInstanceState != null) { mWaitingToPreview = (OCFile) savedInstanceState.getParcelable(FileDisplayActivity.KEY_WAITING_TO_PREVIEW); - + mSyncInProgress = savedInstanceState.getBoolean(KEY_SYNC_IN_PROGRESS); + } else { mWaitingToPreview = null; - } + mSyncInProgress = false; + } /// USER INTERFACE @@ -186,7 +191,7 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa // Action bar setup mDirectories = new CustomArrayAdapter(this, R.layout.sherlock_spinner_dropdown_item); getSupportActionBar().setHomeButtonEnabled(true); // mandatory since Android ICS, according to the official documentation - setSupportProgressBarIndeterminateVisibility(false); // always AFTER setContentView(...) ; to work around bug in its implementation + setSupportProgressBarIndeterminateVisibility(mSyncInProgress); // always AFTER setContentView(...) ; to work around bug in its implementation Log_OC.d(TAG, "onCreate() end"); } @@ -227,27 +232,20 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa } if (file == null) { // fall back to root folder - file = mStorageManager.getFileByPath(OCFile.PATH_SEPARATOR); // never returns null + file = mStorageManager.getFileByPath(OCFile.ROOT_PATH); // never returns null } setFile(file); - mDirectories.clear(); - OCFile fileIt = file; - while(fileIt != null && fileIt.getFileName() != OCFile.PATH_SEPARATOR) { - if (fileIt.isDirectory()) { - mDirectories.add(fileIt.getFileName()); - } - // get parent from path - parentPath = fileIt.getRemotePath().substring(0, fileIt.getRemotePath().lastIndexOf(fileIt.getFileName())); - fileIt = mStorageManager.getFileByPath(parentPath); - } - mDirectories.add(OCFile.PATH_SEPARATOR); + setNavigationListWithFolder(file); if (!stateWasRecovered) { Log_OC.e(TAG, "Initializing Fragments in onAccountChanged.."); initFragmentsWithFile(); + if (file.isFolder()) { + startSyncFolderOperation(file); + } } else { - updateFragmentsVisibility(!file.isDirectory()); - updateNavigationElementsInActionBar(file.isDirectory() ? null : file); + updateFragmentsVisibility(!file.isFolder()); + updateNavigationElementsInActionBar(file.isFolder() ? null : file); } @@ -257,6 +255,23 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa } + private void setNavigationListWithFolder(OCFile file) { + mDirectories.clear(); + OCFile fileIt = file; + String parentPath; + while(fileIt != null && fileIt.getFileName() != OCFile.ROOT_PATH) { + if (fileIt.isFolder()) { + mDirectories.add(fileIt.getFileName()); + } + //fileIt = mStorageManager.getFileById(fileIt.getParentId()); + // get parent from path + parentPath = fileIt.getRemotePath().substring(0, fileIt.getRemotePath().lastIndexOf(fileIt.getFileName())); + fileIt = mStorageManager.getFileByPath(parentPath); + } + mDirectories.add(OCFile.PATH_SEPARATOR); + } + + private void createMinFragments() { OCFileListFragment listOfFiles = new OCFileListFragment(); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); @@ -299,7 +314,7 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa private Fragment chooseInitialSecondFragment(OCFile file) { Fragment secondFragment = null; - if (file != null && !file.isDirectory()) { + if (file != null && !file.isFolder()) { if (file.isDown() && PreviewMediaFragment.canBePreviewed(file) && file.getLastSyncDateForProperties() > 0 // temporal fix ) { @@ -464,8 +479,9 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa FileFragment second = getSecondFragment(); OCFile currentDir = getCurrentDir(); if((currentDir != null && currentDir.getParentId() != 0) || - (second != null && second.getFile() != null)) { + (second != null && second.getFile() != null)) { onBackPressed(); + } break; } @@ -487,15 +503,22 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa @Override public boolean onNavigationItemSelected(int itemPosition, long itemId) { - int i = itemPosition; - while (i-- != 0) { - onBackPressed(); - } - // the next operation triggers a new call to this method, but it's necessary to - // ensure that the name exposed in the action bar is the current directory when the - // user selected it in the navigation list - if (itemPosition != 0) + if (itemPosition != 0) { + String targetPath = ""; + for (int i=itemPosition; i < mDirectories.getCount() - 1; i++) { + targetPath = mDirectories.getItem(i) + OCFile.PATH_SEPARATOR + targetPath; + } + targetPath = OCFile.PATH_SEPARATOR + targetPath; + OCFile targetFolder = mStorageManager.getFileByPath(targetPath); + if (targetFolder != null) { + browseTo(targetFolder); + } + + // the next operation triggers a new call to this method, but it's necessary to + // ensure that the name exposed in the action bar is the current directory when the + // user selected it in the navigation list getSupportActionBar().setSelectedNavigationItem(0); + } return true; } @@ -600,14 +623,17 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa finish(); return; } - popDirname(); - listOfFiles.onBrowseUp(); + int levelsUp = listOfFiles.onBrowseUp(); + for (int i=0; i < levelsUp && mDirectories.getCount() > 1 ; i++) { + popDirname(); + } } } if (listOfFiles != null) { // should never be null, indeed setFile(listOfFiles.getCurrentFile()); } cleanSecondFragment(); + } @Override @@ -616,6 +642,8 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa Log_OC.e(TAG, "onSaveInstanceState() start"); super.onSaveInstanceState(outState); outState.putParcelable(FileDisplayActivity.KEY_WAITING_TO_PREVIEW, mWaitingToPreview); + outState.putBoolean(FileDisplayActivity.KEY_SYNC_IN_PROGRESS, mSyncInProgress); + Log_OC.d(TAG, "onSaveInstanceState() end"); } @@ -803,10 +831,10 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa /** * Pushes a directory to the drop down list * @param directory to push - * @throws IllegalArgumentException If the {@link OCFile#isDirectory()} returns false. + * @throws IllegalArgumentException If the {@link OCFile#isFolder()} returns false. */ public void pushDirname(OCFile directory) { - if(!directory.isDirectory()){ + if(!directory.isFolder()){ throw new IllegalArgumentException("Only directories may be pushed!"); } mDirectories.insert(directory.getFileName(), 0); @@ -858,8 +886,7 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa public void onReceive(Context context, Intent intent) { boolean inProgress = intent.getBooleanExtra(FileSyncService.IN_PROGRESS, false); String accountName = intent.getStringExtra(FileSyncService.ACCOUNT_NAME); - - Log_OC.d(TAG, "sync of account " + accountName + " is in_progress: " + inProgress); + RemoteOperationResult synchResult = (RemoteOperationResult)intent.getSerializableExtra(FileSyncService.SYNC_RESULT); if (getAccount() != null && accountName.equals(getAccount().name) && mStorageManager != null @@ -867,31 +894,39 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa String synchFolderRemotePath = intent.getStringExtra(FileSyncService.SYNC_FOLDER_REMOTE_PATH); - boolean fillBlankRoot = false; - OCFile currentDir = getCurrentDir(); - if (currentDir == null) { - currentDir = mStorageManager.getFileByPath(OCFile.PATH_SEPARATOR); - fillBlankRoot = (currentDir != null); - } + OCFile currentFile = (getFile() == null) ? null : mStorageManager.getFileByPath(getFile().getRemotePath()); + OCFile currentDir = (getCurrentDir() == null) ? null : mStorageManager.getFileByPath(getCurrentDir().getRemotePath()); - if ((synchFolderRemotePath != null && currentDir != null && (currentDir.getRemotePath().equals(synchFolderRemotePath))) - || fillBlankRoot ) { - if (!fillBlankRoot) - currentDir = mStorageManager.getFileByPath(synchFolderRemotePath); - OCFileListFragment fileListFragment = getListOfFilesFragment(); - if (fileListFragment != null) { - fileListFragment.listDirectory(currentDir); + if (currentDir == null) { + // current folder was removed from the server + Toast.makeText( FileDisplayActivity.this, + String.format(getString(R.string.sync_current_folder_was_removed), mDirectories.getItem(0)), + Toast.LENGTH_LONG) + .show(); + browseToRoot(); + + } else { + if (currentFile == null && !getFile().isFolder()) { + // currently selected file was removed in the server, and now we know it + cleanSecondFragment(); + currentFile = currentDir; } - if (getSecondFragment() == null) - setFile(currentDir); + + if (synchFolderRemotePath != null && currentDir.getRemotePath().equals(synchFolderRemotePath)) { + OCFileListFragment fileListFragment = getListOfFilesFragment(); + if (fileListFragment != null) { + fileListFragment.listDirectory(currentDir); + } + } + setFile(currentFile); } setSupportProgressBarIndeterminateVisibility(inProgress); removeStickyBroadcast(intent); + mSyncInProgress = inProgress; } - - RemoteOperationResult synchResult = (RemoteOperationResult)intent.getSerializableExtra(FileSyncService.SYNC_RESULT); + if (synchResult != null) { if (synchResult.getCode().equals(RemoteOperationResult.ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED)) { mLastSslUntrustedServerResult = synchResult; @@ -900,7 +935,7 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa } } } - + private class UploadFinishReceiver extends BroadcastReceiver { /** @@ -960,11 +995,43 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa * {@inheritDoc} */ @Override - public DataStorageManager getStorageManager() { + public FileDataStorageManager getStorageManager() { return mStorageManager; } + public void browseToRoot() { + OCFileListFragment listOfFiles = getListOfFilesFragment(); + if (listOfFiles != null) { // should never be null, indeed + while (mDirectories.getCount() > 1) { + popDirname(); + } + OCFile root = mStorageManager.getFileByPath(OCFile.ROOT_PATH); + listOfFiles.listDirectory(root); + setFile(listOfFiles.getCurrentFile()); + startSyncFolderOperation(root); + } + cleanSecondFragment(); + } + + + public void browseTo(OCFile folder) { + if (folder == null || !folder.isFolder()) { + throw new IllegalArgumentException("Trying to browse to invalid folder " + folder); + } + OCFileListFragment listOfFiles = getListOfFilesFragment(); + if (listOfFiles != null) { + setNavigationListWithFolder(folder); + listOfFiles.listDirectory(folder); + setFile(listOfFiles.getCurrentFile()); + startSyncFolderOperation(folder); + } else { + Log_OC.e(TAG, "Unexpected null when accessing list fragment"); + } + cleanSecondFragment(); + } + + /** * {@inheritDoc} * @@ -974,6 +1041,10 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa public void onBrowsedDownTo(OCFile directory) { pushDirname(directory); cleanSecondFragment(); + + // Sync Folder + startSyncFolderOperation(directory); + } /** @@ -1062,6 +1133,19 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa } +// private void updateDisplayHomeAtSync(){ +// ActionBar actionBar = getSupportActionBar(); +// OCFile currentDir = getCurrentDir(); +// if (currentDir.getParentId() != DataStorageManager.ROOT_PARENT_ID) { +// actionBar.setHomeButtonEnabled(!mSyncInProgress); +// actionBar.setDisplayHomeAsUpEnabled(!mSyncInProgress); +// } +// else { +// actionBar.setHomeButtonEnabled(true); +// actionBar.setDisplayHomeAsUpEnabled(false); +// } +// } +// /** * {@inheritDoc} */ @@ -1182,7 +1266,8 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa } else if (operation instanceof CreateFolderOperation) { onCreateFolderOperationFinish((CreateFolderOperation)operation, result); - } + + } } @@ -1230,7 +1315,6 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa refeshListOfFilesFragment(); } else { - //dismissDialog(DIALOG_SHORT_WAIT); dismissLoadingDialog(); try { Toast msg = Toast.makeText(FileDisplayActivity.this, R.string.create_dir_fail_msg, Toast.LENGTH_LONG); @@ -1291,10 +1375,7 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, getAccount()); startActivity(i); - } else { - Toast msg = Toast.makeText(this, R.string.sync_file_fail_msg, Toast.LENGTH_LONG); - msg.show(); - } + } } else { if (operation.transferWasRequested()) { @@ -1363,7 +1444,7 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa private OCFile getCurrentDir() { OCFile file = getFile(); if (file != null) { - if (file.isDirectory()) { + if (file.isFolder()) { return file; } else if (mStorageManager != null) { String parentPath = file.getRemotePath().substring(0, file.getRemotePath().lastIndexOf(file.getFileName())); @@ -1372,5 +1453,35 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa } return null; } + + public void startSyncFolderOperation(OCFile folder) { + long currentSyncTime = System.currentTimeMillis(); + + mSyncInProgress = true; + + // perform folder synchronization + RemoteOperation synchFolderOp = new SynchronizeFolderOperation( folder, + currentSyncTime, + false, + getStorageManager(), + getAccount(), + getApplicationContext() + ); + synchFolderOp.execute(getAccount(), this, null, null, this); + + setSupportProgressBarIndeterminateVisibility(true); + } + +// public void enableDisableViewGroup(ViewGroup viewGroup, boolean enabled) { +// int childCount = viewGroup.getChildCount(); +// for (int i = 0; i < childCount; i++) { +// View view = viewGroup.getChildAt(i); +// view.setEnabled(enabled); +// view.setClickable(!enabled); +// if (view instanceof ViewGroup) { +// enableDisableViewGroup((ViewGroup) view, enabled); +// } +// } +// } } diff --git a/src/com/owncloud/android/ui/adapter/FileListListAdapter.java b/src/com/owncloud/android/ui/adapter/FileListListAdapter.java index 3e8eecd5..5e6d01b2 100644 --- a/src/com/owncloud/android/ui/adapter/FileListListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FileListListAdapter.java @@ -31,7 +31,7 @@ import android.widget.TextView; import com.owncloud.android.DisplayUtils; import com.owncloud.android.R; import com.owncloud.android.authentication.AccountUtils; -import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; @@ -51,7 +51,7 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { private Context mContext; private OCFile mFile = null; private Vector mFiles = null; - private DataStorageManager mStorageManager; + private FileDataStorageManager mStorageManager; private Account mAccount; private TransferServiceGetter mTransferServiceGetter; //total size of a directory (recursive) @@ -135,7 +135,7 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { TextView lastModV = (TextView) view.findViewById(R.id.last_mod); ImageView checkBoxV = (ImageView) view.findViewById(R.id.custom_checkbox); - if (!file.isDirectory()) { + if (!file.isFolder()) { fileSizeV.setVisibility(View.VISIBLE); fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.getFileLength())); lastModV.setVisibility(View.VISIBLE); @@ -195,14 +195,14 @@ public class FileListListAdapter extends BaseAdapter implements ListAdapter { * @param directory New file to adapt. Can be NULL, meaning "no content to adapt". * @param updatedStorageManager Optional updated storage manager; used to replace mStorageManager if is different (and not NULL) */ - public void swapDirectory(OCFile directory, DataStorageManager updatedStorageManager) { + public void swapDirectory(OCFile directory, FileDataStorageManager updatedStorageManager) { mFile = directory; if (updatedStorageManager != null && updatedStorageManager != mStorageManager) { mStorageManager = updatedStorageManager; mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext); } if (mStorageManager != null) { - mFiles = mStorageManager.getDirectoryContent(mFile); + mFiles = mStorageManager.getFolderContent(mFile); } else { mFiles = null; } diff --git a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java index d1269f58..ae040389 100644 --- a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java +++ b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java @@ -416,7 +416,7 @@ public class FileDetailFragment extends FileFragment implements private void renameFile() { OCFile file = getFile(); String fileName = file.getFileName(); - int extensionStart = file.isDirectory() ? -1 : fileName.lastIndexOf("."); + int extensionStart = file.isFolder() ? -1 : fileName.lastIndexOf("."); int selectionEnd = (extensionStart >= 0) ? extensionStart : fileName.length(); EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), fileName, 0, selectionEnd, this); dialog.show(getFragmentManager(), "nameeditdialog"); @@ -447,7 +447,7 @@ public class FileDetailFragment extends FileFragment implements } } else { - mLastRemoteOperation = new SynchronizeFileOperation(file, null, mStorageManager, mAccount, true, false, getActivity()); + mLastRemoteOperation = new SynchronizeFileOperation(file, null, mStorageManager, mAccount, true, getActivity()); mLastRemoteOperation.execute(mAccount, getSherlockActivity(), this, mHandler, getSherlockActivity()); // update ui @@ -836,10 +836,7 @@ public class FileDetailFragment extends FileFragment implements i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, mAccount); startActivity(i); - } else { - Toast msg = Toast.makeText(getActivity(), R.string.sync_file_fail_msg, Toast.LENGTH_LONG); - msg.show(); - } + } if (file.isDown()) { setButtonsForDown(); diff --git a/src/com/owncloud/android/ui/fragment/OCFileListFragment.java b/src/com/owncloud/android/ui/fragment/OCFileListFragment.java index be596eb1..2627f93a 100644 --- a/src/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/src/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -24,7 +24,7 @@ import java.util.List; import com.owncloud.android.Log_OC; import com.owncloud.android.R; import com.owncloud.android.authentication.AccountUtils; -import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.FileHandler; import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; @@ -121,24 +121,52 @@ public class OCFileListFragment extends ExtendedListFragment implements EditName /** - * Call this, when the user presses the up button + * Call this, when the user presses the up button. + * + * Tries to move up the current folder one level. If the parent folder was removed from the database, + * it continues browsing up until finding an existing folders. + * + * return Count of folder levels browsed up. */ - public void onBrowseUp() { + public int onBrowseUp() { OCFile parentDir = null; + int moveCount = 0; if(mFile != null){ - DataStorageManager storageManager = mContainerActivity.getStorageManager(); - parentDir = storageManager.getFileById(mFile.getParentId()); - mFile = parentDir; + FileDataStorageManager storageManager = mContainerActivity.getStorageManager(); + + String parentPath = null; + if (mFile.getParentId() != FileDataStorageManager.ROOT_PARENT_ID) { + parentPath = new File(mFile.getRemotePath()).getParent(); + parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR; + parentDir = storageManager.getFileByPath(parentPath); + moveCount++; + } else { + parentDir = storageManager.getFileByPath(OCFile.ROOT_PATH); // never returns null; keep the path in root folder + } + while (parentDir == null) { + parentPath = new File(parentPath).getParent(); + parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR; + parentDir = storageManager.getFileByPath(parentPath); + moveCount++; + } // exit is granted because storageManager.getFileByPath("/") never returns null + mFile = parentDir; } - listDirectory(parentDir); + + if (mFile != null) { + listDirectory(mFile); + + mContainerActivity.startSyncFolderOperation(mFile); + } // else - should never happen now + + return moveCount; } @Override public void onItemClick(AdapterView l, View v, int position, long id) { OCFile file = (OCFile) mAdapter.getItem(position); if (file != null) { - if (file.isDirectory()) { + if (file.isFolder()) { // update state and view of this fragment listDirectory(file); // then, notify parent activity to let it update its state and view, and other fragments @@ -185,7 +213,7 @@ public class OCFileListFragment extends ExtendedListFragment implements EditName List toDisable = new ArrayList(); MenuItem item = null; - if (targetFile.isDirectory()) { + if (targetFile.isFolder()) { // contextual menu for folders toHide.add(R.id.action_open_file_with); toHide.add(R.id.action_download_file); @@ -257,7 +285,7 @@ public class OCFileListFragment extends ExtendedListFragment implements EditName switch (item.getItemId()) { case R.id.action_rename_file: { String fileName = mTargetFile.getFileName(); - int extensionStart = mTargetFile.isDirectory() ? -1 : fileName.lastIndexOf("."); + int extensionStart = mTargetFile.isFolder() ? -1 : fileName.lastIndexOf("."); int selectionEnd = (extensionStart >= 0) ? extensionStart : fileName.length(); EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), fileName, 0, selectionEnd, this); dialog.show(getFragmentManager(), EditNameDialog.TAG); @@ -267,7 +295,7 @@ public class OCFileListFragment extends ExtendedListFragment implements EditName int messageStringId = R.string.confirmation_remove_alert; int posBtnStringId = R.string.confirmation_remove_remote; int neuBtnStringId = -1; - if (mTargetFile.isDirectory()) { + if (mTargetFile.isFolder()) { messageStringId = R.string.confirmation_remove_folder_alert; posBtnStringId = R.string.confirmation_remove_remote_and_local; neuBtnStringId = R.string.confirmation_remove_folder_local; @@ -287,7 +315,7 @@ public class OCFileListFragment extends ExtendedListFragment implements EditName } case R.id.action_sync_file: { Account account = AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()); - RemoteOperation operation = new SynchronizeFileOperation(mTargetFile, null, mContainerActivity.getStorageManager(), account, true, false, getSherlockActivity()); + RemoteOperation operation = new SynchronizeFileOperation(mTargetFile, null, mContainerActivity.getStorageManager(), account, true, getSherlockActivity()); operation.execute(account, getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity()); ((FileDisplayActivity) getSherlockActivity()).showLoadingDialog(); return true; @@ -346,7 +374,7 @@ public class OCFileListFragment extends ExtendedListFragment implements EditName * @param directory File to be listed */ public void listDirectory(OCFile directory) { - DataStorageManager storageManager = mContainerActivity.getStorageManager(); + FileDataStorageManager storageManager = mContainerActivity.getStorageManager(); if (storageManager != null) { // Check input parameters for null @@ -361,7 +389,7 @@ public class OCFileListFragment extends ExtendedListFragment implements EditName // If that's not a directory -> List its parent - if(!directory.isDirectory()){ + if(!directory.isFolder()){ Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString()); directory = storageManager.getFileById(directory.getParentId()); } @@ -371,7 +399,6 @@ public class OCFileListFragment extends ExtendedListFragment implements EditName mList.setSelectionFromTop(0, 0); } mFile = directory; - } } @@ -396,11 +423,13 @@ public class OCFileListFragment extends ExtendedListFragment implements EditName public void startMediaPreview(OCFile file, int i, boolean b); public void startImagePreview(OCFile file); + + public void startSyncFolderOperation(OCFile folder); /** * Getter for the current DataStorageManager in the container activity */ - public DataStorageManager getStorageManager(); + public FileDataStorageManager getStorageManager(); /** @@ -454,16 +483,7 @@ public class OCFileListFragment extends ExtendedListFragment implements EditName @Override public void onNeutral(String callerTag) { - File f = null; - if (mTargetFile.isDirectory()) { - // TODO run in a secondary thread? - mContainerActivity.getStorageManager().removeDirectory(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); } diff --git a/src/com/owncloud/android/ui/preview/PreviewImageActivity.java b/src/com/owncloud/android/ui/preview/PreviewImageActivity.java index 50cab7ee..4b3364a6 100644 --- a/src/com/owncloud/android/ui/preview/PreviewImageActivity.java +++ b/src/com/owncloud/android/ui/preview/PreviewImageActivity.java @@ -36,7 +36,6 @@ import com.actionbarsherlock.app.ActionBar; import com.actionbarsherlock.view.MenuItem; import com.actionbarsherlock.view.Window; import com.owncloud.android.authentication.AccountUtils; -import com.owncloud.android.datamodel.DataStorageManager; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileDownloader; @@ -67,7 +66,7 @@ public class PreviewImageActivity extends FileActivity implements FileFragment.C private static final String DIALOG_WAIT_TAG = "DIALOG_WAIT"; - private DataStorageManager mStorageManager; + private FileDataStorageManager mStorageManager; private ViewPager mViewPager; private PreviewImagePagerAdapter mPreviewImagePagerAdapter; @@ -109,7 +108,7 @@ public class PreviewImageActivity extends FileActivity implements FileFragment.C //OCFile parentFolder = mStorageManager.getFileById(getFile().getParentId()); if (parentFolder == null) { // should not be necessary - parentFolder = mStorageManager.getFileByPath(OCFile.PATH_SEPARATOR); + parentFolder = mStorageManager.getFileByPath(OCFile.ROOT_PATH); } mPreviewImagePagerAdapter = new PreviewImagePagerAdapter(getSupportFragmentManager(), parentFolder, getAccount(), mStorageManager); mViewPager = (ViewPager) findViewById(R.id.fragmentPager); @@ -434,7 +433,7 @@ public class PreviewImageActivity extends FileActivity implements FileFragment.C mStorageManager = new FileDataStorageManager(getAccount(), getContentResolver()); // Update file according to DB file, if it is possible - if (file.getFileId() > DataStorageManager.ROOT_PARENT_ID) + if (file.getFileId() > FileDataStorageManager.ROOT_PARENT_ID) file = mStorageManager.getFileById(file.getFileId()); if (file != null) { diff --git a/src/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java b/src/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java index 3db62e96..6ef06189 100644 --- a/src/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java +++ b/src/com/owncloud/android/ui/preview/PreviewImagePagerAdapter.java @@ -28,7 +28,7 @@ import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentStatePagerAdapter; import android.view.ViewGroup; -import com.owncloud.android.datamodel.DataStorageManager; +import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.ui.fragment.FileFragment; @@ -45,7 +45,7 @@ public class PreviewImagePagerAdapter extends FragmentStatePagerAdapter { private Set mObsoleteFragments; private Set mObsoletePositions; private Set mDownloadErrors; - private DataStorageManager mStorageManager; + private FileDataStorageManager mStorageManager; private Map mCachedFragments; @@ -56,7 +56,7 @@ public class PreviewImagePagerAdapter extends FragmentStatePagerAdapter { * @param parentFolder Folder where images will be searched for. * @param storageManager Bridge to database. */ - public PreviewImagePagerAdapter(FragmentManager fragmentManager, OCFile parentFolder, Account account, DataStorageManager storageManager) { + public PreviewImagePagerAdapter(FragmentManager fragmentManager, OCFile parentFolder, Account account, FileDataStorageManager storageManager) { super(fragmentManager); if (fragmentManager == null) { @@ -71,7 +71,7 @@ public class PreviewImagePagerAdapter extends FragmentStatePagerAdapter { mAccount = account; mStorageManager = storageManager; - mImageFiles = mStorageManager.getDirectoryImages(parentFolder); + mImageFiles = mStorageManager.getFolderImages(parentFolder); mObsoleteFragments = new HashSet(); mObsoletePositions = new HashSet(); mDownloadErrors = new HashSet(); diff --git a/src/com/owncloud/android/ui/preview/PreviewVideoActivity.java b/src/com/owncloud/android/ui/preview/PreviewVideoActivity.java index 0cab2e2b..47441ffb 100644 --- a/src/com/owncloud/android/ui/preview/PreviewVideoActivity.java +++ b/src/com/owncloud/android/ui/preview/PreviewVideoActivity.java @@ -32,7 +32,6 @@ import android.widget.VideoView; import com.owncloud.android.Log_OC; import com.owncloud.android.R; -import com.owncloud.android.datamodel.DataStorageManager; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.authentication.AccountUtils.AccountNotFoundException; @@ -60,7 +59,7 @@ public class PreviewVideoActivity extends FileActivity implements OnCompletionLi private static final String TAG = PreviewVideoActivity.class.getSimpleName(); - private DataStorageManager mStorageManager; + private FileDataStorageManager mStorageManager; private int mSavedPlaybackPosition; // in the unit time handled by MediaPlayer.getCurrentPosition() private boolean mAutoplay; // when 'true', the playback starts immediately with the activity diff --git a/src/eu/alefzero/webdav/WebdavClient.java b/src/eu/alefzero/webdav/WebdavClient.java index f25e3906..320bc526 100644 --- a/src/eu/alefzero/webdav/WebdavClient.java +++ b/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) { diff --git a/src/eu/alefzero/webdav/WebdavEntry.java b/src/eu/alefzero/webdav/WebdavEntry.java index 46923c66..665a850c 100644 --- a/src/eu/alefzero/webdav/WebdavEntry.java +++ b/src/eu/alefzero/webdav/WebdavEntry.java @@ -28,7 +28,7 @@ import com.owncloud.android.Log_OC; import android.net.Uri; public class WebdavEntry { - private String mName, mPath, mUri, mContentType; + private String mName, mPath, mUri, mContentType, mEtag; private long mContentLength, mCreateTimestamp, mModifiedTimestamp; public WebdavEntry(MultiStatusResponse ms, String splitElement) { @@ -42,8 +42,10 @@ public class WebdavEntry { DavPropertySet propSet = ms.getProperties(status); @SuppressWarnings("rawtypes") DavProperty prop = propSet.get(DavPropertyName.DISPLAYNAME); - if (prop != null) + if (prop != null) { mName = (String) prop.getName().toString(); + mName = mName.substring(1, mName.length()-1); + } else { String[] tmp = mPath.split("/"); if (tmp.length > 0) @@ -87,6 +89,12 @@ public class WebdavEntry { .parseResponseDate((String) prop.getValue()); mCreateTimestamp = (d != null) ? d.getTime() : 0; } + + prop = propSet.get(DavPropertyName.GETETAG); + if (prop != null) { + mEtag = (String) prop.getValue(); + mEtag = mEtag.substring(1, mEtag.length()-1); + } } else { Log_OC.e("WebdavEntry", @@ -129,6 +137,10 @@ public class WebdavEntry { public long modifiedTimestamp() { return mModifiedTimestamp; } + + public String etag() { + return mEtag; + } private void resetData() { mName = mUri = mContentType = null;