Clean up of synchronization code

This commit is contained in:
David A. Velasco 2013-10-04 15:07:05 +02:00
parent 8a27bf186a
commit 23c9be24a2
7 changed files with 387 additions and 308 deletions

View file

@ -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() != 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())

View file

@ -23,7 +23,6 @@ import java.io.FileOutputStream;
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;
@ -50,25 +49,29 @@ 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;
/** 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;
@ -76,33 +79,47 @@ public class SynchronizeFolderOperation extends RemoteOperation {
/** 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;
@ -126,178 +143,53 @@ 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<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());
@ -311,17 +203,17 @@ public class SynchronizeFolderOperation extends RemoteOperation {
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);
}
}
@ -329,6 +221,155 @@ public class SynchronizeFolderOperation extends RemoteOperation {
}
/**
* 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);
}
@ -346,7 +387,6 @@ 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;
}
@ -415,6 +455,25 @@ 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.
*

View file

@ -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..
*
* 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;
}
*/
}

View file

@ -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;
@ -44,6 +43,7 @@ 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;
@ -52,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<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}
*/
@ -99,20 +123,20 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
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;
@ -125,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");
}
@ -142,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
}
@ -159,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() {
@ -183,23 +209,37 @@ 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;
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(),
@ -209,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) {
@ -224,11 +265,13 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
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++;
@ -264,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<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");
}

View file

@ -22,12 +22,15 @@ import android.content.Intent;
import android.os.IBinder;
/**
* Background service for syncing files to our local Database
* Background service for synchronizing remote files with their local state.
*
* Serves as a connector to an instance of {@link FileSyncAdapter}, as required by standard Android APIs.
*
* @author Bartek Przybylski
*
* @author David A. Velasco
*/
public class FileSyncService extends Service {
public static final String SYNC_MESSAGE = "ACCOUNT_SYNC";
public static final String SYNC_FOLDER_REMOTE_PATH = "SYNC_FOLDER_REMOTE_PATH";
public static final String IN_PROGRESS = "SYNC_IN_PROGRESS";
@ -48,4 +51,5 @@ public class FileSyncService extends Service {
public IBinder onBind(Intent intent) {
return new FileSyncAdapter(getApplicationContext(), true).getSyncAdapterBinder();
}
}

View file

@ -862,74 +862,44 @@ 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);
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)) {
@ -1015,7 +985,7 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa
cleanSecondFragment();
// Sync Folder
startSyncFolderOperation(directory.getRemotePath(), directory.getFileId());
startSyncFolderOperation(directory);
}
@ -1425,21 +1395,20 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa
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);

View file

@ -141,7 +141,7 @@ public class OCFileListFragment extends ExtendedListFragment implements EditName
if (mFile != null) {
listDirectory(mFile);
mContainerActivity.startSyncFolderOperation(mFile.getRemotePath(), mFile.getFileId());
mContainerActivity.startSyncFolderOperation(mFile);
}
}
@ -408,7 +408,7 @@ public class OCFileListFragment extends ExtendedListFragment implements EditName
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