/** * Creates a status notification to show the upload progress * * @param upload Upload operation starting. */ @SuppressWarnings("deprecation") private void notifyUploadStart(UploadFileOperation upload) { // / create status notification with a progress bar mLastPercent = 0; mNotification = new Notification( DisplayUtils.getSeasonalIconId(), getString(R.string.uploader_upload_in_progress_ticker), System.currentTimeMillis()); mNotification.flags |= Notification.FLAG_ONGOING_EVENT; mDefaultNotificationContentView = mNotification.contentView; mNotification.contentView = new RemoteViews(getApplicationContext().getPackageName(), R.layout.progressbar_layout); mNotification.contentView.setProgressBar(R.id.status_progress, 100, 0, false); mNotification.contentView.setTextViewText( R.id.status_text, String.format( getString(R.string.uploader_upload_in_progress_content), 0, upload.getFileName())); mNotification.contentView.setImageViewResource( R.id.status_icon, DisplayUtils.getSeasonalIconId()); /// includes a pending intent in the notification showing the details view of the file Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class); showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, upload.getFile()); showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, upload.getAccount()); showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); mNotification.contentIntent = PendingIntent.getActivity( getApplicationContext(), (int) System.currentTimeMillis(), showDetailsIntent, 0); mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotification); }
/** * Saves a OC File after a successful upload. * * <p>A PROPFIND is necessary to keep the props in the local database synchronized with the * server, specially the modification time and Etag (where available) * * <p>TODO refactor this ugly thing */ private void saveUploadedFile() { OCFile file = mCurrentUpload.getFile(); if (file.fileExists()) { file = mStorageManager.getFileById(file.getFileId()); } long syncDate = System.currentTimeMillis(); file.setLastSyncDateForData(syncDate); // new PROPFIND to keep data consistent with server // in theory, should return the same we already have ReadRemoteFileOperation operation = new ReadRemoteFileOperation(mCurrentUpload.getRemotePath()); RemoteOperationResult result = operation.execute(mUploadClient); if (result.isSuccess()) { updateOCFile(file, (RemoteFile) result.getData().get(0)); file.setLastSyncDateForProperties(syncDate); } // / maybe this would be better as part of UploadFileOperation... or // maybe all this method if (mCurrentUpload.wasRenamed()) { OCFile oldFile = mCurrentUpload.getOldFile(); if (oldFile.fileExists()) { oldFile.setStoragePath(null); mStorageManager.saveFile(oldFile); } // 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() } mStorageManager.saveFile(file); }
/** * 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 */ public void cancel(Account account, OCFile file) { UploadFileOperation upload = null; synchronized (mPendingUploads) { upload = mPendingUploads.remove(buildRemoteName(account, file)); } if (upload != null) { upload.cancel(); } }
/** * 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 */ private void sendFinalBroadcast(UploadFileOperation upload, RemoteOperationResult uploadResult) { Intent end = new Intent(getUploadFinishMessage()); end.putExtra(EXTRA_REMOTE_PATH, upload.getRemotePath()); // real remote // 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()); sendStickyBroadcast(end); }
/** * Checks the existence of the folder where the current file will be uploaded both in the remote * server and in the local database. * * <p>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. */ 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()) { operation = new CreateFolderOperation(pathToGrant, true, mStorageManager); result = operation.execute(mUploadClient); } if (result.isSuccess()) { OCFile parentDir = mStorageManager.getFileByPath(pathToGrant); if (parentDir == null) { parentDir = createLocalFolder(pathToGrant); } if (parentDir != null) { result = new RemoteOperationResult(ResultCode.OK); } else { result = new RemoteOperationResult(ResultCode.UNKNOWN_ERROR); } } return result; }
/** * Updates the status notification with the result of an upload operation. * * @param uploadResult Result of the upload operation. * @param upload Finished upload operation */ private void notifyUploadResult(RemoteOperationResult uploadResult, UploadFileOperation upload) { Log_OC.d(TAG, "NotifyUploadResult with resultCode: " + uploadResult.getCode()); if (uploadResult.isCancelled()) { // / cancelled operation -> silent removal of progress notification mNotificationManager.cancel(R.string.uploader_upload_in_progress_ticker); } else if (uploadResult.isSuccess()) { // / success -> silent update of progress notification to success // message mNotification.flags ^= Notification.FLAG_ONGOING_EVENT; // remove // the // ongoing // flag mNotification.flags |= Notification.FLAG_AUTO_CANCEL; mNotification.contentView = mDefaultNotificationContentView; /// includes a pending intent in the notification showing the details view of the file Intent showDetailsIntent = null; if (PreviewImageFragment.canBePreviewed(upload.getFile())) { showDetailsIntent = new Intent(this, PreviewImageActivity.class); } else { showDetailsIntent = new Intent(this, FileDisplayActivity.class); } showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, upload.getFile()); showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, upload.getAccount()); showDetailsIntent.putExtra(FileActivity.EXTRA_FROM_NOTIFICATION, true); showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); mNotification.contentIntent = PendingIntent.getActivity( getApplicationContext(), (int) System.currentTimeMillis(), showDetailsIntent, 0); mNotification.setLatestEventInfo( getApplicationContext(), getString(R.string.uploader_upload_succeeded_ticker), String.format( getString(R.string.uploader_upload_succeeded_content_single), upload.getFileName()), mNotification.contentIntent); mNotificationManager.notify( R.string.uploader_upload_in_progress_ticker, mNotification); // NOT // AN DbHandler db = new DbHandler(this.getBaseContext()); db.removeIUPendingFile(mCurrentUpload.getOriginalStoragePath()); db.close(); } else { // / fail -> explicit failure notification mNotificationManager.cancel(R.string.uploader_upload_in_progress_ticker); Notification finalNotification = new Notification( DisplayUtils.getSeasonalIconId(), getString(R.string.uploader_upload_failed_ticker), System.currentTimeMillis()); finalNotification.flags |= Notification.FLAG_AUTO_CANCEL; String content = null; boolean needsToUpdateCredentials = (uploadResult.getCode() == ResultCode.UNAUTHORIZED || // (uploadResult.isTemporalRedirection() && uploadResult.isIdPRedirection() && (uploadResult.isIdPRedirection() && mUploadClient.getCredentials() == null)); // MainApp.getAuthTokenTypeSamlSessionCookie().equals(mUploadClient.getAuthTokenType()))); if (needsToUpdateCredentials) { // let the user update credentials with one click Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class); updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, upload.getAccount()); updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ENFORCED_UPDATE, true); updateAccountCredentials.putExtra( AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_TOKEN); updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND); finalNotification.contentIntent = PendingIntent.getActivity( this, (int) System.currentTimeMillis(), updateAccountCredentials, PendingIntent.FLAG_ONE_SHOT); content = String.format( getString(R.string.uploader_upload_failed_content_single), upload.getFileName()); finalNotification.setLatestEventInfo( getApplicationContext(), getString(R.string.uploader_upload_failed_ticker), content, finalNotification.contentIntent); mUploadClient = null; // grant that future retries on the same account will get the fresh credentials } else { // TODO put something smart in the contentIntent below // finalNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), // (int)System.currentTimeMillis(), new Intent(), 0); // } if (uploadResult.getCode() == ResultCode.LOCAL_STORAGE_FULL || uploadResult.getCode() == ResultCode.LOCAL_STORAGE_NOT_COPIED) { // TODO we need a class to provide error messages for the users // from a RemoteOperationResult and a RemoteOperation content = String.format( getString(R.string.error__upload__local_file_not_copied), upload.getFileName(), getString(R.string.app_name)); } else if (uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED) { content = getString(R.string.failed_upload_quota_exceeded_text); } else { content = String.format( getString(R.string.uploader_upload_failed_content_single), upload.getFileName()); } // we add only for instant-uploads the InstantUploadActivity and the // db entry Intent detailUploadIntent = null; if (upload.isInstant() && InstantUploadActivity.IS_ENABLED) { detailUploadIntent = new Intent(this, InstantUploadActivity.class); detailUploadIntent.putExtra(FileUploader.KEY_ACCOUNT, upload.getAccount()); } else { detailUploadIntent = new Intent(this, FailedUploadActivity.class); detailUploadIntent.putExtra(FailedUploadActivity.MESSAGE, content); } finalNotification.contentIntent = PendingIntent.getActivity( getApplicationContext(), (int) System.currentTimeMillis(), detailUploadIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT); if (upload.isInstant()) { DbHandler db = null; try { db = new DbHandler(this.getBaseContext()); String message = uploadResult.getLogMessage() + " errorCode: " + uploadResult.getCode(); Log_OC.e(TAG, message + " Http-Code: " + uploadResult.getHttpCode()); if (uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED) { message = getString(R.string.failed_upload_quota_exceeded_text); if (db.updateFileState( upload.getOriginalStoragePath(), DbHandler.UPLOAD_STATUS_UPLOAD_FAILED, message) == 0) { db.putFileForLater( upload.getOriginalStoragePath(), upload.getAccount().name, message); } } } finally { if (db != null) { db.close(); } } } } finalNotification.setLatestEventInfo( getApplicationContext(), getString(R.string.uploader_upload_failed_ticker), content, finalNotification.contentIntent); mNotificationManager.notify(R.string.uploader_upload_failed_ticker, finalNotification); } }
/** * Core upload method: sends the file(s) to upload * * @param uploadKey Key to access the upload to perform, contained in mPendingUploads */ public void uploadFile(String uploadKey) { synchronized (mPendingUploads) { 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()); mUploadClient = OwnCloudClientFactory.createOwnCloudClient(mLastAccount, getApplicationContext()); } /// 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 { 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; } } /// notify result notifyUploadResult(uploadResult, mCurrentUpload); sendFinalBroadcast(mCurrentUpload, uploadResult); } }
/** * Entry point to add one or several files to the queue of uploads. * * <p>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) { 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"); return Service.START_NOT_STICKY; } int uploadType = intent.getIntExtra(KEY_UPLOAD_TYPE, -1); if (uploadType == -1) { Log_OC.e(TAG, "Incorrect upload type provided"); return Service.START_NOT_STICKY; } Account account = intent.getParcelableExtra(KEY_ACCOUNT); String[] localPaths = null, remotePaths = null, mimeTypes = null; OCFile[] files = null; if (uploadType == UPLOAD_SINGLE_FILE) { if (intent.hasExtra(KEY_FILE)) { files = new OCFile[] {intent.getParcelableExtra(KEY_FILE)}; } else { localPaths = new String[] {intent.getStringExtra(KEY_LOCAL_FILE)}; remotePaths = new String[] {intent.getStringExtra(KEY_REMOTE_FILE)}; mimeTypes = new String[] {intent.getStringExtra(KEY_MIME_TYPE)}; } } else { // mUploadType == UPLOAD_MULTIPLE_FILES if (intent.hasExtra(KEY_FILE)) { files = (OCFile[]) intent.getParcelableArrayExtra(KEY_FILE); // TODO // will // this // casting // work // fine? } else { localPaths = intent.getStringArrayExtra(KEY_LOCAL_FILE); remotePaths = intent.getStringArrayExtra(KEY_REMOTE_FILE); mimeTypes = intent.getStringArrayExtra(KEY_MIME_TYPE); } } 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; } else if (!intent.hasExtra(KEY_FILE)) { if (localPaths == null) { Log_OC.e(TAG, "Incorrect array for local paths provided in upload intent"); return Service.START_NOT_STICKY; } if (remotePaths == null) { Log_OC.e(TAG, "Incorrect array for remote paths provided in upload intent"); return Service.START_NOT_STICKY; } if (localPaths.length != remotePaths.length) { Log_OC.e(TAG, "Different number of remote paths and local paths!"); return Service.START_NOT_STICKY; } 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); if (files[i] == null) { // TODO @andomaex add failure Notification return Service.START_NOT_STICKY; } } } AccountManager aMgr = AccountManager.get(this); String version = aMgr.getUserData(account, OwnCloudAccount.Constants.KEY_OC_VERSION); String versionString = aMgr.getUserData(account, OwnCloudAccount.Constants.KEY_OC_VERSION_STRING); OwnCloudVersion ocv = new OwnCloudVersion(version, versionString); boolean chunked = FileUploader.chunkedUploadIsSupported(ocv); AbstractList<String> requestedUploads = new Vector<String>(); 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()); 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); } } catch (IllegalArgumentException e) { Log_OC.e(TAG, "Not enough information provided in intent: " + e.getMessage()); return START_NOT_STICKY; } catch (IllegalStateException e) { Log_OC.e(TAG, "Bad information provided in intent: " + e.getMessage()); return START_NOT_STICKY; } catch (Exception e) { Log_OC.e(TAG, "Unexpected exception while processing upload intent", e); return START_NOT_STICKY; } if (requestedUploads.size() > 0) { Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = requestedUploads; mServiceHandler.sendMessage(msg); } Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size()); return Service.START_NOT_STICKY; }