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() != DataStorageManager.ROOT_PARENT_ID)
+ //if (file.getParentId() != DataStorageManager.ROOT_PARENT_ID)
cv.put(ProviderTableMeta.FILE_PARENT, file.getParentId());
cv.put(ProviderTableMeta.FILE_PATH, file.getRemotePath());
if (!file.isDirectory())
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
- * 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;
- /** Boolean to indicate if is mandatory to update the folder */
- private boolean mEnforceMetadataUpdate;
+ /** '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<OCFile> 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<String, String> 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 enforceMetadataUpdate,
+ boolean updateFolderProperties,
boolean syncFullAccount,
DataStorageManager dataStorageManager,
Account account,
Context context ) {
- mRemotePath = remotePath;
+ mLocalFolder = folder;
mCurrentSyncTime = currentSyncTime;
- mParentId = parentId;
- mEnforceMetadataUpdate = enforceMetadataUpdate;
+ mUpdateFolderProperties = updateFolderProperties;
mSyncFullAccount = syncFullAccount;
mStorageManager = dataStorageManager;
mAccount = account;
/**
* 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<OCFile> getChildren() {
return mChildren;
}
- public String getRemotePath() {
- return mRemotePath;
- }
-
- public long getParentId() {
- return mParentId;
- }
-
+ /**
+ * Performs the synchronization.
+ *
+ * {@inheritDoc}
+ */
@Override
protected RemoteOperationResult run(WebdavClient client) {
RemoteOperationResult result = null;
mFailsInFavouritesFound = 0;
mConflictsFound = 0;
mForgottenLocalFiles.clear();
- boolean dirChanged = false;
-
- // 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
- WebdavEntry we = new WebdavEntry(resp.getResponses()[0], client.getBaseUri().getPath());
-
- // Properties of server folder
- OCFile parent = fillOCFile(we);
- // Properties of local folder
- OCFile localParent = mStorageManager.getFileByPath(mRemotePath);
- if (localParent == null || !(parent.getEtag().equalsIgnoreCase(localParent.getEtag())) || mEnforceMetadataUpdate) {
- if (localParent != null) {
- parent.setParentId(localParent.getParentId());
- }
- mStorageManager.saveFile(parent);
- if (mParentId == DataStorageManager.ROOT_PARENT_ID)
- mParentId = parent.getFileId();
- dirChanged = true;
- }
-
- if (dirChanged) {
- // read contents in folder
- List<String> filesOnServer = new ArrayList<String> (); // Contains the lists of files on server
- List<OCFile> updatedFiles = new Vector<OCFile>(resp.getResponses().length - 1);
- List<SynchronizeFileOperation> filesToSyncContents = new Vector<SynchronizeFileOperation>();
- for (int i = 1; i < resp.getResponses().length; ++i) {
- /// new OCFile instance with the data from the server
- we = new WebdavEntry(resp.getResponses()[i], client.getBaseUri().getPath());
- OCFile file = fillOCFile(we);
-
- filesOnServer.add(file.getRemotePath()); // Registry the file in the list
-
- /// set data about local state, keeping unchanged former data if existing
- file.setLastSyncDateForProperties(mCurrentSyncTime);
- OCFile oldFile = mStorageManager.getFileByPath(file.getRemotePath());
-
- // Check if it is needed to synchronize the folder
- if (oldFile != null) {
- if (!file.getEtag().equalsIgnoreCase(oldFile.getEtag())) {
- }
- }
-
- 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());
- if (file.isDirectory())
- file.setEtag(oldFile.getEtag());
- } else
- if (file.isDirectory())
- file.setEtag("");
-
- /// 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
- 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
- }
-
- // 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++;
- }
- }
-
- } else {
- client.exhaustResponse(query.getResponseBodyAsStream());
- }
-
-
- // prepare result object
- if (!dirChanged) {
- result = new RemoteOperationResult(ResultCode.OK_NO_CHANGES_ON_DIR);
- mChildren = mStorageManager.getDirectoryContent(mStorageManager.getFileById(mParentId));
-
- } else {
+ // 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 {
result = new RemoteOperationResult(true, status, query.getResponseHeaders());
}
+ } else {
+ result = new RemoteOperationResult(ResultCode.OK_NO_CHANGES_ON_DIR);
}
} else {
+ // synchronization failed
+ client.exhaustResponse(query.getResponseBodyAsStream());
if (status == HttpStatus.SC_NOT_FOUND) {
- OCFile dir = mStorageManager.getFileByPath(mRemotePath);
- if (dir != null) {
+ if (mStorageManager.fileExists(mLocalFolder.getFileId())) {
String currentSavePath = FileStorageUtils.getSavePath(mAccount.name);
- mStorageManager.removeDirectory(dir, true, (dir.isDown() && dir.getStoragePath().startsWith(currentSavePath)));
+ mStorageManager.removeDirectory(mLocalFolder, true, (mLocalFolder.isDown() && mLocalFolder.getStoragePath().startsWith(currentSavePath)));
}
}
result = new RemoteOperationResult(false, status, query.getResponseHeaders());
if (query != null)
query.releaseConnection(); // let the connection available for other methods
if (result.isSuccess()) {
- Log_OC.i(TAG, "Synchroned " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage());
+ Log_OC.i(TAG, "Synchronized " + mAccount.name + remotePath + ": " + result.getLogMessage());
} else {
if (result.isException()) {
- Log_OC.e(TAG, "Synchroned " + 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, "Synchroned " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage());
+ Log_OC.e(TAG, "Synchroned " + mAccount.name + remotePath + ": " + result.getLogMessage());
}
}
if (!mSyncFullAccount) {
- sendStickyBroadcast(false, mRemotePath, result);
+ sendStickyBroadcast(false, remotePath, 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<OCFile> updatedFiles = new Vector<OCFile>(dataInServer.getResponses().length - 1);
+ List<SynchronizeFileOperation> filesToSyncContents = new Vector<SynchronizeFileOperation>();
+
+ // 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<SynchronizeFileOperation> 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);
}
file.setFileLength(we.contentLength());
file.setMimetype(we.contentType());
file.setModificationTimestamp(we.modifiedTimestamp());
- file.setParentId(mParentId);
file.setEtag(we.etag());
return file;
}
}
}
+
+ /**
+ * 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.
*
package com.owncloud.android.syncadapter;\r
\r
import java.io.IOException;\r
-import java.net.UnknownHostException;\r
-import java.util.Date;\r
+//import java.net.UnknownHostException;\r
+//import java.util.Date;\r
\r
import org.apache.http.HttpRequest;\r
import org.apache.http.HttpResponse;\r
import org.apache.http.client.ClientProtocolException;\r
-import org.apache.http.conn.ConnectionKeepAliveStrategy;\r
-import org.apache.http.protocol.HttpContext;\r
+//import org.apache.http.conn.ConnectionKeepAliveStrategy;\r
+//import org.apache.http.protocol.HttpContext;\r
\r
import com.owncloud.android.authentication.AccountUtils;\r
import com.owncloud.android.authentication.AccountUtils.AccountNotFoundException;\r
import eu.alefzero.webdav.WebdavClient;\r
\r
/**\r
- * Base SyncAdapter for OwnCloud Designed to be subclassed for the concrete\r
- * SyncAdapter, like ConcatsSync, CalendarSync, FileSync etc..\r
+ * Base synchronization adapter for ownCloud designed to be subclassed for different\r
+ * resource types, like FileSync, ConcatsSync, CalendarSync, etc..\r
* \r
- * @author sassman\r
+ * Implements the standard {@link AbstractThreadedSyncAdapter}.\r
* \r
+ * @author sassman\r
+ * @author David A. Velasco\r
*/\r
public abstract class AbstractOwnCloudSyncAdapter extends\r
AbstractThreadedSyncAdapter {\r
private AccountManager accountManager;\r
private Account account;\r
private ContentProviderClient contentProvider;\r
- private Date lastUpdated;\r
+ //private Date lastUpdated;\r
private DataStorageManager mStoreManager;\r
\r
private WebdavClient mClient = null;\r
this.contentProvider = contentProvider;\r
}\r
\r
- public Date getLastUpdated() {\r
- return lastUpdated;\r
- }\r
-\r
- public void setLastUpdated(Date lastUpdated) {\r
- this.lastUpdated = lastUpdated;\r
- }\r
-\r
public void setStorageManager(DataStorageManager storage_manager) {\r
mStoreManager = storage_manager;\r
}\r
return mStoreManager;\r
}\r
\r
+ protected void initClientForCurrentAccount() throws OperationCanceledException, AuthenticatorException, IOException, AccountNotFoundException {\r
+ AccountUtils.constructFullURLForAccount(getContext(), account);\r
+ mClient = OwnCloudClientUtils.createOwnCloudClient(account, getContext());\r
+ }\r
+ \r
+ protected WebdavClient getClient() {\r
+ return mClient;\r
+ }\r
+ \r
+ \r
+ /* method called by ContactSyncAdapter, that is never used */\r
+ protected HttpResponse fireRawRequest(HttpRequest query)\r
+ throws ClientProtocolException, OperationCanceledException,\r
+ AuthenticatorException, IOException {\r
+ /*\r
+ * BasicHttpContext httpContext = new BasicHttpContext(); BasicScheme\r
+ * basicAuth = new BasicScheme();\r
+ * httpContext.setAttribute("preemptive-auth", basicAuth);\r
+ * \r
+ * HttpResponse response = getClient().execute(mHost, query,\r
+ * httpContext);\r
+ */\r
+ return null;\r
+ }\r
+\r
+ /* methods never used below */\r
+ /*\r
+ public Date getLastUpdated() {\r
+ return lastUpdated;\r
+ }\r
+\r
+ public void setLastUpdated(Date lastUpdated) {\r
+ this.lastUpdated = lastUpdated;\r
+ }\r
+\r
protected ConnectionKeepAliveStrategy getKeepAliveStrategy() {\r
return new ConnectionKeepAliveStrategy() {\r
public long getKeepAliveDuration(HttpResponse response,\r
}\r
};\r
}\r
-\r
- protected HttpResponse fireRawRequest(HttpRequest query)\r
- throws ClientProtocolException, OperationCanceledException,\r
- AuthenticatorException, IOException {\r
- /*\r
- * BasicHttpContext httpContext = new BasicHttpContext(); BasicScheme\r
- * basicAuth = new BasicScheme();\r
- * httpContext.setAttribute("preemptive-auth", basicAuth);\r
- * \r
- * HttpResponse response = getClient().execute(mHost, query,\r
- * httpContext);\r
- */\r
- return null;\r
- }\r
-\r
- protected void initClientForCurrentAccount() throws OperationCanceledException, AuthenticatorException, IOException, AccountNotFoundException {\r
- AccountUtils.constructFullURLForAccount(getContext(), account);\r
- mClient = OwnCloudClientUtils.createOwnCloudClient(account, getContext());\r
- }\r
- \r
- protected WebdavClient getClient() {\r
- return mClient;\r
- }\r
+ */\r
}
\ No newline at end of file
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;
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;
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<String, String> 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}
*/
mForgottenLocalFiles = new HashMap<String, String>();
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;
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");
}
/// 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
}
/**
* 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() {
/**
- * 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;
- boolean enforceMetadataUpdate = (parentId == DataStorageManager.ROOT_PARENT_ID);
-
- // 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,
- enforceMetadataUpdate,
+ updateFolderProperties,
true,
getStorageManager(),
getAccount(),
// 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) {
List<OCFile> 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++;
}
/**
- * 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<OCFile> 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 <files.size()) Log_OC.d(TAG, "Leaving synchronization before synchronizing " + files.get(i).getRemotePath() + " because cancelation request");
+ if (mCancellation && i <files.size()) Log_OC.d(TAG, "Leaving synchronization before synchronizing " + files.get(i).getRemotePath() + " due to cancelation request");
}
import android.os.IBinder;\r
\r
/**\r
- * Background service for syncing files to our local Database\r
+ * Background service for synchronizing remote files with their local state.\r
* \r
- * @author Bartek Przybylski\r
+ * Serves as a connector to an instance of {@link FileSyncAdapter}, as required by standard Android APIs. \r
* \r
+ * @author Bartek Przybylski\r
+ * @author David A. Velasco\r
*/\r
public class FileSyncService extends Service {\r
+ \r
public static final String SYNC_MESSAGE = "ACCOUNT_SYNC";\r
public static final String SYNC_FOLDER_REMOTE_PATH = "SYNC_FOLDER_REMOTE_PATH";\r
public static final String IN_PROGRESS = "SYNC_IN_PROGRESS";\r
public IBinder onBind(Intent intent) {\r
return new FileSyncAdapter(getApplicationContext(), true).getSyncAdapterBinder();\r
}\r
+ \r
}\r
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);
- RemoteOperationResult synchResult = (RemoteOperationResult)intent.getSerializableExtra(FileSyncService.SYNC_RESULT);
-
if (getAccount() != null && accountName.equals(getAccount().name)) {
String synchFolderRemotePath = intent.getStringExtra(FileSyncService.SYNC_FOLDER_REMOTE_PATH);
- /*
- boolean fillBlankRoot = false;
- 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 (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 (synchFolderRemotePath != null && currentDir.getRemotePath().equals(synchFolderRemotePath)) {
-
- /*OCFile synchDir = getStorageManager().getFileByPath(synchFolderRemotePath);
- //boolean needToRefresh = false;
- if (synchDir == null) {
- // after synchronizing the current folder does not exist (was deleted in the server) ; need to move to other
- /*
- String synchPath = synchFolderRemotePath;
- do {
- String synchParentPath = new File(synchPath).getParent();
- synchParentPath = synchParentPath.endsWith(OCFile.PATH_SEPARATOR) ? synchParentPath : synchParentPath + OCFile.PATH_SEPARATOR;
- synchDir = getStorageManager().getFileByPath(synchParentPath);
- popDirname();
- synchPath = synchParentPath;
- } while (synchDir == null); // sooner of later will get ROOT, that never is null
- currentDir = synchDir;
- *-/
- Toast.makeText(FileDisplayActivity.this,
- String.format(getString(R.string.sync_current_folder_was_removed), "LOLO"),
- Toast.LENGTH_LONG).show();
- //needToRefresh = true;
- onBackPressed();
- } else {*/
-
- OCFileListFragment fileListFragment = getListOfFilesFragment();
- if (fileListFragment != null) {
- fileListFragment.listDirectory(currentDir);
+ } else {
+ if (currentFile == null && !getFile().isDirectory()) {
+ // currently selected file was removed in the server, and now we know it
+ cleanSecondFragment();
+ currentFile = currentDir;
}
- //boolean existsSecondFragment = (getSecondFragment() != null);
- //if (!existsSecondFragment) {
- if (getSecondFragment() == null) {
- setFile(currentDir);
+
+ if (synchFolderRemotePath != null && currentDir.getRemotePath().equals(synchFolderRemotePath)) {
+ OCFileListFragment fileListFragment = getListOfFilesFragment();
+ if (fileListFragment != null) {
+ fileListFragment.listDirectory(currentDir);
+ }
}
- //updateFragmentsVisibility(existsSecondFragment);
- //updateNavigationElementsInActionBar(existsSecondFragment ? getFile() : null);
+ setFile(currentFile);
}
setSupportProgressBarIndeterminateVisibility(inProgress);
removeStickyBroadcast(intent);
-
mSyncInProgress = inProgress;
}
-
if (synchResult != null) {
if (synchResult.getCode().equals(RemoteOperationResult.ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED)) {
cleanSecondFragment();
// Sync Folder
- startSyncFolderOperation(directory.getRemotePath(), directory.getFileId());
+ startSyncFolderOperation(directory);
}
return null;
}
- public void startSyncFolderOperation(String remotePath, long parentId) {
+ public void startSyncFolderOperation(OCFile folder) {
long currentSyncTime = System.currentTimeMillis();
mSyncInProgress = true;
// perform folder synchronization
- RemoteOperation synchFolderOp = new SynchronizeFolderOperation( remotePath,
- currentSyncTime,
- parentId,
- false,
- false,
- getStorageManager(),
- getAccount(),
- getApplicationContext()
- );
+ RemoteOperation synchFolderOp = new SynchronizeFolderOperation( folder,
+ currentSyncTime,
+ false,
+ false,
+ getStorageManager(),
+ getAccount(),
+ getApplicationContext()
+ );
synchFolderOp.execute(getAccount(), this, null, null, this);
setSupportProgressBarIndeterminateVisibility(true);
if (mFile != null) {
listDirectory(mFile);
- mContainerActivity.startSyncFolderOperation(mFile.getRemotePath(), mFile.getFileId());
+ mContainerActivity.startSyncFolderOperation(mFile);
}
}
public void startImagePreview(OCFile file);
- public void startSyncFolderOperation(String remotePath, long parentId);
+ public void startSyncFolderOperation(OCFile folder);
/**
* Getter for the current DataStorageManager in the container activity