-/* ownCloud Android client application
+/**
+ * ownCloud Android client application
+ *
* Copyright (C) 2012 Bartek Przybylski
- * Copyright (C) 2012-2013 ownCloud Inc.
+ * Copyright (C) 2012-2015 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,
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountsException;
+import android.accounts.OnAccountsUpdateListener;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
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.lib.common.accounts.AccountUtils.Constants;
import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.utils.UriUtils;
-
-public class FileUploader extends Service implements OnDatatransferProgressListener {
+public class FileUploader extends Service
+ implements OnDatatransferProgressListener, OnAccountsUpdateListener {
private static final String UPLOAD_FINISH_MESSAGE = "UPLOAD_FINISH";
public static final String EXTRA_UPLOAD_RESULT = "RESULT";
private Account mLastAccount = null;
private FileDataStorageManager mStorageManager;
- private ConcurrentMap<String, UploadFileOperation> mPendingUploads = new ConcurrentHashMap<String, UploadFileOperation>();
+ private ConcurrentMap<String, UploadFileOperation> mPendingUploads =
+ new ConcurrentHashMap<String, UploadFileOperation>();
private UploadFileOperation mCurrentUpload = null;
private NotificationManager mNotificationManager;
public static String getUploadFinishMessage() {
- return FileUploader.class.getName().toString() + UPLOAD_FINISH_MESSAGE;
+ return FileUploader.class.getName() + UPLOAD_FINISH_MESSAGE;
}
/**
@Override
public void onCreate() {
super.onCreate();
- Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size());
+ Log_OC.d(TAG, "Creating service");
mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
- HandlerThread thread = new HandlerThread("FileUploaderThread", Process.THREAD_PRIORITY_BACKGROUND);
+ HandlerThread thread = new HandlerThread("FileUploaderThread",
+ Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper, this);
mBinder = new FileUploaderBinder();
+
+ // add AccountsUpdatedListener
+ AccountManager am = AccountManager.get(getApplicationContext());
+ am.addOnAccountsUpdatedListener(this, null, false);
}
/**
+ * Service clean up
+ */
+ @Override
+ public void onDestroy() {
+ Log_OC.v(TAG, "Destroying service" );
+ mBinder = null;
+ mServiceHandler = null;
+ mServiceLooper.quit();
+ mServiceLooper = null;
+ mNotificationManager = null;
+
+ // remove AccountsUpdatedListener
+ AccountManager am = AccountManager.get(getApplicationContext());
+ am.removeOnAccountsUpdatedListener(this);
+
+ super.onDestroy();
+ }
+
+
+ /**
* Entry point to add one or several files to the queue of uploads.
*
* New uploads are added calling to startService(), resulting in a call to
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
+ Log_OC.d(TAG, "Starting command with id " + startId);
+
if (!intent.hasExtra(KEY_ACCOUNT) || !intent.hasExtra(KEY_UPLOAD_TYPE)
|| !(intent.hasExtra(KEY_LOCAL_FILE) || intent.hasExtra(KEY_FILE))) {
Log_OC.e(TAG, "Not enough information provided in intent");
if (uploadType == UPLOAD_SINGLE_FILE) {
if (intent.hasExtra(KEY_FILE)) {
- files = new OCFile[] { (OCFile) intent.getParcelableExtra(KEY_FILE) };
+ files = new OCFile[] { intent.getParcelableExtra(KEY_FILE) };
} else {
localPaths = new String[] { intent.getStringExtra(KEY_LOCAL_FILE) };
if (intent.hasExtra(KEY_FILE)) {
files = (OCFile[]) intent.getParcelableArrayExtra(KEY_FILE); // TODO
- // will
- // this
- // casting
- // work
- // fine?
+ // will
+ // this
+ // casting
+ // work
+ // fine?
} else {
localPaths = intent.getStringArrayExtra(KEY_LOCAL_FILE);
}
}
- FileDataStorageManager storageManager = new FileDataStorageManager(account, getContentResolver());
+ FileDataStorageManager storageManager = new FileDataStorageManager(account,
+ getContentResolver());
boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false);
boolean isInstant = intent.getBooleanExtra(KEY_INSTANT_UPLOAD, false);
files = new OCFile[localPaths.length];
for (int i = 0; i < localPaths.length; i++) {
- files[i] = obtainNewOCFileToUpload(remotePaths[i], localPaths[i], ((mimeTypes != null) ? mimeTypes[i]
- : (String) null), storageManager);
+ files[i] = obtainNewOCFileToUpload(remotePaths[i], localPaths[i],
+ ((mimeTypes != null) ? mimeTypes[i] : null), storageManager);
if (files[i] == null) {
// TODO @andomaex add failure Notification
return Service.START_NOT_STICKY;
}
}
- AccountManager aMgr = AccountManager.get(this);
- String version = aMgr.getUserData(account, Constants.KEY_OC_VERSION);
- OwnCloudVersion ocv = new OwnCloudVersion(version);
+ OwnCloudVersion ocv = AccountUtils.getServerVersion(account);
boolean chunked = FileUploader.chunkedUploadIsSupported(ocv);
AbstractList<String> requestedUploads = new Vector<String>();
try {
for (int i = 0; i < files.length; i++) {
uploadKey = buildRemoteName(account, files[i].getRemotePath());
- newUpload = new UploadFileOperation(account, files[i], chunked, isInstant, forceOverwrite, localAction,
+ newUpload = new UploadFileOperation(account, files[i], chunked, isInstant,
+ forceOverwrite, localAction,
getApplicationContext());
if (isInstant) {
newUpload.setRemoteFolderToBeCreated();
}
- mPendingUploads.putIfAbsent(uploadKey, newUpload); // Grants that the file only upload once time
+ // Grants that the file only upload once time
+ mPendingUploads.putIfAbsent(uploadKey, newUpload);
newUpload.addDatatransferProgressListener(this);
newUpload.addDatatransferProgressListener((FileUploaderBinder)mBinder);
return false; // not accepting rebinding (default behaviour)
}
+ @Override
+ public void onAccountsUpdated(Account[] accounts) {
+ // Review current upload, and cancel it if its account doen't exist
+ if (mCurrentUpload != null &&
+ !AccountUtils.exists(mCurrentUpload.getAccount(), getApplicationContext())) {
+ mCurrentUpload.cancel();
+ }
+ // The rest of uploads are cancelled when they try to start
+ }
/**
* Binder to let client components to perform operations on the queue of
public class FileUploaderBinder extends Binder implements OnDatatransferProgressListener {
/**
- * Map of listeners that will be reported about progress of uploads from a {@link FileUploaderBinder} instance
+ * Map of listeners that will be reported about progress of uploads from a
+ * {@link FileUploaderBinder} instance
*/
- private Map<String, OnDatatransferProgressListener> mBoundListeners = new HashMap<String, OnDatatransferProgressListener>();
+ private Map<String, OnDatatransferProgressListener> mBoundListeners =
+ new HashMap<String, OnDatatransferProgressListener>();
/**
* Cancels a pending or current upload of a remote file.
* @param file A file in the queue of pending uploads
*/
public void cancel(Account account, OCFile file) {
- UploadFileOperation upload = null;
+ UploadFileOperation upload;
synchronized (mPendingUploads) {
upload = mPendingUploads.remove(buildRemoteName(account, file));
}
}
}
+ /**
+ * Cancels a pending or current upload for an account
+ *
+ * @param account Owncloud accountName where the remote file will be stored.
+ */
+ public void cancel(Account account) {
+ Log_OC.d(TAG, "Account= " + account.name);
+ if (mCurrentUpload != null) {
+ Log_OC.d(TAG, "Current Upload Account= " + mCurrentUpload.getAccount().name);
+ if (mCurrentUpload.getAccount().name.equals(account.name)) {
+ mCurrentUpload.cancel();
+ }
+ }
+ // Cancel pending uploads
+ cancelUploadForAccount(account.name);
+ }
public void clearListeners() {
mBoundListeners.clear();
}
-
-
-
/**
* Returns True when the file described by 'file' is being uploaded to
* the ownCloud account 'account' or waiting for it
*
- * If 'file' is a directory, returns 'true' if some of its descendant files is uploading or waiting to upload.
+ * If 'file' is a directory, returns 'true' if some of its descendant files
+ * is uploading or waiting to upload.
*
* @param account ownCloud account where the remote file will be stored.
* @param file A file that could be in the queue of pending uploads
* @param account ownCloud account holding the file of interest.
* @param file {@link OCFile} of interest for listener.
*/
- public void addDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) {
+ public void addDatatransferProgressListener (OnDatatransferProgressListener listener,
+ Account account, OCFile file) {
if (account == null || file == null || listener == null) return;
String targetKey = buildRemoteName(account, file);
mBoundListeners.put(targetKey, listener);
* @param account ownCloud account holding the file of interest.
* @param file {@link OCFile} of interest for listener.
*/
- public void removeDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) {
+ public void removeDatatransferProgressListener (OnDatatransferProgressListener listener,
+ Account account, OCFile file) {
if (account == null || file == null || listener == null) return;
String targetKey = buildRemoteName(account, file);
if (mBoundListeners.get(targetKey) == listener) {
@Override
- public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer,
- String fileName) {
+ public void onTransferProgress(long progressRate, long totalTransferredSoFar,
+ long totalToTransfer, String fileName) {
String key = buildRemoteName(mCurrentUpload.getAccount(), mCurrentUpload.getFile());
OnDatatransferProgressListener boundListener = mBoundListeners.get(key);
if (boundListener != null) {
- boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, fileName);
+ boundListener.onTransferProgress(progressRate, totalTransferredSoFar,
+ totalToTransfer, fileName);
}
}
+ /**
+ * Review uploads and cancel it if its account doesn't exist
+ */
+ public void checkAccountOfCurrentUpload() {
+ if (mCurrentUpload != null &&
+ !AccountUtils.exists(mCurrentUpload.getAccount(), getApplicationContext())) {
+ mCurrentUpload.cancel();
+ }
+ // The rest of uploads are cancelled when they try to start
+ }
}
/**
mService.uploadFile(it.next());
}
}
+ Log_OC.d(TAG, "Stopping command after id " + msg.arg1);
mService.stopSelf(msg.arg1);
}
}
// Detect if the account exists
if (AccountUtils.exists(mCurrentUpload.getAccount(), getApplicationContext())) {
- Log_OC.d(TAG, "Account " + mCurrentUpload.getAccount().toString() + " exists");
+ Log_OC.d(TAG, "Account " + mCurrentUpload.getAccount().name + " exists");
notifyUploadStart(mCurrentUpload);
- RemoteOperationResult uploadResult = null, grantResult = null;
+ RemoteOperationResult uploadResult = null, grantResult;
try {
/// prepare client object to send requests to the ownCloud server
- if (mUploadClient == null || !mLastAccount.equals(mCurrentUpload.getAccount())) {
+ if (mUploadClient == null ||
+ !mLastAccount.equals(mCurrentUpload.getAccount())) {
mLastAccount = mCurrentUpload.getAccount();
mStorageManager =
new FileDataStorageManager(mLastAccount, getContentResolver());
/// check the existence of the parent folder for the file to upload
String remoteParentPath = new File(mCurrentUpload.getRemotePath()).getParent();
- remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ? remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR;
+ remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ?
+ remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR;
grantResult = grantFolderExistence(remoteParentPath);
/// perform the upload
}
} catch (AccountsException e) {
- Log_OC.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e);
+ Log_OC.e(TAG, "Error while trying to get autorization for " +
+ mLastAccount.name, e);
uploadResult = new RemoteOperationResult(e);
} catch (IOException e) {
- Log_OC.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e);
+ Log_OC.e(TAG, "Error while trying to get autorization for " +
+ mLastAccount.name, e);
uploadResult = new RemoteOperationResult(e);
} finally {
mPendingUploads.remove(uploadKey);
Log_OC.i(TAG, "Remove CurrentUploadItem from pending upload Item Map.");
}
- if (uploadResult.isException()) {
- // enforce the creation of a new client object for next uploads; this grant that a new socket will
- // be created in the future if the current exception is due to an abrupt lose of network connection
+ if (uploadResult != null && uploadResult.isException()) {
+ // enforce the creation of a new client object for next uploads;
+ // this grant that a new socket will be created in the future if
+ // the current exception is due to an abrupt lose of network connection
mUploadClient = null;
}
}
} else {
// Cancel the transfer
- Log_OC.d(TAG, "Account " + mCurrentUpload.getAccount().toString() + " doesn't exist");
+ Log_OC.d(TAG, "Account " + mCurrentUpload.getAccount().toString() +
+ " doesn't exist");
+ cancelUploadForAccount(mCurrentUpload.getAccount().name);
+
}
}
}
/**
- * Checks the existence of the folder where the current file will be uploaded both in the remote server
- * and in the local database.
+ * Checks the existence of the folder where the current file will be uploaded both
+ * in the remote server and in the local database.
*
- * If the upload is set to enforce the creation of the folder, the method tries to create it both remote
- * and locally.
+ * If the upload is set to enforce the creation of the folder, the method tries to
+ * create it both remote and locally.
*
* @param pathToGrant Full remote path whose existence will be granted.
- * @return An {@link OCFile} instance corresponding to the folder where the file will be uploaded.
+ * @return An {@link OCFile} instance corresponding to the folder where the file
+ * will be uploaded.
*/
private RemoteOperationResult grantFolderExistence(String pathToGrant) {
RemoteOperation operation = new ExistenceCheckRemoteOperation(pathToGrant, this, false);
RemoteOperationResult result = operation.execute(mUploadClient);
- if (!result.isSuccess() && result.getCode() == ResultCode.FILE_NOT_FOUND && mCurrentUpload.isRemoteFolderToBeCreated()) {
+ if (!result.isSuccess() && result.getCode() == ResultCode.FILE_NOT_FOUND &&
+ mCurrentUpload.isRemoteFolderToBeCreated()) {
SyncOperation syncOp = new CreateFolderOperation( pathToGrant, true);
result = syncOp.execute(mUploadClient, mStorageManager);
}
private OCFile createLocalFolder(String remotePath) {
String parentPath = new File(remotePath).getParent();
- parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR;
+ parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ?
+ parentPath : parentPath + OCFile.PATH_SEPARATOR;
OCFile parent = mStorageManager.getFileByPath(parentPath);
if (parent == null) {
parent = createLocalFolder(parentPath);
// new PROPFIND to keep data consistent with server
// in theory, should return the same we already have
- ReadRemoteFileOperation operation = new ReadRemoteFileOperation(mCurrentUpload.getRemotePath());
+ ReadRemoteFileOperation operation =
+ new ReadRemoteFileOperation(mCurrentUpload.getRemotePath());
RemoteOperationResult result = operation.execute(mUploadClient);
if (result.isSuccess()) {
updateOCFile(file, (RemoteFile) result.getData().get(0));
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
remotePath.substring(remotePath.lastIndexOf('.') + 1));
} catch (IndexOutOfBoundsException e) {
- Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " + remotePath);
+ Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " +
+ remotePath);
}
}
if (mimeType == null) {
* Callback method to update the progress bar in the status notification
*/
@Override
- public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String filePath) {
+ public void onTransferProgress(long progressRate, long totalTransferredSoFar,
+ long totalToTransfer, String filePath) {
int percent = (int) (100.0 * ((double) totalTransferredSoFar) / ((double) totalToTransfer));
if (percent != mLastPercent) {
mNotificationBuilder.setProgress(100, percent, false);
- String fileName = filePath.substring(filePath.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1);
+ String fileName = filePath.substring(
+ filePath.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1);
String text = String.format(getString(R.string.uploader_upload_in_progress_content), percent, fileName);
mNotificationBuilder.setContentText(text);
mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotificationBuilder.build());
int tickerId = (uploadResult.isSuccess()) ? R.string.uploader_upload_succeeded_ticker :
R.string.uploader_upload_failed_ticker;
- String content = null;
+ String content;
// check credentials error
boolean needsToUpdateCredentials = (
/**
* Checks if content provider, using the content:// scheme, returns a file with mime-type
* 'application/pdf' but file has not extension
- * @param localPath
- * @param mimeType
+ * @param localPath Full path to a file in the local file system.
+ * @param mimeType MIME type of the file.
* @return true if is needed to add the pdf file extension to the file
*/
- private boolean isPdfFileFromContentProviderWithoutExtension(String localPath, String mimeType) {
+ private boolean isPdfFileFromContentProviderWithoutExtension(String localPath,
+ String mimeType) {
return localPath.startsWith(UriUtils.URI_CONTENT_SCHEME) &&
mimeType.equals(MIME_TYPE_PDF) &&
!localPath.endsWith(FILE_EXTENSION_PDF);
}
+ /**
+ * Remove uploads of an account
+ * @param accountName Name of an OC account
+ */
+ private void cancelUploadForAccount(String accountName){
+ // this can be slow if there are many uploads :(
+ Iterator<String> it = mPendingUploads.keySet().iterator();
+ Log_OC.d(TAG, "Number of pending updloads= " + mPendingUploads.size());
+ while (it.hasNext()) {
+ String key = it.next();
+ Log_OC.d(TAG, "mPendingUploads CANCELLED " + key);
+ if (key.startsWith(accountName)) {
+ synchronized (mPendingUploads) {
+ mPendingUploads.remove(key);
+ }
+ }
+ }
+ }
}