X-Git-Url: http://git.linex4red.de/pub/Android/ownCloud.git/blobdiff_plain/10a12fb96085121482a500c55cd4784cd8970088..HEAD:/src/com/owncloud/android/files/services/FileUploader.java diff --git a/src/com/owncloud/android/files/services/FileUploader.java b/src/com/owncloud/android/files/services/FileUploader.java index 04804402..5c2cd1e4 100644 --- a/src/com/owncloud/android/files/services/FileUploader.java +++ b/src/com/owncloud/android/files/services/FileUploader.java @@ -1,6 +1,8 @@ -/* 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, @@ -19,18 +21,15 @@ package com.owncloud.android.files.services; import java.io.File; -import java.io.IOException; import java.util.AbstractList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Vector; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; 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; @@ -43,6 +42,7 @@ import android.os.Looper; import android.os.Message; import android.os.Process; import android.support.v4.app.NotificationCompat; +import android.util.Pair; import android.webkit.MimeTypeMap; import com.owncloud.android.R; @@ -54,7 +54,6 @@ import com.owncloud.android.db.DbHandler; 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; @@ -76,14 +75,15 @@ import com.owncloud.android.utils.ErrorMessageAdapter; 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"; public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH"; public static final String EXTRA_OLD_REMOTE_PATH = "OLD_REMOTE_PATH"; public static final String EXTRA_OLD_FILE_PATH = "OLD_FILE_PATH"; + public static final String EXTRA_LINKED_TO_PATH = "LINKED_TO"; public static final String ACCOUNT_NAME = "ACCOUNT_NAME"; public static final String KEY_FILE = "FILE"; @@ -111,10 +111,10 @@ public class FileUploader extends Service implements OnDatatransferProgressListe private ServiceHandler mServiceHandler; private IBinder mBinder; private OwnCloudClient mUploadClient = null; - private Account mLastAccount = null; + private Account mCurrentAccount = null; private FileDataStorageManager mStorageManager; - private ConcurrentMap mPendingUploads = new ConcurrentHashMap(); + private IndexedForest mPendingUploads = new IndexedForest(); private UploadFileOperation mCurrentUpload = null; private NotificationManager mNotificationManager; @@ -124,32 +124,20 @@ public class FileUploader extends Service implements OnDatatransferProgressListe private static final String MIME_TYPE_PDF = "application/pdf"; private static final String FILE_EXTENSION_PDF = ".pdf"; - - public static String getUploadFinishMessage() { - return FileUploader.class.getName().toString() + UPLOAD_FINISH_MESSAGE; - } - - /** - * Builds a key for mPendingUploads from the account and file to upload - * - * @param account Account where the file to upload is stored - * @param file File to upload - */ - private String buildRemoteName(Account account, OCFile file) { - return account.name + file.getRemotePath(); - } - private String buildRemoteName(Account account, String remotePath) { - return account.name + remotePath; + public static String getUploadFinishMessage() { + return FileUploader.class.getName() + UPLOAD_FINISH_MESSAGE; } /** * Checks if an ownCloud server version should support chunked uploads. - * + * * @param version OwnCloud version instance corresponding to an ownCloud * server. * @return 'True' if the ownCloud server with version supports chunked * uploads. + * + * TODO - move to OCClient */ private static boolean chunkedUploadIsSupported(OwnCloudVersion version) { return (version != null && version.compareTo(OwnCloudVersion.owncloud_v4_5) >= 0); @@ -161,24 +149,51 @@ public class FileUploader extends Service implements OnDatatransferProgressListe @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 * this method. This ensures the service will keep on working although the * caller activity goes away. */ @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"); @@ -224,12 +239,13 @@ public class FileUploader extends Service implements OnDatatransferProgressListe } } - 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); int localAction = intent.getIntExtra(KEY_LOCAL_BEHAVIOUR, LOCAL_BEHAVIOUR_COPY); - + if (intent.hasExtra(KEY_FILE) && files == null) { Log_OC.e(TAG, "Incorrect array for OCFiles provided in upload intent"); return Service.START_NOT_STICKY; @@ -250,8 +266,8 @@ public class FileUploader extends Service implements OnDatatransferProgressListe 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)); if (files[i] == null) { // TODO @andomaex add failure Notification return Service.START_NOT_STICKY; @@ -259,27 +275,34 @@ public class FileUploader extends Service implements OnDatatransferProgressListe } } - 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 requestedUploads = new Vector(); String uploadKey = null; UploadFileOperation newUpload = null; 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, - getApplicationContext()); + 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 - newUpload.addDatatransferProgressListener(this); - newUpload.addDatatransferProgressListener((FileUploaderBinder)mBinder); - requestedUploads.add(uploadKey); + newUpload.addDatatransferProgressListener((FileUploaderBinder) mBinder); + Pair putResult = mPendingUploads.putIfAbsent( + account, files[i].getRemotePath(), newUpload + ); + if (putResult != null) { + uploadKey = putResult.first; + requestedUploads.add(uploadKey); + } // else, file already in the queue of uploads; don't repeat the request } } catch (IllegalArgumentException e) { @@ -302,14 +325,13 @@ public class FileUploader extends Service implements OnDatatransferProgressListe msg.obj = requestedUploads; mServiceHandler.sendMessage(msg); } - Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size()); return Service.START_NOT_STICKY; } /** * Provides a binder object that clients can use to perform operations on * the queue of uploads, excepting the addition of new files. - * + * * Implemented to perform cancellation, pause and resume of existing * uploads. */ @@ -317,7 +339,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe public IBinder onBind(Intent arg0) { return mBinder; } - + /** * Called when ALL the bound clients were onbound. */ @@ -326,98 +348,116 @@ public class FileUploader extends Service implements OnDatatransferProgressListe ((FileUploaderBinder)mBinder).clearListeners(); 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 * uploads. - * + * * It provides by itself the available operations. */ 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 mBoundListeners = new HashMap(); - + private Map mBoundListeners = + new HashMap(); + /** * Cancels a pending or current upload of a remote file. - * - * @param account Owncloud account where the remote file will be stored. - * @param file A file in the queue of pending uploads + * + * @param account ownCloud account where the remote file will be stored. + * @param file A file in the queue of pending uploads */ public void cancel(Account account, OCFile file) { - UploadFileOperation upload = null; - synchronized (mPendingUploads) { - upload = mPendingUploads.remove(buildRemoteName(account, file)); - } + Pair removeResult = mPendingUploads.remove(account, file.getRemotePath()); + UploadFileOperation upload = removeResult.first; if (upload != null) { upload.cancel(); + } else { + if (mCurrentUpload != null && mCurrentAccount != null && + mCurrentUpload.getRemotePath().startsWith(file.getRemotePath()) && + account.name.equals(mCurrentAccount.name)) { + mCurrentUpload.cancel(); + } } } - - - + + /** + * Cancels all the uploads for an account + * + * @param account ownCloud account. + */ + 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 + cancelUploadsForAccount(account); + } + 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. - * - * @param account Owncloud account where the remote file will be stored. - * @param file A file that could be in the queue of pending uploads + * + * 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 */ public boolean isUploading(Account account, OCFile file) { - if (account == null || file == null) - return false; - String targetKey = buildRemoteName(account, file); - synchronized (mPendingUploads) { - if (file.isFolder()) { - // this can be slow if there are many uploads :( - Iterator it = mPendingUploads.keySet().iterator(); - boolean found = false; - while (it.hasNext() && !found) { - found = it.next().startsWith(targetKey); - } - return found; - } else { - return (mPendingUploads.containsKey(targetKey)); - } - } + if (account == null || file == null) return false; + return (mPendingUploads.contains(account, file.getRemotePath())); } /** * Adds a listener interested in the progress of the upload for a concrete file. - * + * * @param listener Object to notify about progress of transfer. * @param account ownCloud account holding the file of interest. - * @param file {@link OCfile} of interest for listener. + * @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); } - - - + + + /** * Removes a listener interested in the progress of the upload for a concrete file. - * + * * @param listener Object to notify about progress of transfer. * @param account ownCloud account holding the file of interest. - * @param file {@link OCfile} of interest for listener. + * @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) { @@ -427,21 +467,36 @@ public class FileUploader extends Service implements OnDatatransferProgressListe @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); } } - + + /** + * Builds a key for the map of listeners. + * + * TODO remove and replace key with file.getFileId() after changing current policy (upload file, then + * add to local database) to better policy (add to local database, then upload) + * + * @param account ownCloud account where the file to upload belongs. + * @param file File to upload + * @return Key + */ + private String buildRemoteName(Account account, OCFile file) { + return account.name + file.getRemotePath(); + } + } /** * Upload worker. Performs the pending uploads in the order they were * requested. - * + * * Created with the Looper of a new thread, started in * {@link FileUploader#onCreate()}. */ @@ -467,99 +522,118 @@ public class FileUploader extends Service implements OnDatatransferProgressListe mService.uploadFile(it.next()); } } + Log_OC.d(TAG, "Stopping command after id " + msg.arg1); mService.stopSelf(msg.arg1); } } /** * Core upload method: sends the file(s) to upload - * - * @param uploadKey Key to access the upload to perform, contained in - * mPendingUploads + * + * @param uploadKey Key to access the upload to perform, contained in mPendingUploads */ public void uploadFile(String uploadKey) { - synchronized (mPendingUploads) { - mCurrentUpload = mPendingUploads.get(uploadKey); - } + mCurrentUpload = mPendingUploads.get(uploadKey); if (mCurrentUpload != null) { - - notifyUploadStart(mCurrentUpload); - - RemoteOperationResult uploadResult = null, grantResult = null; - - try { - /// prepare client object to send requests to the ownCloud server - if (mUploadClient == null || !mLastAccount.equals(mCurrentUpload.getAccount())) { - mLastAccount = mCurrentUpload.getAccount(); - mStorageManager = - new FileDataStorageManager(mLastAccount, getContentResolver()); - OwnCloudAccount ocAccount = new OwnCloudAccount(mLastAccount, this); + // Detect if the account exists + if (AccountUtils.exists(mCurrentUpload.getAccount(), getApplicationContext())) { + Log_OC.d(TAG, "Account " + mCurrentUpload.getAccount().name + " exists"); + + notifyUploadStart(mCurrentUpload); + + RemoteOperationResult uploadResult = null, grantResult; + + try { + /// prepare client object to send the request to the ownCloud server + if (mCurrentAccount == null || !mCurrentAccount.equals(mCurrentUpload.getAccount())) { + mCurrentAccount = mCurrentUpload.getAccount(); + mStorageManager = new FileDataStorageManager( + mCurrentAccount, + getContentResolver() + ); + } // else, reuse storage manager from previous operation + + // always get client from client manager, to get fresh credentials in case of update + OwnCloudAccount ocAccount = new OwnCloudAccount(mCurrentAccount, this); mUploadClient = OwnCloudClientManagerFactory.getDefaultSingleton(). getClientFor(ocAccount, this); - } - - /// 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; - grantResult = grantFolderExistence(remoteParentPath); - - /// perform the upload - if (grantResult.isSuccess()) { - OCFile parent = mStorageManager.getFileByPath(remoteParentPath); - mCurrentUpload.getFile().setParentId(parent.getFileId()); - uploadResult = mCurrentUpload.execute(mUploadClient); - if (uploadResult.isSuccess()) { - saveUploadedFile(); + + + /// 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; + grantResult = grantFolderExistence(remoteParentPath); + + /// perform the upload + if (grantResult.isSuccess()) { + OCFile parent = mStorageManager.getFileByPath(remoteParentPath); + mCurrentUpload.getFile().setParentId(parent.getFileId()); + uploadResult = mCurrentUpload.execute(mUploadClient); + if (uploadResult.isSuccess()) { + saveUploadedFile(); + + } else if (uploadResult.getCode() == ResultCode.SYNC_CONFLICT) { + mStorageManager.saveConflict(mCurrentUpload.getFile(), + mCurrentUpload.getFile().getEtagInConflict()); + } + } else { + uploadResult = grantResult; } - } else { - uploadResult = grantResult; - } - - } catch (AccountsException 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); - uploadResult = new RemoteOperationResult(e); - - } finally { - synchronized (mPendingUploads) { - 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 - mUploadClient = null; + + } catch (Exception e) { + Log_OC.e(TAG, "Error uploading", e); + uploadResult = new RemoteOperationResult(e); + + } finally { + Pair removeResult; + if (mCurrentUpload.wasRenamed()) { + removeResult = mPendingUploads.removePayload( + mCurrentAccount, + mCurrentUpload.getOldFile().getRemotePath() + ); + } else { + removeResult = mPendingUploads.removePayload( + mCurrentAccount, + mCurrentUpload.getRemotePath() + ); + } + + /// notify result + notifyUploadResult(mCurrentUpload, uploadResult); + + sendBroadcastUploadFinished(mCurrentUpload, uploadResult, removeResult.second); } - } - - /// notify result - - notifyUploadResult(uploadResult, mCurrentUpload); - sendFinalBroadcast(mCurrentUpload, uploadResult); + } else { + // Cancel the transfer + Log_OC.d(TAG, "Account " + mCurrentUpload.getAccount().toString() + + " doesn't exist"); + cancelUploadsForAccount(mCurrentUpload.getAccount()); + + } } } /** - * 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. - * + * 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. + * * @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); } @@ -577,10 +651,11 @@ public class FileUploader extends Service implements OnDatatransferProgressListe return result; } - + 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); @@ -594,16 +669,16 @@ public class FileUploader extends Service implements OnDatatransferProgressListe } return null; } - + /** * Saves a OC File after a successful upload. - * + * * A PROPFIND is necessary to keep the props in the local database * synchronized with the server, specially the modification time and Etag * (where available) - * - * TODO refactor this ugly thing + * + * TODO move into UploadFileOperation */ private void saveUploadedFile() { OCFile file = mCurrentUpload.getFile(); @@ -615,13 +690,16 @@ public class FileUploader extends Service implements OnDatatransferProgressListe // 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)); file.setLastSyncDateForProperties(syncDate); + } else { + Log_OC.e(TAG, "Error reading properties of file after successful upload; this is gonna hurt..."); } - + // / maybe this would be better as part of UploadFileOperation... or // maybe all this method if (mCurrentUpload.wasRenamed()) { @@ -629,13 +707,18 @@ public class FileUploader extends Service implements OnDatatransferProgressListe if (oldFile.fileExists()) { oldFile.setStoragePath(null); mStorageManager.saveFile(oldFile); + mStorageManager.saveConflict(oldFile, null); } // else: it was just an automatic renaming due to a name - // coincidence; nothing else is needed, the storagePath is right - // in the instance returned by mCurrentUpload.getFile() + // coincidence; nothing else is needed, the storagePath is right + // in the instance returned by mCurrentUpload.getFile() } file.setNeedsUpdateThumbnail(true); mStorageManager.saveFile(file); + mStorageManager.saveConflict(file, null); + + mStorageManager.triggerMediaScan(file.getStoragePath()); + } private void updateOCFile(OCFile file, RemoteFile remoteFile) { @@ -644,12 +727,11 @@ public class FileUploader extends Service implements OnDatatransferProgressListe file.setMimetype(remoteFile.getMimeType()); file.setModificationTimestamp(remoteFile.getModifiedTimestamp()); file.setModificationTimestampAtLastSyncForData(remoteFile.getModifiedTimestamp()); - // file.setEtag(remoteFile.getEtag()); // TODO Etag, where available + file.setEtag(remoteFile.getEtag()); file.setRemoteId(remoteFile.getRemoteId()); } - private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType, - FileDataStorageManager storageManager) { + private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType) { // MIME type if (mimeType == null || mimeType.length() <= 0) { @@ -657,7 +739,8 @@ public class FileUploader extends Service implements OnDatatransferProgressListe 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) { @@ -679,7 +762,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe newFile.setFileLength(localFile.length()); newFile.setLastSyncDateForData(localFile.lastModified()); } // don't worry about not assigning size, the problems with localPath - // are checked when the UploadFileOperation instance is created + // are checked when the UploadFileOperation instance is created newFile.setMimetype(mimeType); @@ -689,13 +772,13 @@ public class FileUploader extends Service implements OnDatatransferProgressListe /** * Creates a status notification to show the upload progress - * + * * @param upload Upload operation starting. */ private void notifyUploadStart(UploadFileOperation upload) { // / create status notification with a progress bar mLastPercent = 0; - mNotificationBuilder = + mNotificationBuilder = NotificationBuilderWithProgressBar.newNotificationBuilderWithProgressBar(this); mNotificationBuilder .setOngoing(true) @@ -712,7 +795,7 @@ public class FileUploader extends Service implements OnDatatransferProgressListe showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, upload.getAccount()); showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); mNotificationBuilder.setContentIntent(PendingIntent.getActivity( - this, (int) System.currentTimeMillis(), showDetailsIntent, 0 + this, (int) System.currentTimeMillis(), showDetailsIntent, 0 )); mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotificationBuilder.build()); @@ -722,11 +805,13 @@ public class FileUploader extends Service implements OnDatatransferProgressListe * 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()); @@ -736,42 +821,42 @@ public class FileUploader extends Service implements OnDatatransferProgressListe /** * Updates the status notification with the result of an upload operation. - * - * @param uploadResult Result of the upload operation. - * @param upload Finished upload operation + * + * @param uploadResult Result of the upload operation. + * @param upload Finished upload operation */ - private void notifyUploadResult( - RemoteOperationResult uploadResult, UploadFileOperation upload) { + private void notifyUploadResult(UploadFileOperation upload, + RemoteOperationResult uploadResult) { Log_OC.d(TAG, "NotifyUploadResult with resultCode: " + uploadResult.getCode()); // / cancelled operation or success -> silent removal of progress notification mNotificationManager.cancel(R.string.uploader_upload_in_progress_ticker); - + // Show the result: success or fail notification if (!uploadResult.isCancelled()) { - int tickerId = (uploadResult.isSuccess()) ? R.string.uploader_upload_succeeded_ticker : - R.string.uploader_upload_failed_ticker; - - String content = null; + int tickerId = (uploadResult.isSuccess()) ? R.string.uploader_upload_succeeded_ticker : + R.string.uploader_upload_failed_ticker; + + String content; // check credentials error boolean needsToUpdateCredentials = ( - uploadResult.getCode() == ResultCode.UNAUTHORIZED || - uploadResult.isIdPRedirection() + uploadResult.getCode() == ResultCode.UNAUTHORIZED || + uploadResult.isIdPRedirection() ); - tickerId = (needsToUpdateCredentials) ? + tickerId = (needsToUpdateCredentials) ? R.string.uploader_upload_failed_credentials_error : tickerId; mNotificationBuilder - .setTicker(getString(tickerId)) - .setContentTitle(getString(tickerId)) - .setAutoCancel(true) - .setOngoing(false) - .setProgress(0, 0, false); - + .setTicker(getString(tickerId)) + .setContentTitle(getString(tickerId)) + .setAutoCancel(true) + .setOngoing(false) + .setProgress(0, 0, false); + content = ErrorMessageAdapter.getErrorCauseMessage( uploadResult, upload, getResources() ); - + if (needsToUpdateCredentials) { // let the user update credentials with one click Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class); @@ -779,24 +864,24 @@ public class FileUploader extends Service implements OnDatatransferProgressListe AuthenticatorActivity.EXTRA_ACCOUNT, upload.getAccount() ); updateAccountCredentials.putExtra( - AuthenticatorActivity.EXTRA_ACTION, + AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN ); updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND); mNotificationBuilder.setContentIntent(PendingIntent.getActivity( - this, - (int) System.currentTimeMillis(), - updateAccountCredentials, - PendingIntent.FLAG_ONE_SHOT + this, + (int) System.currentTimeMillis(), + updateAccountCredentials, + PendingIntent.FLAG_ONE_SHOT )); - - mUploadClient = null; - // grant that future retries on the same account will get the fresh credentials + + mUploadClient = null; + // grant that future retries on the same account will get the fresh credentials } else { mNotificationBuilder.setContentText(content); - + if (upload.isInstant()) { DbHandler db = null; try { @@ -807,12 +892,12 @@ public class FileUploader extends Service implements OnDatatransferProgressListe if (uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED) { //message = getString(R.string.failed_upload_quota_exceeded_text); if (db.updateFileState( - upload.getOriginalStoragePath(), + upload.getOriginalStoragePath(), DbHandler.UPLOAD_STATUS_UPLOAD_FAILED, message) == 0) { db.putFileForLater( - upload.getOriginalStoragePath(), - upload.getAccount().name, + upload.getOriginalStoragePath(), + upload.getAccount().name, message ); } @@ -824,22 +909,22 @@ public class FileUploader extends Service implements OnDatatransferProgressListe } } } - + mNotificationBuilder.setContentText(content); mNotificationManager.notify(tickerId, mNotificationBuilder.build()); - + if (uploadResult.isSuccess()) { - + DbHandler db = new DbHandler(this.getBaseContext()); db.removeIUPendingFile(mCurrentUpload.getOriginalStoragePath()); db.close(); // remove success notification, with a delay of 2 seconds NotificationDelayer.cancelWithDelay( - mNotificationManager, - R.string.uploader_upload_succeeded_ticker, + mNotificationManager, + R.string.uploader_upload_succeeded_ticker, 2000); - + } } } @@ -847,37 +932,58 @@ public class FileUploader extends Service implements OnDatatransferProgressListe /** * Sends a broadcast in order to the interested activities can update their * view - * - * @param upload Finished upload operation - * @param uploadResult Result of the upload operation + * + * @param upload Finished upload operation + * @param uploadResult Result of the upload operation + * @param unlinkedFromRemotePath Path in the uploads tree where the upload was unlinked from */ - private void sendFinalBroadcast(UploadFileOperation upload, RemoteOperationResult uploadResult) { + private void sendBroadcastUploadFinished( + UploadFileOperation upload, + RemoteOperationResult uploadResult, + String unlinkedFromRemotePath) { + Intent end = new Intent(getUploadFinishMessage()); end.putExtra(EXTRA_REMOTE_PATH, upload.getRemotePath()); // real remote - // path, after - // possible - // automatic - // renaming + // path, after + // possible + // automatic + // renaming if (upload.wasRenamed()) { end.putExtra(EXTRA_OLD_REMOTE_PATH, upload.getOldFile().getRemotePath()); } end.putExtra(EXTRA_OLD_FILE_PATH, upload.getOriginalStoragePath()); end.putExtra(ACCOUNT_NAME, upload.getAccount().name); end.putExtra(EXTRA_UPLOAD_RESULT, uploadResult.isSuccess()); + if (unlinkedFromRemotePath != null) { + end.putExtra(EXTRA_LINKED_TO_PATH, unlinkedFromRemotePath); + } + sendStickyBroadcast(end); } /** * 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 + * + * TODO - move to OCFile or Utils class */ - private boolean isPdfFileFromContentProviderWithoutExtension(String localPath, String mimeType) { - return localPath.startsWith(UriUtils.URI_CONTENT_SCHEME) && - mimeType.equals(MIME_TYPE_PDF) && + 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 account Downloads account to remove + */ + private void cancelUploadsForAccount(Account account){ + // Cancel pending uploads + mPendingUploads.remove(account); + } }