From: David A. Velasco Date: Tue, 8 Oct 2013 06:52:14 +0000 (+0200) Subject: Merge branch 'develop' into refresh_folder_contents_when_browsed_into X-Git-Tag: oc-android-1.5.5~155^2~23 X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/commitdiff_plain/d7c4d1248587e87ad5320c6f1df66e8d422ea3aa?hp=a67a6766a6ca69bb97d2fdc87256ec9f0fa5122d Merge branch 'develop' into refresh_folder_contents_when_browsed_into --- diff --git a/res/values/strings.xml b/res/values/strings.xml index 0872084a..f36f7d7d 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. + Current folder does not exist "Move all" "All files were moved" "Some files could not be moved" diff --git a/src/com/owncloud/android/datamodel/FileDataStorageManager.java b/src/com/owncloud/android/datamodel/FileDataStorageManager.java index 5f6321d8..1c9ff7a5 100644 --- a/src/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/src/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -126,7 +126,7 @@ public class FileDataStorageManager implements DataStorageManager { cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, file.getFileLength()); cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimetype()); cv.put(ProviderTableMeta.FILE_NAME, file.getFileName()); - if (file.getParentId() != 0) + //if (file.getParentId() != DataStorageManager.ROOT_PARENT_ID) cv.put(ProviderTableMeta.FILE_PARENT, file.getParentId()); cv.put(ProviderTableMeta.FILE_PATH, file.getRemotePath()); if (!file.isDirectory()) @@ -135,6 +135,7 @@ public class FileDataStorageManager implements DataStorageManager { 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()); boolean sameRemotePath = fileExists(file.getRemotePath()); boolean changesSizeOfAncestors = false; @@ -222,7 +223,7 @@ public class FileDataStorageManager implements DataStorageManager { cv.put(ProviderTableMeta.FILE_CONTENT_LENGTH, file.getFileLength()); cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimetype()); cv.put(ProviderTableMeta.FILE_NAME, file.getFileName()); - if (file.getParentId() != 0) + if (file.getParentId() != DataStorageManager.ROOT_PARENT_ID) cv.put(ProviderTableMeta.FILE_PARENT, file.getParentId()); cv.put(ProviderTableMeta.FILE_PATH, file.getRemotePath()); if (!file.isDirectory()) @@ -231,6 +232,7 @@ public class FileDataStorageManager implements DataStorageManager { 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()); @@ -472,6 +474,8 @@ 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; } @@ -674,7 +678,7 @@ public class FileDataStorageManager implements DataStorageManager { OCFile file; - while (parentId != 0) { + while (parentId != DataStorageManager.ROOT_PARENT_ID) { // Update the size of the parent calculateFolderSize(parentId); diff --git a/src/com/owncloud/android/datamodel/OCFile.java b/src/com/owncloud/android/datamodel/OCFile.java index 6b8e1785..87a38a69 100644 --- a/src/com/owncloud/android/datamodel/OCFile.java +++ b/src/com/owncloud/android/datamodel/OCFile.java @@ -60,6 +60,7 @@ public class OCFile implements Parcelable, Comparable { private String mEtag; + /** * Create new {@link OCFile} with given path. * @@ -95,6 +96,7 @@ public class OCFile implements Parcelable, Comparable { mKeepInSync = source.readInt() == 1; mLastSyncDateForProperties = source.readLong(); mLastSyncDateForData = source.readLong(); + mEtag = source.readString(); } @Override @@ -112,6 +114,7 @@ public class OCFile implements Parcelable, Comparable { dest.writeInt(mKeepInSync ? 1 : 0); dest.writeLong(mLastSyncDateForProperties); dest.writeLong(mLastSyncDateForData); + dest.writeString(mEtag); } /** @@ -319,6 +322,7 @@ public class OCFile implements Parcelable, Comparable { mLastSyncDateForData = 0; mKeepInSync = false; mNeedsUpdating = false; + mEtag = null; } /** @@ -439,8 +443,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 +452,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/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/RemoteOperationResult.java b/src/com/owncloud/android/operations/RemoteOperationResult.java index 999974cf..ca88210d 100644 --- a/src/com/owncloud/android/operations/RemoteOperationResult.java +++ b/src/com/owncloud/android/operations/RemoteOperationResult.java @@ -60,6 +60,7 @@ public class RemoteOperationResult implements Serializable { OK, OK_SSL, OK_NO_SSL, + OK_NO_CHANGES_ON_DIR, UNHANDLED_HTTP_CODE, UNAUTHORIZED, FILE_NOT_FOUND, @@ -98,7 +99,7 @@ public class RemoteOperationResult implements Serializable { public RemoteOperationResult(ResultCode code) { mCode = code; - mSuccess = (code == ResultCode.OK || code == ResultCode.OK_SSL || code == ResultCode.OK_NO_SSL); + mSuccess = (code == ResultCode.OK || code == ResultCode.OK_SSL || code == ResultCode.OK_NO_SSL || code == ResultCode.OK_NO_CHANGES_ON_DIR); } private RemoteOperationResult(boolean success, int httpCode) { diff --git a/src/com/owncloud/android/operations/SynchronizeFileOperation.java b/src/com/owncloud/android/operations/SynchronizeFileOperation.java index 5be116bc..cb4485e2 100644 --- a/src/com/owncloud/android/operations/SynchronizeFileOperation.java +++ b/src/com/owncloud/android/operations/SynchronizeFileOperation.java @@ -215,6 +215,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 67c7389e..433a0908 100644 --- a/src/com/owncloud/android/operations/SynchronizeFolderOperation.java +++ b/src/com/owncloud/android/operations/SynchronizeFolderOperation.java @@ -28,18 +28,19 @@ 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.MultiStatus; 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.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; @@ -48,51 +49,78 @@ 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; + /** 'True' means that the properties of the folder should be updated also, not just its content */ + private boolean mUpdateFolderProperties; + /** Access to the local database */ private DataStorageManager 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; - 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, + boolean updateFolderProperties, + boolean syncFullAccount, DataStorageManager dataStorageManager, Account account, Context context ) { - mRemotePath = remotePath; + mLocalFolder = folder; mCurrentSyncTime = currentSyncTime; - mParentId = parentId; + mUpdateFolderProperties = updateFolderProperties; + mSyncFullAccount = syncFullAccount; mStorageManager = dataStorageManager; mAccount = account; mContext = context; @@ -115,142 +143,58 @@ 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; mFailsInFavouritesFound = 0; mConflictsFound = 0; mForgottenLocalFiles.clear(); - - // code before in FileSyncAdapter.fetchData + 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, "Synchronizing " + mAccount.name + remotePath); + // remote request - query = new PropFindMethod(client.getBaseUri() + WebdavUtils.encodePath(mRemotePath)); + query = new PropFindMethod(client.getBaseUri() + WebdavUtils.encodePath(remotePath)); 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); - } - - 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); - - // 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); + // check and process response + if (isMultiStatus(status)) { + boolean folderChanged = synchronizeData(query.getResponseBodyAsMultiStatus(), client); + if (folderChanged) { + if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) { + result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT); // should be different result, but will do the job } else { - i++; + result = new RemoteOperationResult(true, status, query.getResponseHeaders()); } + } else { + result = new RemoteOperationResult(ResultCode.OK_NO_CHANGES_ON_DIR); } } else { + // synchronization failed client.exhaustResponse(query.getResponseBodyAsStream()); - } - - // prepare result object - if (isMultiStatus(status)) { - 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()); + if (status == HttpStatus.SC_NOT_FOUND) { + if (mStorageManager.fileExists(mLocalFolder.getFileId())) { + String currentSavePath = FileStorageUtils.getSavePath(mAccount.name); + mStorageManager.removeDirectory(mLocalFolder, true, (mLocalFolder.isDown() && mLocalFolder.getStoragePath().startsWith(currentSavePath))); + } } - } else { result = new RemoteOperationResult(false, status, query.getResponseHeaders()); } - - - + } catch (Exception e) { result = new RemoteOperationResult(e); @@ -259,19 +203,172 @@ 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, "Synchroned " + mAccount.name + remotePath + ": " + result.getLogMessage(), result.getException()); } else { - Log_OC.e(TAG, "Synchronizing " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage()); + Log_OC.e(TAG, "Synchroned " + mAccount.name + remotePath + ": " + result.getLogMessage()); } } + + if (!mSyncFullAccount) { + sendStickyBroadcast(false, remotePath, result); + } } - + return result; } - + + + /** + * 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 boolean 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()); + + // check if remote and local folder are different + boolean folderChanged = !(remoteFolder.getEtag().equalsIgnoreCase(mLocalFolder.getEtag())); + + if (!folderChanged) { + if (mUpdateFolderProperties) { // TODO check if this is really necessary + mStorageManager.saveFile(remoteFolder); + } + + mChildren = mStorageManager.getDirectoryContent(mLocalFolder); + + } else { + // read info of folder contents + List updatedFiles = new Vector(dataInServer.getResponses().length - 1); + List filesToSyncContents = new Vector(); + + // 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()); + + /// add to the remoteFile (the new one) data about LOCAL STATE (not existing in the server side) + remoteFile.setLastSyncDateForProperties(mCurrentSyncTime); + if (localFile != null) { + // properties of local state are kept unmodified + 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) + } 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 need, 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, + false, + 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.saveFiles(updatedFiles); + + // 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); + + } + + return folderChanged; + + } + + + /** + * Removes obsolete children in the folder after saving all the new data. + */ + private void removeObsoleteFiles() { + mChildren = mStorageManager.getDirectoryContent(mLocalFolder); + 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++; + } + } + } + + + /** + * 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); @@ -290,7 +387,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; } @@ -301,7 +398,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) { @@ -359,4 +456,41 @@ 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.isDirectory()) { + 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); + } + } diff --git a/src/com/owncloud/android/providers/FileContentProvider.java b/src/com/owncloud/android/providers/FileContentProvider.java index bd2b8072..cc70d631 100644 --- a/src/com/owncloud/android/providers/FileContentProvider.java +++ b/src/com/owncloud/android/providers/FileContentProvider.java @@ -78,6 +78,8 @@ 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; @@ -229,7 +231,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 +286,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..26bfff7e 100644 --- a/src/com/owncloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java +++ b/src/com/owncloud/android/syncadapter/AbstractOwnCloudSyncAdapter.java @@ -19,14 +19,14 @@ 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; @@ -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,7 +57,7 @@ public abstract class AbstractOwnCloudSyncAdapter extends private AccountManager accountManager; private Account account; private ContentProviderClient contentProvider; - private Date lastUpdated; + //private Date lastUpdated; private DataStorageManager mStoreManager; private WebdavClient mClient = null; @@ -89,14 +91,6 @@ public abstract class AbstractOwnCloudSyncAdapter extends this.contentProvider = contentProvider; } - public Date getLastUpdated() { - return lastUpdated; - } - - public void setLastUpdated(Date lastUpdated) { - this.lastUpdated = lastUpdated; - } - public void setStorageManager(DataStorageManager storage_manager) { mStoreManager = storage_manager; } @@ -105,6 +99,41 @@ public abstract class AbstractOwnCloudSyncAdapter extends return mStoreManager; } + 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; + } + + /* methods never used below */ + /* + public Date getLastUpdated() { + return lastUpdated; + } + + public void setLastUpdated(Date lastUpdated) { + this.lastUpdated = lastUpdated; + } + protected ConnectionKeepAliveStrategy getKeepAliveStrategy() { return new ConnectionKeepAliveStrategy() { public long getKeepAliveDuration(HttpResponse response, @@ -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..4b40ff49 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.PATH_SEPARATOR), true); } 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,38 @@ 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. + * @param updateFolderProperties When 'true', updates also the properties of the of the target folder. */ - private void fetchData(String remotePath, long parentId) { + private void synchronizeFolder(OCFile folder, boolean updateFolderProperties) { 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, + updateFolderProperties, + true, getStorageManager(), getAccount(), getContext() @@ -206,8 +249,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) { @@ -221,11 +265,13 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { List children = synchFolderOp.getChildren(); fetchChildren(children); // beware of the 'hidden' recursion here! - sendStickyBroadcast(true, remotePath, null); + // update folder size again after recursive synchronization + getStorageManager().calculateFolderSize(folder.getFileId()); + sendStickyBroadcast(true, folder.getRemotePath(), null); // notify again } 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 +307,20 @@ 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) { int i; 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()); + synchronizeFolder(newFile, false); } } - if (mCancellation && i (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"); } @@ -458,8 +464,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; } @@ -602,6 +609,7 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa setFile(listOfFiles.getCurrentFile()); } cleanSecondFragment(); + } @Override @@ -610,6 +618,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"); } @@ -852,38 +862,45 @@ 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); + RemoteOperationResult synchResult = (RemoteOperationResult)intent.getSerializableExtra(FileSyncService.SYNC_RESULT); Log_OC.d(TAG, "sync of account " + accountName + " is in_progress: " + inProgress); - + if (getAccount() != null && accountName.equals(getAccount().name)) { String synchFolderRemotePath = intent.getStringExtra(FileSyncService.SYNC_FOLDER_REMOTE_PATH); - boolean fillBlankRoot = false; - OCFile currentDir = getCurrentDir(); - if (currentDir == null) { - currentDir = mStorageManager.getFileByPath(OCFile.PATH_SEPARATOR); - fillBlankRoot = (currentDir != null); - } + OCFile currentFile = mStorageManager.getFileById(getFile().getFileId()); + OCFile currentDir = mStorageManager.getFileById(getCurrentDir().getFileId()); - if ((synchFolderRemotePath != null && currentDir != null && (currentDir.getRemotePath().equals(synchFolderRemotePath))) - || fillBlankRoot ) { - if (!fillBlankRoot) - currentDir = getStorageManager().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, getString(R.string.sync_current_folder_was_removed), Toast.LENGTH_LONG) + .show(); + onBackPressed(); + + } else { + if (currentFile == null && !getFile().isDirectory()) { + // 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; @@ -892,7 +909,7 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa } } } - + private class UploadFinishReceiver extends BroadcastReceiver { /** @@ -966,6 +983,10 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa public void onBrowsedDownTo(OCFile directory) { pushDirname(directory); cleanSecondFragment(); + + // Sync Folder + startSyncFolderOperation(directory); + } /** @@ -1054,6 +1075,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} */ @@ -1174,7 +1208,8 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa } else if (operation instanceof CreateFolderOperation) { onCreateFolderOperationFinish((CreateFolderOperation)operation, result); - } + + } } @@ -1222,7 +1257,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); @@ -1283,10 +1317,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,5 +1394,36 @@ 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, + 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/fragment/FileDetailFragment.java b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java index 5380a883..b5f1186d 100644 --- a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java +++ b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java @@ -829,10 +829,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..58ad639f 100644 --- a/src/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/src/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -128,10 +128,22 @@ public class OCFileListFragment extends ExtendedListFragment implements EditName if(mFile != null){ DataStorageManager storageManager = mContainerActivity.getStorageManager(); - parentDir = storageManager.getFileById(mFile.getParentId()); - mFile = parentDir; + if (mFile.getParentId() == 0) { + parentDir = storageManager.getFileById(1); + } + else { + parentDir = storageManager.getFileById(mFile.getParentId()); + } + + mFile = parentDir; } - listDirectory(parentDir); + + if (mFile != null) { + listDirectory(mFile); + + mContainerActivity.startSyncFolderOperation(mFile); + } + } @Override @@ -371,7 +383,6 @@ public class OCFileListFragment extends ExtendedListFragment implements EditName mList.setSelectionFromTop(0, 0); } mFile = directory; - } } @@ -396,6 +407,8 @@ 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 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;