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_LENGTH, file.getFileLength());
cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimetype()); cv.put(ProviderTableMeta.FILE_CONTENT_TYPE, file.getMimetype());
cv.put(ProviderTableMeta.FILE_NAME, file.getFileName()); 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_PARENT, file.getParentId());
cv.put(ProviderTableMeta.FILE_PATH, file.getRemotePath()); cv.put(ProviderTableMeta.FILE_PATH, file.getRemotePath());
if (!file.isDirectory()) if (!file.isDirectory())

View file

@ -23,7 +23,6 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -50,7 +49,13 @@ 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
*/ */
@ -58,17 +63,15 @@ public class SynchronizeFolderOperation extends RemoteOperation {
private static final String TAG = SynchronizeFolderOperation.class.getSimpleName(); 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; private long mCurrentSyncTime;
/** Id of the folder to synchronize in the local database */ /** Remote folder to synchronize */
private long mParentId; private OCFile mLocalFolder;
/** Boolean to indicate if is mandatory to update the folder */ /** 'True' means that the properties of the folder should be updated also, not just its content */
private boolean mEnforceMetadataUpdate; private boolean mUpdateFolderProperties;
/** Access to the local database */ /** Access to the local database */
private DataStorageManager mStorageManager; private DataStorageManager mStorageManager;
@ -76,33 +79,47 @@ public class SynchronizeFolderOperation extends RemoteOperation {
/** Account where the file to synchronize belongs */ /** Account where the file to synchronize belongs */
private Account mAccount; 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; 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; private List<OCFile> mChildren;
/** Counter of conflicts found between local and remote files */
private int mConflictsFound; private int mConflictsFound;
/** Counter of failed operations in synchronization of kept-in-sync files */
private int mFailsInFavouritesFound; 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; private Map<String, String> mForgottenLocalFiles;
/** 'True' means that this operation is part of a full account synchronization */
private boolean mSyncFullAccount; 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 currentSyncTime,
long parentId, boolean updateFolderProperties,
boolean enforceMetadataUpdate,
boolean syncFullAccount, boolean syncFullAccount,
DataStorageManager dataStorageManager, DataStorageManager dataStorageManager,
Account account, Account account,
Context context ) { Context context ) {
mRemotePath = remotePath; mLocalFolder = folder;
mCurrentSyncTime = currentSyncTime; mCurrentSyncTime = currentSyncTime;
mParentId = parentId; mUpdateFolderProperties = updateFolderProperties;
mEnforceMetadataUpdate = enforceMetadataUpdate;
mSyncFullAccount = syncFullAccount; mSyncFullAccount = syncFullAccount;
mStorageManager = dataStorageManager; mStorageManager = dataStorageManager;
mAccount = account; mAccount = account;
@ -132,99 +149,147 @@ public class SynchronizeFolderOperation extends RemoteOperation {
return mChildren; return mChildren;
} }
public String getRemotePath() { /**
return mRemotePath; * Performs the synchronization.
} *
* {@inheritDoc}
public long getParentId() { */
return mParentId;
}
@Override @Override
protected RemoteOperationResult run(WebdavClient client) { protected RemoteOperationResult run(WebdavClient client) {
RemoteOperationResult result = null; RemoteOperationResult result = null;
mFailsInFavouritesFound = 0; mFailsInFavouritesFound = 0;
mConflictsFound = 0; mConflictsFound = 0;
mForgottenLocalFiles.clear(); mForgottenLocalFiles.clear();
boolean dirChanged = false; String remotePath = null;
// code before in FileSyncAdapter.fetchData
PropFindMethod query = null; PropFindMethod query = null;
try { 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 // remote request
query = new PropFindMethod(client.getBaseUri() + WebdavUtils.encodePath(mRemotePath)); query = new PropFindMethod(client.getBaseUri() + WebdavUtils.encodePath(remotePath));
int status = client.executeMethod(query); int status = client.executeMethod(query);
// check and process response - /// TODO take into account all the possible status per child-resource // check and process response
if (isMultiStatus(status)) { if (isMultiStatus(status)) {
MultiStatus resp = query.getResponseBodyAsMultiStatus(); boolean folderChanged = synchronizeData(query.getResponseBodyAsMultiStatus(), client);
if (folderChanged) {
// synchronize properties of the parent folder, if necessary if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) {
WebdavEntry we = new WebdavEntry(resp.getResponses()[0], client.getBaseUri().getPath()); result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT); // should be different result, but will do the job
} else {
// Properties of server folder result = new RemoteOperationResult(true, status, query.getResponseHeaders());
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); } else {
if (mParentId == DataStorageManager.ROOT_PARENT_ID) result = new RemoteOperationResult(ResultCode.OK_NO_CHANGES_ON_DIR);
mParentId = parent.getFileId();
dirChanged = true;
} }
if (dirChanged) { } else {
// read contents in folder // synchronization failed
List<String> filesOnServer = new ArrayList<String> (); // Contains the lists of files on server client.exhaustResponse(query.getResponseBodyAsStream());
List<OCFile> updatedFiles = new Vector<OCFile>(resp.getResponses().length - 1); 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)));
}
}
result = new RemoteOperationResult(false, status, query.getResponseHeaders());
}
} catch (Exception e) {
result = new RemoteOperationResult(e);
} finally {
if (query != null)
query.releaseConnection(); // let the connection available for other methods
if (result.isSuccess()) {
Log_OC.i(TAG, "Synchronized " + mAccount.name + remotePath + ": " + result.getLogMessage());
} else {
if (result.isException()) {
Log_OC.e(TAG, "Synchroned " + mAccount.name + remotePath + ": " + result.getLogMessage(), result.getException());
} else {
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<OCFile> updatedFiles = new Vector<OCFile>(dataInServer.getResponses().length - 1);
List<SynchronizeFileOperation> filesToSyncContents = new Vector<SynchronizeFileOperation>(); List<SynchronizeFileOperation> filesToSyncContents = new Vector<SynchronizeFileOperation>();
for (int i = 1; i < resp.getResponses().length; ++i) {
// 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 /// new OCFile instance with the data from the server
we = new WebdavEntry(resp.getResponses()[i], client.getBaseUri().getPath()); we = new WebdavEntry(dataInServer.getResponses()[i], client.getBaseUri().getPath());
OCFile file = fillOCFile(we); remoteFile = fillOCFile(we);
remoteFile.setParentId(mLocalFolder.getFileId());
filesOnServer.add(file.getRemotePath()); // Registry the file in the list /// retrieve local data for the read file
localFile = mStorageManager.getFileByPath(remoteFile.getRemotePath());
/// set data about local state, keeping unchanged former data if existing /// add to the remoteFile (the new one) data about LOCAL STATE (not existing in the server side)
file.setLastSyncDateForProperties(mCurrentSyncTime); remoteFile.setLastSyncDateForProperties(mCurrentSyncTime);
OCFile oldFile = mStorageManager.getFileByPath(file.getRemotePath()); if (localFile != null) {
// properties of local state are kept unmodified
// Check if it is needed to synchronize the folder remoteFile.setKeepInSync(localFile.keepInSync());
if (oldFile != null) { remoteFile.setLastSyncDateForData(localFile.getLastSyncDateForData());
if (!file.getEtag().equalsIgnoreCase(oldFile.getEtag())) { 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)
} }
if (oldFile != null) { /// check and fix, if need, local storage path
file.setKeepInSync(oldFile.keepInSync()); checkAndFixForeignStoragePath(remoteFile); // fixing old policy - now local files must be copied into the ownCloud local folder
file.setLastSyncDateForData(oldFile.getLastSyncDateForData()); searchForLocalFileInDefaultPath(remoteFile); // legacy
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 /// prepare content synchronization for kept-in-sync files
if (file.keepInSync()) { if (remoteFile.keepInSync()) {
SynchronizeFileOperation operation = new SynchronizeFileOperation( oldFile, SynchronizeFileOperation operation = new SynchronizeFileOperation( localFile,
file, remoteFile,
mStorageManager, mStorageManager,
mAccount, mAccount,
true, true,
@ -234,13 +299,58 @@ public class SynchronizeFolderOperation extends RemoteOperation {
filesToSyncContents.add(operation); filesToSyncContents.add(operation);
} }
updatedFiles.add(file); 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) // save updated contents in local database; all at once, trying to get a best performance in database update (not a big deal, indeed)
mStorageManager.saveFiles(updatedFiles); mStorageManager.saveFiles(updatedFiles);
// request for the synchronization of files AFTER saving last properties // 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; RemoteOperationResult contentsResult = null;
for (SynchronizeFileOperation op: filesToSyncContents) { for (SynchronizeFileOperation op: filesToSyncContents) {
contentsResult = op.execute(client); // returns without waiting for upload or download finishes contentsResult = op.execute(client); // returns without waiting for upload or download finishes
@ -257,75 +367,6 @@ public class SynchronizeFolderOperation extends RemoteOperation {
} }
} // won't let these fails break the synchronization process } // 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 {
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 {
if (status == HttpStatus.SC_NOT_FOUND) {
OCFile dir = mStorageManager.getFileByPath(mRemotePath);
if (dir != null) {
String currentSavePath = FileStorageUtils.getSavePath(mAccount.name);
mStorageManager.removeDirectory(dir, true, (dir.isDown() && dir.getStoragePath().startsWith(currentSavePath)));
}
}
result = new RemoteOperationResult(false, status, query.getResponseHeaders());
}
} catch (Exception e) {
result = new RemoteOperationResult(e);
} finally {
if (query != null)
query.releaseConnection(); // let the connection available for other methods
if (result.isSuccess()) {
Log_OC.i(TAG, "Synchroned " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage());
} else {
if (result.isException()) {
Log_OC.e(TAG, "Synchroned " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage(), result.getException());
} else {
Log_OC.e(TAG, "Synchroned " + mAccount.name + ", folder " + mRemotePath + ": " + result.getLogMessage());
}
}
if (!mSyncFullAccount) {
sendStickyBroadcast(false, mRemotePath, result);
}
}
return result;
} }
@ -346,7 +387,6 @@ public class SynchronizeFolderOperation extends RemoteOperation {
file.setFileLength(we.contentLength()); file.setFileLength(we.contentLength());
file.setMimetype(we.contentType()); file.setMimetype(we.contentType());
file.setModificationTimestamp(we.modifiedTimestamp()); file.setModificationTimestamp(we.modifiedTimestamp());
file.setParentId(mParentId);
file.setEtag(we.etag()); file.setEtag(we.etag());
return file; 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. * 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; package com.owncloud.android.syncadapter;
import java.io.IOException; import java.io.IOException;
import java.net.UnknownHostException; //import java.net.UnknownHostException;
import java.util.Date; //import java.util.Date;
import org.apache.http.HttpRequest; import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException; import org.apache.http.client.ClientProtocolException;
import org.apache.http.conn.ConnectionKeepAliveStrategy; //import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.protocol.HttpContext; //import org.apache.http.protocol.HttpContext;
import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.authentication.AccountUtils;
import com.owncloud.android.authentication.AccountUtils.AccountNotFoundException; import com.owncloud.android.authentication.AccountUtils.AccountNotFoundException;
@ -43,11 +43,13 @@ import android.content.Context;
import eu.alefzero.webdav.WebdavClient; import eu.alefzero.webdav.WebdavClient;
/** /**
* Base SyncAdapter for OwnCloud Designed to be subclassed for the concrete * Base synchronization adapter for ownCloud designed to be subclassed for different
* SyncAdapter, like ConcatsSync, CalendarSync, FileSync etc.. * resource types, like FileSync, ConcatsSync, CalendarSync, etc..
*
* Implements the standard {@link AbstractThreadedSyncAdapter}.
* *
* @author sassman * @author sassman
* * @author David A. Velasco
*/ */
public abstract class AbstractOwnCloudSyncAdapter extends public abstract class AbstractOwnCloudSyncAdapter extends
AbstractThreadedSyncAdapter { AbstractThreadedSyncAdapter {
@ -55,7 +57,7 @@ public abstract class AbstractOwnCloudSyncAdapter extends
private AccountManager accountManager; private AccountManager accountManager;
private Account account; private Account account;
private ContentProviderClient contentProvider; private ContentProviderClient contentProvider;
private Date lastUpdated; //private Date lastUpdated;
private DataStorageManager mStoreManager; private DataStorageManager mStoreManager;
private WebdavClient mClient = null; private WebdavClient mClient = null;
@ -89,14 +91,6 @@ public abstract class AbstractOwnCloudSyncAdapter extends
this.contentProvider = contentProvider; this.contentProvider = contentProvider;
} }
public Date getLastUpdated() {
return lastUpdated;
}
public void setLastUpdated(Date lastUpdated) {
this.lastUpdated = lastUpdated;
}
public void setStorageManager(DataStorageManager storage_manager) { public void setStorageManager(DataStorageManager storage_manager) {
mStoreManager = storage_manager; mStoreManager = storage_manager;
} }
@ -105,6 +99,41 @@ public abstract class AbstractOwnCloudSyncAdapter extends
return mStoreManager; 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() { protected ConnectionKeepAliveStrategy getKeepAliveStrategy() {
return new ConnectionKeepAliveStrategy() { return new ConnectionKeepAliveStrategy() {
public long getKeepAliveDuration(HttpResponse response, 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.R;
import com.owncloud.android.authentication.AccountAuthenticator; import com.owncloud.android.authentication.AccountAuthenticator;
import com.owncloud.android.authentication.AuthenticatorActivity; import com.owncloud.android.authentication.AuthenticatorActivity;
import com.owncloud.android.datamodel.DataStorageManager;
import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.operations.RemoteOperationResult; import com.owncloud.android.operations.RemoteOperationResult;
@ -44,6 +43,7 @@ import android.accounts.AccountsException;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient; import android.content.ContentProviderClient;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
@ -52,36 +52,60 @@ import android.content.SyncResult;
import android.os.Bundle; import android.os.Bundle;
/** /**
* SyncAdapter implementation for syncing sample SyncAdapter contacts to the * Implementation of {@link AbstractThreadedSyncAdapter} responsible for synchronizing
* platform ContactOperations provider. * ownCloud files.
*
* Performs a full synchronization of the account recieved in {@link #onPerformSync(Account, Bundle, String, ContentProviderClient, SyncResult)}.
* *
* @author Bartek Przybylski * @author Bartek Przybylski
* @author David A. Velasco * @author David A. Velasco
*/ */
public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter { 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; private static final int MAX_FAILED_RESULTS = 3;
/** Time stamp for the current synchronization process, used to distinguish fresh data */
private long mCurrentSyncTime; private long mCurrentSyncTime;
/** Flag made 'true' when a request to cancel the synchronization is received */
private boolean mCancellation; 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 boolean mIsManualSync;
/** Counter for failed operations in the synchronization process */
private int mFailedResultsCounter; private int mFailedResultsCounter;
/** Result of the last failed operation */
private RemoteOperationResult mLastFailedResult; private RemoteOperationResult mLastFailedResult;
private SyncResult mSyncResult;
/** Counter of conflicts found between local and remote files */
private int mConflictsFound; private int mConflictsFound;
/** Counter of failed operations in synchronization of kept-in-sync files */
private int mFailsInFavouritesFound; 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; 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) { public FileSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize); super(context, autoInitialize);
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
*/ */
@ -99,20 +123,20 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
mForgottenLocalFiles = new HashMap<String, String>(); mForgottenLocalFiles = new HashMap<String, String>();
mSyncResult = syncResult; mSyncResult = syncResult;
mSyncResult.fullSyncRequested = false; 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.setAccount(account);
this.setContentProvider(provider); this.setContentProvider(provider);
this.setStorageManager(new FileDataStorageManager(account, getContentProvider())); this.setStorageManager(new FileDataStorageManager(account, provider));
try { try {
this.initClientForCurrentAccount(); this.initClientForCurrentAccount();
} catch (IOException e) { } 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; mSyncResult.tooManyRetries = true;
notifyFailedSynchronization(); notifyFailedSynchronization();
return; return;
} catch (AccountsException e) { } 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; mSyncResult.tooManyRetries = true;
notifyFailedSynchronization(); notifyFailedSynchronization();
return; return;
@ -125,10 +149,10 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
updateOCVersion(); updateOCVersion();
mCurrentSyncTime = System.currentTimeMillis(); mCurrentSyncTime = System.currentTimeMillis();
if (!mCancellation) { if (!mCancellation) {
fetchData(OCFile.PATH_SEPARATOR, DataStorageManager.ROOT_PARENT_ID); synchronizeFolder(getStorageManager().getFileByPath(OCFile.PATH_SEPARATOR), true);
} else { } 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 /// notify the user about the failure of MANUAL synchronization
notifyFailedSynchronization(); notifyFailedSynchronization();
} }
if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) { if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) {
notifyFailsInFavourites(); notifyFailsInFavourites();
} }
if (mForgottenLocalFiles.size() > 0) { if (mForgottenLocalFiles.size() > 0) {
notifyForgottenLocalFiles(); notifyForgottenLocalFiles();
} }
sendStickyBroadcast(false, null, mLastFailedResult); // message to signal the end to the UI 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. * 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 * Sets the mCancellation flag to 'true'. THe synchronization will be stopped later,
* fetched will be still saved in the database. See onPerformSync implementation. * 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 @Override
public void onSyncCanceled() { 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.
* *
* @param remotePath Remote path to the folder to synchronize. * Fetches the list and properties of the files contained in the given folder, including their
* @param parentId Database Id of the folder to synchronize. * 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 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)) if (mFailedResultsCounter > MAX_FAILED_RESULTS || isFinisher(mLastFailedResult))
return; return;
boolean enforceMetadataUpdate = (parentId == DataStorageManager.ROOT_PARENT_ID); /*
OCFile folder,
long currentSyncTime,
boolean updateFolderProperties,
boolean syncFullAccount,
DataStorageManager dataStorageManager,
Account account,
Context context ) {
// perform folder synchronization }
SynchronizeFolderOperation synchFolderOp = new SynchronizeFolderOperation( remotePath, */
// folder synchronization
SynchronizeFolderOperation synchFolderOp = new SynchronizeFolderOperation( folder,
mCurrentSyncTime, mCurrentSyncTime,
parentId, updateFolderProperties,
enforceMetadataUpdate,
true, true,
getStorageManager(), getStorageManager(),
getAccount(), getAccount(),
@ -209,8 +249,9 @@ public class FileSyncAdapter extends AbstractOwnCloudSyncAdapter {
// synchronized folder -> notice to UI - ALWAYS, although !result.isSuccess // 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.isSuccess() || result.getCode() == ResultCode.SYNC_CONFLICT) {
if (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(); List<OCFile> children = synchFolderOp.getChildren();
fetchChildren(children); // beware of the 'hidden' recursion here! 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 { } else {
// in failures, the statistics for the global result are updated
if (result.getCode() == RemoteOperationResult.ResultCode.UNAUTHORIZED || if (result.getCode() == RemoteOperationResult.ResultCode.UNAUTHORIZED ||
// (result.isTemporalRedirection() && result.isIdPRedirection() &&
( result.isIdPRedirection() && ( result.isIdPRedirection() &&
AccountAuthenticator.AUTH_TOKEN_TYPE_SAML_WEB_SSO_SESSION_COOKIE.equals(getClient().getAuthTokenType()))) { AccountAuthenticator.AUTH_TOKEN_TYPE_SAML_WEB_SSO_SESSION_COOKIE.equals(getClient().getAuthTokenType()))) {
mSyncResult.stats.numAuthExceptions++; 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) { private void fetchChildren(List<OCFile> files) {
int i; int i;
for (i=0; i < files.size() && !mCancellation; i++) { for (i=0; i < files.size() && !mCancellation; i++) {
OCFile newFile = files.get(i); OCFile newFile = files.get(i);
if (newFile.isDirectory()) { if (newFile.isDirectory()) {
fetchData(newFile.getRemotePath(), newFile.getFileId()); synchronizeFolder(newFile, false);
// Update folder size on DB
getStorageManager().calculateFolderSize(newFile.getFileId());
} }
} }
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; 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 Bartek Przybylski
* * @author David A. Velasco
*/ */
public class FileSyncService extends Service { public class FileSyncService extends Service {
public static final String SYNC_MESSAGE = "ACCOUNT_SYNC"; public static final String SYNC_MESSAGE = "ACCOUNT_SYNC";
public static final String SYNC_FOLDER_REMOTE_PATH = "SYNC_FOLDER_REMOTE_PATH"; public static final String SYNC_FOLDER_REMOTE_PATH = "SYNC_FOLDER_REMOTE_PATH";
public static final String IN_PROGRESS = "SYNC_IN_PROGRESS"; public static final String IN_PROGRESS = "SYNC_IN_PROGRESS";
@ -48,4 +51,5 @@ public class FileSyncService extends Service {
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
return new FileSyncAdapter(getApplicationContext(), true).getSyncAdapterBinder(); return new FileSyncAdapter(getApplicationContext(), true).getSyncAdapterBinder();
} }
} }

View file

@ -862,75 +862,45 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
boolean inProgress = intent.getBooleanExtra(FileSyncService.IN_PROGRESS, false); boolean inProgress = intent.getBooleanExtra(FileSyncService.IN_PROGRESS, false);
String accountName = intent.getStringExtra(FileSyncService.ACCOUNT_NAME); 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); 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)) { if (getAccount() != null && accountName.equals(getAccount().name)) {
String synchFolderRemotePath = intent.getStringExtra(FileSyncService.SYNC_FOLDER_REMOTE_PATH); String synchFolderRemotePath = intent.getStringExtra(FileSyncService.SYNC_FOLDER_REMOTE_PATH);
/* OCFile currentFile = mStorageManager.getFileById(getFile().getFileId());
boolean fillBlankRoot = false;
if (currentDir == null) {
currentDir = mStorageManager.getFileByPath(OCFile.PATH_SEPARATOR);
fillBlankRoot = (currentDir != null);
}
*/
OCFile currentDir = mStorageManager.getFileById(getCurrentDir().getFileId()); OCFile currentDir = mStorageManager.getFileById(getCurrentDir().getFileId());
if (currentDir == null) { if (currentDir == null) {
// current folder was removed from the server // current folder was removed from the server
Toast.makeText(FileDisplayActivity.this, getString(R.string.sync_current_folder_was_removed), Toast.LENGTH_LONG) Toast.makeText(FileDisplayActivity.this, getString(R.string.sync_current_folder_was_removed), Toast.LENGTH_LONG)
.show(); .show();
onBackPressed(); onBackPressed();
} else if (synchFolderRemotePath != null && currentDir.getRemotePath().equals(synchFolderRemotePath)) { } else {
if (currentFile == null && !getFile().isDirectory()) {
/*OCFile synchDir = getStorageManager().getFileByPath(synchFolderRemotePath); // currently selected file was removed in the server, and now we know it
//boolean needToRefresh = false; cleanSecondFragment();
if (synchDir == null) { currentFile = currentDir;
// 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 {*/
if (synchFolderRemotePath != null && currentDir.getRemotePath().equals(synchFolderRemotePath)) {
OCFileListFragment fileListFragment = getListOfFilesFragment(); OCFileListFragment fileListFragment = getListOfFilesFragment();
if (fileListFragment != null) { if (fileListFragment != null) {
fileListFragment.listDirectory(currentDir); fileListFragment.listDirectory(currentDir);
} }
//boolean existsSecondFragment = (getSecondFragment() != null);
//if (!existsSecondFragment) {
if (getSecondFragment() == null) {
setFile(currentDir);
} }
//updateFragmentsVisibility(existsSecondFragment); setFile(currentFile);
//updateNavigationElementsInActionBar(existsSecondFragment ? getFile() : null);
} }
setSupportProgressBarIndeterminateVisibility(inProgress); setSupportProgressBarIndeterminateVisibility(inProgress);
removeStickyBroadcast(intent); removeStickyBroadcast(intent);
mSyncInProgress = inProgress; mSyncInProgress = inProgress;
} }
if (synchResult != null) { if (synchResult != null) {
if (synchResult.getCode().equals(RemoteOperationResult.ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED)) { if (synchResult.getCode().equals(RemoteOperationResult.ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED)) {
mLastSslUntrustedServerResult = synchResult; mLastSslUntrustedServerResult = synchResult;
@ -1015,7 +985,7 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa
cleanSecondFragment(); cleanSecondFragment();
// Sync Folder // Sync Folder
startSyncFolderOperation(directory.getRemotePath(), directory.getFileId()); startSyncFolderOperation(directory);
} }
@ -1425,15 +1395,14 @@ OCFileListFragment.ContainerActivity, FileDetailFragment.ContainerActivity, OnNa
return null; return null;
} }
public void startSyncFolderOperation(String remotePath, long parentId) { public void startSyncFolderOperation(OCFile folder) {
long currentSyncTime = System.currentTimeMillis(); long currentSyncTime = System.currentTimeMillis();
mSyncInProgress = true; mSyncInProgress = true;
// perform folder synchronization // perform folder synchronization
RemoteOperation synchFolderOp = new SynchronizeFolderOperation( remotePath, RemoteOperation synchFolderOp = new SynchronizeFolderOperation( folder,
currentSyncTime, currentSyncTime,
parentId,
false, false,
false, false,
getStorageManager(), getStorageManager(),

View file

@ -141,7 +141,7 @@ public class OCFileListFragment extends ExtendedListFragment implements EditName
if (mFile != null) { if (mFile != null) {
listDirectory(mFile); 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 startImagePreview(OCFile file);
public void startSyncFolderOperation(String remotePath, long parentId); public void startSyncFolderOperation(OCFile folder);
/** /**
* Getter for the current DataStorageManager in the container activity * Getter for the current DataStorageManager in the container activity