<string name="prefs_category_security">Security</string>
<string name="prefs_instant_video_upload_path_title">Upload Video Path</string>
+ <string name="download_folder_failed_content">Download of %1$s folder could not be completed</string>
<string name="shared_subject_header">shared</string>
<string name="with_you_subject_header">with you</string>
\r
if (mOperationsServiceBinder != null) {\r
//Log_OC.wtf(TAG, "getting access token..." );\r
- mWaitingForOpId = mOperationsServiceBinder.newOperation(getServerInfoIntent);\r
+ mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(getServerInfoIntent);\r
}\r
}\r
\r
normalizeUrlSuffix(uri)\r
);\r
if (mOperationsServiceBinder != null) {\r
- mWaitingForOpId = mOperationsServiceBinder.newOperation(getServerInfoIntent);\r
+ mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(getServerInfoIntent);\r
} else {\r
Log_OC.wtf(TAG, "Server check tried with OperationService unbound!" );\r
}\r
\r
if (mOperationsServiceBinder != null) {\r
//Log_OC.wtf(TAG, "starting existenceCheckRemoteOperation..." );\r
- mWaitingForOpId = mOperationsServiceBinder.newOperation(existenceCheckIntent);\r
+ mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(existenceCheckIntent);\r
}\r
}\r
\r
\r
if (mOperationsServiceBinder != null) {\r
//Log_OC.wtf(TAG, "starting getRemoteUserNameOperation..." );\r
- mWaitingForOpId = mOperationsServiceBinder.newOperation(getUserNameIntent);\r
+ mWaitingForOpId = mOperationsServiceBinder.queueNewOperation(getUserNameIntent);\r
}\r
}\r
\r
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
import com.owncloud.android.ui.activity.ComponentsGetter;
/**
if (mComponentsGetter != null && mFile != null && mAccount != null) {
FileDownloaderBinder downloaderBinder = mComponentsGetter.getFileDownloaderBinder();
downloading = downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile);
+ OperationsServiceBinder opsBinder = mComponentsGetter.getOperationsServiceBinder();
+ downloading |= (opsBinder != null && opsBinder.isSynchronizing(mAccount, mFile.getRemotePath()));
FileUploaderBinder uploaderBinder = mComponentsGetter.getFileUploaderBinder();
uploading = uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile);
}
/// decision is taken for each possible action on a file in the menu
// DOWNLOAD
- if (mFile == null || mFile.isFolder() || mFile.isDown() || downloading || uploading) {
+ if (mFile == null || mFile.isDown() || downloading || uploading) {
toHide.add(R.id.action_download_file);
} else {
// CANCEL DOWNLOAD
- if (mFile == null || !downloading || mFile.isFolder()) {
+ if (mFile == null || !downloading) {
toHide.add(R.id.action_cancel_download);
} else {
toShow.add(R.id.action_cancel_download);
service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
service.putExtra(OperationsService.EXTRA_SEND_INTENT, sendIntent);
- mWaitingForOpId = mFileActivity.getOperationsServiceBinder().newOperation(service);
+ mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
} else {
Log_OC.wtf(TAG, "Trying to open a NULL OCFile");
service.setAction(OperationsService.ACTION_UNSHARE);
service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
- mWaitingForOpId = mFileActivity.getOperationsServiceBinder().newOperation(service);
+ mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
mFileActivity.showLoadingDialog();
public void syncFile(OCFile file) {
- // Sync file
- Intent service = new Intent(mFileActivity, OperationsService.class);
- service.setAction(OperationsService.ACTION_SYNC_FILE);
- service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
- service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
- service.putExtra(OperationsService.EXTRA_SYNC_FILE_CONTENTS, true);
- mWaitingForOpId = mFileActivity.getOperationsServiceBinder().newOperation(service);
- mFileActivity.showLoadingDialog();
+ if (!file.isFolder()){
+ Intent intent = new Intent(mFileActivity, OperationsService.class);
+ intent.setAction(OperationsService.ACTION_SYNC_FILE);
+ intent.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
+ intent.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
+ intent.putExtra(OperationsService.EXTRA_SYNC_FILE_CONTENTS, true);
+ mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(intent);
+ mFileActivity.showLoadingDialog();
+
+ } else {
+ /*
+ // Add files recursivly
+ FileDataStorageManager storageManager = mFileActivity.getStorageManager();
+ filesList.addAll(storageManager.getFolderContent(file));
+ boolean newfiles;
+ do {
+ Vector<OCFile> tmpFolders = new Vector<OCFile>();
+ for (OCFile tmpfile : filesList) {
+ if (tmpfile.isFolder()) {
+ tmpFolders.add(tmpfile);
+ }
+ }
+ if (tmpFolders.isEmpty()){
+ newfiles = false;
+ }else {
+ for(OCFile tmpFolder : tmpFolders){
+ filesList.remove(tmpFolder);
+ filesList.addAll(storageManager.getFolderContent(tmpFolder));
+ }
+ newfiles = true;
+ }
+ } while(newfiles);
+ */
+ Intent intent = new Intent(mFileActivity, OperationsService.class);
+ intent.setAction(OperationsService.ACTION_SYNC_FOLDER);
+ intent.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
+ intent.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
+ mFileActivity.startService(intent); // reevaluating: with or without Binder?
+ //mFileActivity.getOperationsServiceBinder().queueNewOperation(intent);
+ }
}
-
public void renameFile(OCFile file, String newFilename) {
// RenameFile
Intent service = new Intent(mFileActivity, OperationsService.class);
service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
service.putExtra(OperationsService.EXTRA_NEWNAME, newFilename);
- mWaitingForOpId = mFileActivity.getOperationsServiceBinder().newOperation(service);
+ mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
mFileActivity.showLoadingDialog();
}
service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
service.putExtra(OperationsService.EXTRA_REMOVE_ONLY_LOCAL, onlyLocalCopy);
- mWaitingForOpId = mFileActivity.getOperationsServiceBinder().newOperation(service);
+ mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
mFileActivity.showLoadingDialog();
}
service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
service.putExtra(OperationsService.EXTRA_REMOTE_PATH, remotePath);
service.putExtra(OperationsService.EXTRA_CREATE_FULL_PATH, createFullPath);
- mWaitingForOpId = mFileActivity.getOperationsServiceBinder().newOperation(service);
+ mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
mFileActivity.showLoadingDialog();
}
-
+ /**
+ * Cancel the transference in downloads (files/folders) and file uploads
+ * @param file OCFile
+ */
public void cancelTransference(OCFile file) {
Account account = mFileActivity.getAccount();
- FileDownloaderBinder downloaderBinder = mFileActivity.getFileDownloaderBinder();
- FileUploaderBinder uploaderBinder = mFileActivity.getFileUploaderBinder();
- if (downloaderBinder != null && downloaderBinder.isDownloading(account, file)) {
- // Remove etag for parent, if file is a keep_in_sync
- if (file.keepInSync()) {
- OCFile parent = mFileActivity.getStorageManager().getFileById(file.getParentId());
- parent.setEtag("");
- mFileActivity.getStorageManager().saveFile(parent);
+ if (!file.isFolder()) {
+ FileDownloaderBinder downloaderBinder = mFileActivity.getFileDownloaderBinder();
+ FileUploaderBinder uploaderBinder = mFileActivity.getFileUploaderBinder();
+ if (downloaderBinder != null && downloaderBinder.isDownloading(account, file)) {
+ // Remove etag for parent, if file is a keep_in_sync
+ if (file.keepInSync()) {
+ OCFile parent = mFileActivity.getStorageManager().getFileById(file.getParentId());
+ parent.setEtag("");
+ mFileActivity.getStorageManager().saveFile(parent);
+ }
+
+ downloaderBinder.cancel(account, file);
+
+ } else if (uploaderBinder != null && uploaderBinder.isUploading(account, file)) {
+ uploaderBinder.cancel(account, file);
}
-
- downloaderBinder.cancel(account, file);
-
- } else if (uploaderBinder != null && uploaderBinder.isUploading(account, file)) {
- uploaderBinder.cancel(account, file);
+ } else {
+ Intent intent = new Intent(mFileActivity, OperationsService.class);
+ intent.setAction(OperationsService.ACTION_CANCEL_SYNC_FOLDER);
+ intent.putExtra(OperationsService.EXTRA_ACCOUNT, account);
+ intent.putExtra(OperationsService.EXTRA_FILE, file);
+ mFileActivity.startService(intent);
+
}
}
service.putExtra(OperationsService.EXTRA_NEW_PARENT_PATH, newfile.getRemotePath());
service.putExtra(OperationsService.EXTRA_REMOTE_PATH, currentFile.getRemotePath());
service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
- mWaitingForOpId = mFileActivity.getOperationsServiceBinder().newOperation(service);
+ mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
mFileActivity.showLoadingDialog();
}
import java.io.File;
import java.io.IOException;
import java.util.AbstractList;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public static final String EXTRA_ACCOUNT = "ACCOUNT";
public static final String EXTRA_FILE = "FILE";
-
+
+ public static final String ACTION_CANCEL_FILE_DOWNLOAD = "CANCEL_FILE_DOWNLOAD";
+
private static final String DOWNLOAD_ADDED_MESSAGE = "DOWNLOAD_ADDED";
private static final String DOWNLOAD_FINISH_MESSAGE = "DOWNLOAD_FINISH";
public static final String EXTRA_DOWNLOAD_RESULT = "RESULT";
private NotificationManager mNotificationManager;
private NotificationCompat.Builder mNotificationBuilder;
private int mLastPercent;
+
+ private Account mAccount;
+ private OCFile mFile;
public static String getDownloadAddedMessage() {
) {
Log_OC.e(TAG, "Not enough information provided in intent");
return START_NOT_STICKY;
- }
- Account account = intent.getParcelableExtra(EXTRA_ACCOUNT);
- OCFile file = intent.getParcelableExtra(EXTRA_FILE);
-
- AbstractList<String> requestedDownloads = new Vector<String>(); // dvelasco: now this always contains just one element, but that can change in a near future (download of multiple selection)
- String downloadKey = buildRemoteName(account, file);
- try {
- DownloadFileOperation newDownload = new DownloadFileOperation(account, file);
- mPendingDownloads.putIfAbsent(downloadKey, newDownload);
- newDownload.addDatatransferProgressListener(this);
- newDownload.addDatatransferProgressListener((FileDownloaderBinder)mBinder);
- requestedDownloads.add(downloadKey);
- sendBroadcastNewDownload(newDownload);
-
- } catch (IllegalArgumentException e) {
- Log_OC.e(TAG, "Not enough information provided in intent: " + e.getMessage());
- return START_NOT_STICKY;
- }
-
- if (requestedDownloads.size() > 0) {
- Message msg = mServiceHandler.obtainMessage();
- msg.arg1 = startId;
- msg.obj = requestedDownloads;
- mServiceHandler.sendMessage(msg);
+ } else {
+ mAccount = intent.getParcelableExtra(EXTRA_ACCOUNT);
+ mFile = intent.getParcelableExtra(EXTRA_FILE);
+
+ if (ACTION_CANCEL_FILE_DOWNLOAD.equals(intent.getAction())) {
+
+ new Thread(new Runnable() {
+ public void run() {
+ // Cancel the download
+ cancel(mAccount,mFile);
+ }
+ }).start();
+
+ } else {
+
+ AbstractList<String> requestedDownloads = new Vector<String>(); // dvelasco: now this always contains just one element, but that can change in a near future (download of multiple selection)
+ String downloadKey = buildRemoteName(mAccount, mFile);
+ try {
+ DownloadFileOperation newDownload = new DownloadFileOperation(mAccount, mFile);
+ mPendingDownloads.putIfAbsent(downloadKey, newDownload);
+ newDownload.addDatatransferProgressListener(this);
+ newDownload.addDatatransferProgressListener((FileDownloaderBinder) mBinder);
+ requestedDownloads.add(downloadKey);
+ sendBroadcastNewDownload(newDownload);
+
+ } catch (IllegalArgumentException e) {
+ Log_OC.e(TAG, "Not enough information provided in intent: " + e.getMessage());
+ return START_NOT_STICKY;
+ }
+
+ if (requestedDownloads.size() > 0) {
+ Message msg = mServiceHandler.obtainMessage();
+ msg.arg1 = startId;
+ msg.obj = requestedDownloads;
+ mServiceHandler.sendMessage(msg);
+ }
+ }
}
return START_NOT_STICKY;
* Map of listeners that will be reported about progress of downloads from a {@link FileDownloaderBinder} instance
*/
private Map<String, OnDatatransferProgressListener> mBoundListeners = new HashMap<String, OnDatatransferProgressListener>();
-
-
+
+
/**
* Cancels a pending or current download of a remote file.
- *
+ *
* @param account Owncloud account where the remote file is stored.
* @param file A file in the queue of pending downloads
*/
sendStickyBroadcast(added);
}
+ /**
+ * Cancel operation
+ * @param account Owncloud account where the remote file is stored.
+ * @param file File OCFile
+ */
+ public void cancel(Account account, OCFile file){
+ DownloadFileOperation download = null;
+ String targetKey = buildRemoteName(account, file);
+ ArrayList<String> keyItems = new ArrayList<String>();
+ synchronized (mPendingDownloads) {
+ if (file.isFolder()) {
+ Log_OC.d(TAG, "Folder download. Canceling pending downloads (from folder)");
+ Iterator<String> it = mPendingDownloads.keySet().iterator();
+ boolean found = false;
+ while (it.hasNext()) {
+ String keyDownloadOperation = it.next();
+ found = keyDownloadOperation.startsWith(targetKey);
+ if (found) {
+ keyItems.add(keyDownloadOperation);
+ }
+ }
+ } else {
+ // this is not really expected...
+ Log_OC.d(TAG, "Canceling file download");
+ keyItems.add(buildRemoteName(account, file));
+ }
+ }
+ for (String item: keyItems) {
+ download = mPendingDownloads.remove(item);
+ Log_OC.d(TAG, "Key removed: " + item);
+
+ if (download != null) {
+ download.cancel();
+ }
+ }
+ }
}
--- /dev/null
+/* ownCloud Android client application
+ * Copyright (C) 2012-2014 ownCloud Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.operations;
+
+import java.io.File;
+import java.io.FileInputStream;
+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;
+import java.util.Vector;
+
+import org.apache.http.HttpStatus;
+import android.accounts.Account;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+//import android.support.v4.content.LocalBroadcastManager;
+
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.resources.shares.OCShare;
+import com.owncloud.android.lib.common.operations.RemoteOperation;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.lib.resources.shares.GetRemoteSharesForFileOperation;
+import com.owncloud.android.lib.resources.files.FileUtils;
+import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
+import com.owncloud.android.lib.resources.files.ReadRemoteFolderOperation;
+import com.owncloud.android.lib.resources.files.RemoteFile;
+
+import com.owncloud.android.syncadapter.FileSyncAdapter;
+import com.owncloud.android.utils.FileStorageUtils;
+
+
+
+/**
+ * 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
+ */
+public class RefreshFolderOperation extends RemoteOperation {
+
+ private static final String TAG = RefreshFolderOperation.class.getSimpleName();
+
+ public static final String EVENT_SINGLE_FOLDER_CONTENTS_SYNCED =
+ RefreshFolderOperation.class.getName() + ".EVENT_SINGLE_FOLDER_CONTENTS_SYNCED";
+ public static final String EVENT_SINGLE_FOLDER_SHARES_SYNCED =
+ RefreshFolderOperation.class.getName() + ".EVENT_SINGLE_FOLDER_SHARES_SYNCED";
+
+ /** Time stamp for the synchronization process in progress */
+ private long mCurrentSyncTime;
+
+ /** Remote folder to synchronize */
+ private OCFile mLocalFolder;
+
+ /** Access to the local database */
+ private FileDataStorageManager mStorageManager;
+
+ /** Account where the file to synchronize belongs */
+ private Account mAccount;
+
+ /** Android context; necessary to send requests to the download service */
+ private Context mContext;
+
+ /** 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;
+
+ /** 'True' means that Share resources bound to the files into should be refreshed also */
+ private boolean mIsShareSupported;
+
+ /** 'True' means that the remote folder changed and should be fetched */
+ private boolean mRemoteFolderChanged;
+
+ /** 'True' means that Etag will be ignored */
+ private boolean mIgnoreETag;
+
+
+ /**
+ * Creates a new instance of {@link RefreshFolderOperation}.
+ *
+ * @param folder Folder to synchronize.
+ * @param currentSyncTime Time stamp for the synchronization process in progress.
+ * @param syncFullAccount 'True' means that this operation is part of a full account
+ * synchronization.
+ * @param isShareSupported 'True' means that the server supports the sharing API.
+ * @param ignoreEtag 'True' means that the content of the remote folder should
+ * be fetched and updated even though the 'eTag' did not
+ * change.
+ * @param dataStorageManager Interface with the local database.
+ * @param account ownCloud account where the folder is located.
+ * @param context Application context.
+ */
+ public RefreshFolderOperation(OCFile folder,
+ long currentSyncTime,
+ boolean syncFullAccount,
+ boolean isShareSupported,
+ boolean ignoreETag,
+ FileDataStorageManager dataStorageManager,
+ Account account,
+ Context context) {
+ mLocalFolder = folder;
+ mCurrentSyncTime = currentSyncTime;
+ mSyncFullAccount = syncFullAccount;
+ mIsShareSupported = isShareSupported;
+ mStorageManager = dataStorageManager;
+ mAccount = account;
+ mContext = context;
+ mForgottenLocalFiles = new HashMap<String, String>();
+ mRemoteFolderChanged = false;
+ mIgnoreETag = ignoreETag;
+ }
+
+
+ public int getConflictsFound() {
+ return mConflictsFound;
+ }
+
+ public int getFailsInFavouritesFound() {
+ return mFailsInFavouritesFound;
+ }
+
+ public Map<String, String> getForgottenLocalFiles() {
+ return mForgottenLocalFiles;
+ }
+
+ /**
+ * 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.
+ */
+ public List<OCFile> getChildren() {
+ return mChildren;
+ }
+
+ /**
+ * Performs the synchronization.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ protected RemoteOperationResult run(OwnCloudClient client) {
+ RemoteOperationResult result = null;
+ mFailsInFavouritesFound = 0;
+ mConflictsFound = 0;
+ mForgottenLocalFiles.clear();
+
+ if (FileUtils.PATH_SEPARATOR.equals(mLocalFolder.getRemotePath()) && !mSyncFullAccount) {
+ updateOCVersion(client);
+ }
+
+ result = checkForChanges(client);
+
+ if (result.isSuccess()) {
+ if (mRemoteFolderChanged) {
+ result = fetchAndSyncRemoteFolder(client);
+ } else {
+ mChildren = mStorageManager.getFolderContent(mLocalFolder);
+ }
+ }
+
+ if (!mSyncFullAccount) {
+ sendLocalBroadcast(
+ EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, mLocalFolder.getRemotePath(), result
+ );
+ }
+
+ if (result.isSuccess() && mIsShareSupported && !mSyncFullAccount) {
+ refreshSharesForFolder(client); // share result is ignored
+ }
+
+ if (!mSyncFullAccount) {
+ sendLocalBroadcast(
+ EVENT_SINGLE_FOLDER_SHARES_SYNCED, mLocalFolder.getRemotePath(), result
+ );
+ }
+
+ return result;
+
+ }
+
+
+ private void updateOCVersion(OwnCloudClient client) {
+ UpdateOCVersionOperation update = new UpdateOCVersionOperation(mAccount, mContext);
+ RemoteOperationResult result = update.execute(client);
+ if (result.isSuccess()) {
+ mIsShareSupported = update.getOCVersion().isSharedSupported();
+ }
+ }
+
+
+ private RemoteOperationResult checkForChanges(OwnCloudClient client) {
+ mRemoteFolderChanged = true;
+ RemoteOperationResult result = null;
+ String remotePath = null;
+
+ remotePath = mLocalFolder.getRemotePath();
+ Log_OC.d(TAG, "Checking changes in " + mAccount.name + remotePath);
+
+ // remote request
+ ReadRemoteFileOperation operation = new ReadRemoteFileOperation(remotePath);
+ result = operation.execute(client);
+ if (result.isSuccess()){
+ OCFile remoteFolder = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0));
+
+ if (!mIgnoreETag) {
+ // check if remote and local folder are different
+ mRemoteFolderChanged =
+ !(remoteFolder.getEtag().equalsIgnoreCase(mLocalFolder.getEtag()));
+ }
+
+ result = new RemoteOperationResult(ResultCode.OK);
+
+ Log_OC.i(TAG, "Checked " + mAccount.name + remotePath + " : " +
+ (mRemoteFolderChanged ? "changed" : "not changed"));
+
+ } else {
+ // check failed
+ if (result.getCode() == ResultCode.FILE_NOT_FOUND) {
+ removeLocalFolder();
+ }
+ if (result.isException()) {
+ Log_OC.e(TAG, "Checked " + mAccount.name + remotePath + " : " +
+ result.getLogMessage(), result.getException());
+ } else {
+ Log_OC.e(TAG, "Checked " + mAccount.name + remotePath + " : " +
+ result.getLogMessage());
+ }
+ }
+
+ return result;
+ }
+
+
+ private RemoteOperationResult fetchAndSyncRemoteFolder(OwnCloudClient client) {
+ String remotePath = mLocalFolder.getRemotePath();
+ ReadRemoteFolderOperation operation = new ReadRemoteFolderOperation(remotePath);
+ RemoteOperationResult result = operation.execute(client);
+ Log_OC.d(TAG, "Synchronizing " + mAccount.name + remotePath);
+
+ if (result.isSuccess()) {
+ synchronizeData(result.getData(), client);
+ if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) {
+ result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
+ // should be a different result code, but will do the job
+ }
+ } else {
+ if (result.getCode() == ResultCode.FILE_NOT_FOUND)
+ removeLocalFolder();
+ }
+
+ return result;
+ }
+
+
+ private void removeLocalFolder() {
+ if (mStorageManager.fileExists(mLocalFolder.getFileId())) {
+ String currentSavePath = FileStorageUtils.getSavePath(mAccount.name);
+ mStorageManager.removeFolder(
+ mLocalFolder,
+ true,
+ ( mLocalFolder.isDown() &&
+ mLocalFolder.getStoragePath().startsWith(currentSavePath)
+ )
+ );
+ }
+ }
+
+
+ /**
+ * Synchronizes the data retrieved from the server about the contents of the target folder
+ * with the current data in the local database.
+ *
+ * Grants that mChildren is updated with fresh data after execution.
+ *
+ * @param folderAndFiles Remote folder and children files in Folder
+ *
+ * @param client Client instance to the remote server where the data were
+ * retrieved.
+ * @return 'True' when any change was made in the local data, 'false' otherwise
+ */
+ private void synchronizeData(ArrayList<Object> folderAndFiles, OwnCloudClient client) {
+ // get 'fresh data' from the database
+ mLocalFolder = mStorageManager.getFileByPath(mLocalFolder.getRemotePath());
+
+ // parse data from remote folder
+ OCFile remoteFolder = fillOCFile((RemoteFile)folderAndFiles.get(0));
+ remoteFolder.setParentId(mLocalFolder.getParentId());
+ remoteFolder.setFileId(mLocalFolder.getFileId());
+
+ Log_OC.d(TAG, "Remote folder " + mLocalFolder.getRemotePath()
+ + " changed - starting update of local data ");
+
+ List<OCFile> updatedFiles = new Vector<OCFile>(folderAndFiles.size() - 1);
+ List<SynchronizeFileOperation> filesToSyncContents = new Vector<SynchronizeFileOperation>();
+
+ // get current data about local contents of the folder to synchronize
+ List<OCFile> localFiles = mStorageManager.getFolderContent(mLocalFolder);
+ Map<String, OCFile> localFilesMap = new HashMap<String, OCFile>(localFiles.size());
+ for (OCFile file : localFiles) {
+ localFilesMap.put(file.getRemotePath(), file);
+ }
+
+ // loop to update every child
+ OCFile remoteFile = null, localFile = null;
+ for (int i=1; i<folderAndFiles.size(); i++) {
+ /// new OCFile instance with the data from the server
+ remoteFile = fillOCFile((RemoteFile)folderAndFiles.get(i));
+ remoteFile.setParentId(mLocalFolder.getFileId());
+
+ /// retrieve local data for the read file
+ // localFile = mStorageManager.getFileByPath(remoteFile.getRemotePath());
+ localFile = localFilesMap.remove(remoteFile.getRemotePath());
+
+ /// add to the remoteFile (the new one) data about LOCAL STATE (not existing in server)
+ remoteFile.setLastSyncDateForProperties(mCurrentSyncTime);
+ if (localFile != null) {
+ // some properties of local state are kept unmodified
+ remoteFile.setFileId(localFile.getFileId());
+ remoteFile.setKeepInSync(localFile.keepInSync());
+ remoteFile.setLastSyncDateForData(localFile.getLastSyncDateForData());
+ remoteFile.setModificationTimestampAtLastSyncForData(
+ localFile.getModificationTimestampAtLastSyncForData()
+ );
+ remoteFile.setStoragePath(localFile.getStoragePath());
+ // eTag will not be updated unless contents are synchronized
+ // (Synchronize[File|Folder]Operation with remoteFile as parameter)
+ remoteFile.setEtag(localFile.getEtag());
+ if (remoteFile.isFolder()) {
+ remoteFile.setFileLength(localFile.getFileLength());
+ // TODO move operations about size of folders to FileContentProvider
+ } else if (mRemoteFolderChanged && remoteFile.isImage() &&
+ remoteFile.getModificationTimestamp() != localFile.getModificationTimestamp()) {
+ remoteFile.setNeedsUpdateThumbnail(true);
+ Log.d(TAG, "Image " + remoteFile.getFileName() + " updated on the server");
+ }
+ remoteFile.setPublicLink(localFile.getPublicLink());
+ remoteFile.setShareByLink(localFile.isShareByLink());
+ } else {
+ // remote eTag will not be updated unless contents are synchronized
+ // (Synchronize[File|Folder]Operation with remoteFile as parameter)
+ remoteFile.setEtag("");
+ }
+
+ /// check and fix, if needed, local storage path
+ checkAndFixForeignStoragePath(remoteFile); // policy - local files are 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,
+ mAccount,
+ true,
+ mContext
+ );
+
+ filesToSyncContents.add(operation);
+ }
+
+ updatedFiles.add(remoteFile);
+ }
+
+ // save updated contents in local database
+ mStorageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values());
+
+ // request for the synchronization of file contents AFTER saving current remote properties
+ startContentSynchronizations(filesToSyncContents, client);
+
+ mChildren = updatedFiles;
+ }
+
+ /**
+ * Performs a list of synchronization operations, determining if a download or upload is needed
+ * or if exists conflict due to changes both in local and remote contents of the each file.
+ *
+ * If download or upload is needed, request the operation to the corresponding service and goes
+ * on.
+ *
+ * @param filesToSyncContents Synchronization operations to execute.
+ * @param client Interface to the remote ownCloud server.
+ */
+ private void startContentSynchronizations(
+ List<SynchronizeFileOperation> filesToSyncContents, OwnCloudClient client
+ ) {
+ RemoteOperationResult contentsResult = null;
+ for (SynchronizeFileOperation op: filesToSyncContents) {
+ contentsResult = op.execute(mStorageManager, mContext); // async
+ 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);
+ }
+
+ /**
+ * Creates and populates a new {@link OCFile} object with the data read from the server.
+ *
+ * @param remote remote file read from the server (remote file or folder).
+ * @return New OCFile instance representing the remote resource described by we.
+ */
+ private OCFile fillOCFile(RemoteFile remote) {
+ OCFile file = new OCFile(remote.getRemotePath());
+ file.setCreationTimestamp(remote.getCreationTimestamp());
+ file.setFileLength(remote.getLength());
+ file.setMimetype(remote.getMimeType());
+ file.setModificationTimestamp(remote.getModifiedTimestamp());
+ file.setEtag(remote.getEtag());
+ file.setPermissions(remote.getPermissions());
+ file.setRemoteId(remote.getRemoteId());
+ return file;
+ }
+
+
+ /**
+ * Checks the storage path of the OCFile received as parameter.
+ * If it's out of the local ownCloud folder, tries to copy the file inside it.
+ *
+ * If the copy fails, the link to the local file is nullified. The account of forgotten
+ * files is kept in {@link #mForgottenLocalFiles}
+ *)
+ * @param file File to check and fix.
+ */
+ private void checkAndFixForeignStoragePath(OCFile file) {
+ String storagePath = file.getStoragePath();
+ String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, file);
+ if (storagePath != null && !storagePath.equals(expectedPath)) {
+ /// fix storagePaths out of the local ownCloud folder
+ File originalFile = new File(storagePath);
+ if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) {
+ mForgottenLocalFiles.put(file.getRemotePath(), storagePath);
+ file.setStoragePath(null);
+
+ } else {
+ InputStream in = null;
+ OutputStream out = null;
+ try {
+ File expectedFile = new File(expectedPath);
+ File expectedParent = expectedFile.getParentFile();
+ expectedParent.mkdirs();
+ if (!expectedParent.isDirectory()) {
+ throw new IOException(
+ "Unexpected error: parent directory could not be created"
+ );
+ }
+ expectedFile.createNewFile();
+ if (!expectedFile.isFile()) {
+ throw new IOException("Unexpected error: target file could not be created");
+ }
+ in = new FileInputStream(originalFile);
+ out = new FileOutputStream(expectedFile);
+ byte[] buf = new byte[1024];
+ int len;
+ while ((len = in.read(buf)) > 0){
+ out.write(buf, 0, len);
+ }
+ file.setStoragePath(expectedPath);
+
+ } catch (Exception e) {
+ Log_OC.e(TAG, "Exception while copying foreign file " + expectedPath, e);
+ mForgottenLocalFiles.put(file.getRemotePath(), storagePath);
+ file.setStoragePath(null);
+
+ } finally {
+ try {
+ if (in != null) in.close();
+ } catch (Exception e) {
+ Log_OC.d(TAG, "Weird exception while closing input stream for "
+ + storagePath + " (ignoring)", e);
+ }
+ try {
+ if (out != null) out.close();
+ } catch (Exception e) {
+ Log_OC.d(TAG, "Weird exception while closing output stream for "
+ + expectedPath + " (ignoring)", e);
+ }
+ }
+ }
+ }
+ }
+
+
+ private RemoteOperationResult refreshSharesForFolder(OwnCloudClient client) {
+ RemoteOperationResult result = null;
+
+ // remote request
+ GetRemoteSharesForFileOperation operation =
+ new GetRemoteSharesForFileOperation(mLocalFolder.getRemotePath(), false, true);
+ result = operation.execute(client);
+
+ if (result.isSuccess()) {
+ // update local database
+ ArrayList<OCShare> shares = new ArrayList<OCShare>();
+ for(Object obj: result.getData()) {
+ shares.add((OCShare) obj);
+ }
+ mStorageManager.saveSharesInFolder(shares, mLocalFolder);
+ }
+
+ return result;
+ }
+
+
+ /**
+ * Scans the default location for saving local copies of files searching for
+ * a 'lost' file with the same full name as the {@link OCFile} received as
+ * parameter.
+ *
+ * @param file File to associate a possible 'lost' local file.
+ */
+ private void searchForLocalFileInDefaultPath(OCFile file) {
+ if (file.getStoragePath() == null && !file.isFolder()) {
+ File f = new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, file));
+ if (f.exists()) {
+ file.setStoragePath(f.getAbsolutePath());
+ file.setLastSyncDateForData(f.lastModified());
+ }
+ }
+ }
+
+
+ /**
+ * Sends a message to any application component interested in the progress
+ * of the synchronization.
+ *
+ * @param event
+ * @param dirRemotePath Remote path of a folder that was just synchronized
+ * (with or without success)
+ * @param result
+ */
+ private void sendLocalBroadcast(
+ String event, String dirRemotePath, RemoteOperationResult result
+ ) {
+ Log_OC.d(TAG, "Send broadcast " + event);
+ Intent intent = new Intent(event);
+ intent.putExtra(FileSyncAdapter.EXTRA_ACCOUNT_NAME, mAccount.name);
+ if (dirRemotePath != null) {
+ intent.putExtra(FileSyncAdapter.EXTRA_FOLDER_PATH, dirRemotePath);
+ }
+ intent.putExtra(FileSyncAdapter.EXTRA_RESULT, result);
+ mContext.sendStickyBroadcast(intent);
+ //LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
+ }
+
+
+ public boolean getRemoteFolderChanged() {
+ return mRemoteFolderChanged;
+ }
+
+}
private boolean mTransferWasRequested = false;
+ /**
+ * When 'false', uploads to the server are not done; only downloads or conflict detection.
+ * This is a temporal field.
+ * TODO Remove when 'folder synchronization' replaces 'folder download'.
+ */
+ private boolean mAllowUploads;
+
/**
- * Constructor.
+ * Constructor for "full synchronization mode".
*
- * Uses remotePath to retrieve all the data in local cache and remote server when the operation
+ * Uses remotePath to retrieve all the data both in local cache and in the remote OC server when the operation
* is executed, instead of reusing {@link OCFile} instances.
*
+ * Useful for direct synchronization of a single file.
+ *
* @param
* @param account ownCloud account holding the file.
* @param syncFileContents When 'true', transference of data will be started by the
mAccount = account;
mSyncFileContents = syncFileContents;
mContext = context;
+ mAllowUploads = true;
}
/**
- * Constructor allowing to reuse {@link OCFile} instances just queried from cache or network.
+ * Constructor allowing to reuse {@link OCFile} instances just queried from local cache or from remote OC server.
*
- * Useful for folder / account synchronizations.
+ * Useful to include this operation as part of the synchronization of a folder (or a full account), avoiding the
+ * repetition of fetch operations (both in local database or remote server).
*
- * @param localFile Data of file currently hold in device cache. MUSTN't be null.
- * @param serverFile Data of file just retrieved from network. If null, will be
+ * At least one of localFile or serverFile MUST NOT BE NULL. If you don't have none of them, use the other
+ * constructor.
+ *
+ * @param localFile Data of file (just) retrieved from local cache/database.
+ * @param serverFile Data of file (just) retrieved from a remote server. If null, will be
* retrieved from network by the operation when executed.
* @param account ownCloud account holding the file.
* @param syncFileContents When 'true', transference of data will be started by the
mLocalFile = localFile;
mServerFile = serverFile;
- mRemotePath = localFile.getRemotePath();
+ if (mLocalFile != null) {
+ mRemotePath = mLocalFile.getRemotePath();
+ if (mServerFile != null && !mServerFile.getRemotePath().equals(mRemotePath)) {
+ throw new IllegalArgumentException("serverFile and localFile do not correspond to the same OC file");
+ }
+ } else if (mServerFile != null) {
+ mRemotePath = mServerFile.getRemotePath();
+ } else {
+ throw new IllegalArgumentException("Both serverFile and localFile are NULL");
+ }
mAccount = account;
mSyncFileContents = syncFileContents;
mContext = context;
+ mAllowUploads = true;
+ }
+
+
+ /**
+ * Temporal constructor.
+ *
+ * Extends the previous one to allow constrained synchronizations where uploads are never performed - only
+ * downloads or conflict detection.
+ *
+ * Do not use unless you are involved in 'folder synchronization' or 'folder download' work in progress.
+ *
+ * TODO Remove when 'folder synchronization' replaces 'folder download'.
+ *
+ * @param localFile Data of file (just) retrieved from local cache/database. MUSTN't be null.
+ * @param serverFile Data of file (just) retrieved from a remote server. If null, will be
+ * retrieved from network by the operation when executed.
+ * @param account ownCloud account holding the file.
+ * @param syncFileContents When 'true', transference of data will be started by the
+ * operation if needed and no conflict is detected.
+ * @param allowUploads When 'false', uploads to the server are not done; only downloads or conflict
+ * detection.
+ * @param context Android context; needed to start transfers.
+ */
+ public SynchronizeFileOperation(
+ OCFile localFile,
+ OCFile serverFile,
+ Account account,
+ boolean syncFileContents,
+ boolean allowUploads,
+ Context context) {
+
+ this(localFile, serverFile, account, syncFileContents, context);
+ mAllowUploads = allowUploads;
}
boolean serverChanged = false;
/* time for eTag is coming, but not yet
if (mServerFile.getEtag() != null) {
- serverChanged = (!mServerFile.getEtag().equals(mLocalFile.getEtag())); // TODO could this be dangerous when the user upgrades the server from non-tagged to tagged?
+ serverChanged = (!mServerFile.getEtag().equals(mLocalFile.getEtag()));
} else { */
- // server without etags
- serverChanged = (mServerFile.getModificationTimestamp() != mLocalFile.getModificationTimestampAtLastSyncForData());
+ serverChanged = (
+ mServerFile.getModificationTimestamp() != mLocalFile.getModificationTimestampAtLastSyncForData()
+ );
//}
- boolean localChanged = (mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData());
- // TODO this will be always true after the app is upgraded to database version 2; will result in unnecessary uploads
+ boolean localChanged = (
+ mLocalFile.getLocalModificationTimestamp() > mLocalFile.getLastSyncDateForData()
+ );
/// decide action to perform depending upon changes
//if (!mLocalFile.getEtag().isEmpty() && localChanged && serverChanged) {
result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
} else if (localChanged) {
- if (mSyncFileContents) {
+ if (mSyncFileContents && mAllowUploads) {
requestForUpload(mLocalFile);
// the local update of file properties will be done by the FileUploader service when the upload finishes
} else {
}
- Log_OC.i(TAG, "Synchronizing " + mAccount.name + ", file " + mLocalFile.getRemotePath() + ": " + result.getLogMessage());
+ Log_OC.i(TAG, "Synchronizing " + mAccount.name + ", file " + mLocalFile.getRemotePath() + ": "
+ + result.getLogMessage());
return result;
}
package com.owncloud.android.operations;
-import java.io.File;
-import java.io.FileInputStream;
-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;
-import java.util.Vector;
-
-import org.apache.http.HttpStatus;
import android.accounts.Account;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
-//import android.support.v4.content.LocalBroadcastManager;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
-
+import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.lib.common.OwnCloudClient;
-import com.owncloud.android.lib.resources.shares.OCShare;
-import com.owncloud.android.lib.common.operations.RemoteOperation;
+import com.owncloud.android.lib.common.operations.OperationCancelledException;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.lib.resources.shares.GetRemoteSharesForFileOperation;
-import com.owncloud.android.lib.resources.files.FileUtils;
import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation;
import com.owncloud.android.lib.resources.files.ReadRemoteFolderOperation;
import com.owncloud.android.lib.resources.files.RemoteFile;
-
-import com.owncloud.android.syncadapter.FileSyncAdapter;
+import com.owncloud.android.operations.common.SyncOperation;
import com.owncloud.android.utils.FileStorageUtils;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+//import android.support.v4.content.LocalBroadcastManager;
/**
*
* @author David A. Velasco
*/
-public class SynchronizeFolderOperation extends RemoteOperation {
+public class SynchronizeFolderOperation extends SyncOperation {
private static final String TAG = SynchronizeFolderOperation.class.getSimpleName();
- public static final String EVENT_SINGLE_FOLDER_CONTENTS_SYNCED =
- SynchronizeFolderOperation.class.getName() + ".EVENT_SINGLE_FOLDER_CONTENTS_SYNCED";
- public static final String EVENT_SINGLE_FOLDER_SHARES_SYNCED =
- SynchronizeFolderOperation.class.getName() + ".EVENT_SINGLE_FOLDER_SHARES_SYNCED";
-
/** Time stamp for the synchronization process in progress */
private long mCurrentSyncTime;
-
- /** Remote folder to synchronize */
- private OCFile mLocalFolder;
-
- /** Access to the local database */
- private FileDataStorageManager mStorageManager;
+
+ /** Remote path of the folder to synchronize */
+ private String mRemotePath;
/** Account where the file to synchronize belongs */
private Account mAccount;
-
+
/** Android context; necessary to send requests to the download service */
private Context mContext;
-
+
+ /** Locally cached information about folder to synchronize */
+ private OCFile mLocalFolder;
+
/** 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;
/** Counter of failed operations in synchronization of kept-in-sync files */
- private int mFailsInFavouritesFound;
+ private int mFailsInFileSyncsFound;
- /**
- * 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;
-
- /** 'True' means that Share resources bound to the files into should be refreshed also */
- private boolean mIsShareSupported;
-
/** 'True' means that the remote folder changed and should be fetched */
private boolean mRemoteFolderChanged;
- /** 'True' means that Etag will be ignored */
- private boolean mIgnoreETag;
-
+ private List<OCFile> mFilesForDirectDownload;
+ // to avoid extra PROPFINDs when there was no change in the folder
+ private List<SyncOperation> mFilesToSyncContentsWithoutUpload;
+ // this will go out when 'folder synchronization' replaces 'folder download'; step by step
+
+ private List<SyncOperation> mFavouriteFilesToSyncContents;
+ // this will be used for every file when 'folder synchronization' replaces 'folder download'
+
+ private List<SyncOperation> mFoldersToWalkDown;
+
+ private final AtomicBoolean mCancellationRequested;
+
/**
* Creates a new instance of {@link SynchronizeFolderOperation}.
- *
- * @param folder Folder to synchronize.
- * @param currentSyncTime Time stamp for the synchronization process in progress.
- * @param syncFullAccount 'True' means that this operation is part of a full account
- * synchronization.
- * @param isShareSupported 'True' means that the server supports the sharing API.
- * @param ignoreEtag 'True' means that the content of the remote folder should
- * be fetched and updated even though the 'eTag' did not
- * change.
- * @param dataStorageManager Interface with the local database.
- * @param account ownCloud account where the folder is located.
+ *
* @param context Application context.
+ * @param remotePath Path to synchronize.
+ * @param account ownCloud account where the folder is located.
+ * @param currentSyncTime Time stamp for the synchronization process in progress.
*/
- public SynchronizeFolderOperation( OCFile folder,
- long currentSyncTime,
- boolean syncFullAccount,
- boolean isShareSupported,
- boolean ignoreETag,
- FileDataStorageManager dataStorageManager,
- Account account,
- Context context ) {
- mLocalFolder = folder;
+ public SynchronizeFolderOperation(Context context, String remotePath, Account account, long currentSyncTime){
+ mRemotePath = remotePath;
mCurrentSyncTime = currentSyncTime;
- mSyncFullAccount = syncFullAccount;
- mIsShareSupported = isShareSupported;
- mStorageManager = dataStorageManager;
mAccount = account;
mContext = context;
- mForgottenLocalFiles = new HashMap<String, String>();
mRemoteFolderChanged = false;
- mIgnoreETag = ignoreETag;
+ mFilesForDirectDownload = new Vector<OCFile>();
+ mFilesToSyncContentsWithoutUpload = new Vector<SyncOperation>();
+ mFavouriteFilesToSyncContents = new Vector<SyncOperation>();
+ mFoldersToWalkDown = new Vector<SyncOperation>();
+ mCancellationRequested = new AtomicBoolean(false);
}
-
-
+
+
public int getConflictsFound() {
return mConflictsFound;
}
-
- public int getFailsInFavouritesFound() {
- return mFailsInFavouritesFound;
- }
-
- public Map<String, String> getForgottenLocalFiles() {
- return mForgottenLocalFiles;
- }
-
- /**
- * 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.
- */
- public List<OCFile> getChildren() {
- return mChildren;
+
+ public int getFailsInFileSyncsFound() {
+ return mFailsInFileSyncsFound;
}
-
+
/**
* Performs the synchronization.
- *
+ *
* {@inheritDoc}
*/
@Override
protected RemoteOperationResult run(OwnCloudClient client) {
RemoteOperationResult result = null;
- mFailsInFavouritesFound = 0;
+ mFailsInFileSyncsFound = 0;
mConflictsFound = 0;
- mForgottenLocalFiles.clear();
-
- if (FileUtils.PATH_SEPARATOR.equals(mLocalFolder.getRemotePath()) && !mSyncFullAccount) {
- updateOCVersion(client);
- }
-
- result = checkForChanges(client);
- if (result.isSuccess()) {
- if (mRemoteFolderChanged) {
- result = fetchAndSyncRemoteFolder(client);
- } else {
- mChildren = mStorageManager.getFolderContent(mLocalFolder);
+ try {
+ // get locally cached information about folder
+ mLocalFolder = getStorageManager().getFileByPath(mRemotePath);
+
+ result = checkForChanges(client);
+
+ if (result.isSuccess()) {
+ if (mRemoteFolderChanged) {
+ result = fetchAndSyncRemoteFolder(client);
+
+ } else {
+ prepareOpsFromLocalKnowledge();
+ }
+
+ if (result.isSuccess()) {
+ syncContents(client);
+ }
+
+ if (mFilesForDirectDownload.isEmpty()) {
+ sendBroadcastForNotifyingUIUpdate(result.isSuccess());
+ }
+ }
+ } catch (OperationCancelledException e) {
+ result = new RemoteOperationResult(e);
+
+ // cancel 'child' synchronizations
+ for (SyncOperation synchOp: mFoldersToWalkDown) {
+ ((SynchronizeFolderOperation) synchOp).cancel();
}
}
-
- if (!mSyncFullAccount) {
- sendLocalBroadcast(
- EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, mLocalFolder.getRemotePath(), result
- );
- }
-
- if (result.isSuccess() && mIsShareSupported && !mSyncFullAccount) {
- refreshSharesForFolder(client); // share result is ignored
- }
-
- if (!mSyncFullAccount) {
- sendLocalBroadcast(
- EVENT_SINGLE_FOLDER_SHARES_SYNCED, mLocalFolder.getRemotePath(), result
- );
- }
-
- return result;
-
- }
+ return result;
- private void updateOCVersion(OwnCloudClient client) {
- UpdateOCVersionOperation update = new UpdateOCVersionOperation(mAccount, mContext);
- RemoteOperationResult result = update.execute(client);
- if (result.isSuccess()) {
- mIsShareSupported = update.getOCVersion().isSharedSupported();
- }
}
-
- private RemoteOperationResult checkForChanges(OwnCloudClient client) {
+ private RemoteOperationResult checkForChanges(OwnCloudClient client) throws OperationCancelledException {
+ Log_OC.d(TAG, "Checking changes in " + mAccount.name + mRemotePath);
+
mRemoteFolderChanged = true;
RemoteOperationResult result = null;
- String remotePath = null;
-
- remotePath = mLocalFolder.getRemotePath();
- Log_OC.d(TAG, "Checking changes in " + mAccount.name + remotePath);
- // remote request
- ReadRemoteFileOperation operation = new ReadRemoteFileOperation(remotePath);
+ if (mCancellationRequested.get()) {
+ throw new OperationCancelledException();
+ }
+
+ // remote request
+ ReadRemoteFileOperation operation = new ReadRemoteFileOperation(mRemotePath);
result = operation.execute(client);
if (result.isSuccess()){
OCFile remoteFolder = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0));
- if (!mIgnoreETag) {
- // check if remote and local folder are different
- mRemoteFolderChanged =
+ // check if remote and local folder are different
+ mRemoteFolderChanged =
!(remoteFolder.getEtag().equalsIgnoreCase(mLocalFolder.getEtag()));
- }
result = new RemoteOperationResult(ResultCode.OK);
-
- Log_OC.i(TAG, "Checked " + mAccount.name + remotePath + " : " +
+
+ Log_OC.i(TAG, "Checked " + mAccount.name + mRemotePath + " : " +
(mRemoteFolderChanged ? "changed" : "not changed"));
-
+
} else {
// check failed
if (result.getCode() == ResultCode.FILE_NOT_FOUND) {
removeLocalFolder();
}
if (result.isException()) {
- Log_OC.e(TAG, "Checked " + mAccount.name + remotePath + " : " +
+ Log_OC.e(TAG, "Checked " + mAccount.name + mRemotePath + " : " +
result.getLogMessage(), result.getException());
} else {
- Log_OC.e(TAG, "Checked " + mAccount.name + remotePath + " : " +
+ Log_OC.e(TAG, "Checked " + mAccount.name + mRemotePath + " : " +
result.getLogMessage());
}
+
+ sendBroadcastForNotifyingUIUpdate(result.isSuccess());
}
-
+
return result;
}
- private RemoteOperationResult fetchAndSyncRemoteFolder(OwnCloudClient client) {
- String remotePath = mLocalFolder.getRemotePath();
- ReadRemoteFolderOperation operation = new ReadRemoteFolderOperation(remotePath);
- RemoteOperationResult result = operation.execute(client);
- Log_OC.d(TAG, "Synchronizing " + mAccount.name + remotePath);
+ private RemoteOperationResult fetchAndSyncRemoteFolder(OwnCloudClient client) throws OperationCancelledException {
+ if (mCancellationRequested.get()) {
+ throw new OperationCancelledException();
+ }
+ ReadRemoteFolderOperation operation = new ReadRemoteFolderOperation(mRemotePath);
+ RemoteOperationResult result = operation.execute(client);
+ Log_OC.d(TAG, "Synchronizing " + mAccount.name + mRemotePath);
+
if (result.isSuccess()) {
synchronizeData(result.getData(), client);
- if (mConflictsFound > 0 || mFailsInFavouritesFound > 0) {
- result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
+ if (mConflictsFound > 0 || mFailsInFileSyncsFound > 0) {
+ result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT);
// should be a different result code, but will do the job
}
} else {
removeLocalFolder();
}
+
return result;
}
-
+
private void removeLocalFolder() {
- if (mStorageManager.fileExists(mLocalFolder.getFileId())) {
+ FileDataStorageManager storageManager = getStorageManager();
+ if (storageManager.fileExists(mLocalFolder.getFileId())) {
String currentSavePath = FileStorageUtils.getSavePath(mAccount.name);
- mStorageManager.removeFolder(
- mLocalFolder,
- true,
- ( mLocalFolder.isDown() &&
+ storageManager.removeFolder(
+ mLocalFolder,
+ true,
+ ( mLocalFolder.isDown() && // TODO: debug, I think this is always false for folders
mLocalFolder.getStoragePath().startsWith(currentSavePath)
)
);
/**
- * Synchronizes the data retrieved from the server about the contents of the target folder
+ * 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 folderAndFiles Remote folder and children files in Folder
- *
- * @param client Client instance to the remote server where the data were
- * retrieved.
+ *
+ * @param folderAndFiles Remote folder and children files in Folder
+ *
+ * @param client Client instance to the remote server where the data were
+ * retrieved.
* @return 'True' when any change was made in the local data, 'false' otherwise
*/
private void synchronizeData(ArrayList<Object> folderAndFiles, OwnCloudClient client) {
- // get 'fresh data' from the database
- mLocalFolder = mStorageManager.getFileByPath(mLocalFolder.getRemotePath());
-
- // parse data from remote folder
+ FileDataStorageManager storageManager = getStorageManager();
+
+ // parse data from remote folder
OCFile remoteFolder = fillOCFile((RemoteFile)folderAndFiles.get(0));
remoteFolder.setParentId(mLocalFolder.getParentId());
remoteFolder.setFileId(mLocalFolder.getFileId());
-
- Log_OC.d(TAG, "Remote folder " + mLocalFolder.getRemotePath()
+
+ Log_OC.d(TAG, "Remote folder " + mLocalFolder.getRemotePath()
+ " changed - starting update of local data ");
-
+
List<OCFile> updatedFiles = new Vector<OCFile>(folderAndFiles.size() - 1);
- List<SynchronizeFileOperation> filesToSyncContents = new Vector<SynchronizeFileOperation>();
+ mFilesForDirectDownload.clear();
+ mFilesToSyncContentsWithoutUpload.clear();
+ mFavouriteFilesToSyncContents.clear();
+ mFoldersToWalkDown.clear();
// get current data about local contents of the folder to synchronize
- List<OCFile> localFiles = mStorageManager.getFolderContent(mLocalFolder);
+ List<OCFile> localFiles = storageManager.getFolderContent(mLocalFolder);
Map<String, OCFile> localFilesMap = new HashMap<String, OCFile>(localFiles.size());
for (OCFile file : localFiles) {
localFilesMap.put(file.getRemotePath(), file);
}
-
- // loop to update every child
+
+ // loop to synchronize every child
OCFile remoteFile = null, localFile = null;
for (int i=1; i<folderAndFiles.size(); i++) {
/// new OCFile instance with the data from the server
remoteFile = fillOCFile((RemoteFile)folderAndFiles.get(i));
remoteFile.setParentId(mLocalFolder.getFileId());
- /// retrieve local data for the read file
+ /// retrieve local data for the read file
// localFile = mStorageManager.getFileByPath(remoteFile.getRemotePath());
localFile = localFilesMap.remove(remoteFile.getRemotePath());
-
+
/// add to the remoteFile (the new one) data about LOCAL STATE (not existing in server)
remoteFile.setLastSyncDateForProperties(mCurrentSyncTime);
if (localFile != null) {
localFile.getModificationTimestampAtLastSyncForData()
);
remoteFile.setStoragePath(localFile.getStoragePath());
- // eTag will not be updated unless contents are synchronized
+ // eTag will not be updated unless contents are synchronized
// (Synchronize[File|Folder]Operation with remoteFile as parameter)
- remoteFile.setEtag(localFile.getEtag());
+ remoteFile.setEtag(localFile.getEtag());
if (remoteFile.isFolder()) {
- remoteFile.setFileLength(localFile.getFileLength());
+ remoteFile.setFileLength(localFile.getFileLength());
// TODO move operations about size of folders to FileContentProvider
} else if (mRemoteFolderChanged && remoteFile.isImage() &&
remoteFile.getModificationTimestamp() != localFile.getModificationTimestamp()) {
remoteFile.setPublicLink(localFile.getPublicLink());
remoteFile.setShareByLink(localFile.isShareByLink());
} else {
- // remote eTag will not be updated unless contents are synchronized
+ // remote eTag will not be updated unless contents are synchronized
// (Synchronize[File|Folder]Operation with remoteFile as parameter)
- remoteFile.setEtag("");
+ remoteFile.setEtag("");
}
/// check and fix, if needed, local storage path
- checkAndFixForeignStoragePath(remoteFile); // policy - local files are 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,
- mAccount,
- true,
- mContext
- );
+ searchForLocalFileInDefaultPath(remoteFile);
+
+ /// classify file to sync/download contents later
+ if (remoteFile.isFolder()) {
+ /// to download children files recursively
+ SynchronizeFolderOperation synchFolderOp = new SynchronizeFolderOperation(
+ mContext,
+ remoteFile.getRemotePath(),
+ mAccount,
+ mCurrentSyncTime
+ );
+ mFoldersToWalkDown.add(synchFolderOp);
+
+ } else if (remoteFile.keepInSync()) {
+ /// prepare content synchronization for kept-in-sync files
+ SynchronizeFileOperation operation = new SynchronizeFileOperation(
+ localFile,
+ remoteFile,
+ mAccount,
+ true,
+ mContext
+ );
+ mFavouriteFilesToSyncContents.add(operation);
- filesToSyncContents.add(operation);
+ } else {
+ /// prepare limited synchronization for regular files
+ SynchronizeFileOperation operation = new SynchronizeFileOperation(
+ localFile,
+ remoteFile,
+ mAccount,
+ true,
+ false,
+ mContext
+ );
+ mFilesToSyncContentsWithoutUpload.add(operation);
}
-
+
updatedFiles.add(remoteFile);
}
// save updated contents in local database
- mStorageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values());
+ storageManager.saveFolder(remoteFolder, updatedFiles, localFilesMap.values());
+
+ }
+
+
+ private void prepareOpsFromLocalKnowledge() {
+ List<OCFile> children = getStorageManager().getFolderContent(mLocalFolder);
+ for (OCFile child : children) {
+ /// classify file to sync/download contents later
+ if (child.isFolder()) {
+ /// to download children files recursively
+ SynchronizeFolderOperation synchFolderOp = new SynchronizeFolderOperation(
+ mContext,
+ child.getRemotePath(),
+ mAccount,
+ mCurrentSyncTime
+ );
+ mFoldersToWalkDown.add(synchFolderOp);
+
+ } else {
+ /// prepare limited synchronization for regular files
+ if (!child.isDown()) {
+ mFilesForDirectDownload.add(child);
+ }
+ }
+ }
+ }
- // request for the synchronization of file contents AFTER saving current remote properties
- startContentSynchronizations(filesToSyncContents, client);
- mChildren = updatedFiles;
+ private void syncContents(OwnCloudClient client) throws OperationCancelledException {
+ startDirectDownloads();
+ startContentSynchronizations(mFilesToSyncContentsWithoutUpload, client);
+ startContentSynchronizations(mFavouriteFilesToSyncContents, client);
+ walkSubfolders(client); // this must be the last!
+ }
+
+
+ private void startDirectDownloads() throws OperationCancelledException {
+ for (OCFile file : mFilesForDirectDownload) {
+ if (mCancellationRequested.get()) {
+ throw new OperationCancelledException();
+ }
+ Intent i = new Intent(mContext, FileDownloader.class);
+ i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount);
+ i.putExtra(FileDownloader.EXTRA_FILE, file);
+ mContext.startService(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
+ *
+ * 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, OwnCloudClient client
- ) {
+ private void startContentSynchronizations(List<SyncOperation> filesToSyncContents, OwnCloudClient client)
+ throws OperationCancelledException {
+
RemoteOperationResult contentsResult = null;
- for (SynchronizeFileOperation op: filesToSyncContents) {
- contentsResult = op.execute(mStorageManager, mContext); // async
+ for (SyncOperation op: filesToSyncContents) {
+ if (mCancellationRequested.get()) {
+ throw new OperationCancelledException();
+ }
+ contentsResult = op.execute(getStorageManager(), mContext);
if (!contentsResult.isSuccess()) {
if (contentsResult.getCode() == ResultCode.SYNC_CONFLICT) {
mConflictsFound++;
} else {
- mFailsInFavouritesFound++;
+ mFailsInFileSyncsFound++;
if (contentsResult.getException() != null) {
- Log_OC.e(TAG, "Error while synchronizing favourites : "
+ Log_OC.e(TAG, "Error while synchronizing file : "
+ contentsResult.getLogMessage(), contentsResult.getException());
} else {
- Log_OC.e(TAG, "Error while synchronizing favourites : "
+ Log_OC.e(TAG, "Error while synchronizing file : "
+ contentsResult.getLogMessage());
}
}
+ // TODO - use the errors count in notifications
} // won't let these fails break the synchronization process
}
}
- public boolean isMultiStatus(int status) {
- return (status == HttpStatus.SC_MULTI_STATUS);
+ private void walkSubfolders(OwnCloudClient client) throws OperationCancelledException {
+ RemoteOperationResult contentsResult = null;
+ for (SyncOperation op: mFoldersToWalkDown) {
+ if (mCancellationRequested.get()) {
+ throw new OperationCancelledException();
+ }
+ contentsResult = op.execute(client, getStorageManager()); // to watch out: possibly deep recursion
+ if (!contentsResult.isSuccess()) {
+ // TODO - some kind of error count, and use it with notifications
+ if (contentsResult.getException() != null) {
+ Log_OC.e(TAG, "Non blocking exception : "
+ + contentsResult.getLogMessage(), contentsResult.getException());
+ } else {
+ Log_OC.e(TAG, "Non blocking error : " + contentsResult.getLogMessage());
+ }
+ } // won't let these fails break the synchronization process
+ }
}
+
/**
- * Creates and populates a new {@link OCFile} object with the data read from the server.
- *
+ * Creates and populates a new {@link com.owncloud.android.datamodel.OCFile} object with the data read from the server.
+ *
* @param remote remote file read from the server (remote file or folder).
* @return New OCFile instance representing the remote resource described by we.
*/
file.setRemoteId(remote.getRemoteId());
return file;
}
-
-
- /**
- * Checks the storage path of the OCFile received as parameter.
- * If it's out of the local ownCloud folder, tries to copy the file inside it.
- *
- * If the copy fails, the link to the local file is nullified. The account of forgotten
- * files is kept in {@link #mForgottenLocalFiles}
- *)
- * @param file File to check and fix.
- */
- private void checkAndFixForeignStoragePath(OCFile file) {
- String storagePath = file.getStoragePath();
- String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, file);
- if (storagePath != null && !storagePath.equals(expectedPath)) {
- /// fix storagePaths out of the local ownCloud folder
- File originalFile = new File(storagePath);
- if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) {
- mForgottenLocalFiles.put(file.getRemotePath(), storagePath);
- file.setStoragePath(null);
-
- } else {
- InputStream in = null;
- OutputStream out = null;
- try {
- File expectedFile = new File(expectedPath);
- File expectedParent = expectedFile.getParentFile();
- expectedParent.mkdirs();
- if (!expectedParent.isDirectory()) {
- throw new IOException(
- "Unexpected error: parent directory could not be created"
- );
- }
- expectedFile.createNewFile();
- if (!expectedFile.isFile()) {
- throw new IOException("Unexpected error: target file could not be created");
- }
- in = new FileInputStream(originalFile);
- out = new FileOutputStream(expectedFile);
- byte[] buf = new byte[1024];
- int len;
- while ((len = in.read(buf)) > 0){
- out.write(buf, 0, len);
- }
- file.setStoragePath(expectedPath);
-
- } catch (Exception e) {
- Log_OC.e(TAG, "Exception while copying foreign file " + expectedPath, e);
- mForgottenLocalFiles.put(file.getRemotePath(), storagePath);
- file.setStoragePath(null);
-
- } finally {
- try {
- if (in != null) in.close();
- } catch (Exception e) {
- Log_OC.d(TAG, "Weird exception while closing input stream for "
- + storagePath + " (ignoring)", e);
- }
- try {
- if (out != null) out.close();
- } catch (Exception e) {
- Log_OC.d(TAG, "Weird exception while closing output stream for "
- + expectedPath + " (ignoring)", e);
- }
- }
- }
- }
- }
-
-
- private RemoteOperationResult refreshSharesForFolder(OwnCloudClient client) {
- RemoteOperationResult result = null;
-
- // remote request
- GetRemoteSharesForFileOperation operation =
- new GetRemoteSharesForFileOperation(mLocalFolder.getRemotePath(), false, true);
- result = operation.execute(client);
-
- if (result.isSuccess()) {
- // update local database
- ArrayList<OCShare> shares = new ArrayList<OCShare>();
- for(Object obj: result.getData()) {
- shares.add((OCShare) obj);
- }
- mStorageManager.saveSharesInFolder(shares, mLocalFolder);
- }
- return result;
- }
-
/**
* 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
+ * a 'lost' file with the same full name as the {@link com.owncloud.android.datamodel.OCFile} received as
* parameter.
*
* @param file File to associate a possible 'lost' local file.
}
}
+ private void sendBroadcastForNotifyingUIUpdate(boolean result) {
+ // Send a broadcast message for notifying UI update
+ Intent uiUpdate = new Intent(FileDownloader.getDownloadFinishMessage());
+ uiUpdate.putExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, result);
+ uiUpdate.putExtra(FileDownloader.ACCOUNT_NAME, mAccount.name);
+ uiUpdate.putExtra(FileDownloader.EXTRA_REMOTE_PATH, mRemotePath);
+ uiUpdate.putExtra(FileDownloader.EXTRA_FILE_PATH, mLocalFolder.getRemotePath());
+ mContext.sendStickyBroadcast(uiUpdate);
+ }
+
/**
- * Sends a message to any application component interested in the progress
- * of the synchronization.
- *
- * @param event
- * @param dirRemotePath Remote path of a folder that was just synchronized
- * (with or without success)
- * @param result
+ * Cancel operation
*/
- private void sendLocalBroadcast(
- String event, String dirRemotePath, RemoteOperationResult result
- ) {
- Log_OC.d(TAG, "Send broadcast " + event);
- Intent intent = new Intent(event);
- intent.putExtra(FileSyncAdapter.EXTRA_ACCOUNT_NAME, mAccount.name);
- if (dirRemotePath != null) {
- intent.putExtra(FileSyncAdapter.EXTRA_FOLDER_PATH, dirRemotePath);
- }
- intent.putExtra(FileSyncAdapter.EXTRA_RESULT, result);
- mContext.sendStickyBroadcast(intent);
- //LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
+ public void cancel() {
+ mCancellationRequested.set(true);
}
-
- public boolean getRemoteFolderChanged() {
- return mRemoteFolderChanged;
+ public String getFolderPath() {
+ String path = mLocalFolder.getStoragePath();
+ if (path != null && path.length() > 0) {
+ return path;
+ }
+ return FileStorageUtils.getDefaultSavePathFor(mAccount.name, mLocalFolder);
}
-
}
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.files.services.FileDownloader;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
import com.owncloud.android.operations.RemoveFileOperation;
import com.owncloud.android.operations.RenameFileOperation;
import com.owncloud.android.operations.SynchronizeFileOperation;
+import com.owncloud.android.operations.SynchronizeFolderOperation;
import com.owncloud.android.operations.UnshareLinkOperation;
+import com.owncloud.android.utils.FileStorageUtils;
import android.accounts.Account;
import android.accounts.AccountsException;
public static final String EXTRA_SYNC_FILE_CONTENTS = "SYNC_FILE_CONTENTS";
public static final String EXTRA_RESULT = "RESULT";
public static final String EXTRA_NEW_PARENT_PATH = "NEW_PARENT_PATH";
-
+ public static final String EXTRA_FILE = "FILE";
+
// TODO review if ALL OF THEM are necessary
public static final String EXTRA_SUCCESS_IF_ABSENT = "SUCCESS_IF_ABSENT";
public static final String EXTRA_USERNAME = "USERNAME";
public static final String ACTION_REMOVE = "REMOVE";
public static final String ACTION_CREATE_FOLDER = "CREATE_FOLDER";
public static final String ACTION_SYNC_FILE = "SYNC_FILE";
+ public static final String ACTION_SYNC_FOLDER = "SYNC_FOLDER"; // for the moment, just to download
+ public static final String ACTION_CANCEL_SYNC_FOLDER = "CANCEL_SYNC_FOLDER"; // for the moment, just to download
public static final String ACTION_MOVE_FILE = "MOVE_FILE";
public static final String ACTION_OPERATION_ADDED = OperationsService.class.getName() + ".OPERATION_ADDED";
public static final String ACTION_OPERATION_FINISHED = OperationsService.class.getName() + ".OPERATION_FINISHED";
- private ConcurrentLinkedQueue<Pair<Target, RemoteOperation>> mPendingOperations =
- new ConcurrentLinkedQueue<Pair<Target, RemoteOperation>>();
private ConcurrentMap<Integer, Pair<RemoteOperation, RemoteOperationResult>>
mUndispatchedFinishedOperations =
}
}
- private Looper mServiceLooper;
- private ServiceHandler mServiceHandler;
- private OperationsServiceBinder mBinder;
- private OwnCloudClient mOwnCloudClient = null;
- private Target mLastTarget = null;
- private FileDataStorageManager mStorageManager;
- private RemoteOperation mCurrentOperation = null;
+ private ServiceHandler mOperationsHandler;
+ private OperationsServiceBinder mOperationsBinder;
+ private SyncFolderHandler mSyncFolderHandler;
/**
* Service initialization
@Override
public void onCreate() {
super.onCreate();
- HandlerThread thread = new HandlerThread("Operations service thread", Process.THREAD_PRIORITY_BACKGROUND);
+ /// First worker thread for most of operations
+ HandlerThread thread = new HandlerThread("Operations thread", Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+ mOperationsHandler = new ServiceHandler(thread.getLooper(), this);
+ mOperationsBinder = new OperationsServiceBinder(mOperationsHandler);
+
+ /// Separated worker thread for download of folders (WIP)
+ thread = new HandlerThread("Syncfolder thread", Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
- mServiceLooper = thread.getLooper();
- mServiceHandler = new ServiceHandler(mServiceLooper, this);
- mBinder = new OperationsServiceBinder();
+ mSyncFolderHandler = new SyncFolderHandler(thread.getLooper(), this);
}
*
* New operations are added calling to startService(), resulting in a call to this method.
* This ensures the service will keep on working although the caller activity goes away.
- *
- * IMPORTANT: the only operations performed here right now is {@link GetSharedFilesOperation}. The class
- * is taking advantage of it due to time constraints.
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
- //Log_OC.wtf(TAG, "onStartCommand init" );
- Message msg = mServiceHandler.obtainMessage();
- msg.arg1 = startId;
- mServiceHandler.sendMessage(msg);
- //Log_OC.wtf(TAG, "onStartCommand end" );
+ // WIP: for the moment, only SYNC_FOLDER and CANCEL_SYNC_FOLDER is expected here;
+ // the rest of the operations are requested through the Binder
+ if (ACTION_SYNC_FOLDER.equals(intent.getAction())) {
+ if (!intent.hasExtra(EXTRA_ACCOUNT) || !intent.hasExtra(EXTRA_REMOTE_PATH)) {
+ Log_OC.e(TAG, "Not enough information provided in intent");
+ return START_NOT_STICKY;
+ }
+ Account account = intent.getParcelableExtra(EXTRA_ACCOUNT);
+ String remotePath = intent.getStringExtra(EXTRA_REMOTE_PATH);
+
+ Pair<Account, String> itemSyncKey = new Pair<Account , String>(account, remotePath);
+
+ Pair<Target, RemoteOperation> itemToQueue = newOperation(intent);
+ if (itemToQueue != null) {
+ mSyncFolderHandler.add(account, remotePath, (SynchronizeFolderOperation)itemToQueue.second);
+ sendBroadcastNewSyncFolder(account, remotePath);
+ Message msg = mSyncFolderHandler.obtainMessage();
+ msg.arg1 = startId;
+ msg.obj = itemSyncKey;
+ mSyncFolderHandler.sendMessage(msg);
+ }
+ } else if (ACTION_CANCEL_SYNC_FOLDER.equals(intent.getAction())) {
+ if (!intent.hasExtra(EXTRA_ACCOUNT) || !intent.hasExtra(EXTRA_FILE)) {
+ Log_OC.e(TAG, "Not enough information provided in intent");
+ return START_NOT_STICKY;
+ }
+ Account account = intent.getParcelableExtra(EXTRA_ACCOUNT);
+ OCFile file = intent.getParcelableExtra(EXTRA_FILE);
+
+ // Cancel operation
+ mSyncFolderHandler.cancel(account,file);
+ } else {
+ Message msg = mOperationsHandler.obtainMessage();
+ msg.arg1 = startId;
+ mOperationsHandler.sendMessage(msg);
+ }
+
return START_NOT_STICKY;
}
+ /**
+ * TODO remove this method when "folder synchronization" replaces "folder download"; this is a fast and ugly
+ * patch.
+ */
+ private void sendBroadcastNewSyncFolder(Account account, String remotePath) {
+ Intent added = new Intent(FileDownloader.getDownloadAddedMessage());
+ added.putExtra(FileDownloader.ACCOUNT_NAME, account.name);
+ added.putExtra(FileDownloader.EXTRA_REMOTE_PATH, remotePath);
+ added.putExtra(FileDownloader.EXTRA_FILE_PATH, FileStorageUtils.getSavePath(account.name) + remotePath);
+ sendStickyBroadcast(added);
+ }
+
+
@Override
public void onDestroy() {
//Log_OC.wtf(TAG, "onDestroy init" );
super.onDestroy();
}
-
/**
* Provides a binder object that clients can use to perform actions on the queue of operations,
* except the addition of new operations.
@Override
public IBinder onBind(Intent intent) {
//Log_OC.wtf(TAG, "onBind" );
- return mBinder;
+ return mOperationsBinder;
}
*/
@Override
public boolean onUnbind(Intent intent) {
- ((OperationsServiceBinder)mBinder).clearListeners();
+ ((OperationsServiceBinder)mOperationsBinder).clearListeners();
return false; // not accepting rebinding (default behaviour)
}
-
+
/**
* Binder to let client components to perform actions on the queue of operations.
*
private ConcurrentMap<OnRemoteOperationListener, Handler> mBoundListeners =
new ConcurrentHashMap<OnRemoteOperationListener, Handler>();
+ private ServiceHandler mServiceHandler = null;
+
+
+ public OperationsServiceBinder(ServiceHandler serviceHandler) {
+ mServiceHandler = serviceHandler;
+ }
+
+
/**
* Cancels an operation
*
* @return 'True' when an operation that enforces the user to wait for completion is in process.
*/
public boolean isPerformingBlockingOperation() {
- return (!mPendingOperations.isEmpty());
+ return (!mServiceHandler.mPendingOperations.isEmpty());
}
/**
- * Creates and adds to the queue a new operation, as described by operationIntent
+ * Creates and adds to the queue a new operation, as described by operationIntent.
+ *
+ * Calls startService to make the operation is processed by the ServiceHandler.
*
* @param operationIntent Intent describing a new operation to queue and execute.
* @return Identifier of the operation created, or null if failed.
*/
- public long newOperation(Intent operationIntent) {
- RemoteOperation operation = null;
- Target target = null;
- try {
- if (!operationIntent.hasExtra(EXTRA_ACCOUNT) &&
- !operationIntent.hasExtra(EXTRA_SERVER_URL)) {
- Log_OC.e(TAG, "Not enough information provided in intent");
-
- } else {
- Account account = operationIntent.getParcelableExtra(EXTRA_ACCOUNT);
- String serverUrl = operationIntent.getStringExtra(EXTRA_SERVER_URL);
- String username = operationIntent.getStringExtra(EXTRA_USERNAME);
- String password = operationIntent.getStringExtra(EXTRA_PASSWORD);
- String authToken = operationIntent.getStringExtra(EXTRA_AUTH_TOKEN);
- String cookie = operationIntent.getStringExtra(EXTRA_COOKIE);
- target = new Target(
- account,
- (serverUrl == null) ? null : Uri.parse(serverUrl),
- username,
- password,
- authToken,
- cookie
- );
-
- String action = operationIntent.getAction();
- if (action.equals(ACTION_CREATE_SHARE)) { // Create Share
- String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
- Intent sendIntent = operationIntent.getParcelableExtra(EXTRA_SEND_INTENT);
- if (remotePath.length() > 0) {
- operation = new CreateShareOperation(OperationsService.this, remotePath, ShareType.PUBLIC_LINK,
- "", false, "", 1, sendIntent);
- }
-
- } else if (action.equals(ACTION_UNSHARE)) { // Unshare file
- String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
- if (remotePath.length() > 0) {
- operation = new UnshareLinkOperation(
- remotePath,
- OperationsService.this);
- }
-
- } else if (action.equals(ACTION_GET_SERVER_INFO)) {
- // check OC server and get basic information from it
- operation = new GetServerInfoOperation(serverUrl, OperationsService.this);
-
- } else if (action.equals(ACTION_OAUTH2_GET_ACCESS_TOKEN)) {
- /// GET ACCESS TOKEN to the OAuth server
- String oauth2QueryParameters =
- operationIntent.getStringExtra(EXTRA_OAUTH2_QUERY_PARAMETERS);
- operation = new OAuth2GetAccessToken(
- getString(R.string.oauth2_client_id),
- getString(R.string.oauth2_redirect_uri),
- getString(R.string.oauth2_grant_type),
- oauth2QueryParameters);
-
- } else if (action.equals(ACTION_EXISTENCE_CHECK)) {
- // Existence Check
- String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
- boolean successIfAbsent = operationIntent.getBooleanExtra(EXTRA_SUCCESS_IF_ABSENT, false);
- operation = new ExistenceCheckRemoteOperation(remotePath, OperationsService.this, successIfAbsent);
-
- } else if (action.equals(ACTION_GET_USER_NAME)) {
- // Get User Name
- operation = new GetRemoteUserNameOperation();
-
- } else if (action.equals(ACTION_RENAME)) {
- // Rename file or folder
- String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
- String newName = operationIntent.getStringExtra(EXTRA_NEWNAME);
- operation = new RenameFileOperation(remotePath, newName);
-
- } else if (action.equals(ACTION_REMOVE)) {
- // Remove file or folder
- String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
- boolean onlyLocalCopy = operationIntent.getBooleanExtra(EXTRA_REMOVE_ONLY_LOCAL, false);
- operation = new RemoveFileOperation(remotePath, onlyLocalCopy);
-
- } else if (action.equals(ACTION_CREATE_FOLDER)) {
- // Create Folder
- String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
- boolean createFullPath = operationIntent.getBooleanExtra(EXTRA_CREATE_FULL_PATH, true);
- operation = new CreateFolderOperation(remotePath, createFullPath);
-
- } else if (action.equals(ACTION_SYNC_FILE)) {
- // Sync file
- String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
- boolean syncFileContents = operationIntent.getBooleanExtra(EXTRA_SYNC_FILE_CONTENTS, true);
- operation = new SynchronizeFileOperation(remotePath, account, syncFileContents, getApplicationContext());
- } else if (action.equals(ACTION_MOVE_FILE)) {
- // Move file/folder
- String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
- String newParentPath = operationIntent.getStringExtra(EXTRA_NEW_PARENT_PATH);
- operation = new MoveFileOperation(remotePath,newParentPath,account);
- }
-
- }
-
- } catch (IllegalArgumentException e) {
- Log_OC.e(TAG, "Bad information provided in intent: " + e.getMessage());
- operation = null;
- }
-
- if (operation != null) {
- mPendingOperations.add(new Pair<Target , RemoteOperation>(target, operation));
+ public long queueNewOperation(Intent operationIntent) {
+ Pair<Target, RemoteOperation> itemToQueue = newOperation(operationIntent);
+ if (itemToQueue != null) {
+ mServiceHandler.mPendingOperations.add(itemToQueue);
startService(new Intent(OperationsService.this, OperationsService.class));
- //Log_OC.wtf(TAG, "New operation added, opId: " + operation.hashCode());
- // better id than hash? ; should be good enough by the time being
- return operation.hashCode();
+ return itemToQueue.second.hashCode();
} else {
- //Log_OC.wtf(TAG, "New operation failed, returned Long.MAX_VALUE");
return Long.MAX_VALUE;
}
}
-
+
+
public boolean dispatchResultIfFinished(int operationId, OnRemoteOperationListener listener) {
Pair<RemoteOperation, RemoteOperationResult> undispatched =
mUndispatchedFinishedOperations.remove(operationId);
return true;
//Log_OC.wtf(TAG, "Sending callback later");
} else {
- if (!mPendingOperations.isEmpty()) {
+ if (!mServiceHandler.mPendingOperations.isEmpty()) {
return true;
} else {
return false;
//Log_OC.wtf(TAG, "Not finished yet");
}
}
+
+
+ /**
+ * Returns True when the file described by 'file' in the ownCloud account 'account' is downloading or waiting to download.
+ *
+ * If 'file' is a directory, returns 'true' if some of its descendant files is downloading or waiting to download.
+ *
+ * @param account ownCloud account where the remote file is stored.
+ * @param file A file that could be affected
+ */
+ public boolean isSynchronizing(Account account, String remotePath) {
+ return mSyncFolderHandler.isSynchronizing(account, remotePath);
+ }
}
-
-
+
+
+ /**
+ * SyncFolder worker. Performs the pending operations in the order they were requested.
+ *
+ * Created with the Looper of a new thread, started in {@link OperationsService#onCreate()}.
+ */
+ private static class SyncFolderHandler extends Handler {
+
+ // don't make it a final class, and don't remove the static ; lint will warn about a possible memory leak
+
+ OperationsService mService;
+
+ private ConcurrentMap<String,SynchronizeFolderOperation> mPendingOperations =
+ new ConcurrentHashMap<String,SynchronizeFolderOperation>();
+ private OwnCloudClient mOwnCloudClient = null;
+ private FileDataStorageManager mStorageManager;
+ private SynchronizeFolderOperation mCurrentSyncOperation;
+
+
+ public SyncFolderHandler(Looper looper, OperationsService service) {
+ super(looper);
+ if (service == null) {
+ throw new IllegalArgumentException("Received invalid NULL in parameter 'service'");
+ }
+ mService = service;
+ }
+
+
+ public boolean isSynchronizing(Account account, String remotePath) {
+ if (account == null || remotePath == null) return false;
+ String targetKey = buildRemoteName(account, remotePath);
+ synchronized (mPendingOperations) {
+ return (mPendingOperations.containsKey(targetKey));
+ }
+ }
+
+
+ @Override
+ public void handleMessage(Message msg) {
+ Pair<Account, String> itemSyncKey = (Pair<Account, String>) msg.obj;
+ doOperation(itemSyncKey.first, itemSyncKey.second);
+ mService.stopSelf(msg.arg1);
+ }
+
+
+ /**
+ * Performs the next operation in the queue
+ */
+ private void doOperation(Account account, String remotePath) {
+
+ String syncKey = buildRemoteName(account,remotePath);
+
+ synchronized(mPendingOperations) {
+ mCurrentSyncOperation = mPendingOperations.get(syncKey);
+ }
+
+ if (mCurrentSyncOperation != null) {
+ RemoteOperationResult result = null;
+
+ try {
+
+ OwnCloudAccount ocAccount = new OwnCloudAccount(account, mService);
+ mOwnCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton().
+ getClientFor(ocAccount, mService);
+ mStorageManager = new FileDataStorageManager(
+ account,
+ mService.getContentResolver()
+ );
+
+ result = mCurrentSyncOperation.execute(mOwnCloudClient, mStorageManager);
+
+ } catch (AccountsException e) {
+ Log_OC.e(TAG, "Error while trying to get autorization", e);
+ } catch (IOException e) {
+ Log_OC.e(TAG, "Error while trying to get autorization", e);
+ } finally {
+ synchronized(mPendingOperations) {
+ mPendingOperations.remove(syncKey);
+ }
+
+ mService.dispatchResultToOperationListeners(null, mCurrentSyncOperation, result);
+ }
+ }
+ }
+
+ public void add(Account account, String remotePath, SynchronizeFolderOperation syncFolderOperation){
+ String syncKey = buildRemoteName(account,remotePath);
+ mPendingOperations.putIfAbsent(syncKey,syncFolderOperation);
+ }
+
+
+ /**
+ * Cancels a pending or current sync operation.
+ *
+ * @param account Owncloud account where the remote file is stored.
+ * @param file File
+ */
+ public void cancel(Account account, OCFile file) {
+ SynchronizeFolderOperation syncOperation = null;
+ synchronized (mPendingOperations) {
+ syncOperation = mPendingOperations.remove(buildRemoteName(account, file.getRemotePath()));
+ }
+ if (syncOperation != null) {
+ syncOperation.cancel();
+ }
+
+ /// cancellation of download needs to be done separately in any case; a SynchronizeFolderOperation
+ // may finish much sooner than the real download of the files in the folder
+ Intent intent = new Intent(mService, FileDownloader.class);
+ intent.setAction(FileDownloader.ACTION_CANCEL_FILE_DOWNLOAD);
+ intent.putExtra(FileDownloader.EXTRA_ACCOUNT, account);
+ intent.putExtra(FileDownloader.EXTRA_FILE, file);
+ mService.startService(intent);
+ }
+
+ /**
+ * Builds a key from the account and file to download
+ *
+ * @param account Account where the file to download is stored
+ * @param path File path
+ */
+ private String buildRemoteName(Account account, String path) {
+ return account.name + path;
+ }
+ }
+
+
/**
* Operations worker. Performs the pending operations in the order they were requested.
*
*/
private static class ServiceHandler extends Handler {
// don't make it a final class, and don't remove the static ; lint will warn about a possible memory leak
+
+
OperationsService mService;
+
+
+ private ConcurrentLinkedQueue<Pair<Target, RemoteOperation>> mPendingOperations =
+ new ConcurrentLinkedQueue<Pair<Target, RemoteOperation>>();
+ private RemoteOperation mCurrentOperation = null;
+ private Target mLastTarget = null;
+ private OwnCloudClient mOwnCloudClient = null;
+ private FileDataStorageManager mStorageManager;
+
+
public ServiceHandler(Looper looper, OperationsService service) {
super(looper);
if (service == null) {
@Override
public void handleMessage(Message msg) {
- mService.nextOperation();
+ nextOperation();
mService.stopSelf(msg.arg1);
}
- }
-
-
- /**
- * Performs the next operation in the queue
- */
- private void nextOperation() {
- //Log_OC.wtf(TAG, "nextOperation init" );
- Pair<Target, RemoteOperation> next = null;
- synchronized(mPendingOperations) {
- next = mPendingOperations.peek();
- }
-
- if (next != null) {
+ /**
+ * Performs the next operation in the queue
+ */
+ private void nextOperation() {
- mCurrentOperation = next.second;
- RemoteOperationResult result = null;
- try {
- /// prepare client object to send the request to the ownCloud server
- if (mLastTarget == null || !mLastTarget.equals(next.first)) {
- mLastTarget = next.first;
- if (mLastTarget.mAccount != null) {
- OwnCloudAccount ocAccount = new OwnCloudAccount(mLastTarget.mAccount, this);
- mOwnCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton().
- getClientFor(ocAccount, this);
- mStorageManager =
- new FileDataStorageManager(
- mLastTarget.mAccount,
- getContentResolver());
- } else {
- OwnCloudCredentials credentials = null;
- if (mLastTarget.mUsername != null &&
- mLastTarget.mUsername.length() > 0) {
- credentials = OwnCloudCredentialsFactory.newBasicCredentials(
- mLastTarget.mUsername,
- mLastTarget.mPassword); // basic
-
- } else if (mLastTarget.mAuthToken != null &&
- mLastTarget.mAuthToken.length() > 0) {
- credentials = OwnCloudCredentialsFactory.newBearerCredentials(
- mLastTarget.mAuthToken); // bearer token
-
- } else if (mLastTarget.mCookie != null &&
- mLastTarget.mCookie.length() > 0) {
- credentials = OwnCloudCredentialsFactory.newSamlSsoCredentials(
- mLastTarget.mCookie); // SAML SSO
+ //Log_OC.wtf(TAG, "nextOperation init" );
+
+ Pair<Target, RemoteOperation> next = null;
+ synchronized(mPendingOperations) {
+ next = mPendingOperations.peek();
+ }
+
+ if (next != null) {
+
+ mCurrentOperation = next.second;
+ RemoteOperationResult result = null;
+ try {
+ /// prepare client object to send the request to the ownCloud server
+ if (mLastTarget == null || !mLastTarget.equals(next.first)) {
+ mLastTarget = next.first;
+ if (mLastTarget.mAccount != null) {
+ OwnCloudAccount ocAccount = new OwnCloudAccount(mLastTarget.mAccount, mService);
+ mOwnCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton().
+ getClientFor(ocAccount, mService);
+ mStorageManager = new FileDataStorageManager(
+ mLastTarget.mAccount,
+ mService.getContentResolver()
+ );
+ } else {
+ OwnCloudCredentials credentials = null;
+ if (mLastTarget.mUsername != null &&
+ mLastTarget.mUsername.length() > 0) {
+ credentials = OwnCloudCredentialsFactory.newBasicCredentials(
+ mLastTarget.mUsername,
+ mLastTarget.mPassword); // basic
+
+ } else if (mLastTarget.mAuthToken != null &&
+ mLastTarget.mAuthToken.length() > 0) {
+ credentials = OwnCloudCredentialsFactory.newBearerCredentials(
+ mLastTarget.mAuthToken); // bearer token
+
+ } else if (mLastTarget.mCookie != null &&
+ mLastTarget.mCookie.length() > 0) {
+ credentials = OwnCloudCredentialsFactory.newSamlSsoCredentials(
+ mLastTarget.mCookie); // SAML SSO
+ }
+ OwnCloudAccount ocAccount = new OwnCloudAccount(
+ mLastTarget.mServerUrl, credentials);
+ mOwnCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton().
+ getClientFor(ocAccount, mService);
+ mStorageManager = null;
}
- OwnCloudAccount ocAccount = new OwnCloudAccount(
- mLastTarget.mServerUrl, credentials);
- mOwnCloudClient = OwnCloudClientManagerFactory.getDefaultSingleton().
- getClientFor(ocAccount, this);
- mStorageManager = null;
}
- }
- /// perform the operation
- if (mCurrentOperation instanceof SyncOperation) {
- result = ((SyncOperation)mCurrentOperation).execute(mOwnCloudClient, mStorageManager);
- } else {
- result = mCurrentOperation.execute(mOwnCloudClient);
- }
+ /// perform the operation
+ if (mCurrentOperation instanceof SyncOperation) {
+ result = ((SyncOperation)mCurrentOperation).execute(mOwnCloudClient, mStorageManager);
+ } else {
+ result = mCurrentOperation.execute(mOwnCloudClient);
+ }
+
+ } catch (AccountsException e) {
+ if (mLastTarget.mAccount == null) {
+ Log_OC.e(TAG, "Error while trying to get authorization for a NULL account", e);
+ } else {
+ Log_OC.e(TAG, "Error while trying to get authorization for " + mLastTarget.mAccount.name, e);
+ }
+ result = new RemoteOperationResult(e);
+
+ } catch (IOException e) {
+ if (mLastTarget.mAccount == null) {
+ Log_OC.e(TAG, "Error while trying to get authorization for a NULL account", e);
+ } else {
+ Log_OC.e(TAG, "Error while trying to get authorization for " + mLastTarget.mAccount.name, e);
+ }
+ result = new RemoteOperationResult(e);
+ } catch (Exception e) {
+ if (mLastTarget.mAccount == null) {
+ Log_OC.e(TAG, "Unexpected error for a NULL account", e);
+ } else {
+ Log_OC.e(TAG, "Unexpected error for " + mLastTarget.mAccount.name, e);
+ }
+ result = new RemoteOperationResult(e);
- } catch (AccountsException e) {
- if (mLastTarget.mAccount == null) {
- Log_OC.e(TAG, "Error while trying to get authorization for a NULL account", e);
- } else {
- Log_OC.e(TAG, "Error while trying to get authorization for " + mLastTarget.mAccount.name, e);
+ } finally {
+ synchronized(mPendingOperations) {
+ mPendingOperations.poll();
+ }
}
- result = new RemoteOperationResult(e);
- } catch (IOException e) {
- if (mLastTarget.mAccount == null) {
- Log_OC.e(TAG, "Error while trying to get authorization for a NULL account", e);
- } else {
- Log_OC.e(TAG, "Error while trying to get authorization for " + mLastTarget.mAccount.name, e);
- }
- result = new RemoteOperationResult(e);
- } catch (Exception e) {
- if (mLastTarget.mAccount == null) {
- Log_OC.e(TAG, "Unexpected error for a NULL account", e);
- } else {
- Log_OC.e(TAG, "Unexpected error for " + mLastTarget.mAccount.name, e);
- }
- result = new RemoteOperationResult(e);
-
- } finally {
- synchronized(mPendingOperations) {
- mPendingOperations.poll();
- }
+ //sendBroadcastOperationFinished(mLastTarget, mCurrentOperation, result);
+ mService.dispatchResultToOperationListeners(mLastTarget, mCurrentOperation, result);
}
-
- //sendBroadcastOperationFinished(mLastTarget, mCurrentOperation, result);
- dispatchResultToOperationListeners(mLastTarget, mCurrentOperation, result);
}
+
+
+
}
+
+
+ /**
+ * Creates a new operation, as described by operationIntent.
+ *
+ * TODO - move to ServiceHandler (probably)
+ *
+ * @param operationIntent Intent describing a new operation to queue and execute.
+ * @return Pair with the new operation object and the information about its target server.
+ */
+ private Pair<Target , RemoteOperation> newOperation(Intent operationIntent) {
+ RemoteOperation operation = null;
+ Target target = null;
+ try {
+ if (!operationIntent.hasExtra(EXTRA_ACCOUNT) &&
+ !operationIntent.hasExtra(EXTRA_SERVER_URL)) {
+ Log_OC.e(TAG, "Not enough information provided in intent");
+
+ } else {
+ Account account = operationIntent.getParcelableExtra(EXTRA_ACCOUNT);
+ String serverUrl = operationIntent.getStringExtra(EXTRA_SERVER_URL);
+ String username = operationIntent.getStringExtra(EXTRA_USERNAME);
+ String password = operationIntent.getStringExtra(EXTRA_PASSWORD);
+ String authToken = operationIntent.getStringExtra(EXTRA_AUTH_TOKEN);
+ String cookie = operationIntent.getStringExtra(EXTRA_COOKIE);
+ target = new Target(
+ account,
+ (serverUrl == null) ? null : Uri.parse(serverUrl),
+ username,
+ password,
+ authToken,
+ cookie
+ );
+
+ String action = operationIntent.getAction();
+ if (action.equals(ACTION_CREATE_SHARE)) { // Create Share
+ String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+ Intent sendIntent = operationIntent.getParcelableExtra(EXTRA_SEND_INTENT);
+ if (remotePath.length() > 0) {
+ operation = new CreateShareOperation(OperationsService.this, remotePath, ShareType.PUBLIC_LINK,
+ "", false, "", 1, sendIntent);
+ }
+
+ } else if (action.equals(ACTION_UNSHARE)) { // Unshare file
+ String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+ if (remotePath.length() > 0) {
+ operation = new UnshareLinkOperation(
+ remotePath,
+ OperationsService.this);
+ }
+
+ } else if (action.equals(ACTION_GET_SERVER_INFO)) {
+ // check OC server and get basic information from it
+ operation = new GetServerInfoOperation(serverUrl, OperationsService.this);
+
+ } else if (action.equals(ACTION_OAUTH2_GET_ACCESS_TOKEN)) {
+ /// GET ACCESS TOKEN to the OAuth server
+ String oauth2QueryParameters =
+ operationIntent.getStringExtra(EXTRA_OAUTH2_QUERY_PARAMETERS);
+ operation = new OAuth2GetAccessToken(
+ getString(R.string.oauth2_client_id),
+ getString(R.string.oauth2_redirect_uri),
+ getString(R.string.oauth2_grant_type),
+ oauth2QueryParameters);
+
+ } else if (action.equals(ACTION_EXISTENCE_CHECK)) {
+ // Existence Check
+ String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+ boolean successIfAbsent = operationIntent.getBooleanExtra(EXTRA_SUCCESS_IF_ABSENT, false);
+ operation = new ExistenceCheckRemoteOperation(remotePath, OperationsService.this, successIfAbsent);
+
+ } else if (action.equals(ACTION_GET_USER_NAME)) {
+ // Get User Name
+ operation = new GetRemoteUserNameOperation();
+
+ } else if (action.equals(ACTION_RENAME)) {
+ // Rename file or folder
+ String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+ String newName = operationIntent.getStringExtra(EXTRA_NEWNAME);
+ operation = new RenameFileOperation(remotePath, newName);
+
+ } else if (action.equals(ACTION_REMOVE)) {
+ // Remove file or folder
+ String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+ boolean onlyLocalCopy = operationIntent.getBooleanExtra(EXTRA_REMOVE_ONLY_LOCAL, false);
+ operation = new RemoveFileOperation(remotePath, onlyLocalCopy);
+
+ } else if (action.equals(ACTION_CREATE_FOLDER)) {
+ // Create Folder
+ String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+ boolean createFullPath = operationIntent.getBooleanExtra(EXTRA_CREATE_FULL_PATH, true);
+ operation = new CreateFolderOperation(remotePath, createFullPath);
+
+ } else if (action.equals(ACTION_SYNC_FILE)) {
+ // Sync file
+ String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+ boolean syncFileContents = operationIntent.getBooleanExtra(EXTRA_SYNC_FILE_CONTENTS, true);
+ operation = new SynchronizeFileOperation(
+ remotePath, account, syncFileContents, getApplicationContext()
+ );
+
+ } else if (action.equals(ACTION_SYNC_FOLDER)) {
+ // Sync file
+ String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+ operation = new SynchronizeFolderOperation(
+ this, // TODO remove this dependency from construction time
+ remotePath,
+ account,
+ System.currentTimeMillis() // TODO remove this dependency from construction time
+ );
+
+ } else if (action.equals(ACTION_MOVE_FILE)) {
+ // Move file/folder
+ String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+ String newParentPath = operationIntent.getStringExtra(EXTRA_NEW_PARENT_PATH);
+ operation = new MoveFileOperation(remotePath,newParentPath,account);
+ }
+
+ }
+
+ } catch (IllegalArgumentException e) {
+ Log_OC.e(TAG, "Bad information provided in intent: " + e.getMessage());
+ operation = null;
+ }
+ if (operation != null) {
+ return new Pair<Target , RemoteOperation>(target, operation);
+ } else {
+ return null;
+ }
+ }
+
/**
* Sends a broadcast when a new operation is added to the queue.
/**
* Notifies the currently subscribed listeners about the end of an operation.
- *
+ *
* @param target Account or URL pointing to an OC server.
* @param operation Finished operation.
* @param result Result of the operation.
private void dispatchResultToOperationListeners(
Target target, final RemoteOperation operation, final RemoteOperationResult result) {
int count = 0;
- Iterator<OnRemoteOperationListener> listeners = mBinder.mBoundListeners.keySet().iterator();
+ Iterator<OnRemoteOperationListener> listeners = mOperationsBinder.mBoundListeners.keySet().iterator();
while (listeners.hasNext()) {
final OnRemoteOperationListener listener = listeners.next();
- final Handler handler = mBinder.mBoundListeners.get(listener);
+ final Handler handler = mOperationsBinder.mBoundListeners.get(listener);
if (handler != null) {
handler.post(new Runnable() {
@Override
}
Log_OC.d(TAG, "Called " + count + " listeners");
}
-
-
}
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
-import com.owncloud.android.operations.SynchronizeFolderOperation;
+import com.owncloud.android.operations.RefreshFolderOperation;
import com.owncloud.android.operations.UpdateOCVersionOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
import com.owncloud.android.lib.common.utils.Log_OC;
}
*/
// folder synchronization
- SynchronizeFolderOperation synchFolderOp = new SynchronizeFolderOperation( folder,
+ RefreshFolderOperation synchFolderOp = new RefreshFolderOperation( folder,
mCurrentSyncTime,
true,
mIsShareSupported,
import com.owncloud.android.files.FileOperationsHelper;
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
public interface ComponentsGetter {
/**
- * Callback method invoked when the parent activity is fully created to get a reference to the FileDownloader service API.
- *
- * @return Directory to list firstly. Can be NULL.
+ * To be invoked when the parent activity is fully created to get a reference to the FileDownloader service API.
*/
public FileDownloaderBinder getFileDownloaderBinder();
/**
- * Callback method invoked when the parent activity is fully created to get a reference to the FileUploader service API.
- *
- * @return Directory to list firstly. Can be NULL.
+ * To be invoked when the parent activity is fully created to get a reference to the FileUploader service API.
*/
public FileUploaderBinder getFileUploaderBinder();
+ /**
+ * To be invoked when the parent activity is fully created to get a reference to the OperationsSerivce service API.
+ */
+ public OperationsServiceBinder getOperationsServiceBinder();
+
public FileDataStorageManager getStorageManager();
public FileOperationsHelper getFileOperationsHelper();
+
}
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.operations.CreateShareOperation;
+import com.owncloud.android.operations.SynchronizeFolderOperation;
import com.owncloud.android.operations.UnshareLinkOperation;
import com.owncloud.android.services.OperationsService;
} else if (operation instanceof UnshareLinkOperation) {
onUnshareLinkOperationFinish((UnshareLinkOperation)operation, result);
- }
+ } else if (operation instanceof SynchronizeFolderOperation) {
+ onSynchronizeFolderOperationFinish((SynchronizeFolderOperation)operation, result);
+
+ }
}
protected void requestCredentialsUpdate() {
t.show();
}
}
-
+
+ private void onSynchronizeFolderOperationFinish(SynchronizeFolderOperation operation, RemoteOperationResult result) {
+ if (!result.isSuccess()){
+ Toast t = Toast.makeText(this, ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()),
+ Toast.LENGTH_LONG);
+ t.show();
+ }
+ }
protected void updateFileFromDB(){
OCFile file = getFile();
import com.owncloud.android.operations.RemoveFileOperation;
import com.owncloud.android.operations.RenameFileOperation;
import com.owncloud.android.operations.SynchronizeFileOperation;
-import com.owncloud.android.operations.SynchronizeFolderOperation;
+import com.owncloud.android.operations.RefreshFolderOperation;
import com.owncloud.android.operations.UnshareLinkOperation;
import com.owncloud.android.services.observer.FileObserverService;
import com.owncloud.android.syncadapter.FileSyncAdapter;
IntentFilter syncIntentFilter = new IntentFilter(FileSyncAdapter.EVENT_FULL_SYNC_START);
syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_END);
syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED);
- syncIntentFilter.addAction(SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED);
- syncIntentFilter.addAction(SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED);
+ syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED);
+ syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED);
mSyncBroadcastReceiver = new SyncBroadcastReceiver();
registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
//LocalBroadcastManager.getInstance(this).registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
setFile(currentFile);
}
- mSyncInProgress = (!FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) && !SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED.equals(event));
+ mSyncInProgress = (!FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) && !RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED.equals(event));
- if (SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.
+ if (RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.
equals(event) &&
/// TODO refactor and make common
synchResult != null && !synchResult.isSuccess() &&
mSyncInProgress = true;
// perform folder synchronization
- RemoteOperation synchFolderOp = new SynchronizeFolderOperation( folder,
+ RemoteOperation synchFolderOp = new RefreshFolderOperation( folder,
currentSyncTime,
false,
getFileOperationsHelper().isSharedSupported(),
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.operations.CreateFolderOperation;
-import com.owncloud.android.operations.SynchronizeFolderOperation;
+import com.owncloud.android.operations.RefreshFolderOperation;
import com.owncloud.android.syncadapter.FileSyncAdapter;
import com.owncloud.android.ui.dialog.CreateFolderDialogFragment;
import com.owncloud.android.ui.fragment.FileFragment;
mSyncInProgress = true;
// perform folder synchronization
- RemoteOperation synchFolderOp = new SynchronizeFolderOperation( folder,
+ RemoteOperation synchFolderOp = new RefreshFolderOperation( folder,
currentSyncTime,
false,
getFileOperationsHelper().isSharedSupported(),
IntentFilter syncIntentFilter = new IntentFilter(FileSyncAdapter.EVENT_FULL_SYNC_START);
syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_END);
syncIntentFilter.addAction(FileSyncAdapter.EVENT_FULL_SYNC_FOLDER_CONTENTS_SYNCED);
- syncIntentFilter.addAction(SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED);
- syncIntentFilter.addAction(SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED);
+ syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED);
+ syncIntentFilter.addAction(RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED);
mSyncBroadcastReceiver = new SyncBroadcastReceiver();
registerReceiver(mSyncBroadcastReceiver, syncIntentFilter);
}
mSyncInProgress = (!FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) &&
- !SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED.equals(event));
+ !RefreshFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED.equals(event));
- if (SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.
+ if (RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED.
equals(event) &&
/// TODO refactor and make common
synchResult != null && !synchResult.isSuccess() &&
import com.owncloud.android.datamodel.ThumbnailsCacheManager.AsyncDrawable;\r
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;\r
import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;\r
+import com.owncloud.android.services.OperationsService.OperationsServiceBinder;\r
import com.owncloud.android.ui.activity.ComponentsGetter;\r
import com.owncloud.android.utils.DisplayUtils;\r
import com.owncloud.android.utils.FileStorageUtils;\r
FileDownloaderBinder downloaderBinder = \r
mTransferServiceGetter.getFileDownloaderBinder();\r
FileUploaderBinder uploaderBinder = mTransferServiceGetter.getFileUploaderBinder();\r
- if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) {\r
+ OperationsServiceBinder opsBinder = mTransferServiceGetter.getOperationsServiceBinder();\r
+ if ((downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) ||\r
+ (opsBinder != null && opsBinder.isSynchronizing(mAccount, file.getRemotePath()))) {\r
localStateView.setImageResource(R.drawable.downloading_file_indicator);\r
localStateView.setVisibility(View.VISIBLE);\r
} else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, file)) {\r
import com.owncloud.android.operations.RemoveFileOperation;
import com.owncloud.android.operations.RenameFileOperation;
import com.owncloud.android.operations.SynchronizeFileOperation;
+import com.owncloud.android.operations.SynchronizeFolderOperation;
import com.owncloud.android.operations.UnshareLinkOperation;
import com.owncloud.android.operations.UploadFileOperation;
// Show a Message, operation finished without success
message = res.getString(R.string.move_file_error);
}
+ } else if (operation instanceof SynchronizeFolderOperation) {
+
+ if (!result.isSuccess()) {
+ String folderPathName = new File(
+ ((SynchronizeFolderOperation) operation).getFolderPath()).getName();
+ if (result.getCode() == ResultCode.FILE_NOT_FOUND) {
+ message = String.format(res.getString(R.string.sync_current_folder_was_removed),
+ folderPathName);
+
+ } else { // Generic error
+ // Show a Message, operation finished without success
+ message = String.format(res.getString(R.string.download_folder_failed_content),
+ folderPathName);
+ }
+ }
}
return message;