From 5af3c6d38908c4cbb0ba6c44b593eaa5074a3fa0 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Fri, 31 Oct 2014 13:32:25 +0100 Subject: [PATCH 01/76] cleanup uploader. add comments. uncomment unused functions. rename. --- SETUP.md | 1 + src/com/owncloud/android/db/DbHandler.java | 46 ++-- src/com/owncloud/android/db/ProviderMeta.java | 198 +++++++++--------- .../files/services/FileDownloader.java | 2 +- .../android/files/services/FileUploader.java | 25 +-- 5 files changed, 136 insertions(+), 136 deletions(-) diff --git a/SETUP.md b/SETUP.md index 39539038e9f..3e8161e7e3d 100644 --- a/SETUP.md +++ b/SETUP.md @@ -71,6 +71,7 @@ NOTE: Even though API level is set to 19, APK also runs on older devices because NOTE: You must sign the [Contributor Agreement][1] before your changes can be accepted! * Commit your changes locally: "git commit -a" +* If substantial changes were done to the official repository while you were working, merge those changes: "git merge upstream/develop" * Push your changes to your Github repo: "git push" * Browse to https://github.com/YOURGITHUBNAME/android/pulls and issue pull request * Click "Edit" and set "base:develop" diff --git a/src/com/owncloud/android/db/DbHandler.java b/src/com/owncloud/android/db/DbHandler.java index 717066b7066..ad52e618dfd 100644 --- a/src/com/owncloud/android/db/DbHandler.java +++ b/src/com/owncloud/android/db/DbHandler.java @@ -40,9 +40,8 @@ public class DbHandler { private final String TABLE_INSTANT_UPLOAD = "instant_upload"; - public static final int UPLOAD_STATUS_UPLOAD_LATER = 0; - public static final int UPLOAD_STATUS_UPLOAD_FAILED = 1; - + public enum UploadStatus {UPLOAD_STATUS_UPLOAD_LATER, UPLOAD_STATUS_UPLOAD_FAILED}; + public DbHandler(Context context) { mDatabaseName = MainApp.getDBName(); mHelper = new OpenerHelper(context); @@ -53,20 +52,35 @@ public void close() { mDB.close(); } + /** + * Store a file persistantly for upload. + * @param filepath + * @param account + * @param message + * @return + */ public boolean putFileForLater(String filepath, String account, String message) { ContentValues cv = new ContentValues(); cv.put("path", filepath); cv.put("account", account); - cv.put("attempt", UPLOAD_STATUS_UPLOAD_LATER); + cv.put("attempt", String.valueOf(UploadStatus.UPLOAD_STATUS_UPLOAD_LATER)); cv.put("message", message); long result = mDB.insert(TABLE_INSTANT_UPLOAD, null, cv); Log_OC.d(TABLE_INSTANT_UPLOAD, "putFileForLater returns with: " + result + " for file: " + filepath); return result != -1; } - public int updateFileState(String filepath, Integer status, String message) { + /** + * Update upload status of file. + * + * @param filepath + * @param status + * @param message + * @return + */ + public int updateFileState(String filepath, UploadStatus status, String message) { ContentValues cv = new ContentValues(); - cv.put("attempt", status); + cv.put("attempt", String.valueOf(status)); cv.put("message", message); int result = mDB.update(TABLE_INSTANT_UPLOAD, cv, "path=?", new String[] { filepath }); Log_OC.d(TABLE_INSTANT_UPLOAD, "updateFileState returns with: " + result + " for file: " + filepath); @@ -74,23 +88,25 @@ public int updateFileState(String filepath, Integer status, String message) { } public Cursor getAwaitingFiles() { - return mDB.query(TABLE_INSTANT_UPLOAD, null, "attempt=" + UPLOAD_STATUS_UPLOAD_LATER, null, null, null, null); + return mDB.query(TABLE_INSTANT_UPLOAD, null, "attempt=" + UploadStatus.UPLOAD_STATUS_UPLOAD_LATER, null, null, null, null); } - public Cursor getFailedFiles() { - return mDB.query(TABLE_INSTANT_UPLOAD, null, "attempt>" + UPLOAD_STATUS_UPLOAD_LATER, null, null, null, null); - } + //ununsed until now. uncomment if needed. +// public Cursor getFailedFiles() { +// return mDB.query(TABLE_INSTANT_UPLOAD, null, "attempt>" + UploadStatus.UPLOAD_STATUS_UPLOAD_LATER, null, null, null, null); +// } - public void clearFiles() { - mDB.delete(TABLE_INSTANT_UPLOAD, null, null); - } + //ununsed until now. uncomment if needed. +// public void clearFiles() { +// mDB.delete(TABLE_INSTANT_UPLOAD, null, null); +// } /** - * + * Remove file from upload list. Should be called when upload succeed or failed and should not be retried. * @param localPath * @return true when one or more pending files was removed */ - public boolean removeIUPendingFile(String localPath) { + public boolean removePendingFile(String localPath) { long result = mDB.delete(TABLE_INSTANT_UPLOAD, "path = ?", new String[] { localPath }); Log_OC.d(TABLE_INSTANT_UPLOAD, "delete returns with: " + result + " for file: " + localPath); return result != 0; diff --git a/src/com/owncloud/android/db/ProviderMeta.java b/src/com/owncloud/android/db/ProviderMeta.java index bc59869a090..f0c80b41239 100644 --- a/src/com/owncloud/android/db/ProviderMeta.java +++ b/src/com/owncloud/android/db/ProviderMeta.java @@ -1,99 +1,99 @@ -/* ownCloud Android client application - * Copyright (C) 2011 Bartek Przybylski - * Copyright (C) 2012-2013 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 . - * - */ -package com.owncloud.android.db; - -import android.net.Uri; -import android.provider.BaseColumns; - -import com.owncloud.android.MainApp; - -/** - * Meta-Class that holds various static field information - * - * @author Bartek Przybylski - * - */ -public class ProviderMeta { - - public static final String DB_NAME = "filelist"; - public static final int DB_VERSION = 8; - - private ProviderMeta() { - } - - static public class ProviderTableMeta implements BaseColumns { - public static final String FILE_TABLE_NAME = "filelist"; - public static final String OCSHARES_TABLE_NAME = "ocshares"; - public static final Uri CONTENT_URI = Uri.parse("content://" - + MainApp.getAuthority() + "/"); - public static final Uri CONTENT_URI_FILE = Uri.parse("content://" - + MainApp.getAuthority() + "/file"); - public static final Uri CONTENT_URI_DIR = Uri.parse("content://" - + MainApp.getAuthority() + "/dir"); - public static final Uri CONTENT_URI_SHARE = Uri.parse("content://" - + MainApp.getAuthority() + "/shares"); - - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.owncloud.file"; - public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd.owncloud.file"; - - // Columns of filelist table - public static final String FILE_PARENT = "parent"; - public static final String FILE_NAME = "filename"; - public static final String FILE_CREATION = "created"; - public static final String FILE_MODIFIED = "modified"; - public static final String FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA = "modified_at_last_sync_for_data"; - public static final String FILE_CONTENT_LENGTH = "content_length"; - public static final String FILE_CONTENT_TYPE = "content_type"; - public static final String FILE_STORAGE_PATH = "media_path"; - public static final String FILE_PATH = "path"; - public static final String FILE_ACCOUNT_OWNER = "file_owner"; - public static final String FILE_LAST_SYNC_DATE = "last_sync_date"; // _for_properties, but let's keep it as it is - public static final String FILE_LAST_SYNC_DATE_FOR_DATA = "last_sync_date_for_data"; - public static final String FILE_KEEP_IN_SYNC = "keep_in_sync"; - public static final String FILE_ETAG = "etag"; - public static final String FILE_SHARE_BY_LINK = "share_by_link"; - public static final String FILE_PUBLIC_LINK = "public_link"; - public static final String FILE_PERMISSIONS = "permissions"; - public static final String FILE_REMOTE_ID = "remote_id"; - public static final String FILE_UPDATE_THUMBNAIL = "update_thumbnail"; - - public static final String FILE_DEFAULT_SORT_ORDER = FILE_NAME - + " collate nocase asc"; - - // Columns of ocshares table - public static final String OCSHARES_FILE_SOURCE = "file_source"; - public static final String OCSHARES_ITEM_SOURCE = "item_source"; - public static final String OCSHARES_SHARE_TYPE = "share_type"; - public static final String OCSHARES_SHARE_WITH = "shate_with"; - public static final String OCSHARES_PATH = "path"; - public static final String OCSHARES_PERMISSIONS = "permissions"; - public static final String OCSHARES_SHARED_DATE = "shared_date"; - public static final String OCSHARES_EXPIRATION_DATE = "expiration_date"; - public static final String OCSHARES_TOKEN = "token"; - public static final String OCSHARES_SHARE_WITH_DISPLAY_NAME = "shared_with_display_name"; - public static final String OCSHARES_IS_DIRECTORY = "is_directory"; - public static final String OCSHARES_USER_ID = "user_id"; - public static final String OCSHARES_ID_REMOTE_SHARED = "id_remote_shared"; - public static final String OCSHARES_ACCOUNT_OWNER = "owner_share"; - - public static final String OCSHARES_DEFAULT_SORT_ORDER = OCSHARES_FILE_SOURCE - + " collate nocase asc"; - - - } -} +/* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * Copyright (C) 2012-2013 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 . + * + */ +package com.owncloud.android.db; + +import android.net.Uri; +import android.provider.BaseColumns; + +import com.owncloud.android.MainApp; + +/** + * Meta-Class that holds various static field information + * + * @author Bartek Przybylski + * + */ +public class ProviderMeta { + + public static final String DB_NAME = "filelist"; + public static final int DB_VERSION = 8; + + private ProviderMeta() { + } + + static public class ProviderTableMeta implements BaseColumns { + public static final String FILE_TABLE_NAME = "filelist"; + public static final String OCSHARES_TABLE_NAME = "ocshares"; + public static final Uri CONTENT_URI = Uri.parse("content://" + + MainApp.getAuthority() + "/"); + public static final Uri CONTENT_URI_FILE = Uri.parse("content://" + + MainApp.getAuthority() + "/file"); + public static final Uri CONTENT_URI_DIR = Uri.parse("content://" + + MainApp.getAuthority() + "/dir"); + public static final Uri CONTENT_URI_SHARE = Uri.parse("content://" + + MainApp.getAuthority() + "/shares"); + + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.owncloud.file"; + public static final String CONTENT_TYPE_ITEM = "vnd.android.cursor.item/vnd.owncloud.file"; + + // Columns of filelist table + public static final String FILE_PARENT = "parent"; + public static final String FILE_NAME = "filename"; + public static final String FILE_CREATION = "created"; + public static final String FILE_MODIFIED = "modified"; + public static final String FILE_MODIFIED_AT_LAST_SYNC_FOR_DATA = "modified_at_last_sync_for_data"; + public static final String FILE_CONTENT_LENGTH = "content_length"; + public static final String FILE_CONTENT_TYPE = "content_type"; + public static final String FILE_STORAGE_PATH = "media_path"; + public static final String FILE_PATH = "path"; + public static final String FILE_ACCOUNT_OWNER = "file_owner"; + public static final String FILE_LAST_SYNC_DATE = "last_sync_date"; // _for_properties, but let's keep it as it is + public static final String FILE_LAST_SYNC_DATE_FOR_DATA = "last_sync_date_for_data"; + public static final String FILE_KEEP_IN_SYNC = "keep_in_sync"; + public static final String FILE_ETAG = "etag"; + public static final String FILE_SHARE_BY_LINK = "share_by_link"; + public static final String FILE_PUBLIC_LINK = "public_link"; + public static final String FILE_PERMISSIONS = "permissions"; + public static final String FILE_REMOTE_ID = "remote_id"; + public static final String FILE_UPDATE_THUMBNAIL = "update_thumbnail"; + + public static final String FILE_DEFAULT_SORT_ORDER = FILE_NAME + + " collate nocase asc"; + + // Columns of ocshares table + public static final String OCSHARES_FILE_SOURCE = "file_source"; + public static final String OCSHARES_ITEM_SOURCE = "item_source"; + public static final String OCSHARES_SHARE_TYPE = "share_type"; + public static final String OCSHARES_SHARE_WITH = "shate_with"; + public static final String OCSHARES_PATH = "path"; + public static final String OCSHARES_PERMISSIONS = "permissions"; + public static final String OCSHARES_SHARED_DATE = "shared_date"; + public static final String OCSHARES_EXPIRATION_DATE = "expiration_date"; + public static final String OCSHARES_TOKEN = "token"; + public static final String OCSHARES_SHARE_WITH_DISPLAY_NAME = "shared_with_display_name"; + public static final String OCSHARES_IS_DIRECTORY = "is_directory"; + public static final String OCSHARES_USER_ID = "user_id"; + public static final String OCSHARES_ID_REMOTE_SHARED = "id_remote_shared"; + public static final String OCSHARES_ACCOUNT_OWNER = "owner_share"; + + public static final String OCSHARES_DEFAULT_SORT_ORDER = OCSHARES_FILE_SOURCE + + " collate nocase asc"; + + + } +} diff --git a/src/com/owncloud/android/files/services/FileDownloader.java b/src/com/owncloud/android/files/services/FileDownloader.java index fdc35f8d9c0..88c821392a7 100644 --- a/src/com/owncloud/android/files/services/FileDownloader.java +++ b/src/com/owncloud/android/files/services/FileDownloader.java @@ -301,7 +301,7 @@ public void onTransferProgress(long progressRate, long totalTransferredSoFar, lo /** * Download worker. Performs the pending downloads in the order they were requested. * - * Created with the Looper of a new thread, started in {@link FileUploader#onCreate()}. + * Created with the Looper of a new thread, started in {@link FileDownloader#onCreate()}. */ 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 diff --git a/src/com/owncloud/android/files/services/FileUploader.java b/src/com/owncloud/android/files/services/FileUploader.java index ad2a2cbe44e..c5c84cee014 100644 --- a/src/com/owncloud/android/files/services/FileUploader.java +++ b/src/com/owncloud/android/files/services/FileUploader.java @@ -76,7 +76,7 @@ -public class FileUploader extends Service implements OnDatatransferProgressListener { +public class FileUploader extends Service { private static final String UPLOAD_FINISH_MESSAGE = "UPLOAD_FINISH"; public static final String EXTRA_UPLOAD_RESULT = "RESULT"; @@ -273,7 +273,6 @@ public int onStartCommand(Intent intent, int flags, int startId) { } mPendingUploads.putIfAbsent(uploadKey, newUpload); // Grants that the file only upload once time - newUpload.addDatatransferProgressListener(this); newUpload.addDatatransferProgressListener((FileUploaderBinder)mBinder); requestedUploads.add(uploadKey); } @@ -473,7 +472,7 @@ public void handleMessage(Message msg) { * @param uploadKey Key to access the upload to perform, contained in * mPendingUploads */ - public void uploadFile(String uploadKey) { + private void uploadFile(String uploadKey) { synchronized (mPendingUploads) { mCurrentUpload = mPendingUploads.get(uploadKey); @@ -707,22 +706,6 @@ private void notifyUploadStart(UploadFileOperation upload) { mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotificationBuilder.build()); } - /** - * Callback method to update the progress bar in the status notification - */ - @Override - 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 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()); - } - mLastPercent = percent; - } - /** * Updates the status notification with the result of an upload operation. * @@ -797,7 +780,7 @@ uploadResult, upload, getResources() //message = getString(R.string.failed_upload_quota_exceeded_text); if (db.updateFileState( upload.getOriginalStoragePath(), - DbHandler.UPLOAD_STATUS_UPLOAD_FAILED, + DbHandler.UploadStatus.UPLOAD_STATUS_UPLOAD_FAILED, message) == 0) { db.putFileForLater( upload.getOriginalStoragePath(), @@ -820,7 +803,7 @@ uploadResult, upload, getResources() if (uploadResult.isSuccess()) { DbHandler db = new DbHandler(this.getBaseContext()); - db.removeIUPendingFile(mCurrentUpload.getOriginalStoragePath()); + db.removePendingFile(mCurrentUpload.getOriginalStoragePath()); db.close(); // remove success notification, with a delay of 2 seconds From ac002ced1d94f077258a6576c086f703efca9439 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Fri, 31 Oct 2014 13:42:59 +0100 Subject: [PATCH 02/76] rename DbHandler to UploadDbHandler. Increase DB version. Fix UploadStatus enum. --- .../{DbHandler.java => UploadDbHandler.java} | 68 +++++++++++-------- .../files/InstantUploadBroadcastReceiver.java | 6 +- .../android/files/services/FileUploader.java | 10 +-- .../android/ui/activity/Preferences.java | 6 +- 4 files changed, 52 insertions(+), 38 deletions(-) rename src/com/owncloud/android/db/{DbHandler.java => UploadDbHandler.java} (59%) diff --git a/src/com/owncloud/android/db/DbHandler.java b/src/com/owncloud/android/db/UploadDbHandler.java similarity index 59% rename from src/com/owncloud/android/db/DbHandler.java rename to src/com/owncloud/android/db/UploadDbHandler.java index ad52e618dfd..416e5b886bb 100644 --- a/src/com/owncloud/android/db/DbHandler.java +++ b/src/com/owncloud/android/db/UploadDbHandler.java @@ -27,22 +27,32 @@ import android.database.sqlite.SQLiteOpenHelper; /** - * Custom database helper for ownCloud + * Database helper for storing list of files to be uploaded, including status information for each file. * * @author Bartek Przybylski + * @author LukeOwncloud * */ -public class DbHandler { +public class UploadDbHandler { private SQLiteDatabase mDB; private OpenerHelper mHelper; private final String mDatabaseName; - private final int mDatabaseVersion = 3; + private final int mDatabaseVersion = 4; - private final String TABLE_INSTANT_UPLOAD = "instant_upload"; + static private final String TABLE_UPLOAD = "list_of_uploads"; - public enum UploadStatus {UPLOAD_STATUS_UPLOAD_LATER, UPLOAD_STATUS_UPLOAD_FAILED}; + public enum UploadStatus { + UPLOAD_STATUS_UPLOAD_LATER(0), UPLOAD_STATUS_UPLOAD_FAILED(1); + private final int value; + private UploadStatus(int value) { + this.value = value; + } + public int getValue() { + return value; + } + }; - public DbHandler(Context context) { + public UploadDbHandler(Context context) { mDatabaseName = MainApp.getDBName(); mHelper = new OpenerHelper(context); mDB = mHelper.getWritableDatabase(); @@ -53,42 +63,46 @@ public void close() { } /** - * Store a file persistantly for upload. - * @param filepath - * @param account - * @param message - * @return + * Store a file persistently for upload. + * @param filepath local file path to file + * @param account account for uploading + * @param message optional message. can be null. + * @return false if an error occurred, else true. */ public boolean putFileForLater(String filepath, String account, String message) { ContentValues cv = new ContentValues(); cv.put("path", filepath); cv.put("account", account); - cv.put("attempt", String.valueOf(UploadStatus.UPLOAD_STATUS_UPLOAD_LATER)); + cv.put("attempt", UploadStatus.UPLOAD_STATUS_UPLOAD_LATER.getValue()); cv.put("message", message); - long result = mDB.insert(TABLE_INSTANT_UPLOAD, null, cv); - Log_OC.d(TABLE_INSTANT_UPLOAD, "putFileForLater returns with: " + result + " for file: " + filepath); + long result = mDB.insert(TABLE_UPLOAD, null, cv); + Log_OC.d(TABLE_UPLOAD, "putFileForLater returns with: " + result + " for file: " + filepath); return result != -1; } /** * Update upload status of file. * - * @param filepath - * @param status - * @param message - * @return + * @param filepath local file path to file. used as identifier. + * @param status new status. + * @param message new message. + * @return 1 if file status was updated, else 0. */ public int updateFileState(String filepath, UploadStatus status, String message) { ContentValues cv = new ContentValues(); - cv.put("attempt", String.valueOf(status)); + cv.put("attempt", status.getValue()); cv.put("message", message); - int result = mDB.update(TABLE_INSTANT_UPLOAD, cv, "path=?", new String[] { filepath }); - Log_OC.d(TABLE_INSTANT_UPLOAD, "updateFileState returns with: " + result + " for file: " + filepath); + int result = mDB.update(TABLE_UPLOAD, cv, "path=?", new String[] { filepath }); + Log_OC.d(TABLE_UPLOAD, "updateFileState returns with: " + result + " for file: " + filepath); return result; } + /** + * Get all files with status {@link UploadStatus}.UPLOAD_STATUS_UPLOAD_LATER. + * @return + */ public Cursor getAwaitingFiles() { - return mDB.query(TABLE_INSTANT_UPLOAD, null, "attempt=" + UploadStatus.UPLOAD_STATUS_UPLOAD_LATER, null, null, null, null); + return mDB.query(TABLE_UPLOAD, null, "attempt=" + UploadStatus.UPLOAD_STATUS_UPLOAD_LATER, null, null, null, null); } //ununsed until now. uncomment if needed. @@ -107,8 +121,8 @@ public Cursor getAwaitingFiles() { * @return true when one or more pending files was removed */ public boolean removePendingFile(String localPath) { - long result = mDB.delete(TABLE_INSTANT_UPLOAD, "path = ?", new String[] { localPath }); - Log_OC.d(TABLE_INSTANT_UPLOAD, "delete returns with: " + result + " for file: " + localPath); + long result = mDB.delete(TABLE_UPLOAD, "path = ?", new String[] { localPath }); + Log_OC.d(TABLE_UPLOAD, "delete returns with: " + result + " for file: " + localPath); return result != 0; } @@ -120,16 +134,16 @@ public OpenerHelper(Context context) { @Override public void onCreate(SQLiteDatabase db) { - db.execSQL("CREATE TABLE " + TABLE_INSTANT_UPLOAD + " (" + " _id INTEGER PRIMARY KEY, " + " path TEXT," + db.execSQL("CREATE TABLE " + TABLE_UPLOAD + " (" + " _id INTEGER PRIMARY KEY, " + " path TEXT," + " account TEXT,attempt INTEGER,message TEXT);"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion < 2) { - db.execSQL("ALTER TABLE " + TABLE_INSTANT_UPLOAD + " ADD COLUMN attempt INTEGER;"); + db.execSQL("ALTER TABLE " + TABLE_UPLOAD + " ADD COLUMN attempt INTEGER;"); } - db.execSQL("ALTER TABLE " + TABLE_INSTANT_UPLOAD + " ADD COLUMN message TEXT;"); + db.execSQL("ALTER TABLE " + TABLE_UPLOAD + " ADD COLUMN message TEXT;"); } } diff --git a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java index efaa803c234..819c603cbf0 100644 --- a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java +++ b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java @@ -22,7 +22,7 @@ import com.owncloud.android.MainApp; import com.owncloud.android.authentication.AccountUtils; -import com.owncloud.android.db.DbHandler; +import com.owncloud.android.db.UploadDbHandler; import com.owncloud.android.files.services.FileUploader; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.utils.FileStorageUtils; @@ -105,7 +105,7 @@ private void handleNewPictureAction(Context context, Intent intent) { Log_OC.d(TAG, file_path + ""); // save always temporally the picture to upload - DbHandler db = new DbHandler(context); + UploadDbHandler db = new UploadDbHandler(context); db.putFileForLater(file_path, account.name, null); db.close(); @@ -178,7 +178,7 @@ private void handleConnectivityAction(Context context, Intent intent) { if (!intent.hasExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY) && isOnline(context) && (!instantPictureUploadViaWiFiOnly(context) || (instantPictureUploadViaWiFiOnly(context) == isConnectedViaWiFi(context) == true))) { - DbHandler db = new DbHandler(context); + UploadDbHandler db = new UploadDbHandler(context); Cursor c = db.getAwaitingFiles(); if (c.moveToFirst()) { do { diff --git a/src/com/owncloud/android/files/services/FileUploader.java b/src/com/owncloud/android/files/services/FileUploader.java index c5c84cee014..9aa1e7fb8c8 100644 --- a/src/com/owncloud/android/files/services/FileUploader.java +++ b/src/com/owncloud/android/files/services/FileUploader.java @@ -50,7 +50,7 @@ import com.owncloud.android.authentication.AuthenticatorActivity; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.db.DbHandler; +import com.owncloud.android.db.UploadDbHandler; import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; @@ -770,9 +770,9 @@ uploadResult, upload, getResources() mNotificationBuilder.setContentText(content); if (upload.isInstant()) { - DbHandler db = null; + UploadDbHandler db = null; try { - db = new DbHandler(this.getBaseContext()); + db = new UploadDbHandler(this.getBaseContext()); String message = uploadResult.getLogMessage() + " errorCode: " + uploadResult.getCode(); Log_OC.e(TAG, message + " Http-Code: " + uploadResult.getHttpCode()); @@ -780,7 +780,7 @@ uploadResult, upload, getResources() //message = getString(R.string.failed_upload_quota_exceeded_text); if (db.updateFileState( upload.getOriginalStoragePath(), - DbHandler.UploadStatus.UPLOAD_STATUS_UPLOAD_FAILED, + UploadDbHandler.UploadStatus.UPLOAD_STATUS_UPLOAD_FAILED, message) == 0) { db.putFileForLater( upload.getOriginalStoragePath(), @@ -802,7 +802,7 @@ uploadResult, upload, getResources() if (uploadResult.isSuccess()) { - DbHandler db = new DbHandler(this.getBaseContext()); + UploadDbHandler db = new UploadDbHandler(this.getBaseContext()); db.removePendingFile(mCurrentUpload.getOriginalStoragePath()); db.close(); diff --git a/src/com/owncloud/android/ui/activity/Preferences.java b/src/com/owncloud/android/ui/activity/Preferences.java index 20330931b40..e203f01f6f7 100644 --- a/src/com/owncloud/android/ui/activity/Preferences.java +++ b/src/com/owncloud/android/ui/activity/Preferences.java @@ -50,7 +50,7 @@ import com.owncloud.android.R; import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.authentication.AuthenticatorActivity; -import com.owncloud.android.db.DbHandler; +import com.owncloud.android.db.UploadDbHandler; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.ui.LongClickableCheckBoxPreference; import com.owncloud.android.utils.DisplayUtils; @@ -66,7 +66,7 @@ public class Preferences extends SherlockPreferenceActivity implements AccountMa private static final String TAG = "OwnCloudPreferences"; - private DbHandler mDbHandler; + private UploadDbHandler mDbHandler; private CheckBoxPreference pCode; private Preference pAboutApp; @@ -81,7 +81,7 @@ public class Preferences extends SherlockPreferenceActivity implements AccountMa @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mDbHandler = new DbHandler(getBaseContext()); + mDbHandler = new UploadDbHandler(getBaseContext()); addPreferencesFromResource(R.xml.preferences); ActionBar actionBar = getSherlock().getActionBar(); From 64827cf4adc3fefd5361100767903ed6d928a8c7 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Fri, 31 Oct 2014 15:05:56 +0100 Subject: [PATCH 03/76] refactoring, commenting, understanding code. added TODO for FileUploadService. --- AndroidManifest.xml | 3 - .../owncloud/android/db/UploadDbHandler.java | 8 +- .../android/files/FileMenuFilter.java | 6 +- .../android/files/FileOperationsHelper.java | 2 +- .../files/InstantUploadBroadcastReceiver.java | 163 ++++++++---------- ...leUploader.java => FileUploadService.java} | 133 +++++++++----- .../operations/SynchronizeFileOperation.java | 12 +- .../operations/UploadFileOperation.java | 18 +- .../android/ui/activity/ComponentsGetter.java | 2 +- .../ui/activity/ConflictsResolveActivity.java | 14 +- .../android/ui/activity/FileActivity.java | 6 +- .../ui/activity/FileDisplayActivity.java | 40 ++--- .../android/ui/activity/Uploader.java | 12 +- .../ui/adapter/FileListListAdapter.java | 2 +- .../ui/fragment/FileDetailFragment.java | 2 +- .../android/ui/fragment/FileFragment.java | 2 +- .../ui/preview/PreviewImageActivity.java | 8 +- 17 files changed, 229 insertions(+), 204 deletions(-) rename src/com/owncloud/android/files/services/{FileUploader.java => FileUploadService.java} (89%) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a5990afd456..a64963fca7a 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -174,9 +174,6 @@ - - - diff --git a/src/com/owncloud/android/db/UploadDbHandler.java b/src/com/owncloud/android/db/UploadDbHandler.java index 416e5b886bb..c318be3daa8 100644 --- a/src/com/owncloud/android/db/UploadDbHandler.java +++ b/src/com/owncloud/android/db/UploadDbHandler.java @@ -37,7 +37,7 @@ public class UploadDbHandler { private SQLiteDatabase mDB; private OpenerHelper mHelper; private final String mDatabaseName; - private final int mDatabaseVersion = 4; + private final int mDatabaseVersion = 3; static private final String TABLE_UPLOAD = "list_of_uploads"; @@ -63,13 +63,13 @@ public void close() { } /** - * Store a file persistently for upload. + * Store a file persistently (to be uploaded later). * @param filepath local file path to file * @param account account for uploading * @param message optional message. can be null. * @return false if an error occurred, else true. */ - public boolean putFileForLater(String filepath, String account, String message) { + public boolean storeFile(String filepath, String account, String message) { ContentValues cv = new ContentValues(); cv.put("path", filepath); cv.put("account", account); @@ -120,7 +120,7 @@ public Cursor getAwaitingFiles() { * @param localPath * @return true when one or more pending files was removed */ - public boolean removePendingFile(String localPath) { + public boolean removeFile(String localPath) { long result = mDB.delete(TABLE_UPLOAD, "path = ?", new String[] { localPath }); Log_OC.d(TABLE_UPLOAD, "delete returns with: " + result + " for file: " + localPath); return result != 0; diff --git a/src/com/owncloud/android/files/FileMenuFilter.java b/src/com/owncloud/android/files/FileMenuFilter.java index 6eb746cbf80..d3c54a4d79e 100644 --- a/src/com/owncloud/android/files/FileMenuFilter.java +++ b/src/com/owncloud/android/files/FileMenuFilter.java @@ -29,8 +29,8 @@ import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileDownloader; 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.files.services.FileUploadService; +import com.owncloud.android.files.services.FileUploadService.FileUploaderBinder; import com.owncloud.android.ui.activity.ComponentsGetter; /** @@ -52,7 +52,7 @@ public class FileMenuFilter { * @param targetFile {@link OCFile} target of the action to filter in the {@link Menu}. * @param account ownCloud {@link Account} holding targetFile. * @param cg Accessor to app components, needed to get access the - * {@link FileUploader} and {@link FileDownloader} services. + * {@link FileUploadService} and {@link FileDownloader} services. * @param context Android {@link Context}, needed to access build setup resources. */ public FileMenuFilter(OCFile targetFile, Account account, ComponentsGetter cg, Context context) { diff --git a/src/com/owncloud/android/files/FileOperationsHelper.java b/src/com/owncloud/android/files/FileOperationsHelper.java index e1ab19536ce..626595c2c7a 100644 --- a/src/com/owncloud/android/files/FileOperationsHelper.java +++ b/src/com/owncloud/android/files/FileOperationsHelper.java @@ -30,7 +30,7 @@ import com.owncloud.android.R; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; -import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; +import com.owncloud.android.files.services.FileUploadService.FileUploaderBinder; import com.owncloud.android.lib.common.accounts.AccountUtils.Constants; import com.owncloud.android.lib.common.network.WebdavUtils; diff --git a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java index 819c603cbf0..c72b3218301 100644 --- a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java +++ b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java @@ -18,16 +18,6 @@ package com.owncloud.android.files; -import java.io.File; - -import com.owncloud.android.MainApp; -import com.owncloud.android.authentication.AccountUtils; -import com.owncloud.android.db.UploadDbHandler; -import com.owncloud.android.files.services.FileUploader; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.utils.FileStorageUtils; - - import android.accounts.Account; import android.content.BroadcastReceiver; import android.content.Context; @@ -38,7 +28,12 @@ import android.preference.PreferenceManager; import android.provider.MediaStore.Images; import android.provider.MediaStore.Video; -import android.webkit.MimeTypeMap; + +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.db.UploadDbHandler; +import com.owncloud.android.files.services.FileUploadService; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.utils.FileStorageUtils; public class InstantUploadBroadcastReceiver extends BroadcastReceiver { @@ -56,9 +51,7 @@ public class InstantUploadBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Log_OC.d(TAG, "Received: " + intent.getAction()); - if (intent.getAction().equals(android.net.ConnectivityManager.CONNECTIVITY_ACTION)) { - handleConnectivityAction(context, intent); - }else if (intent.getAction().equals(NEW_PHOTO_ACTION_UNOFFICIAL)) { + if (intent.getAction().equals(NEW_PHOTO_ACTION_UNOFFICIAL)) { handleNewPictureAction(context, intent); Log_OC.d(TAG, "UNOFFICIAL processed: com.android.camera.NEW_PICTURE"); } else if (intent.getAction().equals(NEW_PHOTO_ACTION)) { @@ -104,22 +97,14 @@ private void handleNewPictureAction(Context context, Intent intent) { Log_OC.d(TAG, file_path + ""); - // save always temporally the picture to upload - UploadDbHandler db = new UploadDbHandler(context); - db.putFileForLater(file_path, account.name, null); - db.close(); - - if (!isOnline(context) || (instantPictureUploadViaWiFiOnly(context) && !isConnectedViaWiFi(context))) { - return; - } - - Intent i = new Intent(context, FileUploader.class); - i.putExtra(FileUploader.KEY_ACCOUNT, account); - i.putExtra(FileUploader.KEY_LOCAL_FILE, file_path); - i.putExtra(FileUploader.KEY_REMOTE_FILE, FileStorageUtils.getInstantUploadFilePath(context, file_name)); - i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE); - i.putExtra(FileUploader.KEY_MIME_TYPE, mime_type); - i.putExtra(FileUploader.KEY_INSTANT_UPLOAD, true); + Intent i = new Intent(context, FileUploadService.class); + i.putExtra(FileUploadService.KEY_ACCOUNT, account); + i.putExtra(FileUploadService.KEY_LOCAL_FILE, file_path); + i.putExtra(FileUploadService.KEY_REMOTE_FILE, FileStorageUtils.getInstantUploadFilePath(context, file_name)); + i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UPLOAD_SINGLE_FILE); + i.putExtra(FileUploadService.KEY_MIME_TYPE, mime_type); + i.putExtra(FileUploadService.KEY_CREATE_REMOTE_FOLDER, true); + i.putExtra(FileUploadService.KEY_WIFI_ONLY, instantPictureUploadViaWiFiOnly(context)); context.startService(i); } @@ -154,69 +139,67 @@ private void handleNewVideoAction(Context context, Intent intent) { c.close(); Log_OC.d(TAG, file_path + ""); - if (!isOnline(context) || (instantVideoUploadViaWiFiOnly(context) && !isConnectedViaWiFi(context))) { - return; - } - - Intent i = new Intent(context, FileUploader.class); - i.putExtra(FileUploader.KEY_ACCOUNT, account); - i.putExtra(FileUploader.KEY_LOCAL_FILE, file_path); - i.putExtra(FileUploader.KEY_REMOTE_FILE, FileStorageUtils.getInstantUploadFilePath(context, file_name)); - i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE); - i.putExtra(FileUploader.KEY_MIME_TYPE, mime_type); - i.putExtra(FileUploader.KEY_INSTANT_UPLOAD, true); + Intent i = new Intent(context, FileUploadService.class); + i.putExtra(FileUploadService.KEY_ACCOUNT, account); + i.putExtra(FileUploadService.KEY_LOCAL_FILE, file_path); + i.putExtra(FileUploadService.KEY_REMOTE_FILE, FileStorageUtils.getInstantUploadFilePath(context, file_name)); + i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UPLOAD_SINGLE_FILE); + i.putExtra(FileUploadService.KEY_MIME_TYPE, mime_type); + i.putExtra(FileUploadService.KEY_CREATE_REMOTE_FOLDER, true); + i.putExtra(FileUploadService.KEY_WIFI_ONLY, instantVideoUploadViaWiFiOnly(context)); context.startService(i); - } - private void handleConnectivityAction(Context context, Intent intent) { - if (!instantPictureUploadEnabled(context)) { - Log_OC.d(TAG, "Instant upload disabled, don't upload anything"); - return; - } - - if (!intent.hasExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY) - && isOnline(context) - && (!instantPictureUploadViaWiFiOnly(context) || (instantPictureUploadViaWiFiOnly(context) == isConnectedViaWiFi(context) == true))) { - UploadDbHandler db = new UploadDbHandler(context); - Cursor c = db.getAwaitingFiles(); - if (c.moveToFirst()) { - do { - String account_name = c.getString(c.getColumnIndex("account")); - String file_path = c.getString(c.getColumnIndex("path")); - File f = new File(file_path); - if (f.exists()) { - Account account = new Account(account_name, MainApp.getAccountType()); - - String mimeType = null; - try { - mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension( - f.getName().substring(f.getName().lastIndexOf('.') + 1)); - - } catch (Throwable e) { - Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " + f.getName()); - } - if (mimeType == null) - mimeType = "application/octet-stream"; - - Intent i = new Intent(context, FileUploader.class); - i.putExtra(FileUploader.KEY_ACCOUNT, account); - i.putExtra(FileUploader.KEY_LOCAL_FILE, file_path); - i.putExtra(FileUploader.KEY_REMOTE_FILE, FileStorageUtils.getInstantUploadFilePath(context, f.getName())); - i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE); - i.putExtra(FileUploader.KEY_INSTANT_UPLOAD, true); - context.startService(i); - - } else { - Log_OC.w(TAG, "Instant upload file " + f.getAbsolutePath() + " dont exist anymore"); - } - } while (c.moveToNext()); - } - c.close(); - db.close(); - } - - } + //obsolete. delete. +// private void handleConnectivityAction(Context context, Intent intent) { +// if (!instantPictureUploadEnabled(context)) { +// Log_OC.d(TAG, "Instant upload disabled, don't upload anything"); +// return; +// } +// +// if (!intent.hasExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY) +// && isOnline(context) +// && (!instantPictureUploadViaWiFiOnly(context) || (instantPictureUploadViaWiFiOnly(context) == isConnectedViaWiFi(context) == true))) { +// UploadDbHandler db = new UploadDbHandler(context); +// Cursor c = db.getAwaitingFiles(); +// if (c.moveToFirst()) { +// do { +// String account_name = c.getString(c.getColumnIndex("account")); +// String file_path = c.getString(c.getColumnIndex("path")); +// File f = new File(file_path); +// if (f.exists()) { +// Account account = new Account(account_name, MainApp.getAccountType()); +// +// String mimeType = null; +// try { +// mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension( +// f.getName().substring(f.getName().lastIndexOf('.') + 1)); +// +// } catch (Throwable e) { +// Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " + f.getName()); +// } +// if (mimeType == null) +// mimeType = "application/octet-stream"; +// +// Intent i = new Intent(context, FileUploader.class); +// i.putExtra(FileUploader.KEY_ACCOUNT, account); +// i.putExtra(FileUploader.KEY_LOCAL_FILE, file_path); +// i.putExtra(FileUploader.KEY_REMOTE_FILE, FileStorageUtils.getInstantUploadFilePath(context, f.getName())); +// i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE); +// i.putExtra(FileUploader.KEY_CREATE_REMOTE_FOLDER, true); +// i.putExtra(FileUploader.KEY_WIFI_ONLY, instantPictureUploadViaWiFiOnly(context)); +// context.startService(i); +// +// } else { +// Log_OC.w(TAG, "Instant upload file " + f.getAbsolutePath() + " dont exist anymore"); +// } +// } while (c.moveToNext()); +// } +// c.close(); +// db.close(); +// } +// +// } public static boolean isOnline(Context context) { ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); diff --git a/src/com/owncloud/android/files/services/FileUploader.java b/src/com/owncloud/android/files/services/FileUploadService.java similarity index 89% rename from src/com/owncloud/android/files/services/FileUploader.java rename to src/com/owncloud/android/files/services/FileUploadService.java index 9aa1e7fb8c8..77eeefcb46e 100644 --- a/src/com/owncloud/android/files/services/FileUploader.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -34,7 +34,11 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; @@ -61,7 +65,6 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation; -import com.owncloud.android.lib.resources.files.FileUtils; import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation; import com.owncloud.android.lib.resources.files.RemoteFile; import com.owncloud.android.lib.resources.status.OwnCloudVersion; @@ -75,8 +78,14 @@ import com.owncloud.android.utils.ErrorMessageAdapter; - -public class FileUploader extends Service { +/** + * Service for uploading files. Invoke using context.startService(...). + * This service retries until upload succeeded. Files to be uploaded are stored persistent using {@link UploadDbHandler}. + * + * @author LukeOwncloud + * + */ +public class FileUploadService extends Service { private static final String UPLOAD_FINISH_MESSAGE = "UPLOAD_FINISH"; public static final String EXTRA_UPLOAD_RESULT = "RESULT"; @@ -94,7 +103,8 @@ public class FileUploader extends Service { public static final String KEY_UPLOAD_TYPE = "UPLOAD_TYPE"; public static final String KEY_FORCE_OVERWRITE = "KEY_FORCE_OVERWRITE"; - public static final String KEY_INSTANT_UPLOAD = "INSTANT_UPLOAD"; + public static final String KEY_CREATE_REMOTE_FOLDER = "CREATE_REMOTE_FOLDER"; + public static final String KEY_WIFI_ONLY = "WIFI_ONLY"; public static final String KEY_LOCAL_BEHAVIOUR = "BEHAVIOUR"; public static final int LOCAL_BEHAVIOUR_COPY = 0; @@ -104,11 +114,12 @@ public class FileUploader extends Service { public static final int UPLOAD_SINGLE_FILE = 0; public static final int UPLOAD_MULTIPLE_FILES = 1; - private static final String TAG = FileUploader.class.getSimpleName(); + private static final String TAG = FileUploadService.class.getSimpleName(); private Looper mServiceLooper; private ServiceHandler mServiceHandler; private IBinder mBinder; + private ConnectivityChangeReceiver mConnectivityChangeReceiver; private OwnCloudClient mUploadClient = null; private Account mLastAccount = null; private FileDataStorageManager mStorageManager; @@ -118,11 +129,10 @@ public class FileUploader extends Service { private NotificationManager mNotificationManager; private NotificationCompat.Builder mNotificationBuilder; - private int mLastPercent; public static String getUploadFinishMessage() { - return FileUploader.class.getName().toString() + UPLOAD_FINISH_MESSAGE; + return FileUploadService.class.getName().toString() + UPLOAD_FINISH_MESSAGE; } /** @@ -164,8 +174,29 @@ public void onCreate() { mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper, this); mBinder = new FileUploaderBinder(); + mConnectivityChangeReceiver = new ConnectivityChangeReceiver(); + registerReceiver( + mConnectivityChangeReceiver, + new IntentFilter( + ConnectivityManager.CONNECTIVITY_ACTION)); } + public class ConnectivityChangeReceiver + extends BroadcastReceiver { + + @Override + public void onReceive(Context arg0, Intent arg1) { + //upload pending wifi only files. + onStartCommand(null, 0, 0); + } + + } + @Override + public void onDestroy() { + unregisterReceiver(mConnectivityChangeReceiver); + super.onDestroy(); + } + /** * Entry point to add one or several files to the queue of uploads. * @@ -175,6 +206,23 @@ public void onCreate() { */ @Override public int onStartCommand(Intent intent, int flags, int startId) { + if(intent == null) { + //service was restarted by OS (after return START_STICKY and kill service) + //or connectivity change was detected. ==> check persistent upload list. + } + + //TODO: +// if (intent.hasExtra(KEY_FILE)) { +// files = (OCFile[]) intent.getParcelableArrayExtra(KEY_FILE); +// +// } else { +// localPaths = intent.getStringArrayExtra(KEY_LOCAL_FILE); +// remotePaths = intent.getStringArrayExtra(KEY_REMOTE_FILE); +// mimeTypes = intent.getStringArrayExtra(KEY_MIME_TYPE); +// CREATE files from there values! +// } + //now work with files. + 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"); @@ -223,7 +271,8 @@ public int onStartCommand(Intent intent, int flags, int startId) { FileDataStorageManager storageManager = new FileDataStorageManager(account, getContentResolver()); boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false); - boolean isInstant = intent.getBooleanExtra(KEY_INSTANT_UPLOAD, false); + boolean isCreateRemoteFolder = intent.getBooleanExtra(KEY_CREATE_REMOTE_FOLDER, false); + boolean isUseWifiOnly = intent.getBooleanExtra(KEY_WIFI_ONLY, true); int localAction = intent.getIntExtra(KEY_LOCAL_BEHAVIOUR, LOCAL_BEHAVIOUR_COPY); if (intent.hasExtra(KEY_FILE) && files == null) { @@ -243,7 +292,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { 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] @@ -254,21 +303,28 @@ public int onStartCommand(Intent intent, int flags, int startId) { } } } + + // save always persistently path of upload, so it can be retried if failed. + UploadDbHandler db = new UploadDbHandler(this.getBaseContext()); + for (int i = 0; i < localPaths.length; i++) { + db.storeFile(localPaths[i], account.name, null); + } + db.close(); AccountManager aMgr = AccountManager.get(this); String version = aMgr.getUserData(account, Constants.KEY_OC_VERSION); OwnCloudVersion ocv = new OwnCloudVersion(version); - boolean chunked = FileUploader.chunkedUploadIsSupported(ocv); + boolean chunked = FileUploadService.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, + newUpload = new UploadFileOperation(account, files[i], chunked, forceOverwrite, localAction, getApplicationContext()); - if (isInstant) { + if (isCreateRemoteFolder) { newUpload.setRemoteFolderToBeCreated(); } mPendingUploads.putIfAbsent(uploadKey, newUpload); // Grants that the file only upload once time @@ -438,14 +494,14 @@ public void onTransferProgress(long progressRate, long totalTransferredSoFar, lo * requested. * * Created with the Looper of a new thread, started in - * {@link FileUploader#onCreate()}. + * {@link FileUploadService#onCreate()}. */ 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 - FileUploader mService; + FileUploadService mService; - public ServiceHandler(Looper looper, FileUploader service) { + public ServiceHandler(Looper looper, FileUploadService service) { super(looper); if (service == null) throw new IllegalArgumentException("Received invalid NULL in parameter 'service'"); @@ -682,7 +738,6 @@ private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, Stri */ private void notifyUploadStart(UploadFileOperation upload) { // / create status notification with a progress bar - mLastPercent = 0; mNotificationBuilder = NotificationBuilderWithProgressBar.newNotificationBuilderWithProgressBar(this); mNotificationBuilder @@ -768,33 +823,29 @@ uploadResult, upload, getResources() // grant that future retries on the same account will get the fresh credentials } else { mNotificationBuilder.setContentText(content); - - if (upload.isInstant()) { - UploadDbHandler db = null; - try { - db = new UploadDbHandler(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(), - UploadDbHandler.UploadStatus.UPLOAD_STATUS_UPLOAD_FAILED, - message) == 0) { - db.putFileForLater( - upload.getOriginalStoragePath(), - upload.getAccount().name, - message - ); - } - } - } finally { - if (db != null) { - db.close(); + + UploadDbHandler db = null; + try { + db = new UploadDbHandler(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); + int updatedFiles = db.updateFileState(upload.getOriginalStoragePath(), + UploadDbHandler.UploadStatus.UPLOAD_STATUS_UPLOAD_FAILED, message); + if (updatedFiles == 0) { // update failed + db.storeFile(upload.getOriginalStoragePath(), upload.getAccount().name, message); } + } else { + // TODO: handle other results + } + } finally { + if (db != null) { + db.close(); } } + } mNotificationBuilder.setContentText(content); @@ -803,7 +854,7 @@ uploadResult, upload, getResources() if (uploadResult.isSuccess()) { UploadDbHandler db = new UploadDbHandler(this.getBaseContext()); - db.removePendingFile(mCurrentUpload.getOriginalStoragePath()); + db.removeFile(mCurrentUpload.getOriginalStoragePath()); db.close(); // remove success notification, with a delay of 2 seconds diff --git a/src/com/owncloud/android/operations/SynchronizeFileOperation.java b/src/com/owncloud/android/operations/SynchronizeFileOperation.java index 45a73057cd2..7e383c06ee8 100644 --- a/src/com/owncloud/android/operations/SynchronizeFileOperation.java +++ b/src/com/owncloud/android/operations/SynchronizeFileOperation.java @@ -20,7 +20,7 @@ import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileDownloader; -import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.files.services.FileUploadService; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.resources.files.RemoteFile; import com.owncloud.android.lib.common.operations.RemoteOperationResult; @@ -207,13 +207,13 @@ protected RemoteOperationResult run(OwnCloudClient client) { * @param file OCFile object representing the file to upload */ private void requestForUpload(OCFile file) { - Intent i = new Intent(mContext, FileUploader.class); - i.putExtra(FileUploader.KEY_ACCOUNT, mAccount); - i.putExtra(FileUploader.KEY_FILE, file); + Intent i = new Intent(mContext, FileUploadService.class); + i.putExtra(FileUploadService.KEY_ACCOUNT, mAccount); + i.putExtra(FileUploadService.KEY_FILE, file); /*i.putExtra(FileUploader.KEY_REMOTE_FILE, mRemotePath); // doing this we would lose the value of keepInSync in the road, and maybe it's not updated in the database when the FileUploader service gets it! i.putExtra(FileUploader.KEY_LOCAL_FILE, localFile.getStoragePath());*/ - i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE); - i.putExtra(FileUploader.KEY_FORCE_OVERWRITE, true); + i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UPLOAD_SINGLE_FILE); + i.putExtra(FileUploadService.KEY_FORCE_OVERWRITE, true); mContext.startService(i); mTransferWasRequested = true; } diff --git a/src/com/owncloud/android/operations/UploadFileOperation.java b/src/com/owncloud/android/operations/UploadFileOperation.java index 43fcaa555a1..bf88ba01cb0 100644 --- a/src/com/owncloud/android/operations/UploadFileOperation.java +++ b/src/com/owncloud/android/operations/UploadFileOperation.java @@ -32,7 +32,7 @@ import org.apache.commons.httpclient.methods.RequestEntity; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.files.services.FileUploadService; import com.owncloud.android.lib.common.network.ProgressiveDataTransferer; import com.owncloud.android.lib.common.network.OnDatatransferProgressListener; import com.owncloud.android.lib.common.OwnCloudClient; @@ -51,7 +51,8 @@ /** - * Remote operation performing the upload of a file to an ownCloud server + * Remote operation performing the upload of a file to an ownCloud server. + * No conditions are checked, just upload performed, which might fail. * * @author David A. Velasco */ @@ -64,10 +65,9 @@ public class UploadFileOperation extends RemoteOperation { private OCFile mOldFile; private String mRemotePath = null; private boolean mChunked = false; - private boolean mIsInstant = false; private boolean mRemoteFolderToBeCreated = false; private boolean mForceOverwrite = false; - private int mLocalBehaviour = FileUploader.LOCAL_BEHAVIOUR_COPY; + private int mLocalBehaviour = FileUploadService.LOCAL_BEHAVIOUR_COPY; private boolean mWasRenamed = false; private String mOriginalFileName = null; private String mOriginalStoragePath = null; @@ -84,7 +84,6 @@ public class UploadFileOperation extends RemoteOperation { public UploadFileOperation( Account account, OCFile file, boolean chunked, - boolean isInstant, boolean forceOverwrite, int localBehaviour, Context context) { @@ -103,7 +102,6 @@ public UploadFileOperation( Account account, mFile = file; mRemotePath = file.getRemotePath(); mChunked = chunked; - mIsInstant = isInstant; mForceOverwrite = forceOverwrite; mLocalBehaviour = localBehaviour; mOriginalStoragePath = mFile.getStoragePath(); @@ -143,10 +141,6 @@ public String getMimeType() { return mFile.getMimetype(); } - public boolean isInstant() { - return mIsInstant; - } - public boolean isRemoteFolderToBeCreated() { return mRemoteFolderToBeCreated; } @@ -210,7 +204,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { // check location of local file; if not the expected, copy to a // temporal file before upload (if COPY is the expected behaviour) - if (!mOriginalStoragePath.equals(expectedPath) && mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_COPY) { + if (!mOriginalStoragePath.equals(expectedPath) && mLocalBehaviour == FileUploadService.LOCAL_BEHAVIOUR_COPY) { if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) { result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_FULL); @@ -286,7 +280,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { /// move local temporal file or original file to its corresponding // location in the ownCloud local folder if (result.isSuccess()) { - if (mLocalBehaviour == FileUploader.LOCAL_BEHAVIOUR_FORGET) { + if (mLocalBehaviour == FileUploadService.LOCAL_BEHAVIOUR_FORGET) { mFile.setStoragePath(null); } else { diff --git a/src/com/owncloud/android/ui/activity/ComponentsGetter.java b/src/com/owncloud/android/ui/activity/ComponentsGetter.java index 076a6cba207..6a40a203073 100644 --- a/src/com/owncloud/android/ui/activity/ComponentsGetter.java +++ b/src/com/owncloud/android/ui/activity/ComponentsGetter.java @@ -21,7 +21,7 @@ import com.owncloud.android.datamodel.FileDataStorageManager; 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.files.services.FileUploadService.FileUploaderBinder; public interface ComponentsGetter { diff --git a/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java b/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java index 509e5c79b0f..51b1e211f16 100644 --- a/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java +++ b/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java @@ -20,7 +20,7 @@ import com.actionbarsherlock.app.ActionBar; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.files.services.FileUploadService; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.ui.dialog.ConflictsResolveDialog; import com.owncloud.android.ui.dialog.ConflictsResolveDialog.Decision; @@ -50,25 +50,25 @@ protected void onCreate(Bundle savedInstanceState) { @Override public void conflictDecisionMade(Decision decision) { - Intent i = new Intent(getApplicationContext(), FileUploader.class); + Intent i = new Intent(getApplicationContext(), FileUploadService.class); switch (decision) { case CANCEL: finish(); return; case OVERWRITE: - i.putExtra(FileUploader.KEY_FORCE_OVERWRITE, true); + i.putExtra(FileUploadService.KEY_FORCE_OVERWRITE, true); break; case KEEP_BOTH: - i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE); + i.putExtra(FileUploadService.KEY_LOCAL_BEHAVIOUR, FileUploadService.LOCAL_BEHAVIOUR_MOVE); break; default: Log_OC.wtf(TAG, "Unhandled conflict decision " + decision); return; } - i.putExtra(FileUploader.KEY_ACCOUNT, getAccount()); - i.putExtra(FileUploader.KEY_FILE, getFile()); - i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE); + i.putExtra(FileUploadService.KEY_ACCOUNT, getAccount()); + i.putExtra(FileUploadService.KEY_FILE, getFile()); + i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UPLOAD_SINGLE_FILE); startService(i); finish(); diff --git a/src/com/owncloud/android/ui/activity/FileActivity.java b/src/com/owncloud/android/ui/activity/FileActivity.java index 136bdb55721..caa5ebdb2fe 100644 --- a/src/com/owncloud/android/ui/activity/FileActivity.java +++ b/src/com/owncloud/android/ui/activity/FileActivity.java @@ -45,9 +45,9 @@ import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.FileOperationsHelper; import com.owncloud.android.files.services.FileDownloader; -import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.files.services.FileUploadService; import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; -import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; +import com.owncloud.android.files.services.FileUploadService.FileUploaderBinder; import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperationResult; @@ -155,7 +155,7 @@ protected void onCreate(Bundle savedInstanceState) { } mUploadServiceConnection = newTransferenceServiceConnection(); if (mUploadServiceConnection != null) { - bindService(new Intent(this, FileUploader.class), mUploadServiceConnection, Context.BIND_AUTO_CREATE); + bindService(new Intent(this, FileUploadService.class), mUploadServiceConnection, Context.BIND_AUTO_CREATE); } } diff --git a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java index b5d543a4e20..705d666764a 100644 --- a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -66,8 +66,8 @@ import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileDownloader; 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.files.services.FileUploadService; +import com.owncloud.android.files.services.FileUploadService.FileUploaderBinder; import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; @@ -634,13 +634,13 @@ private void requestMultipleUpload(Intent data, int resultCode) { remotePaths[j] = remotePathBase + (new File(filePaths[j])).getName(); } - Intent i = new Intent(this, FileUploader.class); - i.putExtra(FileUploader.KEY_ACCOUNT, getAccount()); - i.putExtra(FileUploader.KEY_LOCAL_FILE, filePaths); - i.putExtra(FileUploader.KEY_REMOTE_FILE, remotePaths); - i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_MULTIPLE_FILES); + Intent i = new Intent(this, FileUploadService.class); + i.putExtra(FileUploadService.KEY_ACCOUNT, getAccount()); + i.putExtra(FileUploadService.KEY_LOCAL_FILE, filePaths); + i.putExtra(FileUploadService.KEY_REMOTE_FILE, remotePaths); + i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UPLOAD_MULTIPLE_FILES); if (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE) - i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE); + i.putExtra(FileUploadService.KEY_LOCAL_BEHAVIOUR, FileUploadService.LOCAL_BEHAVIOUR_MOVE); startService(i); } else { @@ -678,8 +678,8 @@ private void requestSimpleUpload(Intent data, int resultCode) { } } - Intent i = new Intent(this, FileUploader.class); - i.putExtra(FileUploader.KEY_ACCOUNT, + Intent i = new Intent(this, FileUploadService.class); + i.putExtra(FileUploadService.KEY_ACCOUNT, getAccount()); String remotepath = new String(); for (int j = mDirectories.getCount() - 2; j >= 0; --j) { @@ -689,11 +689,11 @@ private void requestSimpleUpload(Intent data, int resultCode) { remotepath += OCFile.PATH_SEPARATOR; remotepath += new File(filepath).getName(); - i.putExtra(FileUploader.KEY_LOCAL_FILE, filepath); - i.putExtra(FileUploader.KEY_REMOTE_FILE, remotepath); - i.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_SINGLE_FILE); + i.putExtra(FileUploadService.KEY_LOCAL_FILE, filepath); + i.putExtra(FileUploadService.KEY_REMOTE_FILE, remotepath); + i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UPLOAD_SINGLE_FILE); if (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE) - i.putExtra(FileUploader.KEY_LOCAL_BEHAVIOUR, FileUploader.LOCAL_BEHAVIOUR_MOVE); + i.putExtra(FileUploadService.KEY_LOCAL_BEHAVIOUR, FileUploadService.LOCAL_BEHAVIOUR_MOVE); startService(i); } @@ -765,7 +765,7 @@ protected void onResume() { //LocalBroadcastManager.getInstance(this).registerReceiver(mSyncBroadcastReceiver, syncIntentFilter); // Listen for upload messages - IntentFilter uploadIntentFilter = new IntentFilter(FileUploader.getUploadFinishMessage()); + IntentFilter uploadIntentFilter = new IntentFilter(FileUploadService.getUploadFinishMessage()); mUploadFinishReceiver = new UploadFinishReceiver(); registerReceiver(mUploadFinishReceiver, uploadIntentFilter); @@ -1090,7 +1090,7 @@ private class UploadFinishReceiver extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { try { String uploadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH); - String accountName = intent.getStringExtra(FileUploader.ACCOUNT_NAME); + String accountName = intent.getStringExtra(FileUploadService.ACCOUNT_NAME); boolean sameAccount = getAccount() != null && accountName.equals(getAccount().name); OCFile currentDir = getCurrentDir(); boolean isDescendant = (currentDir != null) && (uploadedRemotePath != null) && @@ -1100,9 +1100,9 @@ public void onReceive(Context context, Intent intent) { refreshListOfFilesFragment(); } - boolean uploadWasFine = intent.getBooleanExtra(FileUploader.EXTRA_UPLOAD_RESULT, false); + boolean uploadWasFine = intent.getBooleanExtra(FileUploadService.EXTRA_UPLOAD_RESULT, false); boolean renamedInUpload = getFile().getRemotePath(). - equals(intent.getStringExtra(FileUploader.EXTRA_OLD_REMOTE_PATH)); + equals(intent.getStringExtra(FileUploadService.EXTRA_OLD_REMOTE_PATH)); boolean sameFile = getFile().getRemotePath().equals(uploadedRemotePath) || renamedInUpload; FileFragment details = getSecondFragment(); @@ -1301,7 +1301,7 @@ public void onServiceConnected(ComponentName component, IBinder service) { } } - } else if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) { + } else if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploadService.class))) { Log_OC.d(TAG, "Upload service connected"); mUploaderBinder = (FileUploaderBinder) service; } else { @@ -1325,7 +1325,7 @@ public void onServiceDisconnected(ComponentName component) { if (component.equals(new ComponentName(FileDisplayActivity.this, FileDownloader.class))) { Log_OC.d(TAG, "Download service disconnected"); mDownloaderBinder = null; - } else if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploader.class))) { + } else if (component.equals(new ComponentName(FileDisplayActivity.this, FileUploadService.class))) { Log_OC.d(TAG, "Upload service disconnected"); mUploaderBinder = null; } diff --git a/src/com/owncloud/android/ui/activity/Uploader.java b/src/com/owncloud/android/ui/activity/Uploader.java index 62ad44a42b5..d4647a84f99 100644 --- a/src/com/owncloud/android/ui/activity/Uploader.java +++ b/src/com/owncloud/android/ui/activity/Uploader.java @@ -31,7 +31,7 @@ import com.owncloud.android.authentication.AccountAuthenticator; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.files.services.FileUploader; +import com.owncloud.android.files.services.FileUploadService; import com.owncloud.android.lib.common.utils.Log_OC; import android.accounts.Account; @@ -402,11 +402,11 @@ else if (mimeType.contains("audio")) { throw new SecurityException(); } - Intent intent = new Intent(getApplicationContext(), FileUploader.class); - intent.putExtra(FileUploader.KEY_UPLOAD_TYPE, FileUploader.UPLOAD_MULTIPLE_FILES); - intent.putExtra(FileUploader.KEY_LOCAL_FILE, local.toArray(new String[local.size()])); - intent.putExtra(FileUploader.KEY_REMOTE_FILE, remote.toArray(new String[remote.size()])); - intent.putExtra(FileUploader.KEY_ACCOUNT, mAccount); + Intent intent = new Intent(getApplicationContext(), FileUploadService.class); + intent.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UPLOAD_MULTIPLE_FILES); + intent.putExtra(FileUploadService.KEY_LOCAL_FILE, local.toArray(new String[local.size()])); + intent.putExtra(FileUploadService.KEY_REMOTE_FILE, remote.toArray(new String[remote.size()])); + intent.putExtra(FileUploadService.KEY_ACCOUNT, mAccount); startService(intent); finish(); } diff --git a/src/com/owncloud/android/ui/adapter/FileListListAdapter.java b/src/com/owncloud/android/ui/adapter/FileListListAdapter.java index 9a2a0d3af31..162f54e8d03 100644 --- a/src/com/owncloud/android/ui/adapter/FileListListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/FileListListAdapter.java @@ -45,7 +45,7 @@ import com.owncloud.android.datamodel.ThumbnailsCacheManager; import com.owncloud.android.datamodel.ThumbnailsCacheManager.AsyncDrawable; import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; -import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; +import com.owncloud.android.files.services.FileUploadService.FileUploaderBinder; import com.owncloud.android.ui.activity.ComponentsGetter; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.FileStorageUtils; diff --git a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java index 2ff292501b9..1e144879b1b 100644 --- a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java +++ b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java @@ -39,7 +39,7 @@ import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.FileMenuFilter; import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; -import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; +import com.owncloud.android.files.services.FileUploadService.FileUploaderBinder; import com.owncloud.android.lib.common.network.OnDatatransferProgressListener; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.services.observer.FileObserverService; diff --git a/src/com/owncloud/android/ui/fragment/FileFragment.java b/src/com/owncloud/android/ui/fragment/FileFragment.java index 3e6fa31c2cb..27e01bbe878 100644 --- a/src/com/owncloud/android/ui/fragment/FileFragment.java +++ b/src/com/owncloud/android/ui/fragment/FileFragment.java @@ -24,7 +24,7 @@ import com.actionbarsherlock.app.SherlockFragment; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; -import com.owncloud.android.files.services.FileUploader.FileUploaderBinder; +import com.owncloud.android.files.services.FileUploadService.FileUploaderBinder; import com.owncloud.android.ui.activity.ComponentsGetter; diff --git a/src/com/owncloud/android/ui/preview/PreviewImageActivity.java b/src/com/owncloud/android/ui/preview/PreviewImageActivity.java index c06f341fb86..29f14f610bc 100644 --- a/src/com/owncloud/android/ui/preview/PreviewImageActivity.java +++ b/src/com/owncloud/android/ui/preview/PreviewImageActivity.java @@ -43,8 +43,8 @@ import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileDownloader; 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.files.services.FileUploadService; +import com.owncloud.android.files.services.FileUploadService.FileUploaderBinder; import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperationResult; @@ -267,7 +267,7 @@ public void onServiceConnected(ComponentName component, IBinder service) { onPageSelected(mViewPager.getCurrentItem()); } - } else if (component.equals(new ComponentName(PreviewImageActivity.this, FileUploader.class))) { + } else if (component.equals(new ComponentName(PreviewImageActivity.this, FileUploadService.class))) { Log_OC.d(TAG, "Upload service connected"); mUploaderBinder = (FileUploaderBinder) service; } else { @@ -281,7 +281,7 @@ public void onServiceDisconnected(ComponentName component) { if (component.equals(new ComponentName(PreviewImageActivity.this, FileDownloader.class))) { Log_OC.d(TAG, "Download service suddenly disconnected"); mDownloaderBinder = null; - } else if (component.equals(new ComponentName(PreviewImageActivity.this, FileUploader.class))) { + } else if (component.equals(new ComponentName(PreviewImageActivity.this, FileUploadService.class))) { Log_OC.d(TAG, "Upload service suddenly disconnected"); mUploaderBinder = null; } From efe184939e9cec33a3f38962b3898907416ceb14 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Tue, 4 Nov 2014 16:49:10 +0100 Subject: [PATCH 04/76] using enums cleaning up FileUploadService.onStartCommand preparing persistant uploads --- .../android/authentication/AccountUtils.java | 16 + .../android/db/PersistentUploadObject.java | 190 +++++++ .../owncloud/android/db/UploadDbHandler.java | 24 +- .../files/services/FileUploadService.java | 535 +++++++++--------- .../operations/UploadFileOperation.java | 9 +- .../ui/activity/ConflictsResolveActivity.java | 2 +- .../ui/activity/FileDisplayActivity.java | 7 +- 7 files changed, 504 insertions(+), 279 deletions(-) create mode 100755 src/com/owncloud/android/db/PersistentUploadObject.java diff --git a/src/com/owncloud/android/authentication/AccountUtils.java b/src/com/owncloud/android/authentication/AccountUtils.java index dc023444fd4..34cf9acaaa5 100644 --- a/src/com/owncloud/android/authentication/AccountUtils.java +++ b/src/com/owncloud/android/authentication/AccountUtils.java @@ -102,6 +102,22 @@ public static boolean exists(Account account, Context context) { return false; } + /** + * Returns owncloud account identified by accountName or null if it does not exist. + * @param context + * @param accountName name of account to be returned + * @return owncloud account named accountName + */ + public static Account getOwnCloudAccountByName(Context context, String accountName) { + Account[] ocAccounts = AccountManager.get(context).getAccountsByType( + MainApp.getAccountType()); + for (Account account : ocAccounts) { + if(account.name.equals(accountName)) + return account; + } + return null; + } + /** * Checks, whether or not there are any ownCloud accounts setup. diff --git a/src/com/owncloud/android/db/PersistentUploadObject.java b/src/com/owncloud/android/db/PersistentUploadObject.java new file mode 100755 index 00000000000..765fd3b038a --- /dev/null +++ b/src/com/owncloud/android/db/PersistentUploadObject.java @@ -0,0 +1,190 @@ +package com.owncloud.android.db; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import com.owncloud.android.files.services.FileUploadService.LocalBehaviour; + +/** + * Stores all information in order to start upload. PersistentUploadObject can + * be stored persistently by {@link UploadDbHandler}. + * + * @author LukeOwncloud + * + */ +public class PersistentUploadObject { + /** + * Local path to file which is to be uploaded. + */ + String localPath; + /** + * Remote path where file is to be uploaded to. + */ + String remotePath; + + /** + * Mime type of upload file. + */ + String mimeType; + /** + * Local action for upload. + */ + LocalBehaviour localAction; + /** + * Overwrite destination file? + */ + boolean forceOverwrite; + /** + * Create destination folder? + */ + boolean isCreateRemoteFolder; + /** + * Upload only via wifi? + */ + boolean isUseWifiOnly; + /** + * Name of Owncloud account to upload file to. + */ + String accountName; + + /** + * @return the localPath + */ + public String getLocalPath() { + return localPath; + } + + /** + * @param localPath the localPath to set + */ + public void setLocalPath(String localPath) { + this.localPath = localPath; + } + + /** + * @return the remotePath + */ + public String getRemotePath() { + return remotePath; + } + + /** + * @param remotePath the remotePath to set + */ + public void setRemotePath(String remotePath) { + this.remotePath = remotePath; + } + + /** + * @return the mimeType + */ + public String getMimeType() { + return mimeType; + } + + /** + * @param mimeType the mimeType to set + */ + public void setMimeType(String mimeType) { + this.mimeType = mimeType; + } + + /** + * @return the localAction + */ + public LocalBehaviour getLocalAction() { + return localAction; + } + + /** + * @param localAction the localAction to set + */ + public void setLocalAction(LocalBehaviour localAction) { + this.localAction = localAction; + } + + /** + * @return the forceOverwrite + */ + public boolean isForceOverwrite() { + return forceOverwrite; + } + + /** + * @param forceOverwrite the forceOverwrite to set + */ + public void setForceOverwrite(boolean forceOverwrite) { + this.forceOverwrite = forceOverwrite; + } + + /** + * @return the isCreateRemoteFolder + */ + public boolean isCreateRemoteFolder() { + return isCreateRemoteFolder; + } + + /** + * @param isCreateRemoteFolder the isCreateRemoteFolder to set + */ + public void setCreateRemoteFolder(boolean isCreateRemoteFolder) { + this.isCreateRemoteFolder = isCreateRemoteFolder; + } + + /** + * @return the isUseWifiOnly + */ + public boolean isUseWifiOnly() { + return isUseWifiOnly; + } + + /** + * @param isUseWifiOnly the isUseWifiOnly to set + */ + public void setUseWifiOnly(boolean isUseWifiOnly) { + this.isUseWifiOnly = isUseWifiOnly; + } + + /** + * @return the accountName + */ + public String getAccountName() { + return accountName; + } + + /** + * @param accountName the accountName to set + */ + public void setAccountName(String accountName) { + this.accountName = accountName; + } + + @Override + public String toString() { + try { + ByteArrayOutputStream bo = new ByteArrayOutputStream(); + ObjectOutputStream so = new ObjectOutputStream(bo); + so.writeObject(this); + so.flush(); + return bo.toString(); + } catch (Exception e) { + System.out.println(e); + } + return null; + } + + public PersistentUploadObject fromString(String serializedObject) { + try { + byte b[] = serializedObject.getBytes(); + ByteArrayInputStream bi = new ByteArrayInputStream(b); + ObjectInputStream si = new ObjectInputStream(bi); + return (PersistentUploadObject) si.readObject(); + } catch (Exception e) { + System.out.println(e); + } + return null; + } + +} diff --git a/src/com/owncloud/android/db/UploadDbHandler.java b/src/com/owncloud/android/db/UploadDbHandler.java index c318be3daa8..75e2c3b908b 100644 --- a/src/com/owncloud/android/db/UploadDbHandler.java +++ b/src/com/owncloud/android/db/UploadDbHandler.java @@ -37,7 +37,7 @@ public class UploadDbHandler { private SQLiteDatabase mDB; private OpenerHelper mHelper; private final String mDatabaseName; - private final int mDatabaseVersion = 3; + private final int mDatabaseVersion = 4; static private final String TABLE_UPLOAD = "list_of_uploads"; @@ -135,7 +135,7 @@ public OpenerHelper(Context context) { @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE " + TABLE_UPLOAD + " (" + " _id INTEGER PRIMARY KEY, " + " path TEXT," - + " account TEXT,attempt INTEGER,message TEXT);"); + + " account TEXT,attempt INTEGER,message TEXT,uploadObject TEXT);"); } @Override @@ -143,8 +143,26 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion < 2) { db.execSQL("ALTER TABLE " + TABLE_UPLOAD + " ADD COLUMN attempt INTEGER;"); } - db.execSQL("ALTER TABLE " + TABLE_UPLOAD + " ADD COLUMN message TEXT;"); + if (oldVersion < 3) { + db.execSQL("ALTER TABLE " + TABLE_UPLOAD + " ADD COLUMN message TEXT;"); + } + if (oldVersion < 4) { + db.execSQL("ALTER TABLE " + TABLE_UPLOAD + " ADD COLUMN uploadObject TEXT;"); + } } } + + public boolean storeUpload(PersistentUploadObject uploadObject, String message) { + ContentValues cv = new ContentValues(); + cv.put("path", uploadObject.getLocalPath()); + cv.put("account", uploadObject.getAccountName()); + cv.put("attempt", UploadStatus.UPLOAD_STATUS_UPLOAD_LATER.getValue()); + cv.put("message", message); + cv.put("uploadObject", uploadObject.toString()); + + long result = mDB.insert(TABLE_UPLOAD, null, cv); + Log_OC.d(TABLE_UPLOAD, "putFileForLater returns with: " + result + " for file: " + uploadObject.getLocalPath()); + return result != -1; + } } diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index 77eeefcb46e..38fffb06f93 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.IOException; import java.util.AbstractList; +import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -54,6 +55,7 @@ import com.owncloud.android.authentication.AuthenticatorActivity; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.db.PersistentUploadObject; import com.owncloud.android.db.UploadDbHandler; import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.OwnCloudClient; @@ -77,13 +79,13 @@ import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.utils.ErrorMessageAdapter; - /** - * Service for uploading files. Invoke using context.startService(...). - * This service retries until upload succeeded. Files to be uploaded are stored persistent using {@link UploadDbHandler}. + * Service for uploading files. Invoke using context.startService(...). This + * service retries until upload succeeded. Files to be uploaded are stored + * persistent using {@link UploadDbHandler}. * * @author LukeOwncloud - * + * */ public class FileUploadService extends Service { @@ -107,10 +109,32 @@ public class FileUploadService extends Service { public static final String KEY_WIFI_ONLY = "WIFI_ONLY"; public static final String KEY_LOCAL_BEHAVIOUR = "BEHAVIOUR"; - public static final int LOCAL_BEHAVIOUR_COPY = 0; - public static final int LOCAL_BEHAVIOUR_MOVE = 1; - public static final int LOCAL_BEHAVIOUR_FORGET = 2; + /** + * Describes local behavior for upload. + */ + public enum LocalBehaviour { + LOCAL_BEHAVIOUR_COPY(0), LOCAL_BEHAVIOUR_MOVE(1), LOCAL_BEHAVIOUR_FORGET(2); + private final int value; + private LocalBehaviour(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + }; + + // public enum UploadSingleMulti { + // UPLOAD_SINGLE_FILE(0), UPLOAD_MULTIPLE_FILES(1); + // private final int value; + // private UploadSingleMulti(int value) { + // this.value = value; + // } + // public int getValue() { + // return value; + // } + // }; public static final int UPLOAD_SINGLE_FILE = 0; public static final int UPLOAD_MULTIPLE_FILES = 1; @@ -130,16 +154,15 @@ public class FileUploadService extends Service { private NotificationManager mNotificationManager; private NotificationCompat.Builder mNotificationBuilder; - public static String getUploadFinishMessage() { return FileUploadService.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 + * @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(); @@ -175,28 +198,25 @@ public void onCreate() { mServiceHandler = new ServiceHandler(mServiceLooper, this); mBinder = new FileUploaderBinder(); mConnectivityChangeReceiver = new ConnectivityChangeReceiver(); - registerReceiver( - mConnectivityChangeReceiver, - new IntentFilter( - ConnectivityManager.CONNECTIVITY_ACTION)); + registerReceiver(mConnectivityChangeReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); } - public class ConnectivityChangeReceiver - extends BroadcastReceiver { + public class ConnectivityChangeReceiver extends BroadcastReceiver { @Override public void onReceive(Context arg0, Intent arg1) { - //upload pending wifi only files. + // upload pending wifi only files. onStartCommand(null, 0, 0); } - + } + @Override public void onDestroy() { unregisterReceiver(mConnectivityChangeReceiver); super.onDestroy(); } - + /** * Entry point to add one or several files to the queue of uploads. * @@ -206,147 +226,145 @@ public void onDestroy() { */ @Override public int onStartCommand(Intent intent, int flags, int startId) { - if(intent == null) { - //service was restarted by OS (after return START_STICKY and kill service) - //or connectivity change was detected. ==> check persistent upload list. - } - - //TODO: -// if (intent.hasExtra(KEY_FILE)) { -// files = (OCFile[]) intent.getParcelableArrayExtra(KEY_FILE); -// -// } else { -// localPaths = intent.getStringArrayExtra(KEY_LOCAL_FILE); -// remotePaths = intent.getStringArrayExtra(KEY_REMOTE_FILE); -// mimeTypes = intent.getStringArrayExtra(KEY_MIME_TYPE); -// CREATE files from there values! -// } - //now work with files. - - 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); - if (!AccountUtils.exists(account, getApplicationContext())) { - return Service.START_NOT_STICKY; - } - - 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) }; + AbstractList requestedUploads = new Vector(); + if (intent == null) { + // service was restarted by OS (after return START_STICKY and kill + // service) or connectivity change was detected. ==> check persistent upload + // list. + // + //TODO fill requestedUploads from DB + } else { + + int uploadType = intent.getIntExtra(KEY_UPLOAD_TYPE, -1); + if (uploadType == -1) { + Log_OC.e(TAG, "Incorrect or no upload type provided"); + return Service.START_NOT_STICKY; } - } 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); + Account account = intent.getParcelableExtra(KEY_ACCOUNT); + if (!AccountUtils.exists(account, getApplicationContext())) { + Log_OC.e(TAG, "KEY_ACCOUNT no set or provided KEY_ACCOUNT does not exist"); + return Service.START_NOT_STICKY; } - } - FileDataStorageManager storageManager = new FileDataStorageManager(account, getContentResolver()); + OCFile[] files = null; + // if KEY_FILE given, use it + if (intent.hasExtra(KEY_FILE)) { + if (uploadType == UPLOAD_SINGLE_FILE) { + files = new OCFile[] { intent.getParcelableExtra(KEY_FILE) }; + } else { + // TODO will this casting work fine? + files = (OCFile[]) intent.getParcelableArrayExtra(KEY_FILE); + } - boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false); - boolean isCreateRemoteFolder = intent.getBooleanExtra(KEY_CREATE_REMOTE_FOLDER, false); - boolean isUseWifiOnly = intent.getBooleanExtra(KEY_WIFI_ONLY, true); - 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 { // else use KEY_LOCAL_FILE, KEY_REMOTE_FILE, and + // KEY_MIME_TYPE - } 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 + if (!intent.hasExtra(KEY_LOCAL_FILE) || !intent.hasExtra(KEY_REMOTE_FILE) + || !(intent.hasExtra(KEY_MIME_TYPE))) { + Log_OC.e(TAG, "Not enough information provided in intent"); return Service.START_NOT_STICKY; } - } - } - - // save always persistently path of upload, so it can be retried if failed. - UploadDbHandler db = new UploadDbHandler(this.getBaseContext()); - for (int i = 0; i < localPaths.length; i++) { - db.storeFile(localPaths[i], account.name, null); - } - db.close(); - - AccountManager aMgr = AccountManager.get(this); - String version = aMgr.getUserData(account, Constants.KEY_OC_VERSION); - OwnCloudVersion ocv = new OwnCloudVersion(version); - - boolean chunked = FileUploadService.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, forceOverwrite, localAction, - getApplicationContext()); - if (isCreateRemoteFolder) { - newUpload.setRemoteFolderToBeCreated(); + + String[] localPaths; + String[] remotePaths; + String[] mimeTypes; + if (uploadType == UPLOAD_SINGLE_FILE) { + 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 { + localPaths = intent.getStringArrayExtra(KEY_LOCAL_FILE); + remotePaths = intent.getStringArrayExtra(KEY_REMOTE_FILE); + mimeTypes = intent.getStringArrayExtra(KEY_MIME_TYPE); + } + if (localPaths.length != remotePaths.length) { + Log_OC.e(TAG, "Different number of remote paths and local paths!"); + return Service.START_NOT_STICKY; } - mPendingUploads.putIfAbsent(uploadKey, newUpload); // Grants that the file only upload once time - newUpload.addDatatransferProgressListener((FileUploaderBinder)mBinder); - requestedUploads.add(uploadKey); - } + files = new OCFile[localPaths.length]; - } catch (IllegalArgumentException e) { - Log_OC.e(TAG, "Not enough information provided in intent: " + e.getMessage()); - return START_NOT_STICKY; + for (int i = 0; i < localPaths.length; i++) { + files[i] = obtainNewOCFileToUpload(remotePaths[i], localPaths[i], + ((mimeTypes != null) ? mimeTypes[i] : (String) null)); + if (files[i] == null) { + Log_OC.e(TAG, "obtainNewOCFileToUpload() returned null for remotePaths[i]:" + remotePaths[i] + + " and localPaths[i]:" + localPaths[i]); + return Service.START_NOT_STICKY; + } + } + } - } catch (IllegalStateException e) { - Log_OC.e(TAG, "Bad information provided in intent: " + e.getMessage()); - return START_NOT_STICKY; + // at this point variable "OCFile[] files" is loaded correctly. - } catch (Exception e) { - Log_OC.e(TAG, "Unexpected exception while processing upload intent", e); - return START_NOT_STICKY; + boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false); + boolean isCreateRemoteFolder = intent.getBooleanExtra(KEY_CREATE_REMOTE_FOLDER, false); + boolean isUseWifiOnly = intent.getBooleanExtra(KEY_WIFI_ONLY, true); + LocalBehaviour localAction = (LocalBehaviour) intent.getSerializableExtra(KEY_LOCAL_BEHAVIOUR); + if (localAction == null) + localAction = LocalBehaviour.LOCAL_BEHAVIOUR_COPY; + // save always persistently path of upload, so it can be retried if + // failed. + UploadDbHandler db = new UploadDbHandler(this.getBaseContext()); + for (int i = 0; i < files.length; i++) { + PersistentUploadObject uploadObject = new PersistentUploadObject(); + uploadObject.setRemotePath(files[i].getRemotePath()); + uploadObject.setLocalPath(files[i].getStoragePath()); + uploadObject.setMimeType(files[0].getMimetype()); + uploadObject.setAccountName(account.name); + uploadObject.setForceOverwrite(forceOverwrite); + uploadObject.setLocalAction(localAction); + uploadObject.setUseWifiOnly(isUseWifiOnly); + db.storeUpload(uploadObject, "upload at " + new Date()); + requestedUploads.add(uploadObject); + } + db.close(); + + // AccountManager aMgr = AccountManager.get(this); + // String version = aMgr.getUserData(account, + // Constants.KEY_OC_VERSION); + // OwnCloudVersion ocv = new OwnCloudVersion(version); + // + // boolean chunked = + // FileUploadService.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, + // forceOverwrite, localAction, + // getApplicationContext()); + // if (isCreateRemoteFolder) { + // newUpload.setRemoteFolderToBeCreated(); + // } + // mPendingUploads.putIfAbsent(uploadKey, newUpload); // Grants that + // the file only upload once time + // + // 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; @@ -368,16 +386,15 @@ public int onStartCommand(Intent intent, int flags, int startId) { public IBinder onBind(Intent arg0) { return mBinder; } - + /** * Called when ALL the bound clients were onbound. */ @Override public boolean onUnbind(Intent intent) { - ((FileUploaderBinder)mBinder).clearListeners(); - return false; // not accepting rebinding (default behaviour) + ((FileUploaderBinder) mBinder).clearListeners(); + return false; // not accepting rebinding (default behaviour) } - /** * Binder to let client components to perform operations on the queue of @@ -386,12 +403,13 @@ public boolean onUnbind(Intent intent) { * 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(); - + /** * Cancels a pending or current upload of a remote file. * @@ -407,21 +425,17 @@ public void cancel(Account account, OCFile file) { upload.cancel(); } } - - - + 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 @@ -445,38 +459,40 @@ public boolean isUploading(Account account, OCFile file) { } } - /** - * Adds a listener interested in the progress of the upload for a concrete file. + * 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 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. */ - public void addDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) { - if (account == null || file == null || listener == null) return; + 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. + * 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 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. */ - public void removeDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) { - if (account == null || file == null || listener == null) return; + 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) { mBoundListeners.remove(targetKey); } } - @Override public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String fileName) { @@ -486,7 +502,7 @@ public void onTransferProgress(long progressRate, long totalTransferredSoFar, lo boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, fileName); } } - + } /** @@ -539,24 +555,25 @@ private void uploadFile(String uploadKey) { notifyUploadStart(mCurrentUpload); RemoteOperationResult uploadResult = null, grantResult = null; - + try { - /// prepare client object to send requests to the ownCloud server + // / 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()); + mStorageManager = new FileDataStorageManager(mLastAccount, getContentResolver()); OwnCloudAccount ocAccount = new OwnCloudAccount(mLastAccount, this); - mUploadClient = OwnCloudClientManagerFactory.getDefaultSingleton(). - getClientFor(ocAccount, this); + mUploadClient = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, this); } - - /// check the existence of the parent folder for the file to upload + + // / 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 + + // / perform the upload if (grantResult.isSuccess()) { OCFile parent = mStorageManager.getFileByPath(remoteParentPath); mCurrentUpload.getFile().setParentId(parent.getFileId()); @@ -567,29 +584,31 @@ private void uploadFile(String uploadKey) { } 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 + // 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 - + + // / notify result + notifyUploadResult(uploadResult, mCurrentUpload); sendFinalBroadcast(mCurrentUpload, uploadResult); @@ -598,20 +617,22 @@ private void uploadFile(String uploadKey) { } /** - * 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. - * - * @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. + * 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()) { - SyncOperation syncOp = new CreateFolderOperation( pathToGrant, true); + if (!result.isSuccess() && result.getCode() == ResultCode.FILE_NOT_FOUND + && mCurrentUpload.isRemoteFolderToBeCreated()) { + SyncOperation syncOp = new CreateFolderOperation(pathToGrant, true); result = syncOp.execute(mUploadClient, mStorageManager); } if (result.isSuccess()) { @@ -628,7 +649,6 @@ private RemoteOperationResult grantFolderExistence(String pathToGrant) { return result; } - private OCFile createLocalFolder(String remotePath) { String parentPath = new File(remotePath).getParent(); parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR; @@ -645,7 +665,6 @@ private OCFile createLocalFolder(String remotePath) { } return null; } - /** * Saves a OC File after a successful upload. @@ -664,7 +683,7 @@ private void saveUploadedFile() { long syncDate = System.currentTimeMillis(); file.setLastSyncDateForData(syncDate); - // new PROPFIND to keep data consistent with server + // 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); @@ -672,7 +691,7 @@ private void saveUploadedFile() { 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()) { @@ -695,12 +714,11 @@ private void updateOCFile(OCFile file, RemoteFile remoteFile) { file.setMimetype(remoteFile.getMimeType()); file.setModificationTimestamp(remoteFile.getModifiedTimestamp()); file.setModificationTimestampAtLastSyncForData(remoteFile.getModifiedTimestamp()); - // file.setEtag(remoteFile.getEtag()); // TODO Etag, where available + // file.setEtag(remoteFile.getEtag()); // TODO Etag, where available file.setRemoteId(remoteFile.getRemoteId()); } - private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType, - FileDataStorageManager storageManager) { + private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType) { OCFile newFile = new OCFile(remotePath); newFile.setStoragePath(localPath); newFile.setLastSyncDateForProperties(0); @@ -738,8 +756,7 @@ private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, Stri */ private void notifyUploadStart(UploadFileOperation upload) { // / create status notification with a progress bar - mNotificationBuilder = - NotificationBuilderWithProgressBar.newNotificationBuilderWithProgressBar(this); + mNotificationBuilder = NotificationBuilderWithProgressBar.newNotificationBuilderWithProgressBar(this); mNotificationBuilder .setOngoing(true) .setSmallIcon(R.drawable.notification_icon) @@ -749,14 +766,14 @@ private void notifyUploadStart(UploadFileOperation upload) { .setContentText( String.format(getString(R.string.uploader_upload_in_progress_content), 0, upload.getFileName())); - /// includes a pending intent in the notification showing the details view of the file + // / 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); - mNotificationBuilder.setContentIntent(PendingIntent.getActivity( - this, (int) System.currentTimeMillis(), showDetailsIntent, 0 - )); + mNotificationBuilder.setContentIntent(PendingIntent.getActivity(this, (int) System.currentTimeMillis(), + showDetailsIntent, 0)); mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotificationBuilder.build()); } @@ -767,60 +784,44 @@ private void notifyUploadStart(UploadFileOperation upload) { * @param uploadResult Result of the upload operation. * @param upload Finished upload operation */ - private void notifyUploadResult( - RemoteOperationResult uploadResult, UploadFileOperation upload) { + private void notifyUploadResult(RemoteOperationResult uploadResult, UploadFileOperation upload) { Log_OC.d(TAG, "NotifyUploadResult with resultCode: " + uploadResult.getCode()); - // / cancelled operation or success -> silent removal of progress notification + // / 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; - + int tickerId = (uploadResult.isSuccess()) ? R.string.uploader_upload_succeeded_ticker + : R.string.uploader_upload_failed_ticker; + String content = null; // check credentials error - boolean needsToUpdateCredentials = ( - uploadResult.getCode() == ResultCode.UNAUTHORIZED || - uploadResult.isIdPRedirection() - ); - 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); - - content = ErrorMessageAdapter.getErrorCauseMessage( - uploadResult, upload, getResources() - ); - + boolean needsToUpdateCredentials = (uploadResult.getCode() == ResultCode.UNAUTHORIZED || uploadResult + .isIdPRedirection()); + 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); + + content = ErrorMessageAdapter.getErrorCauseMessage(uploadResult, upload, getResources()); + 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_ACTION, - AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN - ); + updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, upload.getAccount()); + updateAccountCredentials.putExtra(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 - )); - - mUploadClient = null; - // grant that future retries on the same account will get the fresh credentials + mNotificationBuilder.setContentIntent(PendingIntent.getActivity(this, (int) System.currentTimeMillis(), + updateAccountCredentials, PendingIntent.FLAG_ONE_SHOT)); + + mUploadClient = null; + // grant that future retries on the same account will get the + // fresh credentials } else { mNotificationBuilder.setContentText(content); @@ -847,22 +848,20 @@ uploadResult, upload, getResources() } } - + mNotificationBuilder.setContentText(content); mNotificationManager.notify(tickerId, mNotificationBuilder.build()); - + if (uploadResult.isSuccess()) { - + UploadDbHandler db = new UploadDbHandler(this.getBaseContext()); db.removeFile(mCurrentUpload.getOriginalStoragePath()); db.close(); // remove success notification, with a delay of 2 seconds - NotificationDelayer.cancelWithDelay( - mNotificationManager, - R.string.uploader_upload_succeeded_ticker, + NotificationDelayer.cancelWithDelay(mNotificationManager, R.string.uploader_upload_succeeded_ticker, 2000); - + } } } diff --git a/src/com/owncloud/android/operations/UploadFileOperation.java b/src/com/owncloud/android/operations/UploadFileOperation.java index bf88ba01cb0..6b4f2e30e36 100644 --- a/src/com/owncloud/android/operations/UploadFileOperation.java +++ b/src/com/owncloud/android/operations/UploadFileOperation.java @@ -33,6 +33,7 @@ import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileUploadService; +import com.owncloud.android.files.services.FileUploadService.LocalBehaviour; import com.owncloud.android.lib.common.network.ProgressiveDataTransferer; import com.owncloud.android.lib.common.network.OnDatatransferProgressListener; import com.owncloud.android.lib.common.OwnCloudClient; @@ -67,7 +68,7 @@ public class UploadFileOperation extends RemoteOperation { private boolean mChunked = false; private boolean mRemoteFolderToBeCreated = false; private boolean mForceOverwrite = false; - private int mLocalBehaviour = FileUploadService.LOCAL_BEHAVIOUR_COPY; + private LocalBehaviour mLocalBehaviour = FileUploadService.LocalBehaviour.LOCAL_BEHAVIOUR_COPY; private boolean mWasRenamed = false; private String mOriginalFileName = null; private String mOriginalStoragePath = null; @@ -85,7 +86,7 @@ public UploadFileOperation( Account account, OCFile file, boolean chunked, boolean forceOverwrite, - int localBehaviour, + LocalBehaviour localBehaviour, Context context) { if (account == null) throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation creation"); @@ -204,7 +205,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { // check location of local file; if not the expected, copy to a // temporal file before upload (if COPY is the expected behaviour) - if (!mOriginalStoragePath.equals(expectedPath) && mLocalBehaviour == FileUploadService.LOCAL_BEHAVIOUR_COPY) { + if (!mOriginalStoragePath.equals(expectedPath) && mLocalBehaviour == FileUploadService.LocalBehaviour.LOCAL_BEHAVIOUR_COPY) { if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) { result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_FULL); @@ -280,7 +281,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { /// move local temporal file or original file to its corresponding // location in the ownCloud local folder if (result.isSuccess()) { - if (mLocalBehaviour == FileUploadService.LOCAL_BEHAVIOUR_FORGET) { + if (mLocalBehaviour == FileUploadService.LocalBehaviour.LOCAL_BEHAVIOUR_FORGET) { mFile.setStoragePath(null); } else { diff --git a/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java b/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java index 51b1e211f16..2a777d7076e 100644 --- a/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java +++ b/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java @@ -60,7 +60,7 @@ public void conflictDecisionMade(Decision decision) { i.putExtra(FileUploadService.KEY_FORCE_OVERWRITE, true); break; case KEEP_BOTH: - i.putExtra(FileUploadService.KEY_LOCAL_BEHAVIOUR, FileUploadService.LOCAL_BEHAVIOUR_MOVE); + i.putExtra(FileUploadService.KEY_LOCAL_BEHAVIOUR, FileUploadService.LocalBehaviour.LOCAL_BEHAVIOUR_MOVE); break; default: Log_OC.wtf(TAG, "Unhandled conflict decision " + decision); diff --git a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java index 705d666764a..963aef179c8 100644 --- a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -640,7 +640,7 @@ private void requestMultipleUpload(Intent data, int resultCode) { i.putExtra(FileUploadService.KEY_REMOTE_FILE, remotePaths); i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UPLOAD_MULTIPLE_FILES); if (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE) - i.putExtra(FileUploadService.KEY_LOCAL_BEHAVIOUR, FileUploadService.LOCAL_BEHAVIOUR_MOVE); + i.putExtra(FileUploadService.KEY_LOCAL_BEHAVIOUR, FileUploadService.LocalBehaviour.LOCAL_BEHAVIOUR_MOVE); startService(i); } else { @@ -692,8 +692,9 @@ private void requestSimpleUpload(Intent data, int resultCode) { i.putExtra(FileUploadService.KEY_LOCAL_FILE, filepath); i.putExtra(FileUploadService.KEY_REMOTE_FILE, remotepath); i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UPLOAD_SINGLE_FILE); - if (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE) - i.putExtra(FileUploadService.KEY_LOCAL_BEHAVIOUR, FileUploadService.LOCAL_BEHAVIOUR_MOVE); + if (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE) { + i.putExtra(FileUploadService.KEY_LOCAL_BEHAVIOUR, FileUploadService.LocalBehaviour.LOCAL_BEHAVIOUR_MOVE); + } startService(i); } From 1369ef8f28cf61b01edd6f3a7d0ced6288a92d53 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Wed, 5 Nov 2014 15:49:03 +0100 Subject: [PATCH 05/76] made UploadDbObject serializable --- AndroidManifest.xml | 2 +- .../owncloud/android/db/UploadDbHandler.java | 107 ++++++++++----- ...tUploadObject.java => UploadDbObject.java} | 111 ++++++++++++++-- .../files/InstantUploadBroadcastReceiver.java | 4 +- .../files/services/FileUploadService.java | 124 +++++++++++++----- .../operations/SynchronizeFileOperation.java | 2 +- .../ui/activity/ConflictsResolveActivity.java | 3 +- .../ui/activity/FileDisplayActivity.java | 9 +- .../android/ui/activity/Uploader.java | 2 +- 9 files changed, 281 insertions(+), 83 deletions(-) rename src/com/owncloud/android/db/{PersistentUploadObject.java => UploadDbObject.java} (53%) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a64963fca7a..7454d48ab36 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -152,7 +152,7 @@ - + diff --git a/src/com/owncloud/android/db/UploadDbHandler.java b/src/com/owncloud/android/db/UploadDbHandler.java index 75e2c3b908b..1069653218f 100644 --- a/src/com/owncloud/android/db/UploadDbHandler.java +++ b/src/com/owncloud/android/db/UploadDbHandler.java @@ -17,8 +17,12 @@ */ package com.owncloud.android.db; +import java.util.ArrayList; +import java.util.List; + import com.owncloud.android.MainApp; import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.operations.UploadFileOperation; import android.content.ContentValues; import android.content.Context; @@ -39,10 +43,22 @@ public class UploadDbHandler { private final String mDatabaseName; private final int mDatabaseVersion = 4; + static private final String TAG = "UploadDbHandler"; static private final String TABLE_UPLOAD = "list_of_uploads"; + public void recreateDb() { + mDB.beginTransaction(); + try { + mHelper.onUpgrade(mDB, 0, mDatabaseVersion); + mDB.setTransactionSuccessful(); + } finally { + mDB.endTransaction(); + } + + } + public enum UploadStatus { - UPLOAD_STATUS_UPLOAD_LATER(0), UPLOAD_STATUS_UPLOAD_FAILED(1); + UPLOAD_LATER(0), UPLOAD_FAILED(1), UPLOAD_IN_PROGRESS(2), UPLOAD_PAUSED(3), UPLOAD_SUCCEEDED(4); private final int value; private UploadStatus(int value) { this.value = value; @@ -70,14 +86,17 @@ public void close() { * @return false if an error occurred, else true. */ public boolean storeFile(String filepath, String account, String message) { - ContentValues cv = new ContentValues(); - cv.put("path", filepath); - cv.put("account", account); - cv.put("attempt", UploadStatus.UPLOAD_STATUS_UPLOAD_LATER.getValue()); - cv.put("message", message); - long result = mDB.insert(TABLE_UPLOAD, null, cv); - Log_OC.d(TABLE_UPLOAD, "putFileForLater returns with: " + result + " for file: " + filepath); - return result != -1; + ///OBSOLETE + Log_OC.i(TAG, "obsolete method called"); + return false; +// ContentValues cv = new ContentValues(); +// cv.put("path", filepath); +// cv.put("account", account); +// cv.put("attempt", UploadStatus.UPLOAD_STATUS_UPLOAD_LATER.getValue()); +// cv.put("message", message); +// long result = mDB.insert(TABLE_UPLOAD, null, cv); +// Log_OC.d(TABLE_UPLOAD, "putFileForLater returns with: " + result + " for file: " + filepath); +// return result != -1; } /** @@ -89,12 +108,15 @@ public boolean storeFile(String filepath, String account, String message) { * @return 1 if file status was updated, else 0. */ public int updateFileState(String filepath, UploadStatus status, String message) { - ContentValues cv = new ContentValues(); - cv.put("attempt", status.getValue()); - cv.put("message", message); - int result = mDB.update(TABLE_UPLOAD, cv, "path=?", new String[] { filepath }); - Log_OC.d(TABLE_UPLOAD, "updateFileState returns with: " + result + " for file: " + filepath); - return result; + ///OBSOLETE + Log_OC.i(TAG, "obsolete method called"); + return 0; +// ContentValues cv = new ContentValues(); +// cv.put("attempt", status.getValue()); +// cv.put("message", message); +// int result = mDB.update(TABLE_UPLOAD, cv, "path=?", new String[] { filepath }); +// Log_OC.d(TABLE_UPLOAD, "updateFileState returns with: " + result + " for file: " + filepath); +// return result; } /** @@ -102,7 +124,10 @@ public int updateFileState(String filepath, UploadStatus status, String message) * @return */ public Cursor getAwaitingFiles() { - return mDB.query(TABLE_UPLOAD, null, "attempt=" + UploadStatus.UPLOAD_STATUS_UPLOAD_LATER, null, null, null, null); + //OBSOLETE + Log_OC.i(TAG, "obsolete method called"); + return null; +// return mDB.query(TABLE_UPLOAD, null, "attempt=" + UploadStatus.UPLOAD_STATUS_UPLOAD_LATER, null, null, null, null); } //ununsed until now. uncomment if needed. @@ -121,9 +146,12 @@ public Cursor getAwaitingFiles() { * @return true when one or more pending files was removed */ public boolean removeFile(String localPath) { - long result = mDB.delete(TABLE_UPLOAD, "path = ?", new String[] { localPath }); - Log_OC.d(TABLE_UPLOAD, "delete returns with: " + result + " for file: " + localPath); - return result != 0; + //OBSOLETE + Log_OC.i(TAG, "obsolete method called"); + return false; +// long result = mDB.delete(TABLE_UPLOAD, "path = ?", new String[] { localPath }); +// Log_OC.d(TABLE_UPLOAD, "delete returns with: " + result + " for file: " + localPath); +// return result != 0; } @@ -134,35 +162,46 @@ public OpenerHelper(Context context) { @Override public void onCreate(SQLiteDatabase db) { - db.execSQL("CREATE TABLE " + TABLE_UPLOAD + " (" + " _id INTEGER PRIMARY KEY, " + " path TEXT," - + " account TEXT,attempt INTEGER,message TEXT,uploadObject TEXT);"); + db.execSQL("CREATE TABLE " + TABLE_UPLOAD + " (" + " path TEXT PRIMARY KEY," + + " uploadObject TEXT);"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - if (oldVersion < 2) { - db.execSQL("ALTER TABLE " + TABLE_UPLOAD + " ADD COLUMN attempt INTEGER;"); - } - if (oldVersion < 3) { - db.execSQL("ALTER TABLE " + TABLE_UPLOAD + " ADD COLUMN message TEXT;"); - } - if (oldVersion < 4) { - db.execSQL("ALTER TABLE " + TABLE_UPLOAD + " ADD COLUMN uploadObject TEXT;"); + if (newVersion == 4) { + db.execSQL("DROP TABLE IF EXISTS " + "instant_upload" + ";"); //drop old db (name) + db.execSQL("DROP TABLE IF EXISTS " + TABLE_UPLOAD + ";"); + onCreate(db); } } } - public boolean storeUpload(PersistentUploadObject uploadObject, String message) { + public boolean storeUpload(UploadDbObject uploadObject, String message) { ContentValues cv = new ContentValues(); cv.put("path", uploadObject.getLocalPath()); - cv.put("account", uploadObject.getAccountName()); - cv.put("attempt", UploadStatus.UPLOAD_STATUS_UPLOAD_LATER.getValue()); - cv.put("message", message); cv.put("uploadObject", uploadObject.toString()); long result = mDB.insert(TABLE_UPLOAD, null, cv); - Log_OC.d(TABLE_UPLOAD, "putFileForLater returns with: " + result + " for file: " + uploadObject.getLocalPath()); + Log_OC.d(TAG, "putFileForLater returns with: " + result + " for file: " + uploadObject.getLocalPath()); return result != -1; } + + public List getAllStoredUploads() { + Cursor c = mDB.query(TABLE_UPLOAD, null, null, null, null, null, null); + List list = new ArrayList(); + if (c.moveToFirst()) { + do { + String file_path = c.getString(c.getColumnIndex("path")); + String uploadObjectString = c.getString(c.getColumnIndex("uploadObject")); + UploadDbObject uploadObject = UploadDbObject.fromString(uploadObjectString); + if(uploadObject == null) { + Log_OC.e(TAG, "Could not deserialize UploadDbObject " + uploadObjectString); + } else { + list.add(uploadObject); + } + } while (c.moveToNext()); + } + return list; + } } diff --git a/src/com/owncloud/android/db/PersistentUploadObject.java b/src/com/owncloud/android/db/UploadDbObject.java similarity index 53% rename from src/com/owncloud/android/db/PersistentUploadObject.java rename to src/com/owncloud/android/db/UploadDbObject.java index 765fd3b038a..240f0afab96 100755 --- a/src/com/owncloud/android/db/PersistentUploadObject.java +++ b/src/com/owncloud/android/db/UploadDbObject.java @@ -4,8 +4,18 @@ import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.io.Serializable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Base64; +import android.util.Log; + +import com.owncloud.android.db.UploadDbHandler.UploadStatus; import com.owncloud.android.files.services.FileUploadService.LocalBehaviour; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.common.utils.Log_OC; + /** * Stores all information in order to start upload. PersistentUploadObject can @@ -14,7 +24,12 @@ * @author LukeOwncloud * */ -public class PersistentUploadObject { +public class UploadDbObject implements Serializable{ + + /** Generated - should be refreshed every time the class changes!! */; + private static final long serialVersionUID = -2306246191385279924L; + + private static final String TAG = "UploadDbObject"; /** * Local path to file which is to be uploaded. */ @@ -32,6 +47,34 @@ public class PersistentUploadObject { * Local action for upload. */ LocalBehaviour localAction; + /** + * @return the uploadStatus + */ + public UploadStatus getUploadStatus() { + return uploadStatus; + } + + /** + * @param uploadStatus the uploadStatus to set + */ + public void setUploadStatus(UploadStatus uploadStatus) { + this.uploadStatus = uploadStatus; + } + + /** + * @return the lastResult + */ + public RemoteOperationResult getLastResult() { + return lastResult; + } + + /** + * @param lastResult the lastResult to set + */ + public void setLastResult(RemoteOperationResult lastResult) { + this.lastResult = lastResult; + } + /** * Overwrite destination file? */ @@ -48,6 +91,16 @@ public class PersistentUploadObject { * Name of Owncloud account to upload file to. */ String accountName; + + /** + * Status of upload (later, in_progress, ...). + */ + UploadStatus uploadStatus; + + /** + * Result from last upload operation. Can be null. + */ + RemoteOperationResult lastResult; /** * @return the localPath @@ -95,6 +148,7 @@ public void setMimeType(String mimeType) { * @return the localAction */ public LocalBehaviour getLocalAction() { +// return null; return localAction; } @@ -160,31 +214,72 @@ public String getAccountName() { public void setAccountName(String accountName) { this.accountName = accountName; } - + + /** + * Returns a base64 encoded serialized string of this object. + */ @Override public String toString() { + // serialize the object try { ByteArrayOutputStream bo = new ByteArrayOutputStream(); ObjectOutputStream so = new ObjectOutputStream(bo); so.writeObject(this); so.flush(); - return bo.toString(); + String serializedObjectBase64 = Base64.encodeToString(bo.toByteArray(), Base64.DEFAULT); + so.close(); + bo.close(); + return serializedObjectBase64; } catch (Exception e) { - System.out.println(e); + Log_OC.e(TAG, "Cannot serialize UploadDbObject with localPath:" + getLocalPath(), e); } +// +// try { +// ByteArrayOutputStream bo = new ByteArrayOutputStream(); +// ObjectOutputStream so = new ObjectOutputStream(bo); +// so.writeObject(this); +// so.flush(); +// String base64 = Base64.encodeToString(bo.toString() +// .getBytes(), Base64.DEFAULT); +// return base64; +// } catch (Exception e) { +// System.out.println(e); +// } return null; } - public PersistentUploadObject fromString(String serializedObject) { + /** + * Accepts a base64 encoded serialized string of an {@link UploadDbObject} + * and instantiates and returns an according object. + * + * @param serializedObjectBase64 + * @return + */ + static public UploadDbObject fromString(String serializedObjectBase64) { + // deserialize the object try { - byte b[] = serializedObject.getBytes(); + byte[] b = Base64.decode(serializedObjectBase64, Base64.DEFAULT); ByteArrayInputStream bi = new ByteArrayInputStream(b); ObjectInputStream si = new ObjectInputStream(bi); - return (PersistentUploadObject) si.readObject(); + UploadDbObject obj = (UploadDbObject) si.readObject(); + Log.e(TAG, "SUCCESS!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + return obj; } catch (Exception e) { - System.out.println(e); + Log_OC.e(TAG, "Cannot deserialize UploadDbObject " + serializedObjectBase64, e); } +// try { +// byte b[] = Base64.decode(serializedObject, Base64.DEFAULT); +// ByteArrayInputStream bi = new ByteArrayInputStream(b); +// ObjectInputStream si = new ObjectInputStream(bi); +// return (UploadDbObject) si.readObject(); +// } catch (Exception e) { +// Log_OC.e(TAG, "Cannot deserialize UploadDbObject " + serializedObject, e); +// } return null; } + + } + + diff --git a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java index c72b3218301..8cf7f55ddaa 100644 --- a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java +++ b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java @@ -101,7 +101,7 @@ private void handleNewPictureAction(Context context, Intent intent) { i.putExtra(FileUploadService.KEY_ACCOUNT, account); i.putExtra(FileUploadService.KEY_LOCAL_FILE, file_path); i.putExtra(FileUploadService.KEY_REMOTE_FILE, FileStorageUtils.getInstantUploadFilePath(context, file_name)); - i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UPLOAD_SINGLE_FILE); + i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UploadSingleMulti.UPLOAD_SINGLE_FILE); i.putExtra(FileUploadService.KEY_MIME_TYPE, mime_type); i.putExtra(FileUploadService.KEY_CREATE_REMOTE_FOLDER, true); i.putExtra(FileUploadService.KEY_WIFI_ONLY, instantPictureUploadViaWiFiOnly(context)); @@ -143,7 +143,7 @@ private void handleNewVideoAction(Context context, Intent intent) { i.putExtra(FileUploadService.KEY_ACCOUNT, account); i.putExtra(FileUploadService.KEY_LOCAL_FILE, file_path); i.putExtra(FileUploadService.KEY_REMOTE_FILE, FileStorageUtils.getInstantUploadFilePath(context, file_name)); - i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UPLOAD_SINGLE_FILE); + i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UploadSingleMulti.UPLOAD_SINGLE_FILE); i.putExtra(FileUploadService.KEY_MIME_TYPE, mime_type); i.putExtra(FileUploadService.KEY_CREATE_REMOTE_FOLDER, true); i.putExtra(FileUploadService.KEY_WIFI_ONLY, instantVideoUploadViaWiFiOnly(context)); diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index 38fffb06f93..ba3339cd675 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -18,19 +18,23 @@ package com.owncloud.android.files.services; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.util.AbstractList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; +import java.util.List; 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.app.NotificationManager; import android.app.PendingIntent; @@ -48,6 +52,8 @@ import android.os.Message; import android.os.Process; import android.support.v4.app.NotificationCompat; +import android.util.Base64; +import android.util.Log; import android.webkit.MimeTypeMap; import com.owncloud.android.R; @@ -55,12 +61,12 @@ import com.owncloud.android.authentication.AuthenticatorActivity; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; -import com.owncloud.android.db.PersistentUploadObject; import com.owncloud.android.db.UploadDbHandler; +import com.owncloud.android.db.UploadDbHandler.UploadStatus; +import com.owncloud.android.db.UploadDbObject; 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; @@ -120,23 +126,26 @@ private LocalBehaviour(int value) { this.value = value; } + public int getValue() { + return value; + } + } + + public enum UploadSingleMulti { + UPLOAD_SINGLE_FILE(0), UPLOAD_MULTIPLE_FILES(1); + private final int value; + + private UploadSingleMulti(int value) { + this.value = value; + } + public int getValue() { return value; } }; - // public enum UploadSingleMulti { - // UPLOAD_SINGLE_FILE(0), UPLOAD_MULTIPLE_FILES(1); - // private final int value; - // private UploadSingleMulti(int value) { - // this.value = value; - // } - // public int getValue() { - // return value; - // } - // }; - public static final int UPLOAD_SINGLE_FILE = 0; - public static final int UPLOAD_MULTIPLE_FILES = 1; + // public static final int UPLOAD_SINGLE_FILE = 0; + // public static final int UPLOAD_MULTIPLE_FILES = 1; private static final String TAG = FileUploadService.class.getSimpleName(); @@ -223,20 +232,28 @@ public void onDestroy() { * 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. + * + * First, onStartCommand() stores all information associated with the upload + * in a {@link UploadDbObject} which is stored persistently using + * {@link UploadDbHandler}. Then, {@link ServiceHandler} is invoked which + * performs the upload and updates the DB entry (upload success, failure, + * retry, ...) */ @Override public int onStartCommand(Intent intent, int flags, int startId) { - AbstractList requestedUploads = new Vector(); + AbstractList requestedUploads = new Vector(); if (intent == null) { // service was restarted by OS (after return START_STICKY and kill // service) or connectivity change was detected. ==> check persistent upload // list. // - //TODO fill requestedUploads from DB + UploadDbHandler db = new UploadDbHandler(this.getBaseContext()); + List list = db.getAllStoredUploads(); + requestedUploads.addAll(list); } else { - int uploadType = intent.getIntExtra(KEY_UPLOAD_TYPE, -1); - if (uploadType == -1) { + UploadSingleMulti uploadType = (UploadSingleMulti) intent.getSerializableExtra(KEY_UPLOAD_TYPE); + if (uploadType == null) { Log_OC.e(TAG, "Incorrect or no upload type provided"); return Service.START_NOT_STICKY; } @@ -250,18 +267,16 @@ public int onStartCommand(Intent intent, int flags, int startId) { OCFile[] files = null; // if KEY_FILE given, use it if (intent.hasExtra(KEY_FILE)) { - if (uploadType == UPLOAD_SINGLE_FILE) { + if (uploadType == UploadSingleMulti.UPLOAD_SINGLE_FILE) { files = new OCFile[] { intent.getParcelableExtra(KEY_FILE) }; } else { // TODO will this casting work fine? files = (OCFile[]) intent.getParcelableArrayExtra(KEY_FILE); } - } else { // else use KEY_LOCAL_FILE, KEY_REMOTE_FILE, and - // KEY_MIME_TYPE + } else { // else use KEY_LOCAL_FILE and KEY_REMOTE_FILE - if (!intent.hasExtra(KEY_LOCAL_FILE) || !intent.hasExtra(KEY_REMOTE_FILE) - || !(intent.hasExtra(KEY_MIME_TYPE))) { + if (!intent.hasExtra(KEY_LOCAL_FILE) || !intent.hasExtra(KEY_REMOTE_FILE)) { Log_OC.e(TAG, "Not enough information provided in intent"); return Service.START_NOT_STICKY; } @@ -269,7 +284,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { String[] localPaths; String[] remotePaths; String[] mimeTypes; - if (uploadType == UPLOAD_SINGLE_FILE) { + if (uploadType == UploadSingleMulti.UPLOAD_SINGLE_FILE) { localPaths = new String[] { intent.getStringExtra(KEY_LOCAL_FILE) }; remotePaths = new String[] { intent.getStringExtra(KEY_REMOTE_FILE) }; mimeTypes = new String[] { intent.getStringExtra(KEY_MIME_TYPE) }; @@ -309,18 +324,62 @@ public int onStartCommand(Intent intent, int flags, int startId) { // failed. UploadDbHandler db = new UploadDbHandler(this.getBaseContext()); for (int i = 0; i < files.length; i++) { - PersistentUploadObject uploadObject = new PersistentUploadObject(); + UploadDbObject uploadObject = new UploadDbObject(); uploadObject.setRemotePath(files[i].getRemotePath()); uploadObject.setLocalPath(files[i].getStoragePath()); - uploadObject.setMimeType(files[0].getMimetype()); + uploadObject.setMimeType(files[i].getMimetype()); uploadObject.setAccountName(account.name); uploadObject.setForceOverwrite(forceOverwrite); + uploadObject.setCreateRemoteFolder(isCreateRemoteFolder); uploadObject.setLocalAction(localAction); uploadObject.setUseWifiOnly(isUseWifiOnly); + uploadObject.setLastResult(new RemoteOperationResult(ResultCode.OK)); + uploadObject.setLocalAction(LocalBehaviour.LOCAL_BEHAVIOUR_COPY); + uploadObject.setUploadStatus(UploadStatus.UPLOAD_LATER); + + +// String serializedObjectBase64 = ""; +// +// // serialize the object +// try { +// ByteArrayOutputStream bo = new ByteArrayOutputStream(); +// ObjectOutputStream so = new ObjectOutputStream(bo); +// so.writeObject(uploadObject); +// so.flush(); +// serializedObjectBase64 = Base64.encodeToString(bo.toByteArray(), Base64.DEFAULT); +// so.close(); +// bo.close(); +// } catch (Exception e) { +// System.out.println(e); +// } +// +// // deserialize the object +// try { +// byte[] b = Base64.decode(serializedObjectBase64, Base64.DEFAULT); +// ByteArrayInputStream bi = new ByteArrayInputStream(b); +// ObjectInputStream si = new ObjectInputStream(bi); +// UploadDbObject obj = (UploadDbObject) si.readObject(); +// Log.e(TAG, "SUCCESS!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); +// } catch (Exception e) { +// System.out.println(e); +// } + +// String s = uploadObject.toString(); +// UploadDbObject o = UploadDbObject.fromString(s); +// o = o; + + db.storeUpload(uploadObject, "upload at " + new Date()); requestedUploads.add(uploadObject); + + } db.close(); + + return Service.START_NOT_STICKY; + + // TODO check if would be clever to read entries from + // UploadDbHandler and add to requestedUploads at this point // AccountManager aMgr = AccountManager.get(this); // String version = aMgr.getUserData(account, @@ -335,7 +394,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { // try { // for (int i = 0; i < files.length; i++) { // uploadKey = buildRemoteName(account, files[i].getRemotePath()); - // newUpload = new UploadFileOperation(account, files[i], chunked, +// newUpload = new UploadFileOperation(account, files[i], chunked, // forceOverwrite, localAction, // getApplicationContext()); // if (isCreateRemoteFolder) { @@ -527,11 +586,12 @@ public ServiceHandler(Looper looper, FileUploadService service) { @Override public void handleMessage(Message msg) { @SuppressWarnings("unchecked") - AbstractList requestedUploads = (AbstractList) msg.obj; + AbstractList requestedUploads = (AbstractList) msg.obj; if (msg.obj != null) { - Iterator it = requestedUploads.iterator(); + //TODO iterator returns UploadDbObject! Not a string. + Iterator it = requestedUploads.iterator(); while (it.hasNext()) { - mService.uploadFile(it.next()); + //mService.uploadFile(it.next()); } } mService.stopSelf(msg.arg1); @@ -834,7 +894,7 @@ private void notifyUploadResult(RemoteOperationResult uploadResult, UploadFileOp // message = // getString(R.string.failed_upload_quota_exceeded_text); int updatedFiles = db.updateFileState(upload.getOriginalStoragePath(), - UploadDbHandler.UploadStatus.UPLOAD_STATUS_UPLOAD_FAILED, message); + UploadDbHandler.UploadStatus.UPLOAD_FAILED, message); if (updatedFiles == 0) { // update failed db.storeFile(upload.getOriginalStoragePath(), upload.getAccount().name, message); } diff --git a/src/com/owncloud/android/operations/SynchronizeFileOperation.java b/src/com/owncloud/android/operations/SynchronizeFileOperation.java index 7e383c06ee8..ffa31868fb7 100644 --- a/src/com/owncloud/android/operations/SynchronizeFileOperation.java +++ b/src/com/owncloud/android/operations/SynchronizeFileOperation.java @@ -212,7 +212,7 @@ private void requestForUpload(OCFile file) { i.putExtra(FileUploadService.KEY_FILE, file); /*i.putExtra(FileUploader.KEY_REMOTE_FILE, mRemotePath); // doing this we would lose the value of keepInSync in the road, and maybe it's not updated in the database when the FileUploader service gets it! i.putExtra(FileUploader.KEY_LOCAL_FILE, localFile.getStoragePath());*/ - i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UPLOAD_SINGLE_FILE); + i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UploadSingleMulti.UPLOAD_SINGLE_FILE); i.putExtra(FileUploadService.KEY_FORCE_OVERWRITE, true); mContext.startService(i); mTransferWasRequested = true; diff --git a/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java b/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java index 2a777d7076e..ff269c2883b 100644 --- a/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java +++ b/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java @@ -20,6 +20,7 @@ import com.actionbarsherlock.app.ActionBar; import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.db.UploadDbObject; import com.owncloud.android.files.services.FileUploadService; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.ui.dialog.ConflictsResolveDialog; @@ -68,7 +69,7 @@ public void conflictDecisionMade(Decision decision) { } i.putExtra(FileUploadService.KEY_ACCOUNT, getAccount()); i.putExtra(FileUploadService.KEY_FILE, getFile()); - i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UPLOAD_SINGLE_FILE); + i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UploadSingleMulti.UPLOAD_SINGLE_FILE); startService(i); finish(); diff --git a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java index 963aef179c8..1123fa6f4d9 100644 --- a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -64,6 +64,7 @@ import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.db.UploadDbObject; import com.owncloud.android.files.services.FileDownloader; import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder; import com.owncloud.android.files.services.FileUploadService; @@ -638,7 +639,7 @@ private void requestMultipleUpload(Intent data, int resultCode) { i.putExtra(FileUploadService.KEY_ACCOUNT, getAccount()); i.putExtra(FileUploadService.KEY_LOCAL_FILE, filePaths); i.putExtra(FileUploadService.KEY_REMOTE_FILE, remotePaths); - i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UPLOAD_MULTIPLE_FILES); + i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UploadSingleMulti.UPLOAD_MULTIPLE_FILES); if (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE) i.putExtra(FileUploadService.KEY_LOCAL_BEHAVIOUR, FileUploadService.LocalBehaviour.LOCAL_BEHAVIOUR_MOVE); startService(i); @@ -691,11 +692,13 @@ private void requestSimpleUpload(Intent data, int resultCode) { i.putExtra(FileUploadService.KEY_LOCAL_FILE, filepath); i.putExtra(FileUploadService.KEY_REMOTE_FILE, remotepath); - i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UPLOAD_SINGLE_FILE); + i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UploadSingleMulti.UPLOAD_SINGLE_FILE); if (resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE) { i.putExtra(FileUploadService.KEY_LOCAL_BEHAVIOUR, FileUploadService.LocalBehaviour.LOCAL_BEHAVIOUR_MOVE); } - startService(i); + if(startService(i) == null) { + Log_OC.e(TAG, "FileUploadService could not be started"); + } } /** diff --git a/src/com/owncloud/android/ui/activity/Uploader.java b/src/com/owncloud/android/ui/activity/Uploader.java index d4647a84f99..e6b6e7a0121 100644 --- a/src/com/owncloud/android/ui/activity/Uploader.java +++ b/src/com/owncloud/android/ui/activity/Uploader.java @@ -403,7 +403,7 @@ else if (mimeType.contains("audio")) { } Intent intent = new Intent(getApplicationContext(), FileUploadService.class); - intent.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UPLOAD_MULTIPLE_FILES); + intent.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UploadSingleMulti.UPLOAD_MULTIPLE_FILES); intent.putExtra(FileUploadService.KEY_LOCAL_FILE, local.toArray(new String[local.size()])); intent.putExtra(FileUploadService.KEY_REMOTE_FILE, remote.toArray(new String[remote.size()])); intent.putExtra(FileUploadService.KEY_ACCOUNT, mAccount); From 87d1c0e262e2224fd8bb668113df2a0e116bd38b Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Wed, 5 Nov 2014 16:17:12 +0100 Subject: [PATCH 06/76] mActiveUploads --- .../files/services/FileUploadService.java | 195 ++++++++---------- 1 file changed, 81 insertions(+), 114 deletions(-) diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index ba3339cd675..cbb3c92cc66 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -156,8 +156,14 @@ public int getValue() { private OwnCloudClient mUploadClient = null; private Account mLastAccount = null; private FileDataStorageManager mStorageManager; + //since there can be only one instance of an Android service, there also just one db connection. + private UploadDbHandler mDb = null; - private ConcurrentMap mPendingUploads = new ConcurrentHashMap(); + /** + * List of uploads that currently in progress. Maps from remotePath to where file + * is being uploaded to {@link UploadFileOperation}. + */ + private ConcurrentMap mActiveUploads = new ConcurrentHashMap(); private UploadFileOperation mCurrentUpload = null; private NotificationManager mNotificationManager; @@ -199,7 +205,7 @@ private static boolean chunkedUploadIsSupported(OwnCloudVersion version) { @Override public void onCreate() { super.onCreate(); - Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size()); + Log_OC.i(TAG, "mPendingUploads size:" + mActiveUploads.size()); mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); HandlerThread thread = new HandlerThread("FileUploaderThread", Process.THREAD_PRIORITY_BACKGROUND); thread.start(); @@ -208,6 +214,7 @@ public void onCreate() { mBinder = new FileUploaderBinder(); mConnectivityChangeReceiver = new ConnectivityChangeReceiver(); registerReceiver(mConnectivityChangeReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + mDb = new UploadDbHandler(this.getBaseContext()); } public class ConnectivityChangeReceiver extends BroadcastReceiver { @@ -222,6 +229,7 @@ public void onReceive(Context arg0, Intent arg1) { @Override public void onDestroy() { + mDb.close(); unregisterReceiver(mConnectivityChangeReceiver); super.onDestroy(); } @@ -238,6 +246,8 @@ public void onDestroy() { * {@link UploadDbHandler}. Then, {@link ServiceHandler} is invoked which * performs the upload and updates the DB entry (upload success, failure, * retry, ...) + * + * TODO: correct return values. should not always be NOT_STICKY. */ @Override public int onStartCommand(Intent intent, int flags, int startId) { @@ -247,8 +257,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { // service) or connectivity change was detected. ==> check persistent upload // list. // - UploadDbHandler db = new UploadDbHandler(this.getBaseContext()); - List list = db.getAllStoredUploads(); + List list = mDb.getAllStoredUploads(); requestedUploads.addAll(list); } else { @@ -322,7 +331,6 @@ public int onStartCommand(Intent intent, int flags, int startId) { // save always persistently path of upload, so it can be retried if // failed. - UploadDbHandler db = new UploadDbHandler(this.getBaseContext()); for (int i = 0; i < files.length; i++) { UploadDbObject uploadObject = new UploadDbObject(); uploadObject.setRemotePath(files[i].getRemotePath()); @@ -333,96 +341,15 @@ public int onStartCommand(Intent intent, int flags, int startId) { uploadObject.setCreateRemoteFolder(isCreateRemoteFolder); uploadObject.setLocalAction(localAction); uploadObject.setUseWifiOnly(isUseWifiOnly); - uploadObject.setLastResult(new RemoteOperationResult(ResultCode.OK)); - uploadObject.setLocalAction(LocalBehaviour.LOCAL_BEHAVIOUR_COPY); uploadObject.setUploadStatus(UploadStatus.UPLOAD_LATER); - - -// String serializedObjectBase64 = ""; -// -// // serialize the object -// try { -// ByteArrayOutputStream bo = new ByteArrayOutputStream(); -// ObjectOutputStream so = new ObjectOutputStream(bo); -// so.writeObject(uploadObject); -// so.flush(); -// serializedObjectBase64 = Base64.encodeToString(bo.toByteArray(), Base64.DEFAULT); -// so.close(); -// bo.close(); -// } catch (Exception e) { -// System.out.println(e); -// } -// -// // deserialize the object -// try { -// byte[] b = Base64.decode(serializedObjectBase64, Base64.DEFAULT); -// ByteArrayInputStream bi = new ByteArrayInputStream(b); -// ObjectInputStream si = new ObjectInputStream(bi); -// UploadDbObject obj = (UploadDbObject) si.readObject(); -// Log.e(TAG, "SUCCESS!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); -// } catch (Exception e) { -// System.out.println(e); -// } - -// String s = uploadObject.toString(); -// UploadDbObject o = UploadDbObject.fromString(s); -// o = o; - - - db.storeUpload(uploadObject, "upload at " + new Date()); + mDb.storeUpload(uploadObject, "upload at " + new Date()); requestedUploads.add(uploadObject); - - } - db.close(); - return Service.START_NOT_STICKY; - + // TODO check if would be clever to read entries from // UploadDbHandler and add to requestedUploads at this point - // AccountManager aMgr = AccountManager.get(this); - // String version = aMgr.getUserData(account, - // Constants.KEY_OC_VERSION); - // OwnCloudVersion ocv = new OwnCloudVersion(version); - // - // boolean chunked = - // FileUploadService.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, - // forceOverwrite, localAction, - // getApplicationContext()); - // if (isCreateRemoteFolder) { - // newUpload.setRemoteFolderToBeCreated(); - // } - // mPendingUploads.putIfAbsent(uploadKey, newUpload); // Grants that - // the file only upload once time - // - // 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(); @@ -430,7 +357,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { msg.obj = requestedUploads; mServiceHandler.sendMessage(msg); } - Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size()); + Log_OC.i(TAG, "mPendingUploads size:" + mActiveUploads.size()); return Service.START_NOT_STICKY; } @@ -477,8 +404,8 @@ public class FileUploaderBinder extends Binder implements OnDatatransferProgress */ public void cancel(Account account, OCFile file) { UploadFileOperation upload = null; - synchronized (mPendingUploads) { - upload = mPendingUploads.remove(buildRemoteName(account, file)); + synchronized (mActiveUploads) { + upload = mActiveUploads.remove(buildRemoteName(account, file)); } if (upload != null) { upload.cancel(); @@ -503,17 +430,17 @@ public boolean isUploading(Account account, OCFile file) { if (account == null || file == null) return false; String targetKey = buildRemoteName(account, file); - synchronized (mPendingUploads) { + synchronized (mActiveUploads) { if (file.isFolder()) { // this can be slow if there are many uploads :( - Iterator it = mPendingUploads.keySet().iterator(); + Iterator it = mActiveUploads.keySet().iterator(); boolean found = false; while (it.hasNext() && !found) { found = it.next().startsWith(targetKey); } return found; } else { - return (mPendingUploads.containsKey(targetKey)); + return (mActiveUploads.containsKey(targetKey)); } } } @@ -588,10 +515,9 @@ public void handleMessage(Message msg) { @SuppressWarnings("unchecked") AbstractList requestedUploads = (AbstractList) msg.obj; if (msg.obj != null) { - //TODO iterator returns UploadDbObject! Not a string. Iterator it = requestedUploads.iterator(); while (it.hasNext()) { - //mService.uploadFile(it.next()); + mService.uploadFile(it.next()); } } mService.stopSelf(msg.arg1); @@ -601,13 +527,59 @@ public void handleMessage(Message msg) { /** * Core upload method: sends the file(s) to upload * - * @param uploadKey Key to access the upload to perform, contained in + * @param uploadDbObject Key to access the upload to perform, contained in * mPendingUploads */ - private void uploadFile(String uploadKey) { - - synchronized (mPendingUploads) { - mCurrentUpload = mPendingUploads.get(uploadKey); + private void uploadFile(UploadDbObject uploadDbObject) { + + // AccountManager aMgr = AccountManager.get(this); + // String version = aMgr.getUserData(account, + // Constants.KEY_OC_VERSION); + // OwnCloudVersion ocv = new OwnCloudVersion(version); + // + // boolean chunked = + // FileUploadService.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, + // forceOverwrite, localAction, + // getApplicationContext()); + // if (isCreateRemoteFolder) { + // newUpload.setRemoteFolderToBeCreated(); + // } + // mActiveUploads.putIfAbsent(uploadKey, newUpload); // Grants that + // the file only upload once time + // + // 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; + // + // } + + synchronized (mActiveUploads) { + mCurrentUpload = mActiveUploads.get(uploadDbObject.getRemotePath()); + + //TODO: add object here, to make thread-safe + //mActiveUploads.putIfAbsent(uploadKey, newUpload); // Grants that } if (mCurrentUpload != null) { @@ -654,8 +626,8 @@ private void uploadFile(String uploadKey) { uploadResult = new RemoteOperationResult(e); } finally { - synchronized (mPendingUploads) { - mPendingUploads.remove(uploadKey); + synchronized (mActiveUploads) { + mActiveUploads.remove(uploadDbObject); Log_OC.i(TAG, "Remove CurrentUploadItem from pending upload Item Map."); } if (uploadResult.isException()) { @@ -885,26 +857,23 @@ private void notifyUploadResult(RemoteOperationResult uploadResult, UploadFileOp } else { mNotificationBuilder.setContentText(content); - UploadDbHandler db = null; + try { - db = new UploadDbHandler(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); - int updatedFiles = db.updateFileState(upload.getOriginalStoragePath(), + int updatedFiles = mDb.updateFileState(upload.getOriginalStoragePath(), UploadDbHandler.UploadStatus.UPLOAD_FAILED, message); if (updatedFiles == 0) { // update failed - db.storeFile(upload.getOriginalStoragePath(), upload.getAccount().name, message); + mDb.storeFile(upload.getOriginalStoragePath(), upload.getAccount().name, message); } } else { // TODO: handle other results } } finally { - if (db != null) { - db.close(); - } + } } @@ -914,10 +883,8 @@ private void notifyUploadResult(RemoteOperationResult uploadResult, UploadFileOp if (uploadResult.isSuccess()) { - UploadDbHandler db = new UploadDbHandler(this.getBaseContext()); - db.removeFile(mCurrentUpload.getOriginalStoragePath()); - db.close(); - + mDb.removeFile(mCurrentUpload.getOriginalStoragePath()); + // remove success notification, with a delay of 2 seconds NotificationDelayer.cancelWithDelay(mNotificationManager, R.string.uploader_upload_succeeded_ticker, 2000); From 05d29d9154b39f1bbe42e3ee0da788f11bcf2520 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Wed, 5 Nov 2014 19:54:50 +0100 Subject: [PATCH 07/76] test commit --- SETUP.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SETUP.md b/SETUP.md index 3e8161e7e3d..140b1fd2622 100644 --- a/SETUP.md +++ b/SETUP.md @@ -29,6 +29,8 @@ NOTE: You must have the Android SDK 'tools/', and 'platforms-tools/' folders in ### 2b) Building with console/maven: +WARNING: OBSOLETE! + NOTE: You must have mvn (version >= 3.1.1) in your environment path. Current Android 'platforms-tools' need to be installed. Download/install Android plugin for Maven, install owncloud-android-library, then build ownCloud with mvn: From 8ddb9db4e430b00a1da0657d241977eda7ee3ea5 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Thu, 6 Nov 2014 11:17:18 +0100 Subject: [PATCH 08/76] test commit --- SETUP.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SETUP.md b/SETUP.md index 140b1fd2622..aa1a1b20e1d 100644 --- a/SETUP.md +++ b/SETUP.md @@ -1,8 +1,8 @@ - + If you want to start help developing ownCloud please follow the [contribution guidelines][0] and observe these instructions. If you have any problems, start again with 1) and work your way down. If something still does not work as described here, please open a new issue describing exactly what you did, what happened, and what should have happened. - + ### 1) Fork and download android/develop repository: NOTE: Android SDK with platforms 8, 14 and 19 (and maybe others) need to be installed. From 9d28e9e524b0caa8afa3c86bfab34081a6594422 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Thu, 6 Nov 2014 16:33:04 +0100 Subject: [PATCH 09/76] commenting, commenting, commenting! --- .gitignore | 1 + src/com/owncloud/android/MainApp.java | 4 +- .../owncloud/android/datamodel/OCFile.java | 2 +- .../owncloud/android/db/UploadDbObject.java | 49 ++------ .../files/services/FileUploadService.java | 110 ++++++++++-------- .../operations/UploadFileOperation.java | 22 +++- .../ui/activity/UploadFilesActivity.java | 9 +- .../android/ui/activity/Uploader.java | 11 +- .../android/utils/FileStorageUtils.java | 16 +++ 9 files changed, 124 insertions(+), 100 deletions(-) diff --git a/.gitignore b/.gitignore index 9b9bd8e3965..1be28d0246c 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ oc_workaround/local.properties oc_framework/local.properties oc_framework-test-project/local.properties tests/local.properties +lint.xml # Mac .DS_Store files .DS_Store diff --git a/src/com/owncloud/android/MainApp.java b/src/com/owncloud/android/MainApp.java index c2a4c68bd7a..b1ff29b05c1 100644 --- a/src/com/owncloud/android/MainApp.java +++ b/src/com/owncloud/android/MainApp.java @@ -106,7 +106,9 @@ public static String getDBName() { return getAppContext().getResources().getString(R.string.db_name); } - // data_folder + /** + * name of data_folder, e.g., "owncloud" + */ public static String getDataFolder() { return getAppContext().getResources().getString(R.string.data_folder); } diff --git a/src/com/owncloud/android/datamodel/OCFile.java b/src/com/owncloud/android/datamodel/OCFile.java index 392d03bc88a..09687cb3eff 100644 --- a/src/com/owncloud/android/datamodel/OCFile.java +++ b/src/com/owncloud/android/datamodel/OCFile.java @@ -198,7 +198,7 @@ public String getStoragePath() { } /** - * Can be used to set the path where the file is stored + * Can be used to set the path where the local file is stored * * @param storage_path to set */ diff --git a/src/com/owncloud/android/db/UploadDbObject.java b/src/com/owncloud/android/db/UploadDbObject.java index 240f0afab96..9998e0ab21e 100755 --- a/src/com/owncloud/android/db/UploadDbObject.java +++ b/src/com/owncloud/android/db/UploadDbObject.java @@ -6,17 +6,13 @@ import java.io.ObjectOutputStream; import java.io.Serializable; -import android.os.Parcel; -import android.os.Parcelable; import android.util.Base64; -import android.util.Log; import com.owncloud.android.db.UploadDbHandler.UploadStatus; import com.owncloud.android.files.services.FileUploadService.LocalBehaviour; import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; - /** * Stores all information in order to start upload. PersistentUploadObject can * be stored persistently by {@link UploadDbHandler}. @@ -24,11 +20,12 @@ * @author LukeOwncloud * */ -public class UploadDbObject implements Serializable{ +public class UploadDbObject implements Serializable { - /** Generated - should be refreshed every time the class changes!! */; + /** Generated - should be refreshed every time the class changes!! */ + ; private static final long serialVersionUID = -2306246191385279924L; - + private static final String TAG = "UploadDbObject"; /** * Local path to file which is to be uploaded. @@ -47,6 +44,7 @@ public class UploadDbObject implements Serializable{ * Local action for upload. */ LocalBehaviour localAction; + /** * @return the uploadStatus */ @@ -91,12 +89,12 @@ public void setLastResult(RemoteOperationResult lastResult) { * Name of Owncloud account to upload file to. */ String accountName; - + /** * Status of upload (later, in_progress, ...). */ UploadStatus uploadStatus; - + /** * Result from last upload operation. Can be null. */ @@ -148,7 +146,7 @@ public void setMimeType(String mimeType) { * @return the localAction */ public LocalBehaviour getLocalAction() { -// return null; + // return null; return localAction; } @@ -214,13 +212,13 @@ public String getAccountName() { public void setAccountName(String accountName) { this.accountName = accountName; } - + /** * Returns a base64 encoded serialized string of this object. */ @Override public String toString() { - // serialize the object + // serialize the object try { ByteArrayOutputStream bo = new ByteArrayOutputStream(); ObjectOutputStream so = new ObjectOutputStream(bo); @@ -233,18 +231,6 @@ public String toString() { } catch (Exception e) { Log_OC.e(TAG, "Cannot serialize UploadDbObject with localPath:" + getLocalPath(), e); } -// -// try { -// ByteArrayOutputStream bo = new ByteArrayOutputStream(); -// ObjectOutputStream so = new ObjectOutputStream(bo); -// so.writeObject(this); -// so.flush(); -// String base64 = Base64.encodeToString(bo.toString() -// .getBytes(), Base64.DEFAULT); -// return base64; -// } catch (Exception e) { -// System.out.println(e); -// } return null; } @@ -256,30 +242,17 @@ public String toString() { * @return */ static public UploadDbObject fromString(String serializedObjectBase64) { - // deserialize the object + // deserialize the object try { byte[] b = Base64.decode(serializedObjectBase64, Base64.DEFAULT); ByteArrayInputStream bi = new ByteArrayInputStream(b); ObjectInputStream si = new ObjectInputStream(bi); UploadDbObject obj = (UploadDbObject) si.readObject(); - Log.e(TAG, "SUCCESS!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); return obj; } catch (Exception e) { Log_OC.e(TAG, "Cannot deserialize UploadDbObject " + serializedObjectBase64, e); } -// try { -// byte b[] = Base64.decode(serializedObject, Base64.DEFAULT); -// ByteArrayInputStream bi = new ByteArrayInputStream(b); -// ObjectInputStream si = new ObjectInputStream(bi); -// return (UploadDbObject) si.readObject(); -// } catch (Exception e) { -// Log_OC.e(TAG, "Cannot deserialize UploadDbObject " + serializedObject, e); -// } return null; } - - } - - diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index cbb3c92cc66..974ee537119 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -18,12 +18,8 @@ package com.owncloud.android.files.services; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; import java.util.AbstractList; import java.util.Date; import java.util.HashMap; @@ -52,8 +48,6 @@ import android.os.Message; import android.os.Process; import android.support.v4.app.NotificationCompat; -import android.util.Base64; -import android.util.Log; import android.webkit.MimeTypeMap; import com.owncloud.android.R; @@ -93,6 +87,7 @@ * @author LukeOwncloud * */ +@SuppressWarnings("unused") public class FileUploadService extends Service { private static final String UPLOAD_FINISH_MESSAGE = "UPLOAD_FINISH"; @@ -119,7 +114,22 @@ public class FileUploadService extends Service { * Describes local behavior for upload. */ public enum LocalBehaviour { - LOCAL_BEHAVIOUR_COPY(0), LOCAL_BEHAVIOUR_MOVE(1), LOCAL_BEHAVIOUR_FORGET(2); + /** + * Creates a copy of file and stores it in tmp folder inside owncloud + * folder on sd-card. After upload it is moved to local owncloud + * storage. Original file stays untouched. + */ + LOCAL_BEHAVIOUR_COPY(0), + /** + * Upload file from current storage. Afterwards original file is move to + * local owncloud storage. + */ + LOCAL_BEHAVIOUR_MOVE(1), + /** + * Just uploads file and leaves it where it is. Original file stays + * untouched. + */ + LOCAL_BEHAVIOUR_FORGET(2); private final int value; private LocalBehaviour(int value) { @@ -183,6 +193,7 @@ private String buildRemoteName(Account account, OCFile file) { return account.name + file.getRemotePath(); } + private String buildRemoteName(Account account, String remotePath) { return account.name + remotePath; } @@ -279,7 +290,6 @@ public int onStartCommand(Intent intent, int flags, int startId) { if (uploadType == UploadSingleMulti.UPLOAD_SINGLE_FILE) { files = new OCFile[] { intent.getParcelableExtra(KEY_FILE) }; } else { - // TODO will this casting work fine? files = (OCFile[]) intent.getParcelableArrayExtra(KEY_FILE); } @@ -532,53 +542,53 @@ public void handleMessage(Message msg) { */ private void uploadFile(UploadDbObject uploadDbObject) { - // AccountManager aMgr = AccountManager.get(this); - // String version = aMgr.getUserData(account, - // Constants.KEY_OC_VERSION); - // OwnCloudVersion ocv = new OwnCloudVersion(version); - // - // boolean chunked = - // FileUploadService.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()); +// AccountManager aMgr = AccountManager.get(this); +// String version = aMgr.getUserData(account, +// Constants.KEY_OC_VERSION); +// OwnCloudVersion ocv = new OwnCloudVersion(version); +// +// boolean chunked = +// FileUploadService.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, - // forceOverwrite, localAction, - // getApplicationContext()); - // if (isCreateRemoteFolder) { - // newUpload.setRemoteFolderToBeCreated(); - // } - // mActiveUploads.putIfAbsent(uploadKey, newUpload); // Grants that - // the file only upload once time - // - // 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; - // - // } +// forceOverwrite, localAction, +// getApplicationContext()); +// if (isCreateRemoteFolder) { +// newUpload.setRemoteFolderToBeCreated(); +// } +// mActiveUploads.putIfAbsent(uploadKey, newUpload); // Grants that +// the file only upload once time +// +// 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; +// +// } synchronized (mActiveUploads) { mCurrentUpload = mActiveUploads.get(uploadDbObject.getRemotePath()); - //TODO: add object here, to make thread-safe + //TODO: add object to mCurrentUpload here, to make thread-safe //mActiveUploads.putIfAbsent(uploadKey, newUpload); // Grants that } diff --git a/src/com/owncloud/android/operations/UploadFileOperation.java b/src/com/owncloud/android/operations/UploadFileOperation.java index 6b4f2e30e36..ff4fcf95161 100644 --- a/src/com/owncloud/android/operations/UploadFileOperation.java +++ b/src/com/owncloud/android/operations/UploadFileOperation.java @@ -62,7 +62,14 @@ public class UploadFileOperation extends RemoteOperation { private static final String TAG = UploadFileOperation.class.getSimpleName(); private Account mAccount; + /** + * OCFile which is to be uploaded. + */ private OCFile mFile; + /** + * Original OCFile which is to be uploaded in case file had to be renamed + * (if forceOverwrite==false and remote file already exists). + */ private OCFile mOldFile; private String mRemotePath = null; private boolean mChunked = false; @@ -71,6 +78,9 @@ public class UploadFileOperation extends RemoteOperation { private LocalBehaviour mLocalBehaviour = FileUploadService.LocalBehaviour.LOCAL_BEHAVIOUR_COPY; private boolean mWasRenamed = false; private String mOriginalFileName = null; + /** + * Local path to file which is to be uploaded (before any possible renaming or moving). + */ private String mOriginalStoragePath = null; PutMethod mPutMethod = null; private Set mDataTransferListeners = new HashSet(); @@ -122,6 +132,10 @@ public OCFile getFile() { return mFile; } + /** + * If remote file was renamed, return original OCFile which was uploaded. Is + * null is file was not renamed. + */ public OCFile getOldFile() { return mOldFile; } @@ -205,7 +219,8 @@ protected RemoteOperationResult run(OwnCloudClient client) { // check location of local file; if not the expected, copy to a // temporal file before upload (if COPY is the expected behaviour) - if (!mOriginalStoragePath.equals(expectedPath) && mLocalBehaviour == FileUploadService.LocalBehaviour.LOCAL_BEHAVIOUR_COPY) { + if (!mOriginalStoragePath.equals(expectedPath) + && mLocalBehaviour == FileUploadService.LocalBehaviour.LOCAL_BEHAVIOUR_COPY) { if (FileStorageUtils.getUsableSpace(mAccount.name) < originalFile.length()) { result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_FULL); @@ -346,6 +361,11 @@ protected RemoteOperationResult run(OwnCloudClient client) { return result; } + /** + * Create a new OCFile mFile with new remote path. This is required if forceOverwrite==false. + * New file is stored as mFile, original as mOldFile. + * @param newRemotePath new remote path + */ private void createNewOCFile(String newRemotePath) { // a new OCFile instance must be created for a new remote path OCFile newFile = new OCFile(newRemotePath); diff --git a/src/com/owncloud/android/ui/activity/UploadFilesActivity.java b/src/com/owncloud/android/ui/activity/UploadFilesActivity.java index 83e7bc073e4..b41a394bb07 100644 --- a/src/com/owncloud/android/ui/activity/UploadFilesActivity.java +++ b/src/com/owncloud/android/ui/activity/UploadFilesActivity.java @@ -34,23 +34,24 @@ import com.actionbarsherlock.app.ActionBar; import com.actionbarsherlock.app.ActionBar.OnNavigationListener; -import com.actionbarsherlock.internal.view.menu.ActionMenuItemView; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import com.owncloud.android.R; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.ui.dialog.ConfirmationDialogFragment; -import com.owncloud.android.ui.dialog.IndeterminateProgressDialog; import com.owncloud.android.ui.dialog.ConfirmationDialogFragment.ConfirmationDialogFragmentListener; +import com.owncloud.android.ui.dialog.IndeterminateProgressDialog; import com.owncloud.android.ui.fragment.LocalFileListFragment; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.FileStorageUtils; /** - * Displays local files and let the user choose what of them wants to upload - * to the current ownCloud account + * Displays local files and let the user choose which file to upload to the + * current ownCloud account. Selected files are sent back to the caller as Extra + * named EXTRA_CHOSEN_FILES. Thus, thus activity does not perform the upload + * itself. (It should thus be renamed to FileUploadChooserActivity or something) * * @author David A. Velasco * diff --git a/src/com/owncloud/android/ui/activity/Uploader.java b/src/com/owncloud/android/ui/activity/Uploader.java index a3f5111dcf0..60752665413 100644 --- a/src/com/owncloud/android/ui/activity/Uploader.java +++ b/src/com/owncloud/android/ui/activity/Uploader.java @@ -25,7 +25,6 @@ import java.util.List; import java.util.Stack; import java.util.Vector; - import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.authentication.AccountAuthenticator; @@ -33,9 +32,9 @@ import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.files.services.FileUploadService; import com.owncloud.android.lib.common.utils.Log_OC; - import android.accounts.Account; import android.accounts.AccountManager; +import android.annotation.SuppressLint; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.app.Dialog; @@ -61,14 +60,15 @@ import android.widget.EditText; import android.widget.SimpleAdapter; import android.widget.Toast; - import com.actionbarsherlock.app.ActionBar; import com.actionbarsherlock.app.SherlockListActivity; import com.actionbarsherlock.view.MenuItem; import com.owncloud.android.utils.DisplayUtils; /** - * This can be used to upload things to an ownCloud instance. + * This class is registered for Intents android.intent.action.SEND and android.intent.action.SEND_MULTIPLE + * and causes indicated to be uploaded to an ownCloud instance. User can choose which account to use as well + * as the upload destination. * * @author Bartek Przybylski * @@ -355,7 +355,8 @@ private boolean prepareStreamsToUpload() { return (mStreamsToUpload != null && mStreamsToUpload.get(0) != null); } - public void uploadFiles() { + @SuppressLint("NewApi") + public void uploadFiles() { try { ArrayList local = new ArrayList(); diff --git a/src/com/owncloud/android/utils/FileStorageUtils.java b/src/com/owncloud/android/utils/FileStorageUtils.java index 3895821d6ce..2d90836e0ac 100644 --- a/src/com/owncloud/android/utils/FileStorageUtils.java +++ b/src/com/owncloud/android/utils/FileStorageUtils.java @@ -41,22 +41,38 @@ public class FileStorageUtils { //private static final String LOG_TAG = "FileStorageUtils"; + /** + * Get local owncloud storage path for accountName. + */ public static final String getSavePath(String accountName) { File sdCard = Environment.getExternalStorageDirectory(); return sdCard.getAbsolutePath() + "/" + MainApp.getDataFolder() + "/" + Uri.encode(accountName, "@"); // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B } + /** + * Get local path where OCFile file is to be stored after upload. That is, + * corresponding local path (in local owncloud storage) to remote uploaded + * file. + */ public static final String getDefaultSavePathFor(String accountName, OCFile file) { return getSavePath(accountName) + file.getRemotePath(); } + /** + * Get absolute path to tmp folder inside datafolder in sd-card for given accountName. + */ public static final String getTemporalPath(String accountName) { File sdCard = Environment.getExternalStorageDirectory(); return sdCard.getAbsolutePath() + "/" + MainApp.getDataFolder() + "/tmp/" + Uri.encode(accountName, "@"); // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B } + /** + * Optimistic number of bytes available on sd-card. accountName is ignored. + * @param accountName not used. can thus be null. + * @return Optimistic number of available bytes (can be less) + */ @SuppressLint("NewApi") public static final long getUsableSpace(String accountName) { File savePath = Environment.getExternalStorageDirectory(); From 3e9ecca719b889bed42ddf66b9b5a1ac036cf234 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Thu, 6 Nov 2014 17:51:19 +0100 Subject: [PATCH 10/76] now upload works again as good (or as bad) as before. check FileUploadService.java: //How does this work? Is it thread-safe to set mCurrentUpload here? //What happens if other mCurrentUpload is currently in progress? // //It seems that upload does work, however the upload state is not set //back of the first upload when a second upload starts while first is //in progress (yellow up-arrow does not disappear of first upload) --- .../owncloud/android/db/UploadDbHandler.java | 13 +++ .../owncloud/android/db/UploadDbObject.java | 10 ++ .../files/services/FileUploadService.java | 102 +++++++++--------- 3 files changed, 71 insertions(+), 54 deletions(-) diff --git a/src/com/owncloud/android/db/UploadDbHandler.java b/src/com/owncloud/android/db/UploadDbHandler.java index 1069653218f..1f302013ce4 100644 --- a/src/com/owncloud/android/db/UploadDbHandler.java +++ b/src/com/owncloud/android/db/UploadDbHandler.java @@ -177,6 +177,12 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } } + /** + * + * @param uploadObject + * @param message + * @return true on success. + */ public boolean storeUpload(UploadDbObject uploadObject, String message) { ContentValues cv = new ContentValues(); cv.put("path", uploadObject.getLocalPath()); @@ -187,6 +193,13 @@ public boolean storeUpload(UploadDbObject uploadObject, String message) { return result != -1; } + public boolean removeUpload(String localPath) { + long result = mDB.delete(TABLE_UPLOAD, "path = ?", new String[] { localPath }); + Log_OC.d(TABLE_UPLOAD, "delete returns with: " + result + " for file: " + localPath); + return result != 0; + + } + public List getAllStoredUploads() { Cursor c = mDB.query(TABLE_UPLOAD, null, null, null, null, null, null); List list = new ArrayList(); diff --git a/src/com/owncloud/android/db/UploadDbObject.java b/src/com/owncloud/android/db/UploadDbObject.java index 9998e0ab21e..eec093a5b86 100755 --- a/src/com/owncloud/android/db/UploadDbObject.java +++ b/src/com/owncloud/android/db/UploadDbObject.java @@ -6,8 +6,11 @@ import java.io.ObjectOutputStream; import java.io.Serializable; +import android.accounts.Account; +import android.content.Context; import android.util.Base64; +import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.db.UploadDbHandler.UploadStatus; import com.owncloud.android.files.services.FileUploadService.LocalBehaviour; import com.owncloud.android.lib.common.operations.RemoteOperationResult; @@ -255,4 +258,11 @@ static public UploadDbObject fromString(String serializedObjectBase64) { return null; } + /** + * Returns owncloud account as {@link Account} object. + */ + public Account getAccount(Context context) { + return AccountUtils.getOwnCloudAccountByName(context, getAccountName()); + } + } diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index 974ee537119..aa075f629f4 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -31,6 +31,7 @@ import java.util.concurrent.ConcurrentMap; import android.accounts.Account; +import android.accounts.AccountManager; import android.accounts.AccountsException; import android.app.NotificationManager; import android.app.PendingIntent; @@ -61,6 +62,7 @@ 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; @@ -226,6 +228,7 @@ public void onCreate() { mConnectivityChangeReceiver = new ConnectivityChangeReceiver(); registerReceiver(mConnectivityChangeReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); mDb = new UploadDbHandler(this.getBaseContext()); + mDb.recreateDb(); } public class ConnectivityChangeReceiver extends BroadcastReceiver { @@ -352,7 +355,10 @@ public int onStartCommand(Intent intent, int flags, int startId) { uploadObject.setLocalAction(localAction); uploadObject.setUseWifiOnly(isUseWifiOnly); uploadObject.setUploadStatus(UploadStatus.UPLOAD_LATER); - mDb.storeUpload(uploadObject, "upload at " + new Date()); + boolean success = mDb.storeUpload(uploadObject, "upload at " + new Date()); + if(!success) { + Log_OC.e(TAG, "Could not add upload to database."); + } requestedUploads.add(uploadObject); } @@ -361,14 +367,17 @@ public int onStartCommand(Intent intent, int flags, int startId) { // UploadDbHandler and add to requestedUploads at this point } + Log_OC.i(TAG, "mPendingUploads size:" + mActiveUploads.size()); if (requestedUploads.size() > 0) { Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = requestedUploads; mServiceHandler.sendMessage(msg); - } - Log_OC.i(TAG, "mPendingUploads size:" + mActiveUploads.size()); - return Service.START_NOT_STICKY; + return Service.START_STICKY; // there is work to do. If killed this + // service should be restarted + // eventually. + } + return Service.START_NOT_STICKY; //nothing to do. do not restart. } /** @@ -542,54 +551,39 @@ public void handleMessage(Message msg) { */ private void uploadFile(UploadDbObject uploadDbObject) { -// AccountManager aMgr = AccountManager.get(this); -// String version = aMgr.getUserData(account, -// Constants.KEY_OC_VERSION); -// OwnCloudVersion ocv = new OwnCloudVersion(version); -// -// boolean chunked = -// FileUploadService.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, -// forceOverwrite, localAction, -// getApplicationContext()); -// if (isCreateRemoteFolder) { -// newUpload.setRemoteFolderToBeCreated(); -// } -// mActiveUploads.putIfAbsent(uploadKey, newUpload); // Grants that -// the file only upload once time -// -// 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; -// -// } - synchronized (mActiveUploads) { + //How does this work? Is it thread-safe to set mCurrentUpload here? + //What happens if other mCurrentUpload is currently in progress? + // + //It seems that upload does work, however the upload state is not set + //back of the first upload when a second upload starts while first is + //in progress (yellow up-arrow does not disappear of first upload) mCurrentUpload = mActiveUploads.get(uploadDbObject.getRemotePath()); - //TODO: add object to mCurrentUpload here, to make thread-safe - //mActiveUploads.putIfAbsent(uploadKey, newUpload); // Grants that + //if upload not in progress, start it now + if(mCurrentUpload == null) { + AccountManager aMgr = AccountManager.get(this); + Account account = uploadDbObject.getAccount(getApplicationContext()); + String version = aMgr.getUserData(account, Constants.KEY_OC_VERSION); + OwnCloudVersion ocv = new OwnCloudVersion(version); + + boolean chunked = FileUploadService.chunkedUploadIsSupported(ocv); + String uploadKey = null; + + uploadKey = buildRemoteName(account, uploadDbObject.getRemotePath()); + OCFile file = obtainNewOCFileToUpload(uploadDbObject.getRemotePath(), uploadDbObject.getLocalPath(), + uploadDbObject.getMimeType()); + mCurrentUpload = new UploadFileOperation(account, file, chunked, uploadDbObject.isForceOverwrite(), + uploadDbObject.getLocalAction(), getApplicationContext()); + if (uploadDbObject.isCreateRemoteFolder()) { + mCurrentUpload.setRemoteFolderToBeCreated(); + } + mActiveUploads.putIfAbsent(uploadKey, mCurrentUpload); // Grants that + // the file only upload once time + + mCurrentUpload.addDatatransferProgressListener((FileUploaderBinder) mBinder); + } + } if (mCurrentUpload != null) { @@ -597,7 +591,7 @@ private void uploadFile(UploadDbObject uploadDbObject) { notifyUploadStart(mCurrentUpload); RemoteOperationResult uploadResult = null, grantResult = null; - + UploadFileOperation justFinishedUpload; try { // / prepare client object to send requests to the ownCloud // server @@ -649,10 +643,9 @@ private void uploadFile(UploadDbObject uploadDbObject) { } } - // / notify result - + // notify result notifyUploadResult(uploadResult, mCurrentUpload); - sendFinalBroadcast(mCurrentUpload, uploadResult); + sendFinalBroadcast(mCurrentUpload, uploadResult); } @@ -893,7 +886,8 @@ private void notifyUploadResult(RemoteOperationResult uploadResult, UploadFileOp if (uploadResult.isSuccess()) { - mDb.removeFile(mCurrentUpload.getOriginalStoragePath()); + //TODO just edit state of upload. do not delete here. + mDb.removeUpload(mCurrentUpload.getOriginalStoragePath()); // remove success notification, with a delay of 2 seconds NotificationDelayer.cancelWithDelay(mNotificationManager, R.string.uploader_upload_succeeded_ticker, From 503bcc3f418fcc39e4ced124674aaf208cda8815 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Thu, 6 Nov 2014 17:54:10 +0100 Subject: [PATCH 11/76] remove mDb.recreateDb() --- src/com/owncloud/android/files/services/FileUploadService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index aa075f629f4..330bf34b873 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -228,7 +228,7 @@ public void onCreate() { mConnectivityChangeReceiver = new ConnectivityChangeReceiver(); registerReceiver(mConnectivityChangeReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); mDb = new UploadDbHandler(this.getBaseContext()); - mDb.recreateDb(); +// mDb.recreateDb(); //for testing only } public class ConnectivityChangeReceiver extends BroadcastReceiver { From ba7e62e9f35862772da69007d8d5bd3e9c2f602e Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Fri, 7 Nov 2014 15:26:21 +0100 Subject: [PATCH 12/76] added first draft of uploadListActivity --- AndroidManifest.xml | 14 +- res/layout/errorhandling_showerror.xml | 11 + res/layout/upload_files_layout.xml | 2 +- res/layout/uploads_list_item.xml | 129 ++++++++++++ res/layout/uploads_list_layout.xml | 11 + res/menu/main_menu.xml | 7 +- .../ui/activity/FileDisplayActivity.java | 7 +- .../android/ui/activity/Uploader.java | 2 +- .../ui/activity/UploadsListActivity.java | 43 ++++ .../ui/adapter/UploadsListAdapter.java | 171 ++++++++++++++++ .../ui/errorhandling/ErrorShowActivity.java | 26 +++ .../ui/errorhandling/ExceptionHandler.java | 66 ++++++ .../ui/fragment/UploadsListFragment.java | 192 ++++++++++++++++++ 13 files changed, 676 insertions(+), 5 deletions(-) create mode 100755 res/layout/errorhandling_showerror.xml create mode 100755 res/layout/uploads_list_item.xml create mode 100755 res/layout/uploads_list_layout.xml create mode 100755 src/com/owncloud/android/ui/activity/UploadsListActivity.java create mode 100755 src/com/owncloud/android/ui/adapter/UploadsListAdapter.java create mode 100755 src/com/owncloud/android/ui/errorhandling/ErrorShowActivity.java create mode 100755 src/com/owncloud/android/ui/errorhandling/ExceptionHandler.java create mode 100755 src/com/owncloud/android/ui/fragment/UploadsListFragment.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 7454d48ab36..8f1fde1c202 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -161,8 +161,20 @@ + + + + + + + + + + + + - + diff --git a/res/layout/errorhandling_showerror.xml b/res/layout/errorhandling_showerror.xml new file mode 100755 index 00000000000..14bb522808a --- /dev/null +++ b/res/layout/errorhandling_showerror.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/res/layout/upload_files_layout.xml b/res/layout/upload_files_layout.xml index 6c15ff7e526..7ead19d4b32 100644 --- a/res/layout/upload_files_layout.xml +++ b/res/layout/upload_files_layout.xml @@ -28,7 +28,7 @@ android:layout_width="match_parent" android:layout_height="0dip" android:layout_weight="1" - class="com.owncloud.android.ui.fragment.LocalFileListFragment" /> + class="com.owncloud.android.ui.fragment.UploadsListFragment" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/res/layout/uploads_list_layout.xml b/res/layout/uploads_list_layout.xml new file mode 100755 index 00000000000..894684f168b --- /dev/null +++ b/res/layout/uploads_list_layout.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/res/menu/main_menu.xml b/res/menu/main_menu.xml index b73832293dc..19d6a53535a 100644 --- a/res/menu/main_menu.xml +++ b/res/menu/main_menu.xml @@ -55,7 +55,12 @@ android:orderInCategory="2" android:showAsAction="never" android:title="@string/actionbar_sort"/> - + \ No newline at end of file diff --git a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java index 1123fa6f4d9..240405d835b 100644 --- a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -105,7 +105,7 @@ /** - * Displays, what files the user has available in his ownCloud. + * Displays, what files the user has available in his ownCloud. This is the main view. * * @author Bartek Przybylski * @author David A. Velasco @@ -495,6 +495,11 @@ public boolean onOptionsItemSelected(MenuItem item) { startActivity(loggerIntent); break; } + case R.id.action_uploads_list: { + Intent uploadListIntent = new Intent(getApplicationContext(),UploadsListActivity.class); + startActivity(uploadListIntent); + break; + } case android.R.id.home: { FileFragment second = getSecondFragment(); OCFile currentDir = getCurrentDir(); diff --git a/src/com/owncloud/android/ui/activity/Uploader.java b/src/com/owncloud/android/ui/activity/Uploader.java index 60752665413..811e0b1697c 100644 --- a/src/com/owncloud/android/ui/activity/Uploader.java +++ b/src/com/owncloud/android/ui/activity/Uploader.java @@ -68,7 +68,7 @@ /** * This class is registered for Intents android.intent.action.SEND and android.intent.action.SEND_MULTIPLE * and causes indicated to be uploaded to an ownCloud instance. User can choose which account to use as well - * as the upload destination. + * as the upload destination. Better name: UploadHandlerActivity * * @author Bartek Przybylski * diff --git a/src/com/owncloud/android/ui/activity/UploadsListActivity.java b/src/com/owncloud/android/ui/activity/UploadsListActivity.java new file mode 100755 index 00000000000..033925183fa --- /dev/null +++ b/src/com/owncloud/android/ui/activity/UploadsListActivity.java @@ -0,0 +1,43 @@ +package com.owncloud.android.ui.activity; + +import java.io.File; + +import android.os.Bundle; + +import com.owncloud.android.R; +import com.owncloud.android.db.UploadDbHandler; +import com.owncloud.android.ui.errorhandling.ExceptionHandler; +import com.owncloud.android.ui.fragment.UploadsListFragment; + +/** + * Activity listing pending, active, and completed uploads. User can delete + * completed uploads from view. Content of this list of coming from + * {@link UploadDbHandler}. + */ +public class UploadsListActivity extends FileActivity implements UploadsListFragment.ContainerActivity { + + private static final String TAG = "UploadsListActivity"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler(this)); + setContentView(R.layout.uploads_list_layout); + } + + // //////////////////////////////////////// + // UploadsListFragment.ContainerActivity + // //////////////////////////////////////// + @Override + public void onUploadItemClick(File file) { + // TODO Auto-generated method stub + + } + + @Override + public File getInitialFilter() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/src/com/owncloud/android/ui/adapter/UploadsListAdapter.java b/src/com/owncloud/android/ui/adapter/UploadsListAdapter.java new file mode 100755 index 00000000000..fb12b5c6e1c --- /dev/null +++ b/src/com/owncloud/android/ui/adapter/UploadsListAdapter.java @@ -0,0 +1,171 @@ +package com.owncloud.android.ui.adapter; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import com.owncloud.android.R; +import com.owncloud.android.db.UploadDbHandler; +import com.owncloud.android.db.UploadDbObject; +import com.owncloud.android.utils.DisplayUtils; + +/** + * This Adapter populates a ListView with following types of uploads: pending, active, completed. + * Filtering possible. + * + */ +public class UploadsListAdapter extends BaseAdapter implements ListAdapter { + + private Context mContext; + private UploadDbObject[] mUploads = null; + + public UploadsListAdapter(Context context) { + mContext = context; + loadUploadItemsFromDb(); + } + + @Override + public boolean areAllItemsEnabled() { + return true; + } + + @Override + public boolean isEnabled(int position) { + return true; + } + + @Override + public int getCount() { + return mUploads != null ? mUploads.length : 0; + } + + @Override + public Object getItem(int position) { + if (mUploads == null || mUploads.length <= position) + return null; + return mUploads[position]; + } + + @Override + public long getItemId(int position) { + return mUploads != null && mUploads.length <= position ? position : -1; + } + + @Override + public int getItemViewType(int position) { + return 0; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View view = convertView; + if (view == null) { + LayoutInflater inflator = (LayoutInflater) mContext + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + view = inflator.inflate(R.layout.uploads_list_item, null); + } + if (mUploads != null && mUploads.length > position) { + UploadDbObject file = mUploads[position]; + + TextView fileName = (TextView) view.findViewById(R.id.Filename); + String name = file.getLocalPath(); + fileName.setText(name); + +// ImageView fileIcon = (ImageView) view.findViewById(R.id.imageView1); +// if (!file.isDirectory()) { +// fileIcon.setImageResource(R.drawable.file); +// } else { +// fileIcon.setImageResource(R.drawable.ic_menu_archive); +// } + + TextView fileSizeV = (TextView) view.findViewById(R.id.file_size); + TextView lastModV = (TextView) view.findViewById(R.id.last_mod); + ImageView checkBoxV = (ImageView) view.findViewById(R.id.custom_checkbox); +// if (!file.isDirectory()) { +// fileSizeV.setVisibility(View.VISIBLE); +// fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.length())); +// lastModV.setVisibility(View.VISIBLE); +// lastModV.setText(DisplayUtils.unixTimeToHumanReadable(file.lastModified())); +// ListView parentList = (ListView)parent; +// if (parentList.getChoiceMode() == ListView.CHOICE_MODE_NONE) { +// checkBoxV.setVisibility(View.GONE); +// } else { +// if (parentList.isItemChecked(position)) { +// checkBoxV.setImageResource(android.R.drawable.checkbox_on_background); +// } else { +// checkBoxV.setImageResource(android.R.drawable.checkbox_off_background); +// } +// checkBoxV.setVisibility(View.VISIBLE); +// } +// +// } else { + fileSizeV.setVisibility(View.GONE); + lastModV.setVisibility(View.GONE); + checkBoxV.setVisibility(View.GONE); +// } + + view.findViewById(R.id.imageView2).setVisibility(View.INVISIBLE); // not GONE; the alignment changes; ugly way to keep it + view.findViewById(R.id.imageView3).setVisibility(View.GONE); + + view.findViewById(R.id.sharedIcon).setVisibility(View.GONE); + view.findViewById(R.id.sharedWithMeIcon).setVisibility(View.GONE); + } + + return view; + } + + @Override + public int getViewTypeCount() { + return 1; + } + + @Override + public boolean hasStableIds() { + return false; + } + + @Override + public boolean isEmpty() { + return (mUploads == null || mUploads.length == 0); + } + + /** + * Load upload items from {@link UploadDbHandler}. + */ + public void loadUploadItemsFromDb() { + UploadDbHandler mDb = new UploadDbHandler(mContext); + List list = mDb.getAllStoredUploads(); + mUploads = list.toArray(new UploadDbObject[list.size()]); + if (mUploads != null) { + Arrays.sort(mUploads, new Comparator() { + @Override + public int compare(UploadDbObject lhs, UploadDbObject rhs) { +// if (lhs.isDirectory() && !rhs.isDirectory()) { +// return -1; +// } else if (!lhs.isDirectory() && rhs.isDirectory()) { +// return 1; +// } + return compareNames(lhs, rhs); + } + + private int compareNames(UploadDbObject lhs, UploadDbObject rhs) { + return lhs.getLocalPath().toLowerCase().compareTo(rhs.getLocalPath().toLowerCase()); + } + + }); + } + notifyDataSetChanged(); + } +} diff --git a/src/com/owncloud/android/ui/errorhandling/ErrorShowActivity.java b/src/com/owncloud/android/ui/errorhandling/ErrorShowActivity.java new file mode 100755 index 00000000000..cea31c5acd2 --- /dev/null +++ b/src/com/owncloud/android/ui/errorhandling/ErrorShowActivity.java @@ -0,0 +1,26 @@ +package com.owncloud.android.ui.errorhandling; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.widget.TextView; + +import com.owncloud.android.R; + +public class ErrorShowActivity extends Activity { + + private static final String TAG = ErrorShowActivity.class.getName(); + + TextView error; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Log.e(TAG, "ErrorShowActivity was called. See above for StackTrace."); + Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler(this)); + setContentView(R.layout.errorhandling_showerror); + error = (TextView) findViewById(R.id.errorTextView); + error.setText(getIntent().getStringExtra("error")); + + } +} \ No newline at end of file diff --git a/src/com/owncloud/android/ui/errorhandling/ExceptionHandler.java b/src/com/owncloud/android/ui/errorhandling/ExceptionHandler.java new file mode 100755 index 00000000000..d9f19f38e4c --- /dev/null +++ b/src/com/owncloud/android/ui/errorhandling/ExceptionHandler.java @@ -0,0 +1,66 @@ +package com.owncloud.android.ui.errorhandling; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import android.app.Activity; +import android.content.Intent; +import android.os.Build; +import android.util.Log; + +public class ExceptionHandler implements java.lang.Thread.UncaughtExceptionHandler { + private final Activity myContext; + private final String LINE_SEPARATOR = "\n"; + + private static final String TAG = ExceptionHandler.class.getName(); + + public ExceptionHandler(Activity context) { + myContext = context; + } + + public void uncaughtException(Thread thread, Throwable exception) { + Log.e(TAG, "ExceptionHandler caught UncaughtException", exception); + StringWriter stackTrace = new StringWriter(); + exception.printStackTrace(new PrintWriter(stackTrace)); + StringBuilder errorReport = new StringBuilder(); + errorReport.append("************ CAUSE OF ERROR ************\n\n"); + errorReport.append(stackTrace.toString()); + + errorReport.append("\n************ DEVICE INFORMATION ***********\n"); + errorReport.append("Brand: "); + errorReport.append(Build.BRAND); + errorReport.append(LINE_SEPARATOR); + errorReport.append("Device: "); + errorReport.append(Build.DEVICE); + errorReport.append(LINE_SEPARATOR); + errorReport.append("Model: "); + errorReport.append(Build.MODEL); + errorReport.append(LINE_SEPARATOR); + errorReport.append("Id: "); + errorReport.append(Build.ID); + errorReport.append(LINE_SEPARATOR); + errorReport.append("Product: "); + errorReport.append(Build.PRODUCT); + errorReport.append(LINE_SEPARATOR); + errorReport.append("\n************ FIRMWARE ************\n"); + errorReport.append("SDK: "); + errorReport.append(Build.VERSION.SDK_INT); + errorReport.append(LINE_SEPARATOR); + errorReport.append("Release: "); + errorReport.append(Build.VERSION.RELEASE); + errorReport.append(LINE_SEPARATOR); + errorReport.append("Incremental: "); + errorReport.append(Build.VERSION.INCREMENTAL); + errorReport.append(LINE_SEPARATOR); + + Log.e(TAG, "An exception was thrown and handled by ExceptionHandler:", exception); + + Intent intent = new Intent(myContext, ErrorShowActivity.class); + intent.putExtra("error", errorReport.toString()); + myContext.startActivity(intent); + + android.os.Process.killProcess(android.os.Process.myPid()); + System.exit(1000); + } + +} \ No newline at end of file diff --git a/src/com/owncloud/android/ui/fragment/UploadsListFragment.java b/src/com/owncloud/android/ui/fragment/UploadsListFragment.java new file mode 100755 index 00000000000..a68671b8175 --- /dev/null +++ b/src/com/owncloud/android/ui/fragment/UploadsListFragment.java @@ -0,0 +1,192 @@ +/* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * Copyright (C) 2012-2013 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 . + * + */ +package com.owncloud.android.ui.fragment; + +import java.io.File; +import java.util.ArrayList; + +import android.app.Activity; +import android.os.Bundle; +import android.util.SparseBooleanArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ImageView; +import android.widget.ListView; + +import com.owncloud.android.R; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.ui.adapter.UploadsListAdapter; + +/** + * A Fragment that lists all files and folders in a given LOCAL path. + * + * @author LukeOwncloud + * + */ +public class UploadsListFragment extends ExtendedListFragment { + private static final String TAG = "LocalFileListFragment"; + + /** + * Reference to the Activity which this fragment is attached to. For + * callbacks + */ + private UploadsListFragment.ContainerActivity mContainerActivity; + + /** Adapter to connect the data from the directory with the View object */ + private UploadsListAdapter mAdapter = null; + + /** + * {@inheritDoc} + */ + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + mContainerActivity = (ContainerActivity) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + " must implement " + + UploadsListFragment.ContainerActivity.class.getSimpleName()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + Log_OC.i(TAG, "onCreateView() start"); + View v = super.onCreateView(inflater, container, savedInstanceState); + getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + disableSwipe(); // Disable pull refresh +// setMessageForEmptyList(getString(R.string.local_file_list_empty)); + setMessageForEmptyList("No uploads available."); + Log_OC.i(TAG, "onCreateView() end"); + return v; + } + + /** + * {@inheritDoc} + */ + @Override + public void onActivityCreated(Bundle savedInstanceState) { + Log_OC.i(TAG, "onActivityCreated() start"); + + super.onActivityCreated(savedInstanceState); + mAdapter = new UploadsListAdapter(getActivity()); + setListAdapter(mAdapter); + + Log_OC.i(TAG, "onActivityCreated() stop"); + } + + public void selectAll() { + int numberOfFiles = mAdapter.getCount(); + for (int i = 0; i < numberOfFiles; i++) { + File file = (File) mAdapter.getItem(i); + if (file != null) { + if (!file.isDirectory()) { + // / Click on a file + getListView().setItemChecked(i, true); + // notify the change to the container Activity + mContainerActivity.onUploadItemClick(file); + } + } + } + } + + public void deselectAll() { + mAdapter = new UploadsListAdapter(getActivity()); + setListAdapter(mAdapter); + } + + /** + * Checks the file clicked over. Browses inside if it is a directory. + * Notifies the container activity in any case. + */ + @Override + public void onItemClick(AdapterView l, View v, int position, long id) { + File file = (File) mAdapter.getItem(position); + + if (file != null) { + + // notify the click to container Activity + mContainerActivity.onUploadItemClick(file); + + ImageView checkBoxV = (ImageView) v.findViewById(R.id.custom_checkbox); + if (checkBoxV != null) { + if (getListView().isItemChecked(position)) { + checkBoxV.setImageResource(android.R.drawable.checkbox_on_background); + } else { + checkBoxV.setImageResource(android.R.drawable.checkbox_off_background); + } + } + + } else { + Log_OC.w(TAG, "Null object in ListAdapter!!"); + } + } + + /** + * Returns the fule paths to the files checked by the user + * + * @return File paths to the files checked by the user. + */ + public String[] getCheckedFilePaths() { + ArrayList result = new ArrayList(); + SparseBooleanArray positions = mList.getCheckedItemPositions(); + if (positions.size() > 0) { + for (int i = 0; i < positions.size(); i++) { + if (positions.get(positions.keyAt(i)) == true) { + result.add(((File) mList.getItemAtPosition(positions.keyAt(i))).getAbsolutePath()); + } + } + + Log_OC.d(TAG, "Returning " + result.size() + " selected files"); + } + return result.toArray(new String[result.size()]); + } + + /** + * Interface to implement by any Activity that includes some instance of + * UploadsListFragment + * + * @author LukeOwncloud + */ + public interface ContainerActivity { + + /** + * Callback method invoked when an upload item is clicked by the user on + * the upload list + * + * @param file + */ + public void onUploadItemClick(File file); + + /** + * Callback method invoked when the parent activity is fully created to + * get the filter which is to be applied to the upload list. + * + * @return Filter to be applied. Can be null, then all uploads are + * shown. + */ + public File getInitialFilter(); + + } + +} From 79c3756857f3036cf5be90564410511f3e4f04d5 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Fri, 7 Nov 2014 15:32:41 +0100 Subject: [PATCH 13/76] undo unintended change --- res/layout/upload_files_layout.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/layout/upload_files_layout.xml b/res/layout/upload_files_layout.xml index 7ead19d4b32..6c15ff7e526 100644 --- a/res/layout/upload_files_layout.xml +++ b/res/layout/upload_files_layout.xml @@ -28,7 +28,7 @@ android:layout_width="match_parent" android:layout_height="0dip" android:layout_weight="1" - class="com.owncloud.android.ui.fragment.UploadsListFragment" /> + class="com.owncloud.android.ui.fragment.LocalFileListFragment" /> Date: Fri, 7 Nov 2014 15:38:21 +0100 Subject: [PATCH 14/76] consistently use UploadList (instead UploadsList) --- AndroidManifest.xml | 2 +- ...ads_list_item.xml => upload_list_item.xml} | 0 ...list_layout.xml => upload_list_layout.xml} | 4 +- res/menu/main_menu.xml | 2 +- .../ui/activity/FileDisplayActivity.java | 364 +++++++++--------- ...tActivity.java => UploadListActivity.java} | 10 +- ...istAdapter.java => UploadListAdapter.java} | 6 +- ...tFragment.java => UploadListFragment.java} | 30 +- 8 files changed, 209 insertions(+), 209 deletions(-) rename res/layout/{uploads_list_item.xml => upload_list_item.xml} (100%) rename res/layout/{uploads_list_layout.xml => upload_list_layout.xml} (66%) rename src/com/owncloud/android/ui/activity/{UploadsListActivity.java => UploadListActivity.java} (74%) rename src/com/owncloud/android/ui/adapter/{UploadsListAdapter.java => UploadListAdapter.java} (96%) rename src/com/owncloud/android/ui/fragment/{UploadsListFragment.java => UploadListFragment.java} (90%) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 8f1fde1c202..bae563ff28b 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -163,7 +163,7 @@ - + diff --git a/res/layout/uploads_list_item.xml b/res/layout/upload_list_item.xml similarity index 100% rename from res/layout/uploads_list_item.xml rename to res/layout/upload_list_item.xml diff --git a/res/layout/uploads_list_layout.xml b/res/layout/upload_list_layout.xml similarity index 66% rename from res/layout/uploads_list_layout.xml rename to res/layout/upload_list_layout.xml index 894684f168b..0638592b145 100755 --- a/res/layout/uploads_list_layout.xml +++ b/res/layout/upload_list_layout.xml @@ -3,9 +3,9 @@ android:layout_height="match_parent" > + class="com.owncloud.android.ui.fragment.UploadListFragment" /> \ No newline at end of file diff --git a/res/menu/main_menu.xml b/res/menu/main_menu.xml index 19d6a53535a..e8e1c30562e 100644 --- a/res/menu/main_menu.xml +++ b/res/menu/main_menu.xml @@ -56,7 +56,7 @@ android:showAsAction="never" android:title="@string/actionbar_sort"/> mDirectories; private SyncBroadcastReceiver mSyncBroadcastReceiver; @@ -133,7 +133,7 @@ public class FileDisplayActivity extends HookActivity implements public static final int DIALOG_SHORT_WAIT = 0; private static final int DIALOG_CHOOSE_UPLOAD_SOURCE = 1; private static final int DIALOG_CERT_NOT_SAVED = 2; - + public static final String ACTION_DETAILS = "com.owncloud.android.ui.activity.action.DETAILS"; private static final int ACTION_SELECT_CONTENT_FROM_APPS = 1; @@ -146,13 +146,13 @@ public class FileDisplayActivity extends HookActivity implements private static final String TAG_SECOND_FRAGMENT = "SECOND_FRAGMENT"; private OCFile mWaitingToPreview; - + private boolean mSyncInProgress = false; private String DIALOG_UNTRUSTED_CERT; - + private OCFile mWaitingToSend; - + @Override protected void onCreate(Bundle savedInstanceState) { Log_OC.d(TAG, "onCreate() start"); @@ -172,23 +172,23 @@ protected void onCreate(Bundle savedInstanceState) { Intent initObserversIntent = FileObserverService.makeInitIntent(this); startService(initObserversIntent); } - + /// Load of saved instance state if(savedInstanceState != null) { mWaitingToPreview = (OCFile) savedInstanceState.getParcelable(FileDisplayActivity.KEY_WAITING_TO_PREVIEW); mSyncInProgress = savedInstanceState.getBoolean(KEY_SYNC_IN_PROGRESS); mWaitingToSend = (OCFile) savedInstanceState.getParcelable(FileDisplayActivity.KEY_WAITING_TO_SEND); - + } else { mWaitingToPreview = null; mSyncInProgress = false; mWaitingToSend = null; - } + } /// USER INTERFACE // Inflate and set the layout view - setContentView(R.layout.files); + setContentView(R.layout.files); mDualPane = getResources().getBoolean(R.bool.large_land_layout); mLeftFragmentContainer = findViewById(R.id.left_fragment_container); mRightFragmentContainer = findViewById(R.id.right_fragment_container); @@ -200,12 +200,12 @@ protected void onCreate(Bundle savedInstanceState) { mDirectories = new CustomArrayAdapter(this, R.layout.sherlock_spinner_dropdown_item); getSupportActionBar().setHomeButtonEnabled(true); // mandatory since Android ICS, according to the official documentation setSupportProgressBarIndeterminateVisibility(mSyncInProgress /*|| mRefreshSharesInProgress*/); // always AFTER setContentView(...) ; to work around bug in its implementation - + setBackgroundText(); Log_OC.d(TAG, "onCreate() end"); } - + @Override protected void onStart() { super.onStart(); @@ -219,7 +219,7 @@ protected void onDestroy() { /** * Called when the ownCloud {@link Account} associated to the Activity was just updated. - */ + */ @Override protected void onAccountSet(boolean stateWasRecovered) { super.onAccountSet(stateWasRecovered); @@ -245,14 +245,14 @@ protected void onAccountSet(boolean stateWasRecovered) { } setFile(file); setNavigationListWithFolder(file); - + if (!stateWasRecovered) { Log_OC.e(TAG, "Initializing Fragments in onAccountChanged.."); initFragmentsWithFile(); if (file.isFolder()) { startSyncFolderOperation(file, false); } - + } else { updateFragmentsVisibility(!file.isFolder()); updateNavigationElementsInActionBar(file.isFolder() ? null : file); @@ -283,25 +283,25 @@ private void createMinFragments() { transaction.add(R.id.left_fragment_container, listOfFiles, TAG_LIST_OF_FILES); transaction.commit(); } - + private void initFragmentsWithFile() { if (getAccount() != null && getFile() != null) { /// First fragment - OCFileListFragment listOfFiles = getListOfFilesFragment(); + OCFileListFragment listOfFiles = getListOfFilesFragment(); if (listOfFiles != null) { - listOfFiles.listDirectory(getCurrentDir()); + listOfFiles.listDirectory(getCurrentDir()); } else { Log_OC.e(TAG, "Still have a chance to lose the initializacion of list fragment >("); } - + /// Second fragment - OCFile file = getFile(); + OCFile file = getFile(); Fragment secondFragment = chooseInitialSecondFragment(file); if (secondFragment != null) { setSecondFragment(secondFragment); updateFragmentsVisibility(true); updateNavigationElementsInActionBar(file); - + } else { cleanSecondFragment(); } @@ -320,7 +320,7 @@ private void initFragmentsWithFile() { private Fragment chooseInitialSecondFragment(OCFile file) { Fragment secondFragment = null; if (file != null && !file.isFolder()) { - if (file.isDown() && PreviewMediaFragment.canBePreviewed(file) + if (file.isDown() && PreviewMediaFragment.canBePreviewed(file) && file.getLastSyncDateForProperties() > 0 // temporal fix ) { int startPlaybackPosition = getIntent().getIntExtra(PreviewVideoActivity.EXTRA_START_POSITION, 0); @@ -338,9 +338,9 @@ private Fragment chooseInitialSecondFragment(OCFile file) { /** * Replaces the second fragment managed by the activity with the received as * a parameter. - * - * Assumes never will be more than two fragments managed at the same time. - * + * + * Assumes never will be more than two fragments managed at the same time. + * * @param fragment New second Fragment to set. */ private void setSecondFragment(Fragment fragment) { @@ -408,7 +408,7 @@ protected void cleanSecondFragment() { protected void refreshListOfFilesFragment() { OCFileListFragment fileListFragment = getListOfFilesFragment(); - if (fileListFragment != null) { + if (fileListFragment != null) { fileListFragment.listDirectory(); } } @@ -420,7 +420,7 @@ protected void refreshSecondFragment(String downloadEvent, String downloadedRemo FileDetailFragment detailsFragment = (FileDetailFragment) secondFragment; OCFile fileInFragment = detailsFragment.getFile(); if (fileInFragment != null && !downloadedRemotePath.equals(fileInFragment.getRemotePath())) { - // the user browsed to other file ; forget the automatic preview + // the user browsed to other file ; forget the automatic preview mWaitingToPreview = null; } else if (downloadEvent.equals(FileDownloader.getDownloadAddedMessage())) { @@ -472,7 +472,7 @@ public boolean onOptionsItemSelected(MenuItem item) { boolean retval = true; switch (item.getItemId()) { case R.id.action_create_dir: { - CreateFolderDialogFragment dialog = + CreateFolderDialogFragment dialog = CreateFolderDialogFragment.newInstance(getCurrentDir()); dialog.show(getSupportFragmentManager(), "createdirdialog"); break; @@ -495,34 +495,34 @@ public boolean onOptionsItemSelected(MenuItem item) { startActivity(loggerIntent); break; } - case R.id.action_uploads_list: { - Intent uploadListIntent = new Intent(getApplicationContext(),UploadsListActivity.class); + case R.id.action_upload_list: { + Intent uploadListIntent = new Intent(getApplicationContext(),UploadListActivity.class); startActivity(uploadListIntent); break; } case android.R.id.home: { FileFragment second = getSecondFragment(); OCFile currentDir = getCurrentDir(); - if((currentDir != null && currentDir.getParentId() != 0) || - (second != null && second.getFile() != null)) { - onBackPressed(); - + if((currentDir != null && currentDir.getParentId() != 0) || + (second != null && second.getFile() != null)) { + onBackPressed(); + } break; } case R.id.action_sort: { SharedPreferences appPreferences = PreferenceManager .getDefaultSharedPreferences(this); - + // Read sorting order, default to sort by name ascending Integer sortOrder = appPreferences .getInt("sortOrder", FileListListAdapter.SORT_NAME); - + AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.actionbar_sort_title) .setSingleChoiceItems(R.array.actionbar_sortby, sortOrder , new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { - + switch (which){ case 0: sortByName(true); @@ -530,15 +530,15 @@ public void onClick(DialogInterface dialog, int which) { case 1: sortByDate(false); break; - -// TODO re-enable when server-side folder size calculation is available + +// TODO re-enable when server-side folder size calculation is available // case 2: // sortBySize(false); // break; } - + dialog.dismiss(); - + } }); builder.create().show(); @@ -580,18 +580,18 @@ public boolean onNavigationItemSelected(int itemPosition, long itemId) { if (itemPosition != 0) { String targetPath = ""; for (int i=itemPosition; i < mDirectories.getCount() - 1; i++) { - targetPath = mDirectories.getItem(i) + OCFile.PATH_SEPARATOR + targetPath; + targetPath = mDirectories.getItem(i) + OCFile.PATH_SEPARATOR + targetPath; } targetPath = OCFile.PATH_SEPARATOR + targetPath; OCFile targetFolder = getStorageManager().getFileByPath(targetPath); if (targetFolder != null) { browseTo(targetFolder); } - - // the next operation triggers a new call to this method, but it's necessary to - // ensure that the name exposed in the action bar is the current directory when the + + // the next operation triggers a new call to this method, but it's necessary to + // ensure that the name exposed in the action bar is the current directory when the // user selected it in the navigation list - if (getSupportActionBar().getNavigationMode() == ActionBar.NAVIGATION_MODE_LIST && itemPosition != 0) + if (getSupportActionBar().getNavigationMode() == ActionBar.NAVIGATION_MODE_LIST && itemPosition != 0) getSupportActionBar().setSelectedNavigationItem(0); } return true; @@ -609,18 +609,18 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { } else if (requestCode == ACTION_SELECT_MULTIPLE_FILES && (resultCode == RESULT_OK || resultCode == UploadFilesActivity.RESULT_OK_AND_MOVE)) { requestMultipleUpload(data, resultCode); - } else if (requestCode == ACTION_MOVE_FILES && (resultCode == RESULT_OK || + } else if (requestCode == ACTION_MOVE_FILES && (resultCode == RESULT_OK || resultCode == MoveActivity.RESULT_OK_AND_MOVE)){ final Intent fData = data; - final int fResultCode = resultCode; + final int fResultCode = resultCode; getHandler().postDelayed( new Runnable() { @Override public void run() { requestMoveOperation(fData, fResultCode); } - }, + }, DELAY_TO_REQUEST_OPERATION_ON_ACTIVITY_RESULTS ); } @@ -708,7 +708,7 @@ private void requestSimpleUpload(Intent data, int resultCode) { /** * Request the operation for moving the file/folder from one path to another - * + * * @param data Intent received * @param resultCode Result code received */ @@ -720,7 +720,7 @@ private void requestMoveOperation(Intent data, int resultCode) { @Override public void onBackPressed() { - OCFileListFragment listOfFiles = getListOfFilesFragment(); + OCFileListFragment listOfFiles = getListOfFilesFragment(); if (mDualPane || getSecondFragment() == null) { if (listOfFiles != null) { // should never be null, indeed if (mDirectories.getCount() <= 1) { @@ -752,14 +752,14 @@ protected void onSaveInstanceState(Bundle outState) { Log_OC.d(TAG, "onSaveInstanceState() end"); } - + @Override protected void onResume() { super.onResume(); Log_OC.e(TAG, "onResume() start"); - + // refresh list of files refreshListOfFilesFragment(); @@ -783,7 +783,7 @@ protected void onResume() { downloadIntentFilter.addAction(FileDownloader.getDownloadFinishMessage()); mDownloadFinishReceiver = new DownloadFinishReceiver(); registerReceiver(mDownloadFinishReceiver, downloadIntentFilter); - + Log_OC.d(TAG, "onResume() end"); } @@ -804,8 +804,8 @@ protected void onPause() { unregisterReceiver(mDownloadFinishReceiver); mDownloadFinishReceiver = null; } - - + + Log_OC.d(TAG, "onPause() end"); super.onPause(); } @@ -890,7 +890,7 @@ public String getPath(Uri uri) { .getColumnIndexOrThrow(MediaStore.Images.Media.DATA); cursor.moveToFirst(); return cursor.getString(column_index); - } + } return null; } @@ -928,7 +928,7 @@ public View getView(int position, View convertView, ViewGroup parent) { ((TextView) v).setTextColor(getResources().getColorStateList( android.R.color.white)); - + fixRoot((TextView) v ); return v; } @@ -963,27 +963,27 @@ public void onReceive(Context context, Intent intent) { String event = intent.getAction(); Log_OC.d(TAG, "Received broadcast " + event); String accountName = intent.getStringExtra(FileSyncAdapter.EXTRA_ACCOUNT_NAME); - String synchFolderRemotePath = intent.getStringExtra(FileSyncAdapter.EXTRA_FOLDER_PATH); + String synchFolderRemotePath = intent.getStringExtra(FileSyncAdapter.EXTRA_FOLDER_PATH); RemoteOperationResult synchResult = (RemoteOperationResult)intent.getSerializableExtra(FileSyncAdapter.EXTRA_RESULT); - boolean sameAccount = (getAccount() != null && accountName.equals(getAccount().name) && getStorageManager() != null); - + boolean sameAccount = (getAccount() != null && accountName.equals(getAccount().name) && getStorageManager() != null); + if (sameAccount) { - + if (FileSyncAdapter.EVENT_FULL_SYNC_START.equals(event)) { mSyncInProgress = true; - + } else { OCFile currentFile = (getFile() == null) ? null : getStorageManager().getFileByPath(getFile().getRemotePath()); OCFile currentDir = (getCurrentDir() == null) ? null : getStorageManager().getFileByPath(getCurrentDir().getRemotePath()); - + if (currentDir == null) { - // current folder was removed from the server - Toast.makeText( FileDisplayActivity.this, - String.format(getString(R.string.sync_current_folder_was_removed), mDirectories.getItem(0)), + // current folder was removed from the server + Toast.makeText( FileDisplayActivity.this, + String.format(getString(R.string.sync_current_folder_was_removed), mDirectories.getItem(0)), Toast.LENGTH_LONG) .show(); browseToRoot(); - + } else { if (currentFile == null && !getFile().isFolder()) { // currently selected file was removed in the server, and now we know it @@ -999,21 +999,21 @@ public void onReceive(Context context, Intent intent) { } setFile(currentFile); } - + mSyncInProgress = (!FileSyncAdapter.EVENT_FULL_SYNC_END.equals(event) && !SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_SHARES_SYNCED.equals(event)); - + if (SynchronizeFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED. equals(event) && /// TODO refactor and make common - synchResult != null && !synchResult.isSuccess() && - (synchResult.getCode() == ResultCode.UNAUTHORIZED || + synchResult != null && !synchResult.isSuccess() && + (synchResult.getCode() == ResultCode.UNAUTHORIZED || synchResult.isIdPRedirection() || - (synchResult.isException() && synchResult.getException() + (synchResult.isException() && synchResult.getException() instanceof AuthenticatorException))) { OwnCloudClient client = null; try { - OwnCloudAccount ocAccount = + OwnCloudAccount ocAccount = new OwnCloudAccount(getAccount(), context); client = (OwnCloudClientManagerFactory.getDefaultSingleton(). removeClientFor(ocAccount)); @@ -1027,14 +1027,14 @@ public void onReceive(Context context, Intent intent) { } catch (IOException e) { e.printStackTrace(); } - + if (client != null) { OwnCloudCredentials cred = client.getCredentials(); if (cred != null) { AccountManager am = AccountManager.get(context); if (cred.authTokenExpires()) { am.invalidateAuthToken( - getAccount().type, + getAccount().type, cred.getAuthToken() ); } else { @@ -1042,9 +1042,9 @@ public void onReceive(Context context, Intent intent) { } } } - + requestCredentialsUpdate(); - + } } removeStickyBroadcast(intent); @@ -1052,22 +1052,22 @@ public void onReceive(Context context, Intent intent) { setSupportProgressBarIndeterminateVisibility(mSyncInProgress /*|| mRefreshSharesInProgress*/); setBackgroundText(); - + } - + if (synchResult != null) { if (synchResult.getCode().equals(RemoteOperationResult.ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED)) { mLastSslUntrustedServerResult = synchResult; } } } catch (RuntimeException e) { - // avoid app crashes after changing the serial id of RemoteOperationResult + // avoid app crashes after changing the serial id of RemoteOperationResult // in owncloud library with broadcast notifications pending to process removeStickyBroadcast(intent); } } } - + /** * Show a text message on screen view for notifying user if content is * loading or folder is empty @@ -1102,22 +1102,22 @@ public void onReceive(Context context, Intent intent) { String accountName = intent.getStringExtra(FileUploadService.ACCOUNT_NAME); boolean sameAccount = getAccount() != null && accountName.equals(getAccount().name); OCFile currentDir = getCurrentDir(); - boolean isDescendant = (currentDir != null) && (uploadedRemotePath != null) && + boolean isDescendant = (currentDir != null) && (uploadedRemotePath != null) && (uploadedRemotePath.startsWith(currentDir.getRemotePath())); - + if (sameAccount && isDescendant) { refreshListOfFilesFragment(); } - + boolean uploadWasFine = intent.getBooleanExtra(FileUploadService.EXTRA_UPLOAD_RESULT, false); boolean renamedInUpload = getFile().getRemotePath(). equals(intent.getStringExtra(FileUploadService.EXTRA_OLD_REMOTE_PATH)); - boolean sameFile = getFile().getRemotePath().equals(uploadedRemotePath) || + boolean sameFile = getFile().getRemotePath().equals(uploadedRemotePath) || renamedInUpload; FileFragment details = getSecondFragment(); - boolean detailFragmentIsShown = (details != null && + boolean detailFragmentIsShown = (details != null && details instanceof FileDetailFragment); - + if (sameAccount && sameFile && detailFragmentIsShown) { if (uploadWasFine) { setFile(getStorageManager().getFileByPath(uploadedRemotePath)); @@ -1125,10 +1125,10 @@ public void onReceive(Context context, Intent intent) { if (renamedInUpload) { String newName = (new File(uploadedRemotePath)).getName(); Toast msg = Toast.makeText( - context, + context, String.format( - getString(R.string.filedetails_renamed_in_upload_msg), - newName), + getString(R.string.filedetails_renamed_in_upload_msg), + newName), Toast.LENGTH_LONG); msg.show(); } @@ -1137,27 +1137,27 @@ public void onReceive(Context context, Intent intent) { } else { cleanSecondFragment(); } - + // Force the preview if the file is an image if (uploadWasFine && PreviewImageFragment.canBePreviewed(getFile())) { startImagePreview(getFile()); } // TODO what about other kind of previews? } - + } finally { if (intent != null) { removeStickyBroadcast(intent); } } - + } - + } /** * Class waiting for broadcast events from the {@link FielDownloader} service. - * + * * Updates the UI when a download is started or finished, provided that it is relevant for the * current folder. */ @@ -1168,19 +1168,19 @@ public void onReceive(Context context, Intent intent) { boolean sameAccount = isSameAccount(context, intent); String downloadedRemotePath = intent.getStringExtra(FileDownloader.EXTRA_REMOTE_PATH); boolean isDescendant = isDescendant(downloadedRemotePath); - + if (sameAccount && isDescendant) { refreshListOfFilesFragment(); refreshSecondFragment(intent.getAction(), downloadedRemotePath, intent.getBooleanExtra(FileDownloader.EXTRA_DOWNLOAD_RESULT, false)); } - + if (mWaitingToSend != null) { mWaitingToSend = getStorageManager().getFileByPath(mWaitingToSend.getRemotePath()); // Update the file to send - if (mWaitingToSend.isDown()) { + if (mWaitingToSend.isDown()) { sendDownloadedFile(); } } - + } finally { if (intent != null) { removeStickyBroadcast(intent); @@ -1198,10 +1198,10 @@ private boolean isSameAccount(Context context, Intent intent) { return (accountName != null && getAccount() != null && accountName.equals(getAccount().name)); } } - - + + public void browseToRoot() { - OCFileListFragment listOfFiles = getListOfFilesFragment(); + OCFileListFragment listOfFiles = getListOfFilesFragment(); if (listOfFiles != null) { // should never be null, indeed while (mDirectories.getCount() > 1) { popDirname(); @@ -1213,13 +1213,13 @@ public void browseToRoot() { } cleanSecondFragment(); } - - + + public void browseTo(OCFile folder) { if (folder == null || !folder.isFolder()) { throw new IllegalArgumentException("Trying to browse to invalid folder " + folder); } - OCFileListFragment listOfFiles = getListOfFilesFragment(); + OCFileListFragment listOfFiles = getListOfFilesFragment(); if (listOfFiles != null) { setNavigationListWithFolder(folder); listOfFiles.listDirectory(folder); @@ -1234,23 +1234,23 @@ public void browseTo(OCFile folder) { /** * {@inheritDoc} - * + * * Updates action bar and second fragment, if in dual pane mode. */ @Override public void onBrowsedDownTo(OCFile directory) { pushDirname(directory); cleanSecondFragment(); - + // Sync Folder startSyncFolderOperation(directory, false); - + } /** - * Shows the information of the {@link OCFile} received as a + * Shows the information of the {@link OCFile} received as a * parameter in the second fragment. - * + * * @param file {@link OCFile} whose details will be shown */ @Override @@ -1267,13 +1267,13 @@ public void showDetails(OCFile file) { * TODO */ private void updateNavigationElementsInActionBar(OCFile chosenFile) { - ActionBar actionBar = getSupportActionBar(); + ActionBar actionBar = getSupportActionBar(); if (chosenFile == null || mDualPane) { // only list of files - set for browsing through folders OCFile currentDir = getCurrentDir(); boolean noRoot = (currentDir != null && currentDir.getParentId() != 0); actionBar.setDisplayHomeAsUpEnabled(noRoot); - actionBar.setDisplayShowTitleEnabled(!noRoot); + actionBar.setDisplayShowTitleEnabled(!noRoot); if (!noRoot) { actionBar.setTitle(getString(R.string.default_display_name_for_root_folder)); } @@ -1317,7 +1317,7 @@ public void onServiceConnected(ComponentName component, IBinder service) { return; } // a new chance to get the mDownloadBinder through getFileDownloadBinder() - THIS IS A MESS - OCFileListFragment listOfFiles = getListOfFilesFragment(); + OCFileListFragment listOfFiles = getListOfFilesFragment(); if (listOfFiles != null) { listOfFiles.listDirectory(); } @@ -1339,7 +1339,7 @@ public void onServiceDisconnected(ComponentName component) { mUploaderBinder = null; } } - }; + }; @@ -1377,14 +1377,14 @@ public void onCancelCertificate() { /** * Updates the view associated to the activity after the finish of some operation over files * in the current account. - * + * * @param operation Removal operation performed. * @param result Result of the removal. */ @Override public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) { super.onRemoteOperationFinish(operation, result); - + if (operation instanceof RemoveFileOperation) { onRemoveFileOperationFinish((RemoveFileOperation)operation, result); @@ -1396,20 +1396,20 @@ public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationRe } else if (operation instanceof CreateFolderOperation) { onCreateFolderOperationFinish((CreateFolderOperation)operation, result); - + } else if (operation instanceof CreateShareOperation) { onCreateShareOperationFinish((CreateShareOperation) operation, result); - + } else if (operation instanceof UnshareLinkOperation) { onUnshareLinkOperationFinish((UnshareLinkOperation)operation, result); - + } else if (operation instanceof MoveFileOperation) { onMoveFileOperationFinish((MoveFileOperation)operation, result); } - + } - + private void onCreateShareOperationFinish(CreateShareOperation operation, RemoteOperationResult result) { if (result.isSuccess()) { refreshShowDetails(); @@ -1417,49 +1417,49 @@ private void onCreateShareOperationFinish(CreateShareOperation operation, Remote } } - + private void onUnshareLinkOperationFinish(UnshareLinkOperation operation, RemoteOperationResult result) { if (result.isSuccess()) { refreshShowDetails(); refreshListOfFilesFragment(); - + } else if (result.getCode() == ResultCode.SHARE_NOT_FOUND) { cleanSecondFragment(); refreshListOfFilesFragment(); } } - + private void refreshShowDetails() { FileFragment details = getSecondFragment(); if (details != null) { OCFile file = details.getFile(); if (file != null) { - file = getStorageManager().getFileByPath(file.getRemotePath()); + file = getStorageManager().getFileByPath(file.getRemotePath()); if (details instanceof PreviewMediaFragment) { // Refresh OCFile of the fragment ((PreviewMediaFragment) details).updateFile(file); } else { showDetails(file); - } + } } invalidateOptionsMenu(); - } + } } - + /** - * Updates the view associated to the activity after the finish of an operation trying to remove a - * file. - * + * Updates the view associated to the activity after the finish of an operation trying to remove a + * file. + * * @param operation Removal operation performed. * @param result Result of the removal. */ private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOperationResult result) { dismissLoadingDialog(); - - Toast msg = Toast.makeText(this, ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()), - Toast.LENGTH_LONG); + + Toast msg = Toast.makeText(this, ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()), + Toast.LENGTH_LONG); msg.show(); - + if (result.isSuccess()) { OCFile removedFile = operation.getFile(); FileFragment second = getSecondFragment(); @@ -1481,12 +1481,12 @@ private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOp } } } - - + + /** - * Updates the view associated to the activity after the finish of an operation trying to move a + * Updates the view associated to the activity after the finish of an operation trying to move a * file. - * + * * @param operation Move operation performed. * @param result Result of the move operation. */ @@ -1497,9 +1497,9 @@ private void onMoveFileOperationFinish(MoveFileOperation operation, RemoteOperat } else { dismissLoadingDialog(); try { - Toast msg = Toast.makeText(FileDisplayActivity.this, - ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()), - Toast.LENGTH_LONG); + Toast msg = Toast.makeText(FileDisplayActivity.this, + ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()), + Toast.LENGTH_LONG); msg.show(); } catch (NotFoundException e) { @@ -1510,9 +1510,9 @@ private void onMoveFileOperationFinish(MoveFileOperation operation, RemoteOperat /** - * Updates the view associated to the activity after the finish of an operation trying to rename a - * file. - * + * Updates the view associated to the activity after the finish of an operation trying to rename a + * file. + * * @param operation Renaming operation performed. * @param result Result of the renaming. */ @@ -1536,16 +1536,16 @@ private void onRenameFileOperationFinish(RenameFileOperation operation, RemoteOp } } } - + if (getStorageManager().getFileById(renamedFile.getParentId()).equals(getCurrentDir())) { refreshListOfFilesFragment(); } } else { - Toast msg = Toast.makeText(this, ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()), - Toast.LENGTH_LONG); + Toast msg = Toast.makeText(this, ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()), + Toast.LENGTH_LONG); msg.show(); - + if (result.isSslRecoverableException()) { mLastSslUntrustedServerResult = result; showUntrustedCertDialog(mLastSslUntrustedServerResult); @@ -1563,15 +1563,15 @@ private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, getAccount()); startActivity(i); - } - + } + } else { if (operation.transferWasRequested()) { onTransferStateChanged(syncedFile, true, true); - + } else { - Toast msg = Toast.makeText(this, ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()), - Toast.LENGTH_LONG); + Toast msg = Toast.makeText(this, ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()), + Toast.LENGTH_LONG); msg.show(); } } @@ -1579,7 +1579,7 @@ private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation /** * Updates the view associated to the activity after the finish of an operation trying create a new folder - * + * * @param operation Creation operation performed. * @param result Result of the creation. */ @@ -1590,9 +1590,9 @@ private void onCreateFolderOperationFinish(CreateFolderOperation operation, Remo } else { dismissLoadingDialog(); try { - Toast msg = Toast.makeText(FileDisplayActivity.this, - ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()), - Toast.LENGTH_LONG); + Toast msg = Toast.makeText(FileDisplayActivity.this, + ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()), + Toast.LENGTH_LONG); msg.show(); } catch (NotFoundException e) { @@ -1601,7 +1601,7 @@ private void onCreateFolderOperationFinish(CreateFolderOperation operation, Remo } } - + /** * {@inheritDoc} */ @@ -1620,7 +1620,7 @@ public void onTransferStateChanged(OCFile file, boolean downloading, boolean upl } } } - + } @@ -1647,31 +1647,31 @@ private OCFile getCurrentDir() { } return null; } - + public void startSyncFolderOperation(OCFile folder, boolean ignoreETag) { - long currentSyncTime = System.currentTimeMillis(); - + long currentSyncTime = System.currentTimeMillis(); + mSyncInProgress = true; - + // perform folder synchronization - RemoteOperation synchFolderOp = new SynchronizeFolderOperation( folder, - currentSyncTime, + RemoteOperation synchFolderOp = new SynchronizeFolderOperation( folder, + currentSyncTime, false, getFileOperationsHelper().isSharedSupported(), ignoreETag, - getStorageManager(), - getAccount(), + getStorageManager(), + getAccount(), getApplicationContext() ); synchFolderOp.execute(getAccount(), this, null, null); - + setSupportProgressBarIndeterminateVisibility(true); setBackgroundText(); } /** - * Show untrusted cert dialog + * Show untrusted cert dialog */ public void showUntrustedCertDialog(RemoteOperationResult result) { // Show a dialog with the certificate info @@ -1680,7 +1680,7 @@ public void showUntrustedCertDialog(RemoteOperationResult result) { FragmentTransaction ft = fm.beginTransaction(); dialog.show(ft, DIALOG_UNTRUSTED_CERT); } - + private void requestForDownload(OCFile file) { Account account = getAccount(); if (!mDownloaderBinder.isDownloading(account, file)) { @@ -1690,18 +1690,18 @@ private void requestForDownload(OCFile file) { startService(i); } } - + private void sendDownloadedFile(){ getFileOperationsHelper().sendDownloadedFile(mWaitingToSend); mWaitingToSend = null; } - + /** * Requests the download of the received {@link OCFile} , updates the UI * to monitor the download progress and prepares the activity to send the file * when the download finishes. - * + * * @param file {@link OCFile} to download and preview. */ public void startDownloadForSending(OCFile file) { @@ -1710,10 +1710,10 @@ public void startDownloadForSending(OCFile file) { boolean hasSecondFragment = (getSecondFragment()!= null); updateFragmentsVisibility(hasSecondFragment); } - + /** * Opens the image gallery showing the image {@link OCFile} received as parameter. - * + * * @param file Image {@link OCFile} to show. */ public void startImagePreview(OCFile file) { @@ -1721,12 +1721,12 @@ public void startImagePreview(OCFile file) { showDetailsIntent.putExtra(EXTRA_FILE, file); showDetailsIntent.putExtra(EXTRA_ACCOUNT, getAccount()); startActivity(showDetailsIntent); - + } /** * Stars the preview of an already down media {@link OCFile}. - * + * * @param file Media {@link OCFile} to preview. * @param startPlaybackPosition Media position where the playback will be started, in milliseconds. * @param autoplay When 'true', the playback will start without user interactions. @@ -1743,7 +1743,7 @@ public void startMediaPreview(OCFile file, int startPlaybackPosition, boolean au * Requests the download of the received {@link OCFile} , updates the UI * to monitor the download progress and prepares the activity to preview * or open the file when the download finishes. - * + * * @param file {@link OCFile} to download and preview. */ public void startDownloadForPreview(OCFile file) { @@ -1759,7 +1759,7 @@ public void startDownloadForPreview(OCFile file) { public void cancelTransference(OCFile file) { getFileOperationsHelper().cancelTransference(file); - if (mWaitingToPreview != null && + if (mWaitingToPreview != null && mWaitingToPreview.getRemotePath().equals(file.getRemotePath())) { mWaitingToPreview = null; } diff --git a/src/com/owncloud/android/ui/activity/UploadsListActivity.java b/src/com/owncloud/android/ui/activity/UploadListActivity.java similarity index 74% rename from src/com/owncloud/android/ui/activity/UploadsListActivity.java rename to src/com/owncloud/android/ui/activity/UploadListActivity.java index 033925183fa..cbffb6a597e 100755 --- a/src/com/owncloud/android/ui/activity/UploadsListActivity.java +++ b/src/com/owncloud/android/ui/activity/UploadListActivity.java @@ -7,26 +7,26 @@ import com.owncloud.android.R; import com.owncloud.android.db.UploadDbHandler; import com.owncloud.android.ui.errorhandling.ExceptionHandler; -import com.owncloud.android.ui.fragment.UploadsListFragment; +import com.owncloud.android.ui.fragment.UploadListFragment; /** * Activity listing pending, active, and completed uploads. User can delete * completed uploads from view. Content of this list of coming from * {@link UploadDbHandler}. */ -public class UploadsListActivity extends FileActivity implements UploadsListFragment.ContainerActivity { +public class UploadListActivity extends FileActivity implements UploadListFragment.ContainerActivity { - private static final String TAG = "UploadsListActivity"; + private static final String TAG = "UploadListActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler(this)); - setContentView(R.layout.uploads_list_layout); + setContentView(R.layout.upload_list_layout); } // //////////////////////////////////////// - // UploadsListFragment.ContainerActivity + // UploadListFragment.ContainerActivity // //////////////////////////////////////// @Override public void onUploadItemClick(File file) { diff --git a/src/com/owncloud/android/ui/adapter/UploadsListAdapter.java b/src/com/owncloud/android/ui/adapter/UploadListAdapter.java similarity index 96% rename from src/com/owncloud/android/ui/adapter/UploadsListAdapter.java rename to src/com/owncloud/android/ui/adapter/UploadListAdapter.java index fb12b5c6e1c..e15c197685a 100755 --- a/src/com/owncloud/android/ui/adapter/UploadsListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/UploadListAdapter.java @@ -26,12 +26,12 @@ * Filtering possible. * */ -public class UploadsListAdapter extends BaseAdapter implements ListAdapter { +public class UploadListAdapter extends BaseAdapter implements ListAdapter { private Context mContext; private UploadDbObject[] mUploads = null; - public UploadsListAdapter(Context context) { + public UploadListAdapter(Context context) { mContext = context; loadUploadItemsFromDb(); } @@ -74,7 +74,7 @@ public View getView(int position, View convertView, ViewGroup parent) { if (view == null) { LayoutInflater inflator = (LayoutInflater) mContext .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - view = inflator.inflate(R.layout.uploads_list_item, null); + view = inflator.inflate(R.layout.upload_list_item, null); } if (mUploads != null && mUploads.length > position) { UploadDbObject file = mUploads[position]; diff --git a/src/com/owncloud/android/ui/fragment/UploadsListFragment.java b/src/com/owncloud/android/ui/fragment/UploadListFragment.java similarity index 90% rename from src/com/owncloud/android/ui/fragment/UploadsListFragment.java rename to src/com/owncloud/android/ui/fragment/UploadListFragment.java index a68671b8175..40440e61f9d 100755 --- a/src/com/owncloud/android/ui/fragment/UploadsListFragment.java +++ b/src/com/owncloud/android/ui/fragment/UploadListFragment.java @@ -32,25 +32,25 @@ import com.owncloud.android.R; import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.ui.adapter.UploadsListAdapter; +import com.owncloud.android.ui.adapter.UploadListAdapter; /** * A Fragment that lists all files and folders in a given LOCAL path. - * + * * @author LukeOwncloud - * + * */ -public class UploadsListFragment extends ExtendedListFragment { - private static final String TAG = "LocalFileListFragment"; +public class UploadListFragment extends ExtendedListFragment { + private static final String TAG = "UploadListFragment"; /** * Reference to the Activity which this fragment is attached to. For * callbacks */ - private UploadsListFragment.ContainerActivity mContainerActivity; + private UploadListFragment.ContainerActivity mContainerActivity; /** Adapter to connect the data from the directory with the View object */ - private UploadsListAdapter mAdapter = null; + private UploadListAdapter mAdapter = null; /** * {@inheritDoc} @@ -62,7 +62,7 @@ public void onAttach(Activity activity) { mContainerActivity = (ContainerActivity) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement " - + UploadsListFragment.ContainerActivity.class.getSimpleName()); + + UploadListFragment.ContainerActivity.class.getSimpleName()); } } @@ -89,7 +89,7 @@ public void onActivityCreated(Bundle savedInstanceState) { Log_OC.i(TAG, "onActivityCreated() start"); super.onActivityCreated(savedInstanceState); - mAdapter = new UploadsListAdapter(getActivity()); + mAdapter = new UploadListAdapter(getActivity()); setListAdapter(mAdapter); Log_OC.i(TAG, "onActivityCreated() stop"); @@ -111,7 +111,7 @@ public void selectAll() { } public void deselectAll() { - mAdapter = new UploadsListAdapter(getActivity()); + mAdapter = new UploadListAdapter(getActivity()); setListAdapter(mAdapter); } @@ -144,7 +144,7 @@ public void onItemClick(AdapterView l, View v, int position, long id) { /** * Returns the fule paths to the files checked by the user - * + * * @return File paths to the files checked by the user. */ public String[] getCheckedFilePaths() { @@ -164,8 +164,8 @@ public String[] getCheckedFilePaths() { /** * Interface to implement by any Activity that includes some instance of - * UploadsListFragment - * + * UploadListFragment + * * @author LukeOwncloud */ public interface ContainerActivity { @@ -173,7 +173,7 @@ public interface ContainerActivity { /** * Callback method invoked when an upload item is clicked by the user on * the upload list - * + * * @param file */ public void onUploadItemClick(File file); @@ -181,7 +181,7 @@ public interface ContainerActivity { /** * Callback method invoked when the parent activity is fully created to * get the filter which is to be applied to the upload list. - * + * * @return Filter to be applied. Can be null, then all uploads are * shown. */ From 2b85ed59b4f50b84b93e5800a33a863504ddfb47 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Fri, 14 Nov 2014 17:02:58 +0100 Subject: [PATCH 15/76] working on UploadListActivity --- res/layout/upload_list_item.xml | 4 +- .../owncloud/android/db/UploadDbHandler.java | 144 ++++++++++++--- .../owncloud/android/db/UploadDbObject.java | 2 +- .../files/services/FileUploadService.java | 124 ++++++++----- .../android/ui/activity/Preferences.java | 2 +- .../ui/activity/UploadListActivity.java | 3 + .../android/ui/adapter/UploadListAdapter.java | 165 ++++++++++++------ .../ui/fragment/UploadListFragment.java | 3 +- .../android/utils/FileStorageUtils.java | 13 ++ 9 files changed, 328 insertions(+), 132 deletions(-) diff --git a/res/layout/upload_list_item.xml b/res/layout/upload_list_item.xml index 0d788206388..b2d5c836e66 100755 --- a/res/layout/upload_list_item.xml +++ b/res/layout/upload_list_item.xml @@ -46,7 +46,7 @@ android:orientation="vertical" > getAllStoredUploads() { - Cursor c = mDB.query(TABLE_UPLOAD, null, null, null, null, null, null); + return getUploads(null, null); + } + + private List getUploads(String selection, String[] selectionArgs) { + Cursor c = mDB.query(TABLE_UPLOAD, null, selection, selectionArgs, null, null, null); List list = new ArrayList(); if (c.moveToFirst()) { - do { - String file_path = c.getString(c.getColumnIndex("path")); - String uploadObjectString = c.getString(c.getColumnIndex("uploadObject")); - UploadDbObject uploadObject = UploadDbObject.fromString(uploadObjectString); - if(uploadObject == null) { - Log_OC.e(TAG, "Could not deserialize UploadDbObject " + uploadObjectString); - } else { - list.add(uploadObject); - } - } while (c.moveToNext()); + do { + String uploadObjectString = c.getString(c.getColumnIndex("uploadObject")); + UploadDbObject uploadObject = UploadDbObject.fromString(uploadObjectString); + if (uploadObject == null) { + Log_OC.e(TAG, "Could not deserialize UploadDbObject " + uploadObjectString); + } else { + list.add(uploadObject); + } + } while (c.moveToNext()); } return list; } + + public List getAllPendingUploads() { + return getUploads("uploadStatus!=" + UploadStatus.UPLOAD_SUCCEEDED.value, null); + } + + } diff --git a/src/com/owncloud/android/db/UploadDbObject.java b/src/com/owncloud/android/db/UploadDbObject.java index eec093a5b86..85d8753ffc9 100755 --- a/src/com/owncloud/android/db/UploadDbObject.java +++ b/src/com/owncloud/android/db/UploadDbObject.java @@ -17,7 +17,7 @@ import com.owncloud.android.lib.common.utils.Log_OC; /** - * Stores all information in order to start upload. PersistentUploadObject can + * Stores all information in order to start upload operations. PersistentUploadObject can * be stored persistently by {@link UploadDbHandler}. * * @author LukeOwncloud diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index 330bf34b873..f91b36191d6 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -26,6 +26,8 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -59,6 +61,7 @@ import com.owncloud.android.db.UploadDbHandler; import com.owncloud.android.db.UploadDbHandler.UploadStatus; import com.owncloud.android.db.UploadDbObject; +import com.owncloud.android.files.InstantUploadBroadcastReceiver; import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; @@ -176,8 +179,7 @@ public int getValue() { * is being uploaded to {@link UploadFileOperation}. */ private ConcurrentMap mActiveUploads = new ConcurrentHashMap(); - private UploadFileOperation mCurrentUpload = null; - + private NotificationManager mNotificationManager; private NotificationCompat.Builder mNotificationBuilder; @@ -227,16 +229,19 @@ public void onCreate() { mBinder = new FileUploaderBinder(); mConnectivityChangeReceiver = new ConnectivityChangeReceiver(); registerReceiver(mConnectivityChangeReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); - mDb = new UploadDbHandler(this.getBaseContext()); -// mDb.recreateDb(); //for testing only + mDb = UploadDbHandler.getInstance(this.getBaseContext()); + mDb.recreateDb(); //for testing only } public class ConnectivityChangeReceiver extends BroadcastReceiver { @Override public void onReceive(Context arg0, Intent arg1) { - // upload pending wifi only files. - onStartCommand(null, 0, 0); + if(InstantUploadBroadcastReceiver.isOnline(getApplicationContext())) + { + // upload pending wifi only files. + onStartCommand(null, 0, 0); + } } } @@ -271,7 +276,12 @@ public int onStartCommand(Intent intent, int flags, int startId) { // service) or connectivity change was detected. ==> check persistent upload // list. // - List list = mDb.getAllStoredUploads(); + List list = mDb.getAllPendingUploads(); + for (UploadDbObject uploadDbObject : list) { + uploadDbObject.setUploadStatus(UploadStatus.UPLOAD_LATER); + uploadDbObject.setLastResult(null); + mDb.updateUpload(uploadDbObject); + } requestedUploads.addAll(list); } else { @@ -355,7 +365,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { uploadObject.setLocalAction(localAction); uploadObject.setUseWifiOnly(isUseWifiOnly); uploadObject.setUploadStatus(UploadStatus.UPLOAD_LATER); - boolean success = mDb.storeUpload(uploadObject, "upload at " + new Date()); + boolean success = mDb.storeUpload(uploadObject); if(!success) { Log_OC.e(TAG, "Could not add upload to database."); } @@ -500,11 +510,25 @@ public void removeDatatransferProgressListener(OnDatatransferProgressListener li @Override public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, - String fileName) { - String key = buildRemoteName(mCurrentUpload.getAccount(), mCurrentUpload.getFile()); + String localFileName) { + Set> uploads = mActiveUploads.entrySet(); + UploadFileOperation currentUpload = null; + //unfortunately we do not have the remote upload path here, so search through all uploads. + //however, this may lead to problems, if user uploads same file twice to different destinations. + //this can only be fixed by replacing localFileName with remote path. + for (Entry entry : uploads) { + if(entry.getValue().getStoragePath().equals(localFileName)) { + if(currentUpload != null) { + Log_OC.e(TAG, "Found two current uploads with same remote path. Ignore."); + return; + } + currentUpload = entry.getValue(); + } + } + String key = buildRemoteName(currentUpload.getAccount(), currentUpload.getFile()); OnDatatransferProgressListener boundListener = mBoundListeners.get(key); if (boundListener != null) { - boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, fileName); + boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, localFileName); } } @@ -536,7 +560,8 @@ public void handleMessage(Message msg) { if (msg.obj != null) { Iterator it = requestedUploads.iterator(); while (it.hasNext()) { - mService.uploadFile(it.next()); + UploadDbObject uploadObject = it.next(); + mService.uploadFile(uploadObject); } } mService.stopSelf(msg.arg1); @@ -544,13 +569,19 @@ public void handleMessage(Message msg) { } /** - * Core upload method: sends the file(s) to upload + * Core upload method: sends the file(s) to upload. This function blocks until upload succeeded or failed. * * @param uploadDbObject Key to access the upload to perform, contained in * mPendingUploads */ private void uploadFile(UploadDbObject uploadDbObject) { + + if(uploadDbObject.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED) { + Log_OC.w(TAG, "Already succeeded uploadObject was again scheduled for upload. Fix that!"); + return; + } + UploadFileOperation currentUpload = null; synchronized (mActiveUploads) { //How does this work? Is it thread-safe to set mCurrentUpload here? //What happens if other mCurrentUpload is currently in progress? @@ -558,10 +589,16 @@ private void uploadFile(UploadDbObject uploadDbObject) { //It seems that upload does work, however the upload state is not set //back of the first upload when a second upload starts while first is //in progress (yellow up-arrow does not disappear of first upload) - mCurrentUpload = mActiveUploads.get(uploadDbObject.getRemotePath()); + currentUpload = mActiveUploads.get(uploadDbObject.getRemotePath()); //if upload not in progress, start it now - if(mCurrentUpload == null) { + if(currentUpload == null) { + if (uploadDbObject.isUseWifiOnly() + && !InstantUploadBroadcastReceiver.isConnectedViaWiFi(getApplicationContext())) { + Log_OC.d(TAG, "Do not start upload because it is wifi-only."); + return; + } + AccountManager aMgr = AccountManager.get(this); Account account = uploadDbObject.getAccount(getApplicationContext()); String version = aMgr.getUserData(account, Constants.KEY_OC_VERSION); @@ -573,30 +610,29 @@ private void uploadFile(UploadDbObject uploadDbObject) { uploadKey = buildRemoteName(account, uploadDbObject.getRemotePath()); OCFile file = obtainNewOCFileToUpload(uploadDbObject.getRemotePath(), uploadDbObject.getLocalPath(), uploadDbObject.getMimeType()); - mCurrentUpload = new UploadFileOperation(account, file, chunked, uploadDbObject.isForceOverwrite(), + currentUpload = new UploadFileOperation(account, file, chunked, uploadDbObject.isForceOverwrite(), uploadDbObject.getLocalAction(), getApplicationContext()); if (uploadDbObject.isCreateRemoteFolder()) { - mCurrentUpload.setRemoteFolderToBeCreated(); + currentUpload.setRemoteFolderToBeCreated(); } - mActiveUploads.putIfAbsent(uploadKey, mCurrentUpload); // Grants that + mActiveUploads.putIfAbsent(uploadKey, currentUpload); // Grants that // the file only upload once time - mCurrentUpload.addDatatransferProgressListener((FileUploaderBinder) mBinder); + currentUpload.addDatatransferProgressListener((FileUploaderBinder) mBinder); } } - if (mCurrentUpload != null) { + if (currentUpload != null) { - notifyUploadStart(mCurrentUpload); + notifyUploadStart(currentUpload); RemoteOperationResult uploadResult = null, grantResult = null; - UploadFileOperation justFinishedUpload; try { // / prepare client object to send requests to the ownCloud // server - if (mUploadClient == null || !mLastAccount.equals(mCurrentUpload.getAccount())) { - mLastAccount = mCurrentUpload.getAccount(); + if (mUploadClient == null || !mLastAccount.equals(currentUpload.getAccount())) { + mLastAccount = currentUpload.getAccount(); mStorageManager = new FileDataStorageManager(mLastAccount, getContentResolver()); OwnCloudAccount ocAccount = new OwnCloudAccount(mLastAccount, this); mUploadClient = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, this); @@ -604,18 +640,18 @@ private void uploadFile(UploadDbObject uploadDbObject) { // / check the existence of the parent folder for the file to // upload - String remoteParentPath = new File(mCurrentUpload.getRemotePath()).getParent(); + String remoteParentPath = new File(currentUpload.getRemotePath()).getParent(); remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ? remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR; - grantResult = grantFolderExistence(remoteParentPath); + grantResult = grantFolderExistence(currentUpload, remoteParentPath); // / perform the upload if (grantResult.isSuccess()) { OCFile parent = mStorageManager.getFileByPath(remoteParentPath); - mCurrentUpload.getFile().setParentId(parent.getFileId()); - uploadResult = mCurrentUpload.execute(mUploadClient); + currentUpload.getFile().setParentId(parent.getFileId()); + uploadResult = currentUpload.execute(mUploadClient); if (uploadResult.isSuccess()) { - saveUploadedFile(); + saveUploadedFile(currentUpload); } } else { uploadResult = grantResult; @@ -644,8 +680,8 @@ private void uploadFile(UploadDbObject uploadDbObject) { } // notify result - notifyUploadResult(uploadResult, mCurrentUpload); - sendFinalBroadcast(mCurrentUpload, uploadResult); + notifyUploadResult(uploadResult, currentUpload); + sendFinalBroadcast(currentUpload, uploadResult); } @@ -662,11 +698,11 @@ private void uploadFile(UploadDbObject uploadDbObject) { * @return An {@link OCFile} instance corresponding to the folder where the * file will be uploaded. */ - private RemoteOperationResult grantFolderExistence(String pathToGrant) { + private RemoteOperationResult grantFolderExistence(UploadFileOperation currentUpload, 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()) { + && currentUpload.isRemoteFolderToBeCreated()) { SyncOperation syncOp = new CreateFolderOperation(pathToGrant, true); result = syncOp.execute(mUploadClient, mStorageManager); } @@ -710,8 +746,8 @@ private OCFile createLocalFolder(String remotePath) { * * TODO refactor this ugly thing */ - private void saveUploadedFile() { - OCFile file = mCurrentUpload.getFile(); + private void saveUploadedFile(UploadFileOperation currentUpload) { + OCFile file = currentUpload.getFile(); if (file.fileExists()) { file = mStorageManager.getFileById(file.getFileId()); } @@ -720,7 +756,7 @@ private void saveUploadedFile() { // 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(currentUpload.getRemotePath()); RemoteOperationResult result = operation.execute(mUploadClient); if (result.isSuccess()) { updateOCFile(file, (RemoteFile) result.getData().get(0)); @@ -729,8 +765,8 @@ private void saveUploadedFile() { // / maybe this would be better as part of UploadFileOperation... or // maybe all this method - if (mCurrentUpload.wasRenamed()) { - OCFile oldFile = mCurrentUpload.getOldFile(); + if (currentUpload.wasRenamed()) { + OCFile oldFile = currentUpload.getOldFile(); if (oldFile.fileExists()) { oldFile.setStoragePath(null); mStorageManager.saveFile(oldFile); @@ -811,6 +847,8 @@ private void notifyUploadStart(UploadFileOperation upload) { showDetailsIntent, 0)); mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotificationBuilder.build()); + + mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_IN_PROGRESS, null); } /** @@ -857,10 +895,10 @@ private void notifyUploadResult(RemoteOperationResult uploadResult, UploadFileOp mUploadClient = null; // grant that future retries on the same account will get the // fresh credentials + } else { mNotificationBuilder.setContentText(content); - try { String message = uploadResult.getLogMessage() + " errorCode: " + uploadResult.getCode(); Log_OC.e(TAG, message + " Http-Code: " + uploadResult.getHttpCode()); @@ -886,14 +924,16 @@ private void notifyUploadResult(RemoteOperationResult uploadResult, UploadFileOp if (uploadResult.isSuccess()) { - //TODO just edit state of upload. do not delete here. - mDb.removeUpload(mCurrentUpload.getOriginalStoragePath()); + mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_SUCCEEDED, uploadResult); // remove success notification, with a delay of 2 seconds NotificationDelayer.cancelWithDelay(mNotificationManager, R.string.uploader_upload_succeeded_ticker, 2000); - + } else { + mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_FAILED, uploadResult); } + } else { + mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_FAILED, uploadResult); } } diff --git a/src/com/owncloud/android/ui/activity/Preferences.java b/src/com/owncloud/android/ui/activity/Preferences.java index e203f01f6f7..bfa9754a356 100644 --- a/src/com/owncloud/android/ui/activity/Preferences.java +++ b/src/com/owncloud/android/ui/activity/Preferences.java @@ -81,7 +81,7 @@ public class Preferences extends SherlockPreferenceActivity implements AccountMa @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mDbHandler = new UploadDbHandler(getBaseContext()); + mDbHandler = UploadDbHandler.getInstance(getBaseContext()); addPreferencesFromResource(R.xml.preferences); ActionBar actionBar = getSherlock().getActionBar(); diff --git a/src/com/owncloud/android/ui/activity/UploadListActivity.java b/src/com/owncloud/android/ui/activity/UploadListActivity.java index cbffb6a597e..dde07e06c22 100755 --- a/src/com/owncloud/android/ui/activity/UploadListActivity.java +++ b/src/com/owncloud/android/ui/activity/UploadListActivity.java @@ -13,6 +13,9 @@ * Activity listing pending, active, and completed uploads. User can delete * completed uploads from view. Content of this list of coming from * {@link UploadDbHandler}. + * + * @author LukeOwncloud + * */ public class UploadListActivity extends FileActivity implements UploadListFragment.ContainerActivity { diff --git a/src/com/owncloud/android/ui/adapter/UploadListAdapter.java b/src/com/owncloud/android/ui/adapter/UploadListAdapter.java index e15c197685a..e5b59d980a6 100755 --- a/src/com/owncloud/android/ui/adapter/UploadListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/UploadListAdapter.java @@ -5,8 +5,12 @@ import java.util.Arrays; import java.util.Comparator; import java.util.List; +import java.util.Observable; +import java.util.Observer; +import android.app.Activity; import android.content.Context; +import android.database.DataSetObserver; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -19,23 +23,44 @@ import com.owncloud.android.R; import com.owncloud.android.db.UploadDbHandler; import com.owncloud.android.db.UploadDbObject; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.ui.activity.UploadListActivity; import com.owncloud.android.utils.DisplayUtils; +import com.owncloud.android.utils.FileStorageUtils; /** - * This Adapter populates a ListView with following types of uploads: pending, active, completed. - * Filtering possible. + * This Adapter populates a ListView with following types of uploads: pending, + * active, completed. Filtering possible. * */ -public class UploadListAdapter extends BaseAdapter implements ListAdapter { - - private Context mContext; +public class UploadListAdapter extends BaseAdapter implements ListAdapter, Observer { + + private static final String TAG = "UploadListAdapter"; + private Activity mActivity; private UploadDbObject[] mUploads = null; + UploadDbHandler mDb; - public UploadListAdapter(Context context) { - mContext = context; + public UploadListAdapter(Activity context) { + Log_OC.d(TAG, "UploadListAdapter"); + mActivity = context; + mDb = UploadDbHandler.getInstance(mActivity); loadUploadItemsFromDb(); } + @Override + public void registerDataSetObserver(DataSetObserver observer) { + super.registerDataSetObserver(observer); + mDb.addObserver(this); + Log_OC.d(TAG, "registerDataSetObserver"); + } + + @Override + public void unregisterDataSetObserver(DataSetObserver observer) { + super.unregisterDataSetObserver(observer); + mDb.deleteObserver(this); + Log_OC.d(TAG, "unregisterDataSetObserver"); + } + @Override public boolean areAllItemsEnabled() { return true; @@ -72,53 +97,67 @@ public int getItemViewType(int position) { public View getView(int position, View convertView, ViewGroup parent) { View view = convertView; if (view == null) { - LayoutInflater inflator = (LayoutInflater) mContext - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + LayoutInflater inflator = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); view = inflator.inflate(R.layout.upload_list_item, null); } if (mUploads != null && mUploads.length > position) { - UploadDbObject file = mUploads[position]; - - TextView fileName = (TextView) view.findViewById(R.id.Filename); - String name = file.getLocalPath(); + UploadDbObject uploadObject = mUploads[position]; + + TextView fileName = (TextView) view.findViewById(R.id.upload_name); + String name = FileStorageUtils.removeDataFolderPath(uploadObject.getLocalPath()); fileName.setText(name); - -// ImageView fileIcon = (ImageView) view.findViewById(R.id.imageView1); -// if (!file.isDirectory()) { -// fileIcon.setImageResource(R.drawable.file); -// } else { -// fileIcon.setImageResource(R.drawable.ic_menu_archive); -// } + + TextView statusView = (TextView) view.findViewById(R.id.upload_status); + String status = uploadObject.getUploadStatus().toString(); + if (uploadObject.getLastResult() != null && !uploadObject.getLastResult().isSuccess()) { + status += ": " + uploadObject.getLastResult().getLogMessage(); + } + statusView.setText(status); + + // ImageView fileIcon = (ImageView) + // view.findViewById(R.id.imageView1); + // if (!file.isDirectory()) { + // fileIcon.setImageResource(R.drawable.file); + // } else { + // fileIcon.setImageResource(R.drawable.ic_menu_archive); + // } TextView fileSizeV = (TextView) view.findViewById(R.id.file_size); - TextView lastModV = (TextView) view.findViewById(R.id.last_mod); ImageView checkBoxV = (ImageView) view.findViewById(R.id.custom_checkbox); -// if (!file.isDirectory()) { -// fileSizeV.setVisibility(View.VISIBLE); -// fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.length())); -// lastModV.setVisibility(View.VISIBLE); -// lastModV.setText(DisplayUtils.unixTimeToHumanReadable(file.lastModified())); -// ListView parentList = (ListView)parent; -// if (parentList.getChoiceMode() == ListView.CHOICE_MODE_NONE) { -// checkBoxV.setVisibility(View.GONE); -// } else { -// if (parentList.isItemChecked(position)) { -// checkBoxV.setImageResource(android.R.drawable.checkbox_on_background); -// } else { -// checkBoxV.setImageResource(android.R.drawable.checkbox_off_background); -// } -// checkBoxV.setVisibility(View.VISIBLE); -// } -// -// } else { - fileSizeV.setVisibility(View.GONE); - lastModV.setVisibility(View.GONE); - checkBoxV.setVisibility(View.GONE); -// } - - view.findViewById(R.id.imageView2).setVisibility(View.INVISIBLE); // not GONE; the alignment changes; ugly way to keep it + // if (!file.isDirectory()) { + // fileSizeV.setVisibility(View.VISIBLE); + // fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.length())); + // lastModV.setVisibility(View.VISIBLE); + // lastModV.setText(DisplayUtils.unixTimeToHumanReadable(file.lastModified())); + // ListView parentList = (ListView)parent; + // if (parentList.getChoiceMode() == ListView.CHOICE_MODE_NONE) { + // checkBoxV.setVisibility(View.GONE); + // } else { + // if (parentList.isItemChecked(position)) { + // checkBoxV.setImageResource(android.R.drawable.checkbox_on_background); + // } else { + // checkBoxV.setImageResource(android.R.drawable.checkbox_off_background); + // } + // checkBoxV.setVisibility(View.VISIBLE); + // } + // + // } else { + fileSizeV.setVisibility(View.GONE); + checkBoxV.setVisibility(View.GONE); + // } + + view.findViewById(R.id.imageView2).setVisibility(View.INVISIBLE); // not + // GONE; + // the + // alignment + // changes; + // ugly + // way + // to + // keep + // it view.findViewById(R.id.imageView3).setVisibility(View.GONE); - + view.findViewById(R.id.sharedIcon).setVisibility(View.GONE); view.findViewById(R.id.sharedWithMeIcon).setVisibility(View.GONE); } @@ -144,28 +183,40 @@ public boolean isEmpty() { /** * Load upload items from {@link UploadDbHandler}. */ - public void loadUploadItemsFromDb() { - UploadDbHandler mDb = new UploadDbHandler(mContext); + private void loadUploadItemsFromDb() { + Log_OC.d(TAG, "loadUploadItemsFromDb"); List list = mDb.getAllStoredUploads(); mUploads = list.toArray(new UploadDbObject[list.size()]); if (mUploads != null) { Arrays.sort(mUploads, new Comparator() { @Override public int compare(UploadDbObject lhs, UploadDbObject rhs) { -// if (lhs.isDirectory() && !rhs.isDirectory()) { -// return -1; -// } else if (!lhs.isDirectory() && rhs.isDirectory()) { -// return 1; -// } + // if (lhs.isDirectory() && !rhs.isDirectory()) { + // return -1; + // } else if (!lhs.isDirectory() && rhs.isDirectory()) { + // return 1; + // } return compareNames(lhs, rhs); } - + private int compareNames(UploadDbObject lhs, UploadDbObject rhs) { - return lhs.getLocalPath().toLowerCase().compareTo(rhs.getLocalPath().toLowerCase()); + return lhs.getLocalPath().toLowerCase().compareTo(rhs.getLocalPath().toLowerCase()); } - + }); } - notifyDataSetChanged(); + mActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + notifyDataSetChanged(); + } + }); + + } + + @Override + public void update(Observable arg0, Object arg1) { + Log_OC.d(TAG, "update"); + loadUploadItemsFromDb(); } } diff --git a/src/com/owncloud/android/ui/fragment/UploadListFragment.java b/src/com/owncloud/android/ui/fragment/UploadListFragment.java index 40440e61f9d..c265be28330 100755 --- a/src/com/owncloud/android/ui/fragment/UploadListFragment.java +++ b/src/com/owncloud/android/ui/fragment/UploadListFragment.java @@ -92,9 +92,8 @@ public void onActivityCreated(Bundle savedInstanceState) { mAdapter = new UploadListAdapter(getActivity()); setListAdapter(mAdapter); - Log_OC.i(TAG, "onActivityCreated() stop"); } - + public void selectAll() { int numberOfFiles = mAdapter.getCount(); for (int i = 0; i < numberOfFiles; i++) { diff --git a/src/com/owncloud/android/utils/FileStorageUtils.java b/src/com/owncloud/android/utils/FileStorageUtils.java index 2d90836e0ac..cf487ccf0a0 100644 --- a/src/com/owncloud/android/utils/FileStorageUtils.java +++ b/src/com/owncloud/android/utils/FileStorageUtils.java @@ -41,6 +41,19 @@ public class FileStorageUtils { //private static final String LOG_TAG = "FileStorageUtils"; + /** + * Takes a full path to owncloud file and removes beginning which is path to ownload data folder. + * If fullPath does not start with that folder, fullPath is returned as is. + */ + public static final String removeDataFolderPath(String fullPath) { + File sdCard = Environment.getExternalStorageDirectory(); + String dataFolderPath = sdCard.getAbsolutePath() + "/" + MainApp.getDataFolder() + "/"; + if(fullPath.indexOf(dataFolderPath) == 0) { + return fullPath.substring(dataFolderPath.length()); + } + return fullPath; + } + /** * Get local owncloud storage path for accountName. */ From 263164a948c0f3c83e5a56ed29d71273f57747ad Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Sun, 16 Nov 2014 20:44:17 +0100 Subject: [PATCH 16/76] fix: do upload removed files --- .../owncloud/android/db/UploadDbHandler.java | 18 ++++----- .../files/services/FileUploadService.java | 38 ++++++++----------- 2 files changed, 23 insertions(+), 33 deletions(-) diff --git a/src/com/owncloud/android/db/UploadDbHandler.java b/src/com/owncloud/android/db/UploadDbHandler.java index b993cb44621..a6559b6d58b 100644 --- a/src/com/owncloud/android/db/UploadDbHandler.java +++ b/src/com/owncloud/android/db/UploadDbHandler.java @@ -21,19 +21,16 @@ import java.util.List; import java.util.Observable; -import com.owncloud.android.MainApp; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.operations.UploadFileOperation; -import com.owncloud.android.ui.adapter.UploadListAdapter; - import android.content.ContentValues; import android.content.Context; import android.database.Cursor; -import android.database.DatabaseUtils; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import com.owncloud.android.MainApp; +import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.common.utils.Log_OC; + /** * Database helper for storing list of files to be uploaded, including status information for each file. * @@ -63,7 +60,8 @@ public void recreateDb() { } public enum UploadStatus { - UPLOAD_LATER(0), UPLOAD_FAILED(1), UPLOAD_IN_PROGRESS(2), UPLOAD_PAUSED(3), UPLOAD_SUCCEEDED(4); + UPLOAD_LATER(0), UPLOAD_FAILED(1), UPLOAD_IN_PROGRESS(2), UPLOAD_PAUSED(3), UPLOAD_SUCCEEDED(4), UPLOAD_FAILED_GIVE_UP( + 5); private final int value; private UploadStatus(int value) { this.value = value; @@ -303,8 +301,8 @@ private List getUploads(String selection, String[] selectionArgs } public List getAllPendingUploads() { - return getUploads("uploadStatus!=" + UploadStatus.UPLOAD_SUCCEEDED.value, null); + return getUploads("uploadStatus!=" + UploadStatus.UPLOAD_SUCCEEDED.value + " AND uploadStatus!=" + + UploadStatus.UPLOAD_FAILED_GIVE_UP.value, null); } - } diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index f91b36191d6..ecaf89e7700 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -21,7 +21,6 @@ import java.io.File; import java.io.IOException; import java.util.AbstractList; -import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -599,6 +598,13 @@ private void uploadFile(UploadDbObject uploadDbObject) { return; } + if (!new File(uploadDbObject.getLocalPath()).exists()) { + mDb.updateUpload(uploadDbObject.getLocalPath(), UploadStatus.UPLOAD_FAILED_GIVE_UP, + new RemoteOperationResult(ResultCode.FILE_NOT_FOUND)); + Log_OC.d(TAG, "Do not start upload because local file does not exist."); + return; + } + AccountManager aMgr = AccountManager.get(this); Account account = uploadDbObject.getAccount(getApplicationContext()); String version = aMgr.getUserData(account, Constants.KEY_OC_VERSION); @@ -896,27 +902,6 @@ private void notifyUploadResult(RemoteOperationResult uploadResult, UploadFileOp // grant that future retries on the same account will get the // fresh credentials - } else { - mNotificationBuilder.setContentText(content); - - try { - 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); - int updatedFiles = mDb.updateFileState(upload.getOriginalStoragePath(), - UploadDbHandler.UploadStatus.UPLOAD_FAILED, message); - if (updatedFiles == 0) { // update failed - mDb.storeFile(upload.getOriginalStoragePath(), upload.getAccount().name, message); - } - } else { - // TODO: handle other results - } - } finally { - - } - } mNotificationBuilder.setContentText(content); @@ -930,7 +915,14 @@ private void notifyUploadResult(RemoteOperationResult uploadResult, UploadFileOp NotificationDelayer.cancelWithDelay(mNotificationManager, R.string.uploader_upload_succeeded_ticker, 2000); } else { - mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_FAILED, uploadResult); + // TODO: add other cases in which upload attempt is to be + // abandoned. + if (uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED) { + mDb.updateUpload(upload.getOriginalStoragePath(), + UploadDbHandler.UploadStatus.UPLOAD_FAILED_GIVE_UP, uploadResult); + } else { + mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_FAILED, uploadResult); + } } } else { mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_FAILED, uploadResult); From b2d8cecfffcf80fd0a121b7d1a8d0d7c15c8a784 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Sun, 16 Nov 2014 21:35:54 +0100 Subject: [PATCH 17/76] sort by date --- .../owncloud/android/db/UploadDbHandler.java | 39 ++++++++++++------- .../owncloud/android/db/UploadDbObject.java | 13 ++++++- .../files/services/FileUploadService.java | 12 ++++-- .../android/ui/adapter/UploadListAdapter.java | 18 +++++---- 4 files changed, 55 insertions(+), 27 deletions(-) diff --git a/src/com/owncloud/android/db/UploadDbHandler.java b/src/com/owncloud/android/db/UploadDbHandler.java index a6559b6d58b..d53601198e2 100644 --- a/src/com/owncloud/android/db/UploadDbHandler.java +++ b/src/com/owncloud/android/db/UploadDbHandler.java @@ -49,13 +49,13 @@ public class UploadDbHandler extends Observable { //for testing only public void recreateDb() { -// mDB.beginTransaction(); -// try { -// mHelper.onUpgrade(mDB, 0, mDatabaseVersion); -// mDB.setTransactionSuccessful(); -// } finally { -// mDB.endTransaction(); -// } + // mDB.beginTransaction(); + // try { + // mHelper.onUpgrade(mDB, 0, mDatabaseVersion); + // mDB.setTransactionSuccessful(); + // } finally { + // mDB.endTransaction(); + // } } @@ -74,7 +74,6 @@ public int getValue() { private UploadDbHandler(Context context) { mDatabaseName = MainApp.getDBName(); mHelper = new OpenerHelper(context); - mDB = mHelper.getWritableDatabase(); } private static UploadDbHandler me = null; @@ -86,7 +85,8 @@ static public UploadDbHandler getInstance(Context context) { } public void close() { - mDB.close(); + getDB().close(); + setDB(null); me = null; } @@ -205,7 +205,7 @@ public boolean storeUpload(UploadDbObject uploadObject) { cv.put("uploadStatus", uploadObject.getUploadStatus().value); cv.put("uploadObject", uploadObject.toString()); - long result = mDB.insert(TABLE_UPLOAD, null, cv); + long result = getDB().insert(TABLE_UPLOAD, null, cv); Log_OC.d(TAG, "putFileForLater returns with: " + result + " for file: " + uploadObject.getLocalPath()); if (result == 1) { notifyObserversNow(); @@ -232,7 +232,7 @@ public void updateUpload(UploadDbObject uploadDbObject) { * @return 1 if file status was updated, else 0. */ public int updateUpload(String filepath, UploadStatus status, RemoteOperationResult result) { - Cursor c = mDB.query(TABLE_UPLOAD, null, "path=?", new String[] {filepath}, null, null, null); + Cursor c = getDB().query(TABLE_UPLOAD, null, "path=?", new String[] {filepath}, null, null, null); if(c.getCount() != 1) { Log_OC.e(TAG, c.getCount() + " items for path=" + filepath + " available in UploadDb. Expected 1." ); return 0; @@ -248,7 +248,7 @@ public int updateUpload(String filepath, UploadStatus status, RemoteOperationRes ContentValues cv = new ContentValues(); cv.put("uploadStatus", status.value); cv.put("uploadObject", uploadObjectString); - int r = mDB.update(TABLE_UPLOAD, cv, "path=?", new String[] { filepath }); + int r = getDB().update(TABLE_UPLOAD, cv, "path=?", new String[] { filepath }); if (r == 1) { notifyObserversNow(); } else { @@ -274,7 +274,7 @@ public void notifyObserversNow() { * @return true when one or more upload entries were removed */ public boolean removeUpload(String localPath) { - long result = mDB.delete(TABLE_UPLOAD, "path = ?", new String[] { localPath }); + long result = getDB().delete(TABLE_UPLOAD, "path = ?", new String[] { localPath }); Log_OC.d(TABLE_UPLOAD, "delete returns with: " + result + " for file: " + localPath); return result != 0; } @@ -284,7 +284,7 @@ public List getAllStoredUploads() { } private List getUploads(String selection, String[] selectionArgs) { - Cursor c = mDB.query(TABLE_UPLOAD, null, selection, selectionArgs, null, null, null); + Cursor c = getDB().query(TABLE_UPLOAD, null, selection, selectionArgs, null, null, null); List list = new ArrayList(); if (c.moveToFirst()) { do { @@ -304,5 +304,16 @@ public List getAllPendingUploads() { return getUploads("uploadStatus!=" + UploadStatus.UPLOAD_SUCCEEDED.value + " AND uploadStatus!=" + UploadStatus.UPLOAD_FAILED_GIVE_UP.value, null); } + + private SQLiteDatabase getDB() { + if (mDB == null) { + mDB = mHelper.getWritableDatabase(); + } + return mDB; + } + + private void setDB(SQLiteDatabase mDB) { + this.mDB = mDB; + } } diff --git a/src/com/owncloud/android/db/UploadDbObject.java b/src/com/owncloud/android/db/UploadDbObject.java index 85d8753ffc9..dbbca473405 100755 --- a/src/com/owncloud/android/db/UploadDbObject.java +++ b/src/com/owncloud/android/db/UploadDbObject.java @@ -5,6 +5,8 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.util.Calendar; +import java.util.GregorianCalendar; import android.accounts.Account; import android.content.Context; @@ -27,7 +29,7 @@ public class UploadDbObject implements Serializable { /** Generated - should be refreshed every time the class changes!! */ ; - private static final long serialVersionUID = -2306246191385279924L; + private static final long serialVersionUID = -2306246191385279928L; private static final String TAG = "UploadDbObject"; /** @@ -48,6 +50,15 @@ public class UploadDbObject implements Serializable { */ LocalBehaviour localAction; + /** + * Date and time when this upload was first requested. + */ + Calendar uploadTime = new GregorianCalendar(); + + public Calendar getUploadTime() { + return uploadTime; + } + /** * @return the uploadStatus */ diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index ecaf89e7700..4be4c49107c 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -366,9 +366,10 @@ public int onStartCommand(Intent intent, int flags, int startId) { uploadObject.setUploadStatus(UploadStatus.UPLOAD_LATER); boolean success = mDb.storeUpload(uploadObject); if(!success) { - Log_OC.e(TAG, "Could not add upload to database."); + Log_OC.e(TAG, "Could not add upload to database. It is probably a duplicate. Ignore."); + } else { + requestedUploads.add(uploadObject); } - requestedUploads.add(uploadObject); } @@ -518,19 +519,22 @@ public void onTransferProgress(long progressRate, long totalTransferredSoFar, lo for (Entry entry : uploads) { if(entry.getValue().getStoragePath().equals(localFileName)) { if(currentUpload != null) { - Log_OC.e(TAG, "Found two current uploads with same remote path. Ignore."); + Log_OC.e(TAG, "Found two current uploads with same remote path " + localFileName + ". Ignore."); return; } currentUpload = entry.getValue(); } } + if (currentUpload == null) { + Log_OC.e(TAG, "Found no current upload with remote path " + localFileName + ". Ignore."); + return; + } String key = buildRemoteName(currentUpload.getAccount(), currentUpload.getFile()); OnDatatransferProgressListener boundListener = mBoundListeners.get(key); if (boundListener != null) { boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, localFileName); } } - } /** diff --git a/src/com/owncloud/android/ui/adapter/UploadListAdapter.java b/src/com/owncloud/android/ui/adapter/UploadListAdapter.java index e5b59d980a6..ddcff2c0d2b 100755 --- a/src/com/owncloud/android/ui/adapter/UploadListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/UploadListAdapter.java @@ -1,7 +1,5 @@ package com.owncloud.android.ui.adapter; -import java.io.File; -import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; @@ -11,20 +9,19 @@ import android.app.Activity; import android.content.Context; import android.database.DataSetObserver; +import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.ListAdapter; -import android.widget.ListView; import android.widget.TextView; import com.owncloud.android.R; import com.owncloud.android.db.UploadDbHandler; import com.owncloud.android.db.UploadDbObject; import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.ui.activity.UploadListActivity; import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.FileStorageUtils; @@ -104,8 +101,7 @@ public View getView(int position, View convertView, ViewGroup parent) { UploadDbObject uploadObject = mUploads[position]; TextView fileName = (TextView) view.findViewById(R.id.upload_name); - String name = FileStorageUtils.removeDataFolderPath(uploadObject.getLocalPath()); - fileName.setText(name); + fileName.setText(uploadObject.getLocalPath()); TextView statusView = (TextView) view.findViewById(R.id.upload_status); String status = uploadObject.getUploadStatus().toString(); @@ -123,6 +119,9 @@ public View getView(int position, View convertView, ViewGroup parent) { // } TextView fileSizeV = (TextView) view.findViewById(R.id.file_size); + CharSequence dateString = DisplayUtils.getRelativeDateTimeString(mActivity, uploadObject.getUploadTime() + .getTimeInMillis(), DateUtils.SECOND_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, 0); + fileSizeV.setText(dateString); ImageView checkBoxV = (ImageView) view.findViewById(R.id.custom_checkbox); // if (!file.isDirectory()) { // fileSizeV.setVisibility(View.VISIBLE); @@ -142,7 +141,6 @@ public View getView(int position, View convertView, ViewGroup parent) { // } // // } else { - fileSizeV.setVisibility(View.GONE); checkBoxV.setVisibility(View.GONE); // } @@ -196,13 +194,17 @@ public int compare(UploadDbObject lhs, UploadDbObject rhs) { // } else if (!lhs.isDirectory() && rhs.isDirectory()) { // return 1; // } - return compareNames(lhs, rhs); + return compareUploadTime(lhs, rhs); } private int compareNames(UploadDbObject lhs, UploadDbObject rhs) { return lhs.getLocalPath().toLowerCase().compareTo(rhs.getLocalPath().toLowerCase()); } + private int compareUploadTime(UploadDbObject lhs, UploadDbObject rhs) { + return rhs.getUploadTime().compareTo(lhs.getUploadTime()); + } + }); } mActivity.runOnUiThread(new Runnable() { From 2ec1c65bd69246675566cdaf1e8aa9d89eae3880 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Sun, 16 Nov 2014 21:40:49 +0100 Subject: [PATCH 18/76] fix onclick --- .../ui/activity/UploadListActivity.java | 3 ++- .../ui/fragment/UploadListFragment.java | 21 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/com/owncloud/android/ui/activity/UploadListActivity.java b/src/com/owncloud/android/ui/activity/UploadListActivity.java index dde07e06c22..ae2950de9d1 100755 --- a/src/com/owncloud/android/ui/activity/UploadListActivity.java +++ b/src/com/owncloud/android/ui/activity/UploadListActivity.java @@ -6,6 +6,7 @@ import com.owncloud.android.R; import com.owncloud.android.db.UploadDbHandler; +import com.owncloud.android.db.UploadDbObject; import com.owncloud.android.ui.errorhandling.ExceptionHandler; import com.owncloud.android.ui.fragment.UploadListFragment; @@ -32,7 +33,7 @@ protected void onCreate(Bundle savedInstanceState) { // UploadListFragment.ContainerActivity // //////////////////////////////////////// @Override - public void onUploadItemClick(File file) { + public void onUploadItemClick(UploadDbObject file) { // TODO Auto-generated method stub } diff --git a/src/com/owncloud/android/ui/fragment/UploadListFragment.java b/src/com/owncloud/android/ui/fragment/UploadListFragment.java index c265be28330..99828160dda 100755 --- a/src/com/owncloud/android/ui/fragment/UploadListFragment.java +++ b/src/com/owncloud/android/ui/fragment/UploadListFragment.java @@ -31,6 +31,7 @@ import android.widget.ListView; import com.owncloud.android.R; +import com.owncloud.android.db.UploadDbObject; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.ui.adapter.UploadListAdapter; @@ -97,14 +98,12 @@ public void onActivityCreated(Bundle savedInstanceState) { public void selectAll() { int numberOfFiles = mAdapter.getCount(); for (int i = 0; i < numberOfFiles; i++) { - File file = (File) mAdapter.getItem(i); + UploadDbObject file = (UploadDbObject) mAdapter.getItem(i); if (file != null) { - if (!file.isDirectory()) { - // / Click on a file - getListView().setItemChecked(i, true); - // notify the change to the container Activity - mContainerActivity.onUploadItemClick(file); - } + // / Click on a file + getListView().setItemChecked(i, true); + // notify the change to the container Activity + mContainerActivity.onUploadItemClick(file); } } } @@ -120,12 +119,12 @@ public void deselectAll() { */ @Override public void onItemClick(AdapterView l, View v, int position, long id) { - File file = (File) mAdapter.getItem(position); + UploadDbObject uploadDbObject = (UploadDbObject) mAdapter.getItem(position); - if (file != null) { + if (uploadDbObject != null) { // notify the click to container Activity - mContainerActivity.onUploadItemClick(file); + mContainerActivity.onUploadItemClick(uploadDbObject); ImageView checkBoxV = (ImageView) v.findViewById(R.id.custom_checkbox); if (checkBoxV != null) { @@ -175,7 +174,7 @@ public interface ContainerActivity { * * @param file */ - public void onUploadItemClick(File file); + public void onUploadItemClick(UploadDbObject file); /** * Callback method invoked when the parent activity is fully created to From 537434d9ea7b1610586659b5f37f08c5d474d91e Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Sun, 16 Nov 2014 21:44:03 +0100 Subject: [PATCH 19/76] use file icon --- src/com/owncloud/android/ui/adapter/UploadListAdapter.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/com/owncloud/android/ui/adapter/UploadListAdapter.java b/src/com/owncloud/android/ui/adapter/UploadListAdapter.java index ddcff2c0d2b..34385f7f84b 100755 --- a/src/com/owncloud/android/ui/adapter/UploadListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/UploadListAdapter.java @@ -23,7 +23,6 @@ import com.owncloud.android.db.UploadDbObject; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.utils.DisplayUtils; -import com.owncloud.android.utils.FileStorageUtils; /** * This Adapter populates a ListView with following types of uploads: pending, @@ -110,10 +109,9 @@ public View getView(int position, View convertView, ViewGroup parent) { } statusView.setText(status); - // ImageView fileIcon = (ImageView) - // view.findViewById(R.id.imageView1); + ImageView fileIcon = (ImageView) view.findViewById(R.id.imageView1); // if (!file.isDirectory()) { - // fileIcon.setImageResource(R.drawable.file); + fileIcon.setImageResource(R.drawable.file); // } else { // fileIcon.setImageResource(R.drawable.ic_menu_archive); // } From 1096f40b8df3d2f087d7c05fb1768c129c34f00e Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Tue, 25 Nov 2014 16:59:34 +0100 Subject: [PATCH 20/76] making UploadService an IntentService added menu for uploadListActivity created separate ConnectivityActionReceiver --- .classpath | 2 +- AndroidManifest.xml | 7 + res/menu/upload_list_menu.xml | 28 + .../owncloud/android/db/UploadDbHandler.java | 203 ++-- .../owncloud/android/db/UploadDbObject.java | 2 + .../services/ConnectivityActionReceiver.java | 52 ++ .../files/services/FileUploadService.java | 356 +++---- .../android/files/services/FileUploader.java | 883 ++++++++++++++++++ .../ui/activity/UploadFilesActivity.java | 2 +- .../ui/activity/UploadListActivity.java | 32 + 10 files changed, 1305 insertions(+), 262 deletions(-) create mode 100755 res/menu/upload_list_menu.xml create mode 100755 src/com/owncloud/android/files/services/ConnectivityActionReceiver.java create mode 100755 src/com/owncloud/android/files/services/FileUploader.java diff --git a/.classpath b/.classpath index 51769745b2c..151ff042112 100644 --- a/.classpath +++ b/.classpath @@ -3,7 +3,7 @@ - + diff --git a/AndroidManifest.xml b/AndroidManifest.xml index bae563ff28b..15fb9442322 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -174,6 +174,13 @@ + + + + + + diff --git a/res/menu/upload_list_menu.xml b/res/menu/upload_list_menu.xml new file mode 100755 index 00000000000..b5f6013b8fe --- /dev/null +++ b/res/menu/upload_list_menu.xml @@ -0,0 +1,28 @@ + + + + + + + \ No newline at end of file diff --git a/src/com/owncloud/android/db/UploadDbHandler.java b/src/com/owncloud/android/db/UploadDbHandler.java index d53601198e2..144f6efe898 100644 --- a/src/com/owncloud/android/db/UploadDbHandler.java +++ b/src/com/owncloud/android/db/UploadDbHandler.java @@ -32,7 +32,8 @@ import com.owncloud.android.lib.common.utils.Log_OC; /** - * Database helper for storing list of files to be uploaded, including status information for each file. + * Database helper for storing list of files to be uploaded, including status + * information for each file. * * @author Bartek Przybylski * @author LukeOwncloud @@ -47,7 +48,7 @@ public class UploadDbHandler extends Observable { static private final String TAG = "UploadDbHandler"; static private final String TABLE_UPLOAD = "list_of_uploads"; - //for testing only + // for testing only public void recreateDb() { // mDB.beginTransaction(); // try { @@ -60,9 +61,36 @@ public void recreateDb() { } public enum UploadStatus { - UPLOAD_LATER(0), UPLOAD_FAILED(1), UPLOAD_IN_PROGRESS(2), UPLOAD_PAUSED(3), UPLOAD_SUCCEEDED(4), UPLOAD_FAILED_GIVE_UP( - 5); + /** + * Upload scheduled. + */ + UPLOAD_LATER(0), + /** + * Last upload failed. Will retry. + */ + UPLOAD_FAILED_RETRY(1), + /** + * Upload currently in progress. + */ + UPLOAD_IN_PROGRESS(2), + /** + * Upload paused. Has to be manually resumed by user. + */ + UPLOAD_PAUSED(3), + /** + * Upload was successful. + */ + UPLOAD_SUCCEEDED(4), + /** + * Upload failed with some severe reason. Do not retry. + */ + UPLOAD_FAILED_GIVE_UP(5), + /** + * User has cancelled upload. Do not retry. + */ + UPLOAD_CANCELLED(6); private final int value; + private UploadStatus(int value) { this.value = value; } @@ -70,20 +98,21 @@ public int getValue() { return value; } }; - + private UploadDbHandler(Context context) { mDatabaseName = MainApp.getDBName(); mHelper = new OpenerHelper(context); } - + private static UploadDbHandler me = null; + static public UploadDbHandler getInstance(Context context) { - if(me == null) { - me = new UploadDbHandler(context); + if (me == null) { + me = new UploadDbHandler(context); } return me; } - + public void close() { getDB().close(); setDB(null); @@ -92,80 +121,39 @@ public void close() { /** * Store a file persistently (to be uploaded later). + * * @param filepath local file path to file * @param account account for uploading * @param message optional message. can be null. - * @return false if an error occurred, else true. + * @return false if an error occurred, else true. */ public boolean storeFile(String filepath, String account, String message) { - ///OBSOLETE + // /OBSOLETE Log_OC.i(TAG, "obsolete method called"); return false; -// ContentValues cv = new ContentValues(); -// cv.put("path", filepath); -// cv.put("account", account); -// cv.put("attempt", UploadStatus.UPLOAD_STATUS_UPLOAD_LATER.getValue()); -// cv.put("message", message); -// long result = mDB.insert(TABLE_UPLOAD, null, cv); -// Log_OC.d(TABLE_UPLOAD, "putFileForLater returns with: " + result + " for file: " + filepath); -// return result != -1; - } - - /** - * Update upload status of file. - * - * @param filepath local file path to file. used as identifier. - * @param status new status. - * @param message new message. - * @return 1 if file status was updated, else 0. - */ - public int updateFileState(String filepath, UploadStatus status, String message) { - ///OBSOLETE - Log_OC.i(TAG, "obsolete method called"); - return 0; -// ContentValues cv = new ContentValues(); -// cv.put("attempt", status.getValue()); -// cv.put("message", message); -// int result = mDB.update(TABLE_UPLOAD, cv, "path=?", new String[] { filepath }); -// Log_OC.d(TABLE_UPLOAD, "updateFileState returns with: " + result + " for file: " + filepath); -// return result; + // ContentValues cv = new ContentValues(); + // cv.put("path", filepath); + // cv.put("account", account); + // cv.put("attempt", + // UploadStatus.UPLOAD_STATUS_UPLOAD_LATER.getValue()); + // cv.put("message", message); + // long result = mDB.insert(TABLE_UPLOAD, null, cv); + // Log_OC.d(TABLE_UPLOAD, "putFileForLater returns with: " + result + + // " for file: " + filepath); + // return result != -1; } - /** - * Get all files with status {@link UploadStatus}.UPLOAD_STATUS_UPLOAD_LATER. - * @return - */ - public Cursor getAwaitingFiles() { - //OBSOLETE - Log_OC.i(TAG, "obsolete method called"); - return null; -// return mDB.query(TABLE_UPLOAD, null, "attempt=" + UploadStatus.UPLOAD_STATUS_UPLOAD_LATER, null, null, null, null); - } - //ununsed until now. uncomment if needed. -// public Cursor getFailedFiles() { -// return mDB.query(TABLE_INSTANT_UPLOAD, null, "attempt>" + UploadStatus.UPLOAD_STATUS_UPLOAD_LATER, null, null, null, null); -// } + // ununsed until now. uncomment if needed. + // public Cursor getFailedFiles() { + // return mDB.query(TABLE_INSTANT_UPLOAD, null, "attempt>" + + // UploadStatus.UPLOAD_STATUS_UPLOAD_LATER, null, null, null, null); + // } - //ununsed until now. uncomment if needed. -// public void clearFiles() { -// mDB.delete(TABLE_INSTANT_UPLOAD, null, null); -// } - - /** - * Remove file from upload list. Should be called when upload succeed or failed and should not be retried. - * @param localPath - * @return true when one or more pending files was removed - */ - public boolean removeFile(String localPath) { - //OBSOLETE - Log_OC.i(TAG, "obsolete method called"); - return false; -// long result = mDB.delete(TABLE_UPLOAD, "path = ?", new String[] { localPath }); -// Log_OC.d(TABLE_UPLOAD, "delete returns with: " + result + " for file: " + localPath); -// return result != 0; - - } + // ununsed until now. uncomment if needed. + // public void clearFiles() { + // mDB.delete(TABLE_INSTANT_UPLOAD, null, null); + // } private class OpenerHelper extends SQLiteOpenHelper { public OpenerHelper(Context context) { @@ -185,7 +173,10 @@ public void onCreate(SQLiteDatabase db) { @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (newVersion == 4) { - db.execSQL("DROP TABLE IF EXISTS " + "instant_upload" + ";"); //drop old db (name) + db.execSQL("DROP TABLE IF EXISTS " + "instant_upload" + ";"); // drop + // old + // db + // (name) db.execSQL("DROP TABLE IF EXISTS " + TABLE_UPLOAD + ";"); onCreate(db); } @@ -195,6 +186,7 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { /** * Stores an upload object in DB. + * * @param uploadObject * @param message * @return true on success. @@ -204,7 +196,7 @@ public boolean storeUpload(UploadDbObject uploadObject) { cv.put("path", uploadObject.getLocalPath()); cv.put("uploadStatus", uploadObject.getUploadStatus().value); cv.put("uploadObject", uploadObject.toString()); - + long result = getDB().insert(TABLE_UPLOAD, null, cv); Log_OC.d(TAG, "putFileForLater returns with: " + result + " for file: " + uploadObject.getLocalPath()); if (result == 1) { @@ -212,39 +204,39 @@ public boolean storeUpload(UploadDbObject uploadObject) { } else { Log_OC.e(TAG, "Failed to insert item into upload db."); } - return result != -1; + return result != -1; } - /** * Update upload status of file in DB. - * + * @return 1 if file status was updated, else 0. */ - public void updateUpload(UploadDbObject uploadDbObject) { - updateUpload(uploadDbObject.getLocalPath(), uploadDbObject.getUploadStatus(), uploadDbObject.getLastResult()); + public int updateUpload(UploadDbObject uploadDbObject) { + return updateUpload(uploadDbObject.getLocalPath(), uploadDbObject.getUploadStatus(), uploadDbObject.getLastResult()); } - + /** * Update upload status of file. + * * @param filepath filepath local file path to file. used as identifier. - * @param status new status. - * @param result new result of upload operation + * @param status new status. + * @param result new result of upload operation * @return 1 if file status was updated, else 0. */ public int updateUpload(String filepath, UploadStatus status, RemoteOperationResult result) { - Cursor c = getDB().query(TABLE_UPLOAD, null, "path=?", new String[] {filepath}, null, null, null); - if(c.getCount() != 1) { - Log_OC.e(TAG, c.getCount() + " items for path=" + filepath + " available in UploadDb. Expected 1." ); + Cursor c = getDB().query(TABLE_UPLOAD, null, "path=?", new String[] { filepath }, null, null, null); + if (c.getCount() != 1) { + Log_OC.e(TAG, c.getCount() + " items for path=" + filepath + " available in UploadDb. Expected 1."); return 0; } if (c.moveToFirst()) { - //read upload object and update + // read upload object and update String uploadObjectString = c.getString(c.getColumnIndex("uploadObject")); - UploadDbObject uploadObject = UploadDbObject.fromString(uploadObjectString); - uploadObject.setLastResult(result); + UploadDbObject uploadObject = UploadDbObject.fromString(uploadObjectString); uploadObject.setUploadStatus(status); + uploadObject.setLastResult(result); uploadObjectString = uploadObject.toString(); - //store update upload object to db + // store update upload object to db ContentValues cv = new ContentValues(); cv.put("uploadStatus", status.value); cv.put("uploadObject", uploadObjectString); @@ -258,18 +250,21 @@ public int updateUpload(String filepath, UploadStatus status, RemoteOperationRes } return 0; } - + /** - * Should be called when some value of this DB was changed. All observers are informed. + * Should be called when some value of this DB was changed. All observers + * are informed. */ public void notifyObserversNow() { Log_OC.d("UploadListAdapter", "notifyObserversNow"); setChanged(); notifyObservers(); } - + /** - * Remove upload from upload list. Should be called when cleaning up upload list. + * Remove upload from upload list. Should be called when cleaning up upload + * list. + * * @param localPath * @return true when one or more upload entries were removed */ @@ -301,8 +296,11 @@ private List getUploads(String selection, String[] selectionArgs } public List getAllPendingUploads() { - return getUploads("uploadStatus!=" + UploadStatus.UPLOAD_SUCCEEDED.value + " AND uploadStatus!=" - + UploadStatus.UPLOAD_FAILED_GIVE_UP.value, null); + return getUploads("uploadStatus==" + UploadStatus.UPLOAD_LATER.value + " OR uploadStatus==" + + UploadStatus.UPLOAD_FAILED_RETRY.value, null); + } + public List getCurrentUpload() { + return getUploads("uploadStatus==" + UploadStatus.UPLOAD_IN_PROGRESS.value, null); } private SQLiteDatabase getDB() { @@ -315,5 +313,18 @@ private SQLiteDatabase getDB() { private void setDB(SQLiteDatabase mDB) { this.mDB = mDB; } - + + public long cleanDoneUploads() { + String[] where = new String[3]; + where[0] = String.valueOf(UploadStatus.UPLOAD_CANCELLED.value); + where[1] = String.valueOf(UploadStatus.UPLOAD_FAILED_GIVE_UP.value); + where[2] = String.valueOf(UploadStatus.UPLOAD_SUCCEEDED.value); + long result = getDB().delete(TABLE_UPLOAD, "uploadStatus = ? OR uploadStatus = ? OR uploadStatus = ?", where); + Log_OC.d(TABLE_UPLOAD, "delete all done uploads"); + if(result>0) { + notifyObserversNow(); + } + return result; + } + } diff --git a/src/com/owncloud/android/db/UploadDbObject.java b/src/com/owncloud/android/db/UploadDbObject.java index dbbca473405..5e55ae65ce3 100755 --- a/src/com/owncloud/android/db/UploadDbObject.java +++ b/src/com/owncloud/android/db/UploadDbObject.java @@ -67,10 +67,12 @@ public UploadStatus getUploadStatus() { } /** + * Sets uploadStatus AND SETS lastResult = null; * @param uploadStatus the uploadStatus to set */ public void setUploadStatus(UploadStatus uploadStatus) { this.uploadStatus = uploadStatus; + setLastResult(null); } /** diff --git a/src/com/owncloud/android/files/services/ConnectivityActionReceiver.java b/src/com/owncloud/android/files/services/ConnectivityActionReceiver.java new file mode 100755 index 00000000000..2bac48226da --- /dev/null +++ b/src/com/owncloud/android/files/services/ConnectivityActionReceiver.java @@ -0,0 +1,52 @@ +package com.owncloud.android.files.services; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.os.Bundle; +import android.util.Log; + +public class ConnectivityActionReceiver extends BroadcastReceiver { + private static final String TAG = "ConnectivityActionReceiver"; + + @Override + public void onReceive(final Context context, Intent intent) { + if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) { + Log.v(TAG, "action: " + intent.getAction()); + Log.v(TAG, "component: " + intent.getComponent()); + Bundle extras = intent.getExtras(); + if (extras != null) { + for (String key : extras.keySet()) { + Log.v(TAG, "key [" + key + "]: " + extras.get(key)); + } + } else { + Log.v(TAG, "no extras"); + } + } + } + + static public void enable(Context context) { + PackageManager pm = context.getPackageManager(); + ComponentName compName = + new ComponentName(context.getApplicationContext(), + ConnectivityActionReceiver.class); + pm.setComponentEnabledSetting( + compName, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, + PackageManager.DONT_KILL_APP); + } + + static public void disable(Context context) { + PackageManager pm = context.getPackageManager(); + ComponentName compName = + new ComponentName(context.getApplicationContext(), + ConnectivityActionReceiver.class); + pm.setComponentEnabledSetting( + compName, + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP); + } +} \ No newline at end of file diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index 9fd1a308e6e..ba3edf0c8a6 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.IOException; import java.util.AbstractList; +import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -34,6 +35,7 @@ import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.AccountsException; +import android.app.IntentService; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; @@ -93,7 +95,11 @@ * */ @SuppressWarnings("unused") -public class FileUploadService extends Service { +public class FileUploadService extends IntentService { + + public FileUploadService() { + super("FileUploadService"); + } private static final String UPLOAD_FINISH_MESSAGE = "UPLOAD_FINISH"; public static final String EXTRA_UPLOAD_RESULT = "RESULT"; @@ -106,6 +112,9 @@ public class FileUploadService extends Service { public static final String KEY_LOCAL_FILE = "LOCAL_FILE"; public static final String KEY_REMOTE_FILE = "REMOTE_FILE"; public static final String KEY_MIME_TYPE = "MIME_TYPE"; + + public static final String KEY_RETRY = "KEY_RETRY"; + public static final String KEY_ACCOUNT = "ACCOUNT"; @@ -175,10 +184,14 @@ public int getValue() { private UploadDbHandler mDb = null; /** - * List of uploads that currently in progress. Maps from remotePath to where file + * List of uploads that are currently pending. Maps from remotePath to where file * is being uploaded to {@link UploadFileOperation}. */ - private ConcurrentMap mActiveUploads = new ConcurrentHashMap(); + private ConcurrentMap mPendingUploads = new ConcurrentHashMap(); + /** + * {@link UploadFileOperation} object of ongoing upload. Can be null. Note: There can only be one concurrent upload! + */ + private UploadFileOperation mCurrentUpload = null; private NotificationManager mNotificationManager; private NotificationCompat.Builder mNotificationBuilder; @@ -223,7 +236,7 @@ private static boolean chunkedUploadIsSupported(OwnCloudVersion version) { @Override public void onCreate() { super.onCreate(); - Log_OC.i(TAG, "mPendingUploads size:" + mActiveUploads.size()); + Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size()); mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); HandlerThread thread = new HandlerThread("FileUploaderThread", Process.THREAD_PRIORITY_BACKGROUND); thread.start(); @@ -234,6 +247,13 @@ public void onCreate() { registerReceiver(mConnectivityChangeReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); mDb = UploadDbHandler.getInstance(this.getBaseContext()); mDb.recreateDb(); //for testing only + + //when this service starts there is no upload in progress. if db says so, app probably crashed before. + List current = mDb.getCurrentUpload(); + for (UploadDbObject uploadDbObject : current) { + uploadDbObject.setUploadStatus(UploadStatus.UPLOAD_LATER); + mDb.updateUpload(uploadDbObject); + } } public class ConnectivityChangeReceiver extends BroadcastReceiver { @@ -256,48 +276,62 @@ public void onDestroy() { super.onDestroy(); } + /** + * The IntentService calls this method from the default worker thread with + * the intent that started the service. When this method returns, + * IntentService stops the service, as appropriate. + * + * Note: We use an IntentService here. It does not provide simultaneous + * requests, but instead internally queues them and gets them to this + * onHandleIntent method one after another. + * * 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. * - * First, onStartCommand() stores all information associated with the upload + * First, onHandleIntent() stores all information associated with the upload * in a {@link UploadDbObject} which is stored persistently using - * {@link UploadDbHandler}. Then, {@link ServiceHandler} is invoked which - * performs the upload and updates the DB entry (upload success, failure, - * retry, ...) - * - * TODO: correct return values. should not always be NOT_STICKY. + * {@link UploadDbHandler}. Then, the oldest, pending upload from + * {@link UploadDbHandler} is taken and upload is started. */ + @Override - public int onStartCommand(Intent intent, int flags, int startId) { - AbstractList requestedUploads = new Vector(); - if (intent == null) { + protected void onHandleIntent(Intent intent) { + if (intent == null || intent.hasExtra(KEY_RETRY)) { // service was restarted by OS (after return START_STICKY and kill // service) or connectivity change was detected. ==> check persistent upload // list. // List list = mDb.getAllPendingUploads(); for (UploadDbObject uploadDbObject : list) { - uploadDbObject.setUploadStatus(UploadStatus.UPLOAD_LATER); - uploadDbObject.setLastResult(null); - mDb.updateUpload(uploadDbObject); + // store locally. + String uploadKey = buildRemoteName(uploadDbObject.getAccount(getApplicationContext()), + uploadDbObject.getRemotePath()); + UploadDbObject previous = mPendingUploads.putIfAbsent(uploadKey, uploadDbObject); + + if(previous == null) { + // and store persistently. + uploadDbObject.setUploadStatus(UploadStatus.UPLOAD_LATER); + mDb.updateUpload(uploadDbObject); + } else { + //upload already pending. ignore. + } } - requestedUploads.addAll(list); } else { UploadSingleMulti uploadType = (UploadSingleMulti) intent.getSerializableExtra(KEY_UPLOAD_TYPE); if (uploadType == null) { Log_OC.e(TAG, "Incorrect or no upload type provided"); - return Service.START_NOT_STICKY; + return; } Account account = intent.getParcelableExtra(KEY_ACCOUNT); if (!AccountUtils.exists(account, getApplicationContext())) { Log_OC.e(TAG, "KEY_ACCOUNT no set or provided KEY_ACCOUNT does not exist"); - return Service.START_NOT_STICKY; + return; } OCFile[] files = null; @@ -313,7 +347,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { if (!intent.hasExtra(KEY_LOCAL_FILE) || !intent.hasExtra(KEY_REMOTE_FILE)) { Log_OC.e(TAG, "Not enough information provided in intent"); - return Service.START_NOT_STICKY; + return; } String[] localPaths; @@ -330,7 +364,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { } if (localPaths.length != remotePaths.length) { Log_OC.e(TAG, "Different number of remote paths and local paths!"); - return Service.START_NOT_STICKY; + return; } files = new OCFile[localPaths.length]; @@ -341,7 +375,7 @@ public int onStartCommand(Intent intent, int flags, int startId) { if (files[i] == null) { Log_OC.e(TAG, "obtainNewOCFileToUpload() returned null for remotePaths[i]:" + remotePaths[i] + " and localPaths[i]:" + localPaths[i]); - return Service.START_NOT_STICKY; + return; } } } @@ -368,30 +402,40 @@ public int onStartCommand(Intent intent, int flags, int startId) { uploadObject.setLocalAction(localAction); uploadObject.setUseWifiOnly(isUseWifiOnly); uploadObject.setUploadStatus(UploadStatus.UPLOAD_LATER); - boolean success = mDb.storeUpload(uploadObject); - if(!success) { - Log_OC.e(TAG, "Could not add upload to database. It is probably a duplicate. Ignore."); + + String uploadKey = buildRemoteName(uploadObject.getAccount(getApplicationContext()), + uploadObject.getRemotePath()); + UploadDbObject previous = mPendingUploads.putIfAbsent(uploadKey, uploadObject); + + if(previous == null) + { + boolean success = mDb.storeUpload(uploadObject); + if(!success) { + Log_OC.e(TAG, "Could not add upload to database. It might be a duplicate. Ignore."); + } } else { - requestedUploads.add(uploadObject); + //upload already pending. ignore. } } - // TODO check if would be clever to read entries from // UploadDbHandler and add to requestedUploads at this point } - Log_OC.i(TAG, "mPendingUploads size:" + mActiveUploads.size()); - if (requestedUploads.size() > 0) { - Message msg = mServiceHandler.obtainMessage(); - msg.arg1 = startId; - msg.obj = requestedUploads; - mServiceHandler.sendMessage(msg); - return Service.START_STICKY; // there is work to do. If killed this - // service should be restarted - // eventually. - } - return Service.START_NOT_STICKY; //nothing to do. do not restart. + + // at this point mPendingUploads is filled. + + Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size()); + + try { + Iterator it = mPendingUploads.keySet().iterator(); + while (it.hasNext()) { + UploadDbObject uploadDbObject = mPendingUploads.get(it.next()); + boolean uploadSuccessful = uploadFile(uploadDbObject); + } + } catch (ConcurrentModificationException e) { + // for now: ignore. TODO: fix this. + } } /** @@ -436,12 +480,14 @@ public class FileUploaderBinder extends Binder implements OnDatatransferProgress * @param file A file in the queue of pending uploads */ public void cancel(Account account, OCFile file) { - UploadFileOperation upload = null; - synchronized (mActiveUploads) { - upload = mActiveUploads.remove(buildRemoteName(account, file)); - } - if (upload != null) { - upload.cancel(); + UploadDbObject upload = null; + //remove is atomic operation. no need for synchronize. + upload = mPendingUploads.remove(buildRemoteName(account, file)); + upload.setUploadStatus(UploadStatus.UPLOAD_CANCELLED); + mDb.updateUpload(upload); + if(mCurrentUpload != null) { + mCurrentUpload.cancel(); + mCurrentUpload = null; } } @@ -463,17 +509,17 @@ public boolean isUploading(Account account, OCFile file) { if (account == null || file == null) return false; String targetKey = buildRemoteName(account, file); - synchronized (mActiveUploads) { + synchronized (mPendingUploads) { if (file.isFolder()) { // this can be slow if there are many uploads :( - Iterator it = mActiveUploads.keySet().iterator(); + Iterator it = mPendingUploads.keySet().iterator(); boolean found = false; while (it.hasNext() && !found) { found = it.next().startsWith(targetKey); } return found; } else { - return (mActiveUploads.containsKey(targetKey)); + return (mPendingUploads.containsKey(targetKey)); } } } @@ -515,20 +561,8 @@ public void removeDatatransferProgressListener(OnDatatransferProgressListener li @Override public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String localFileName) { - Set> uploads = mActiveUploads.entrySet(); - UploadFileOperation currentUpload = null; - //unfortunately we do not have the remote upload path here, so search through all uploads. - //however, this may lead to problems, if user uploads same file twice to different destinations. - //this can only be fixed by replacing localFileName with remote path. - for (Entry entry : uploads) { - if(entry.getValue().getStoragePath().equals(localFileName)) { - if(currentUpload != null) { - Log_OC.e(TAG, "Found two current uploads with same remote path " + localFileName + ". Ignore."); - return; - } - currentUpload = entry.getValue(); - } - } + Set> uploads = mPendingUploads.entrySet(); + UploadFileOperation currentUpload = mCurrentUpload; if (currentUpload == null) { Log_OC.e(TAG, "Found no current upload with remote path " + localFileName + ". Ignore."); return; @@ -581,124 +615,112 @@ public void handleMessage(Message msg) { * @param uploadDbObject Key to access the upload to perform, contained in * mPendingUploads */ - private void uploadFile(UploadDbObject uploadDbObject) { + private boolean uploadFile(UploadDbObject uploadDbObject) { if(uploadDbObject.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED) { Log_OC.w(TAG, "Already succeeded uploadObject was again scheduled for upload. Fix that!"); - return; + return false; } - UploadFileOperation currentUpload = null; - synchronized (mActiveUploads) { - //How does this work? Is it thread-safe to set mCurrentUpload here? - //What happens if other mCurrentUpload is currently in progress? - // - //It seems that upload does work, however the upload state is not set - //back of the first upload when a second upload starts while first is - //in progress (yellow up-arrow does not disappear of first upload) - currentUpload = mActiveUploads.get(uploadDbObject.getRemotePath()); - - //if upload not in progress, start it now - if(currentUpload == null) { - if (uploadDbObject.isUseWifiOnly() - && !InstantUploadBroadcastReceiver.isConnectedViaWiFi(getApplicationContext())) { - Log_OC.d(TAG, "Do not start upload because it is wifi-only."); - return; - } - - if (!new File(uploadDbObject.getLocalPath()).exists()) { - mDb.updateUpload(uploadDbObject.getLocalPath(), UploadStatus.UPLOAD_FAILED_GIVE_UP, - new RemoteOperationResult(ResultCode.FILE_NOT_FOUND)); - Log_OC.d(TAG, "Do not start upload because local file does not exist."); - return; - } - - AccountManager aMgr = AccountManager.get(this); - Account account = uploadDbObject.getAccount(getApplicationContext()); - String version = aMgr.getUserData(account, Constants.KEY_OC_VERSION); - OwnCloudVersion ocv = new OwnCloudVersion(version); - - boolean chunked = FileUploadService.chunkedUploadIsSupported(ocv); - String uploadKey = null; - - uploadKey = buildRemoteName(account, uploadDbObject.getRemotePath()); - OCFile file = obtainNewOCFileToUpload(uploadDbObject.getRemotePath(), uploadDbObject.getLocalPath(), - uploadDbObject.getMimeType()); - currentUpload = new UploadFileOperation(account, file, chunked, uploadDbObject.isForceOverwrite(), - uploadDbObject.getLocalAction(), getApplicationContext()); - if (uploadDbObject.isCreateRemoteFolder()) { - currentUpload.setRemoteFolderToBeCreated(); - } - mActiveUploads.putIfAbsent(uploadKey, currentUpload); // Grants that - // the file only upload once time - - currentUpload.addDatatransferProgressListener((FileUploaderBinder) mBinder); - } - + if (mCurrentUpload != null) { + Log_OC.e(TAG, + "mCurrentUpload != null. Meaning there is another upload in progress, cannot start a new one. Fix that!"); + return false; } - if (currentUpload != null) { + if (uploadDbObject.isUseWifiOnly() + && !InstantUploadBroadcastReceiver.isConnectedViaWiFi(getApplicationContext())) { + Log_OC.d(TAG, "Do not start upload because it is wifi-only."); + return false; + } - notifyUploadStart(currentUpload); + if (!new File(uploadDbObject.getLocalPath()).exists()) { + mDb.updateUpload(uploadDbObject.getLocalPath(), UploadStatus.UPLOAD_FAILED_GIVE_UP, + new RemoteOperationResult(ResultCode.FILE_NOT_FOUND)); + Log_OC.d(TAG, "Do not start upload because local file does not exist."); + return false; + } - RemoteOperationResult uploadResult = null, grantResult = null; - try { - // / prepare client object to send requests to the ownCloud - // server - if (mUploadClient == null || !mLastAccount.equals(currentUpload.getAccount())) { - mLastAccount = currentUpload.getAccount(); - mStorageManager = new FileDataStorageManager(mLastAccount, getContentResolver()); - OwnCloudAccount ocAccount = new OwnCloudAccount(mLastAccount, this); - mUploadClient = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, this); - } + AccountManager aMgr = AccountManager.get(this); + Account account = uploadDbObject.getAccount(getApplicationContext()); + String version = aMgr.getUserData(account, Constants.KEY_OC_VERSION); + OwnCloudVersion ocv = new OwnCloudVersion(version); + + boolean chunked = FileUploadService.chunkedUploadIsSupported(ocv); + String uploadKey = null; + + uploadKey = buildRemoteName(account, uploadDbObject.getRemotePath()); + OCFile file = obtainNewOCFileToUpload(uploadDbObject.getRemotePath(), uploadDbObject.getLocalPath(), + uploadDbObject.getMimeType()); + mCurrentUpload = new UploadFileOperation(account, file, chunked, uploadDbObject.isForceOverwrite(), + uploadDbObject.getLocalAction(), getApplicationContext()); + if (uploadDbObject.isCreateRemoteFolder()) { + mCurrentUpload.setRemoteFolderToBeCreated(); + } + mCurrentUpload.addDatatransferProgressListener((FileUploaderBinder) mBinder); + + 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); + mUploadClient = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, this); + } - // / check the existence of the parent folder for the file to - // upload - String remoteParentPath = new File(currentUpload.getRemotePath()).getParent(); - remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ? remoteParentPath - : remoteParentPath + OCFile.PATH_SEPARATOR; - grantResult = grantFolderExistence(currentUpload, remoteParentPath); - - // / perform the upload - if (grantResult.isSuccess()) { - OCFile parent = mStorageManager.getFileByPath(remoteParentPath); - currentUpload.getFile().setParentId(parent.getFileId()); - uploadResult = currentUpload.execute(mUploadClient); - if (uploadResult.isSuccess()) { - saveUploadedFile(currentUpload); - } - } else { - uploadResult = grantResult; + // / 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(mCurrentUpload, 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(mCurrentUpload); } + } 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 (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); + } catch (IOException e) { + Log_OC.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e); + uploadResult = new RemoteOperationResult(e); - } finally { - synchronized (mActiveUploads) { - mActiveUploads.remove(uploadDbObject); - 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; - } + } finally { + synchronized (mPendingUploads) { + mPendingUploads.remove(uploadDbObject); + 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, currentUpload); - sendFinalBroadcast(currentUpload, uploadResult); - } + // notify result + notifyUploadResult(uploadResult, mCurrentUpload); + sendFinalBroadcast(mCurrentUpload, uploadResult); + + mPendingUploads.remove(mCurrentUpload.getRemotePath()); + mCurrentUpload = null; + + return true; } /** @@ -937,11 +959,11 @@ private void notifyUploadResult(RemoteOperationResult uploadResult, UploadFileOp mDb.updateUpload(upload.getOriginalStoragePath(), UploadDbHandler.UploadStatus.UPLOAD_FAILED_GIVE_UP, uploadResult); } else { - mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_FAILED, uploadResult); + mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_FAILED_RETRY, uploadResult); } } } else { - mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_FAILED, uploadResult); + mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_FAILED_RETRY, uploadResult); } } @@ -981,4 +1003,10 @@ private boolean isPdfFileFromContentProviderWithoutExtension(String localPath, S !localPath.endsWith(FILE_EXTENSION_PDF); } + public static void retry(Context context) { + Intent i = new Intent(context, FileUploadService.class); + i.putExtra(FileUploadService.KEY_RETRY, true); + context.startService(i); + } + } diff --git a/src/com/owncloud/android/files/services/FileUploader.java b/src/com/owncloud/android/files/services/FileUploader.java new file mode 100755 index 00000000000..0480440242e --- /dev/null +++ b/src/com/owncloud/android/files/services/FileUploader.java @@ -0,0 +1,883 @@ +/* ownCloud Android client application + * Copyright (C) 2012 Bartek Przybylski + * Copyright (C) 2012-2013 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 . + * + */ + +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.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.support.v4.app.NotificationCompat; +import android.webkit.MimeTypeMap; + +import com.owncloud.android.R; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.authentication.AuthenticatorActivity; +import com.owncloud.android.datamodel.FileDataStorageManager; +import com.owncloud.android.datamodel.OCFile; +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; +import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation; +import com.owncloud.android.lib.resources.files.FileUtils; +import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation; +import com.owncloud.android.lib.resources.files.RemoteFile; +import com.owncloud.android.lib.resources.status.OwnCloudVersion; +import com.owncloud.android.notifications.NotificationBuilderWithProgressBar; +import com.owncloud.android.notifications.NotificationDelayer; +import com.owncloud.android.operations.CreateFolderOperation; +import com.owncloud.android.operations.UploadFileOperation; +import com.owncloud.android.operations.common.SyncOperation; +import com.owncloud.android.ui.activity.FileActivity; +import com.owncloud.android.ui.activity.FileDisplayActivity; +import com.owncloud.android.utils.ErrorMessageAdapter; +import com.owncloud.android.utils.UriUtils; + + + +public class FileUploader extends Service implements OnDatatransferProgressListener { + + 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 ACCOUNT_NAME = "ACCOUNT_NAME"; + + public static final String KEY_FILE = "FILE"; + public static final String KEY_LOCAL_FILE = "LOCAL_FILE"; + public static final String KEY_REMOTE_FILE = "REMOTE_FILE"; + public static final String KEY_MIME_TYPE = "MIME_TYPE"; + + public static final String KEY_ACCOUNT = "ACCOUNT"; + + public static final String KEY_UPLOAD_TYPE = "UPLOAD_TYPE"; + public static final String KEY_FORCE_OVERWRITE = "KEY_FORCE_OVERWRITE"; + public static final String KEY_INSTANT_UPLOAD = "INSTANT_UPLOAD"; + public static final String KEY_LOCAL_BEHAVIOUR = "BEHAVIOUR"; + + public static final int LOCAL_BEHAVIOUR_COPY = 0; + public static final int LOCAL_BEHAVIOUR_MOVE = 1; + public static final int LOCAL_BEHAVIOUR_FORGET = 2; + + public static final int UPLOAD_SINGLE_FILE = 0; + public static final int UPLOAD_MULTIPLE_FILES = 1; + + private static final String TAG = FileUploader.class.getSimpleName(); + + private Looper mServiceLooper; + private ServiceHandler mServiceHandler; + private IBinder mBinder; + private OwnCloudClient mUploadClient = null; + private Account mLastAccount = null; + private FileDataStorageManager mStorageManager; + + private ConcurrentMap mPendingUploads = new ConcurrentHashMap(); + private UploadFileOperation mCurrentUpload = null; + + private NotificationManager mNotificationManager; + private NotificationCompat.Builder mNotificationBuilder; + private int mLastPercent; + + 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; + } + + /** + * 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. + */ + private static boolean chunkedUploadIsSupported(OwnCloudVersion version) { + return (version != null && version.compareTo(OwnCloudVersion.owncloud_v4_5) >= 0); + } + + /** + * Service initialization + */ + @Override + public void onCreate() { + super.onCreate(); + Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size()); + mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + HandlerThread thread = new HandlerThread("FileUploaderThread", Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + mServiceLooper = thread.getLooper(); + mServiceHandler = new ServiceHandler(mServiceLooper, this); + mBinder = new FileUploaderBinder(); + } + + /** + * 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) { + 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); + if (!AccountUtils.exists(account, getApplicationContext())) { + return Service.START_NOT_STICKY; + } + + 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, Constants.KEY_OC_VERSION); + OwnCloudVersion ocv = new OwnCloudVersion(version); + + 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()); + 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; + } + + /** + * 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. + */ + @Override + public IBinder onBind(Intent arg0) { + return mBinder; + } + + /** + * Called when ALL the bound clients were onbound. + */ + @Override + public boolean onUnbind(Intent intent) { + ((FileUploaderBinder)mBinder).clearListeners(); + return false; // not accepting rebinding (default behaviour) + } + + + /** + * 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 + */ + 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 + */ + public void cancel(Account account, OCFile file) { + UploadFileOperation upload = null; + synchronized (mPendingUploads) { + upload = mPendingUploads.remove(buildRemoteName(account, file)); + } + if (upload != null) { + upload.cancel(); + } + } + + + + 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 + */ + 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)); + } + } + } + + + /** + * 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. + */ + 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. + */ + 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) { + mBoundListeners.remove(targetKey); + } + } + + + @Override + 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); + } + } + + } + + /** + * 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()}. + */ + 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 + FileUploader mService; + + public ServiceHandler(Looper looper, FileUploader service) { + super(looper); + if (service == null) + throw new IllegalArgumentException("Received invalid NULL in parameter 'service'"); + mService = service; + } + + @Override + public void handleMessage(Message msg) { + @SuppressWarnings("unchecked") + AbstractList requestedUploads = (AbstractList) msg.obj; + if (msg.obj != null) { + Iterator it = requestedUploads.iterator(); + while (it.hasNext()) { + mService.uploadFile(it.next()); + } + } + 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 + */ + 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()); + OwnCloudAccount ocAccount = new OwnCloudAccount(mLastAccount, 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(); + } + } 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); + + } + + } + + /** + * 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. + */ + 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()) { + SyncOperation syncOp = new CreateFolderOperation( pathToGrant, true); + result = syncOp.execute(mUploadClient, mStorageManager); + } + 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; + } + + + private OCFile createLocalFolder(String remotePath) { + String parentPath = new File(remotePath).getParent(); + parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR; + OCFile parent = mStorageManager.getFileByPath(parentPath); + if (parent == null) { + parent = createLocalFolder(parentPath); + } + if (parent != null) { + OCFile createdFolder = new OCFile(remotePath); + createdFolder.setMimetype("DIR"); + createdFolder.setParentId(parent.getFileId()); + mStorageManager.saveFile(createdFolder); + return createdFolder; + } + 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 + */ + 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() + } + file.setNeedsUpdateThumbnail(true); + mStorageManager.saveFile(file); + } + + private void updateOCFile(OCFile file, RemoteFile remoteFile) { + file.setCreationTimestamp(remoteFile.getCreationTimestamp()); + file.setFileLength(remoteFile.getLength()); + file.setMimetype(remoteFile.getMimeType()); + file.setModificationTimestamp(remoteFile.getModifiedTimestamp()); + file.setModificationTimestampAtLastSyncForData(remoteFile.getModifiedTimestamp()); + // file.setEtag(remoteFile.getEtag()); // TODO Etag, where available + file.setRemoteId(remoteFile.getRemoteId()); + } + + private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType, + FileDataStorageManager storageManager) { + + // MIME type + if (mimeType == null || mimeType.length() <= 0) { + try { + 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); + } + } + if (mimeType == null) { + mimeType = "application/octet-stream"; + } + + if (isPdfFileFromContentProviderWithoutExtension(localPath, mimeType)){ + remotePath += FILE_EXTENSION_PDF; + } + + OCFile newFile = new OCFile(remotePath); + newFile.setStoragePath(localPath); + newFile.setLastSyncDateForProperties(0); + newFile.setLastSyncDateForData(0); + + // size + if (localPath != null && localPath.length() > 0) { + File localFile = new File(localPath); + 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 + + + newFile.setMimetype(mimeType); + + return newFile; + } + + /** + * 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 = + NotificationBuilderWithProgressBar.newNotificationBuilderWithProgressBar(this); + mNotificationBuilder + .setOngoing(true) + .setSmallIcon(R.drawable.notification_icon) + .setTicker(getString(R.string.uploader_upload_in_progress_ticker)) + .setContentTitle(getString(R.string.uploader_upload_in_progress_ticker)) + .setProgress(100, 0, false) + .setContentText( + String.format(getString(R.string.uploader_upload_in_progress_content), 0, upload.getFileName())); + + /// 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); + mNotificationBuilder.setContentIntent(PendingIntent.getActivity( + this, (int) System.currentTimeMillis(), showDetailsIntent, 0 + )); + + mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotificationBuilder.build()); + } + + /** + * Callback method to update the progress bar in the status notification + */ + @Override + 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 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()); + } + mLastPercent = percent; + } + + /** + * 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()); + // / 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; + + // check credentials error + boolean needsToUpdateCredentials = ( + uploadResult.getCode() == ResultCode.UNAUTHORIZED || + uploadResult.isIdPRedirection() + ); + 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); + + content = ErrorMessageAdapter.getErrorCauseMessage( + uploadResult, upload, getResources() + ); + + 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_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 + )); + + 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 { + 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(); + } + } + } + } + + 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, + 2000); + + } + } + } + + /** + * 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 if content provider, using the content:// scheme, returns a file with mime-type + * 'application/pdf' but file has not extension + * @param localPath + * @param mimeType + * @return true if is needed to add the pdf file extension to the file + */ + private boolean isPdfFileFromContentProviderWithoutExtension(String localPath, String mimeType) { + return localPath.startsWith(UriUtils.URI_CONTENT_SCHEME) && + mimeType.equals(MIME_TYPE_PDF) && + !localPath.endsWith(FILE_EXTENSION_PDF); + } + +} diff --git a/src/com/owncloud/android/ui/activity/UploadFilesActivity.java b/src/com/owncloud/android/ui/activity/UploadFilesActivity.java index 3b66d4e3d4e..a6d5688df97 100644 --- a/src/com/owncloud/android/ui/activity/UploadFilesActivity.java +++ b/src/com/owncloud/android/ui/activity/UploadFilesActivity.java @@ -50,7 +50,7 @@ /** * Displays local files and let the user choose which file to upload to the * current ownCloud account. Selected files are sent back to the caller as Extra - * named EXTRA_CHOSEN_FILES. Thus, thus activity does not perform the upload + * named EXTRA_CHOSEN_FILES. Thus, this activity does not perform the upload * itself. (It should thus be renamed to FileUploadChooserActivity or something) * * @author David A. Velasco diff --git a/src/com/owncloud/android/ui/activity/UploadListActivity.java b/src/com/owncloud/android/ui/activity/UploadListActivity.java index ae2950de9d1..403716ac64f 100755 --- a/src/com/owncloud/android/ui/activity/UploadListActivity.java +++ b/src/com/owncloud/android/ui/activity/UploadListActivity.java @@ -4,9 +4,13 @@ import android.os.Bundle; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; import com.owncloud.android.R; import com.owncloud.android.db.UploadDbHandler; import com.owncloud.android.db.UploadDbObject; +import com.owncloud.android.files.services.FileUploadService; import com.owncloud.android.ui.errorhandling.ExceptionHandler; import com.owncloud.android.ui.fragment.UploadListFragment; @@ -43,5 +47,33 @@ public File getInitialFilter() { // TODO Auto-generated method stub return null; } + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + boolean retval = true; + switch (item.getItemId()) { + case R.id.action_retry_uploads: { + FileUploadService.retry(this); + break; + } + case R.id.action_clear_upload_list: { + UploadDbHandler db = UploadDbHandler.getInstance(this); + db.cleanDoneUploads(); + break; + } + default: + retval = super.onOptionsItemSelected(item); + } + return retval; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getSherlock().getMenuInflater(); + inflater.inflate(R.menu.upload_list_menu, menu); + return true; + } + + } From 71c1bfb0d1ce7f8b02607a711c0792fa8fe698ee Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Tue, 25 Nov 2014 17:01:29 +0100 Subject: [PATCH 21/76] removed obsolete file --- .classpath | 1 - .../android/files/services/FileUploader.java | 883 ------------------ 2 files changed, 884 deletions(-) delete mode 100755 src/com/owncloud/android/files/services/FileUploader.java diff --git a/.classpath b/.classpath index 151ff042112..7c76757205a 100644 --- a/.classpath +++ b/.classpath @@ -3,7 +3,6 @@ - diff --git a/src/com/owncloud/android/files/services/FileUploader.java b/src/com/owncloud/android/files/services/FileUploader.java deleted file mode 100755 index 0480440242e..00000000000 --- a/src/com/owncloud/android/files/services/FileUploader.java +++ /dev/null @@ -1,883 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2012 Bartek Przybylski - * Copyright (C) 2012-2013 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 . - * - */ - -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.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Intent; -import android.os.Binder; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.Process; -import android.support.v4.app.NotificationCompat; -import android.webkit.MimeTypeMap; - -import com.owncloud.android.R; -import com.owncloud.android.authentication.AccountUtils; -import com.owncloud.android.authentication.AuthenticatorActivity; -import com.owncloud.android.datamodel.FileDataStorageManager; -import com.owncloud.android.datamodel.OCFile; -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; -import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation; -import com.owncloud.android.lib.resources.files.FileUtils; -import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation; -import com.owncloud.android.lib.resources.files.RemoteFile; -import com.owncloud.android.lib.resources.status.OwnCloudVersion; -import com.owncloud.android.notifications.NotificationBuilderWithProgressBar; -import com.owncloud.android.notifications.NotificationDelayer; -import com.owncloud.android.operations.CreateFolderOperation; -import com.owncloud.android.operations.UploadFileOperation; -import com.owncloud.android.operations.common.SyncOperation; -import com.owncloud.android.ui.activity.FileActivity; -import com.owncloud.android.ui.activity.FileDisplayActivity; -import com.owncloud.android.utils.ErrorMessageAdapter; -import com.owncloud.android.utils.UriUtils; - - - -public class FileUploader extends Service implements OnDatatransferProgressListener { - - 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 ACCOUNT_NAME = "ACCOUNT_NAME"; - - public static final String KEY_FILE = "FILE"; - public static final String KEY_LOCAL_FILE = "LOCAL_FILE"; - public static final String KEY_REMOTE_FILE = "REMOTE_FILE"; - public static final String KEY_MIME_TYPE = "MIME_TYPE"; - - public static final String KEY_ACCOUNT = "ACCOUNT"; - - public static final String KEY_UPLOAD_TYPE = "UPLOAD_TYPE"; - public static final String KEY_FORCE_OVERWRITE = "KEY_FORCE_OVERWRITE"; - public static final String KEY_INSTANT_UPLOAD = "INSTANT_UPLOAD"; - public static final String KEY_LOCAL_BEHAVIOUR = "BEHAVIOUR"; - - public static final int LOCAL_BEHAVIOUR_COPY = 0; - public static final int LOCAL_BEHAVIOUR_MOVE = 1; - public static final int LOCAL_BEHAVIOUR_FORGET = 2; - - public static final int UPLOAD_SINGLE_FILE = 0; - public static final int UPLOAD_MULTIPLE_FILES = 1; - - private static final String TAG = FileUploader.class.getSimpleName(); - - private Looper mServiceLooper; - private ServiceHandler mServiceHandler; - private IBinder mBinder; - private OwnCloudClient mUploadClient = null; - private Account mLastAccount = null; - private FileDataStorageManager mStorageManager; - - private ConcurrentMap mPendingUploads = new ConcurrentHashMap(); - private UploadFileOperation mCurrentUpload = null; - - private NotificationManager mNotificationManager; - private NotificationCompat.Builder mNotificationBuilder; - private int mLastPercent; - - 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; - } - - /** - * 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. - */ - private static boolean chunkedUploadIsSupported(OwnCloudVersion version) { - return (version != null && version.compareTo(OwnCloudVersion.owncloud_v4_5) >= 0); - } - - /** - * Service initialization - */ - @Override - public void onCreate() { - super.onCreate(); - Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size()); - mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - HandlerThread thread = new HandlerThread("FileUploaderThread", Process.THREAD_PRIORITY_BACKGROUND); - thread.start(); - mServiceLooper = thread.getLooper(); - mServiceHandler = new ServiceHandler(mServiceLooper, this); - mBinder = new FileUploaderBinder(); - } - - /** - * 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) { - 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); - if (!AccountUtils.exists(account, getApplicationContext())) { - return Service.START_NOT_STICKY; - } - - 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, Constants.KEY_OC_VERSION); - OwnCloudVersion ocv = new OwnCloudVersion(version); - - 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()); - 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; - } - - /** - * 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. - */ - @Override - public IBinder onBind(Intent arg0) { - return mBinder; - } - - /** - * Called when ALL the bound clients were onbound. - */ - @Override - public boolean onUnbind(Intent intent) { - ((FileUploaderBinder)mBinder).clearListeners(); - return false; // not accepting rebinding (default behaviour) - } - - - /** - * 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 - */ - 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 - */ - public void cancel(Account account, OCFile file) { - UploadFileOperation upload = null; - synchronized (mPendingUploads) { - upload = mPendingUploads.remove(buildRemoteName(account, file)); - } - if (upload != null) { - upload.cancel(); - } - } - - - - 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 - */ - 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)); - } - } - } - - - /** - * 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. - */ - 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. - */ - 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) { - mBoundListeners.remove(targetKey); - } - } - - - @Override - 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); - } - } - - } - - /** - * 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()}. - */ - 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 - FileUploader mService; - - public ServiceHandler(Looper looper, FileUploader service) { - super(looper); - if (service == null) - throw new IllegalArgumentException("Received invalid NULL in parameter 'service'"); - mService = service; - } - - @Override - public void handleMessage(Message msg) { - @SuppressWarnings("unchecked") - AbstractList requestedUploads = (AbstractList) msg.obj; - if (msg.obj != null) { - Iterator it = requestedUploads.iterator(); - while (it.hasNext()) { - mService.uploadFile(it.next()); - } - } - 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 - */ - 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()); - OwnCloudAccount ocAccount = new OwnCloudAccount(mLastAccount, 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(); - } - } 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); - - } - - } - - /** - * 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. - */ - 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()) { - SyncOperation syncOp = new CreateFolderOperation( pathToGrant, true); - result = syncOp.execute(mUploadClient, mStorageManager); - } - 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; - } - - - private OCFile createLocalFolder(String remotePath) { - String parentPath = new File(remotePath).getParent(); - parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR; - OCFile parent = mStorageManager.getFileByPath(parentPath); - if (parent == null) { - parent = createLocalFolder(parentPath); - } - if (parent != null) { - OCFile createdFolder = new OCFile(remotePath); - createdFolder.setMimetype("DIR"); - createdFolder.setParentId(parent.getFileId()); - mStorageManager.saveFile(createdFolder); - return createdFolder; - } - 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 - */ - 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() - } - file.setNeedsUpdateThumbnail(true); - mStorageManager.saveFile(file); - } - - private void updateOCFile(OCFile file, RemoteFile remoteFile) { - file.setCreationTimestamp(remoteFile.getCreationTimestamp()); - file.setFileLength(remoteFile.getLength()); - file.setMimetype(remoteFile.getMimeType()); - file.setModificationTimestamp(remoteFile.getModifiedTimestamp()); - file.setModificationTimestampAtLastSyncForData(remoteFile.getModifiedTimestamp()); - // file.setEtag(remoteFile.getEtag()); // TODO Etag, where available - file.setRemoteId(remoteFile.getRemoteId()); - } - - private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType, - FileDataStorageManager storageManager) { - - // MIME type - if (mimeType == null || mimeType.length() <= 0) { - try { - 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); - } - } - if (mimeType == null) { - mimeType = "application/octet-stream"; - } - - if (isPdfFileFromContentProviderWithoutExtension(localPath, mimeType)){ - remotePath += FILE_EXTENSION_PDF; - } - - OCFile newFile = new OCFile(remotePath); - newFile.setStoragePath(localPath); - newFile.setLastSyncDateForProperties(0); - newFile.setLastSyncDateForData(0); - - // size - if (localPath != null && localPath.length() > 0) { - File localFile = new File(localPath); - 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 - - - newFile.setMimetype(mimeType); - - return newFile; - } - - /** - * 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 = - NotificationBuilderWithProgressBar.newNotificationBuilderWithProgressBar(this); - mNotificationBuilder - .setOngoing(true) - .setSmallIcon(R.drawable.notification_icon) - .setTicker(getString(R.string.uploader_upload_in_progress_ticker)) - .setContentTitle(getString(R.string.uploader_upload_in_progress_ticker)) - .setProgress(100, 0, false) - .setContentText( - String.format(getString(R.string.uploader_upload_in_progress_content), 0, upload.getFileName())); - - /// 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); - mNotificationBuilder.setContentIntent(PendingIntent.getActivity( - this, (int) System.currentTimeMillis(), showDetailsIntent, 0 - )); - - mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotificationBuilder.build()); - } - - /** - * Callback method to update the progress bar in the status notification - */ - @Override - 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 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()); - } - mLastPercent = percent; - } - - /** - * 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()); - // / 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; - - // check credentials error - boolean needsToUpdateCredentials = ( - uploadResult.getCode() == ResultCode.UNAUTHORIZED || - uploadResult.isIdPRedirection() - ); - 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); - - content = ErrorMessageAdapter.getErrorCauseMessage( - uploadResult, upload, getResources() - ); - - 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_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 - )); - - 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 { - 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(); - } - } - } - } - - 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, - 2000); - - } - } - } - - /** - * 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 if content provider, using the content:// scheme, returns a file with mime-type - * 'application/pdf' but file has not extension - * @param localPath - * @param mimeType - * @return true if is needed to add the pdf file extension to the file - */ - private boolean isPdfFileFromContentProviderWithoutExtension(String localPath, String mimeType) { - return localPath.startsWith(UriUtils.URI_CONTENT_SCHEME) && - mimeType.equals(MIME_TYPE_PDF) && - !localPath.endsWith(FILE_EXTENSION_PDF); - } - -} From b1e56545396f38312474022848e327cb97b7570c Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Tue, 25 Nov 2014 21:16:30 +0100 Subject: [PATCH 22/76] undo classpath change --- .classpath | 1 + 1 file changed, 1 insertion(+) diff --git a/.classpath b/.classpath index 7c76757205a..72895a2990d 100644 --- a/.classpath +++ b/.classpath @@ -4,5 +4,6 @@ + From df7ca603d5f8ae9a9aafe89b20ad3ae4f176a993 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Tue, 25 Nov 2014 21:16:43 +0100 Subject: [PATCH 23/76] fixing double upload issues --- .../services/ConnectivityActionReceiver.java | 6 ++ .../files/services/FileUploadService.java | 96 ++++--------------- 2 files changed, 22 insertions(+), 80 deletions(-) diff --git a/src/com/owncloud/android/files/services/ConnectivityActionReceiver.java b/src/com/owncloud/android/files/services/ConnectivityActionReceiver.java index 2bac48226da..73ba2c7148b 100755 --- a/src/com/owncloud/android/files/services/ConnectivityActionReceiver.java +++ b/src/com/owncloud/android/files/services/ConnectivityActionReceiver.java @@ -9,6 +9,8 @@ import android.os.Bundle; import android.util.Log; +import com.owncloud.android.files.InstantUploadBroadcastReceiver; + public class ConnectivityActionReceiver extends BroadcastReceiver { private static final String TAG = "ConnectivityActionReceiver"; @@ -25,6 +27,10 @@ public void onReceive(final Context context, Intent intent) { } else { Log.v(TAG, "no extras"); } + + if (InstantUploadBroadcastReceiver.isOnline(context)) { + FileUploadService.retry(context); + } } } diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index ba3edf0c8a6..822f36a8c40 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -20,7 +20,6 @@ import java.io.File; import java.io.IOException; -import java.util.AbstractList; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.Iterator; @@ -28,7 +27,6 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; -import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -38,18 +36,12 @@ import android.app.IntentService; import android.app.NotificationManager; import android.app.PendingIntent; -import android.app.Service; -import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; -import android.net.ConnectivityManager; import android.os.Binder; -import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; -import android.os.Message; import android.os.Process; import android.support.v4.app.NotificationCompat; import android.webkit.MimeTypeMap; @@ -174,9 +166,7 @@ public int getValue() { private static final String TAG = FileUploadService.class.getSimpleName(); private Looper mServiceLooper; - private ServiceHandler mServiceHandler; private IBinder mBinder; - private ConnectivityChangeReceiver mConnectivityChangeReceiver; private OwnCloudClient mUploadClient = null; private Account mLastAccount = null; private FileDataStorageManager mStorageManager; @@ -218,6 +208,10 @@ private String buildRemoteName(Account account, String remotePath) { return account.name + remotePath; } + private String buildRemoteName(UploadDbObject uploadDbObject) { + return uploadDbObject.getAccountName() + uploadDbObject.getRemotePath(); + } + /** * Checks if an ownCloud server version should support chunked uploads. * @@ -241,10 +235,7 @@ public void onCreate() { HandlerThread thread = new HandlerThread("FileUploaderThread", Process.THREAD_PRIORITY_BACKGROUND); thread.start(); mServiceLooper = thread.getLooper(); - mServiceHandler = new ServiceHandler(mServiceLooper, this); mBinder = new FileUploaderBinder(); - mConnectivityChangeReceiver = new ConnectivityChangeReceiver(); - registerReceiver(mConnectivityChangeReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); mDb = UploadDbHandler.getInstance(this.getBaseContext()); mDb.recreateDb(); //for testing only @@ -256,27 +247,6 @@ public void onCreate() { } } - public class ConnectivityChangeReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context arg0, Intent arg1) { - if(InstantUploadBroadcastReceiver.isOnline(getApplicationContext())) - { - // upload pending wifi only files. - onStartCommand(null, 0, 0); - } - } - - } - - @Override - public void onDestroy() { - mDb.close(); - unregisterReceiver(mConnectivityChangeReceiver); - super.onDestroy(); - } - - /** * The IntentService calls this method from the default worker thread with * the intent that started the service. When this method returns, @@ -308,8 +278,7 @@ protected void onHandleIntent(Intent intent) { List list = mDb.getAllPendingUploads(); for (UploadDbObject uploadDbObject : list) { // store locally. - String uploadKey = buildRemoteName(uploadDbObject.getAccount(getApplicationContext()), - uploadDbObject.getRemotePath()); + String uploadKey = buildRemoteName(uploadDbObject); UploadDbObject previous = mPendingUploads.putIfAbsent(uploadKey, uploadDbObject); if(previous == null) { @@ -403,8 +372,7 @@ protected void onHandleIntent(Intent intent) { uploadObject.setUseWifiOnly(isUseWifiOnly); uploadObject.setUploadStatus(UploadStatus.UPLOAD_LATER); - String uploadKey = buildRemoteName(uploadObject.getAccount(getApplicationContext()), - uploadObject.getRemotePath()); + String uploadKey = buildRemoteName(uploadObject); UploadDbObject previous = mPendingUploads.putIfAbsent(uploadKey, uploadObject); if(previous == null) @@ -414,7 +382,9 @@ protected void onHandleIntent(Intent intent) { Log_OC.e(TAG, "Could not add upload to database. It might be a duplicate. Ignore."); } } else { - //upload already pending. ignore. + Log_OC.w(TAG, "FileUploadService got upload intent for file which is already queued: " + + uploadObject.getRemotePath()); + // upload already pending. ignore. } } @@ -576,44 +546,12 @@ public void onTransferProgress(long progressRate, long totalTransferredSoFar, lo } /** - * Upload worker. Performs the pending uploads in the order they were - * requested. - * - * Created with the Looper of a new thread, started in - * {@link FileUploadService#onCreate()}. - */ - 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 - FileUploadService mService; - - public ServiceHandler(Looper looper, FileUploadService service) { - super(looper); - if (service == null) - throw new IllegalArgumentException("Received invalid NULL in parameter 'service'"); - mService = service; - } - - @Override - public void handleMessage(Message msg) { - @SuppressWarnings("unchecked") - AbstractList requestedUploads = (AbstractList) msg.obj; - if (msg.obj != null) { - Iterator it = requestedUploads.iterator(); - while (it.hasNext()) { - UploadDbObject uploadObject = it.next(); - mService.uploadFile(uploadObject); - } - } - mService.stopSelf(msg.arg1); - } - } - - /** - * Core upload method: sends the file(s) to upload. This function blocks until upload succeeded or failed. + * Core upload method: sends the file(s) to upload. This function blocks + * until upload succeeded or failed. * * @param uploadDbObject Key to access the upload to perform, contained in * mPendingUploads + * @return true on success. */ private boolean uploadFile(UploadDbObject uploadDbObject) { @@ -700,10 +638,8 @@ private boolean uploadFile(UploadDbObject uploadDbObject) { uploadResult = new RemoteOperationResult(e); } finally { - synchronized (mPendingUploads) { - mPendingUploads.remove(uploadDbObject); - Log_OC.i(TAG, "Remove CurrentUploadItem from pending upload Item Map."); - } + mPendingUploads.remove(buildRemoteName(uploadDbObject)); + 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 @@ -717,12 +653,12 @@ private boolean uploadFile(UploadDbObject uploadDbObject) { notifyUploadResult(uploadResult, mCurrentUpload); sendFinalBroadcast(mCurrentUpload, uploadResult); - mPendingUploads.remove(mCurrentUpload.getRemotePath()); mCurrentUpload = null; - return true; + return uploadResult.isSuccess(); } + /** * Checks the existence of the folder where the current file will be * uploaded both in the remote server and in the local database. From 07147e8cb6f2394e554be6e58d924db6799f6be6 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Wed, 26 Nov 2014 07:32:29 +0100 Subject: [PATCH 24/76] start FileUploadService on app start add comments filter duplicate photos in InstantUploadBroadcastReceiver --- .../files/InstantUploadBroadcastReceiver.java | 20 ++++++++++++++++--- .../services/ConnectivityActionReceiver.java | 16 +++++++++++++-- .../files/services/FileUploadService.java | 18 +++++++++++++---- 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java index 8cf7f55ddaa..677fdfc788e 100644 --- a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java +++ b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java @@ -18,6 +18,8 @@ package com.owncloud.android.files; +import java.util.Map.Entry; + import android.accounts.Account; import android.content.BroadcastReceiver; import android.content.Context; @@ -50,6 +52,7 @@ public class InstantUploadBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { + android.os.Debug.waitForDebugger(); Log_OC.d(TAG, "Received: " + intent.getAction()); if (intent.getAction().equals(NEW_PHOTO_ACTION_UNOFFICIAL)) { handleNewPictureAction(context, intent); @@ -58,13 +61,19 @@ public void onReceive(Context context, Intent intent) { handleNewPictureAction(context, intent); Log_OC.d(TAG, "OFFICIAL processed: android.hardware.action.NEW_PICTURE"); } else if (intent.getAction().equals(NEW_VIDEO_ACTION)) { - Log_OC.d(TAG, "OFFICIAL processed: android.hardware.action.NEW_VIDEO"); handleNewVideoAction(context, intent); + Log_OC.d(TAG, "OFFICIAL processed: android.hardware.action.NEW_VIDEO"); } else { Log_OC.e(TAG, "Incorrect intent sent: " + intent.getAction()); } } + /** + * Because we support NEW_PHOTO_ACTION and NEW_PHOTO_ACTION_UNOFFICIAL it can happen that + * handleNewPictureAction is called twice for the same photo. Use this simple static variable to + * remember last uploaded photo to filter duplicates. Must not be null! + */ + static String lastUploadedPhotoPath = ""; private void handleNewPictureAction(Context context, Intent intent) { Cursor c = null; String file_path = null; @@ -95,8 +104,13 @@ private void handleNewPictureAction(Context context, Intent intent) { mime_type = c.getString(c.getColumnIndex(Images.Media.MIME_TYPE)); c.close(); - Log_OC.d(TAG, file_path + ""); - + if(file_path.equals(lastUploadedPhotoPath)) { + Log_OC.d(TAG, "Duplicate detected: " + file_path + ". Ignore."); + return; + } + lastUploadedPhotoPath = file_path; + Log_OC.d(TAG, "Path: " + file_path + ""); + Intent i = new Intent(context, FileUploadService.class); i.putExtra(FileUploadService.KEY_ACCOUNT, account); i.putExtra(FileUploadService.KEY_LOCAL_FILE, file_path); diff --git a/src/com/owncloud/android/files/services/ConnectivityActionReceiver.java b/src/com/owncloud/android/files/services/ConnectivityActionReceiver.java index 73ba2c7148b..da3e5758711 100755 --- a/src/com/owncloud/android/files/services/ConnectivityActionReceiver.java +++ b/src/com/owncloud/android/files/services/ConnectivityActionReceiver.java @@ -11,12 +11,24 @@ import com.owncloud.android.files.InstantUploadBroadcastReceiver; +/** + * Receives all connectivity action from Android OS at all times and performs required OC actions. + * For now that are: + * - Signal connectivity to {@link FileUploadService}. + * + * Later can be added: + * - Signal connectivity to download service, deletion service, ... + * - Handle offline mode (cf. https://github.com/owncloud/android/issues/162) + * + * @author LukeOwncloud + * + */ public class ConnectivityActionReceiver extends BroadcastReceiver { private static final String TAG = "ConnectivityActionReceiver"; @Override public void onReceive(final Context context, Intent intent) { - if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) { +// if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) { Log.v(TAG, "action: " + intent.getAction()); Log.v(TAG, "component: " + intent.getComponent()); Bundle extras = intent.getExtras(); @@ -31,7 +43,7 @@ public void onReceive(final Context context, Intent intent) { if (InstantUploadBroadcastReceiver.isOnline(context)) { FileUploadService.retry(context); } - } +// } } static public void enable(Context context) { diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index 822f36a8c40..371439294b4 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -79,9 +79,15 @@ import com.owncloud.android.utils.UriUtils; /** - * Service for uploading files. Invoke using context.startService(...). This - * service retries until upload succeeded. Files to be uploaded are stored - * persistent using {@link UploadDbHandler}. + * Service for uploading files. Invoke using context.startService(...). Files to + * be uploaded are stored persistently using {@link UploadDbHandler}. + * + * On next invocation of {@link FileUploadService} uploaded files which + * previously failed will be uploaded again until either upload succeeded or a + * fatal error occured. + * + * Every file passed to this service is uploaded. No filtering is performed. + * However, Intent keys (e.g., KEY_WIFI_ONLY) are obeyed. * * @author LukeOwncloud * @@ -244,7 +250,11 @@ public void onCreate() { for (UploadDbObject uploadDbObject : current) { uploadDbObject.setUploadStatus(UploadStatus.UPLOAD_LATER); mDb.updateUpload(uploadDbObject); - } + } + + if(InstantUploadBroadcastReceiver.isOnline(getApplicationContext())) { + FileUploadService.retry(getApplicationContext()); + } } /** From e0e9a7c6ceb7d76c4f72195e545b812a7c61f3e2 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Wed, 26 Nov 2014 07:44:04 +0100 Subject: [PATCH 25/76] remove debug cmd --- .../owncloud/android/files/InstantUploadBroadcastReceiver.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java index 677fdfc788e..27a5b860559 100644 --- a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java +++ b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java @@ -52,7 +52,6 @@ public class InstantUploadBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - android.os.Debug.waitForDebugger(); Log_OC.d(TAG, "Received: " + intent.getAction()); if (intent.getAction().equals(NEW_PHOTO_ACTION_UNOFFICIAL)) { handleNewPictureAction(context, intent); From 8f768d8224e6d33826d38ca7178fdba4f6c39cb5 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Wed, 26 Nov 2014 10:02:04 +0100 Subject: [PATCH 26/76] added click action for UploadListActivity --- .../files/services/FileUploadService.java | 1 + .../ui/activity/UploadListActivity.java | 38 ++++++++++++++++++- .../android/utils/FileStorageUtils.java | 10 +++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index 371439294b4..ecb6a8bd125 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -184,6 +184,7 @@ public int getValue() { * is being uploaded to {@link UploadFileOperation}. */ private ConcurrentMap mPendingUploads = new ConcurrentHashMap(); + /** * {@link UploadFileOperation} object of ongoing upload. Can be null. Note: There can only be one concurrent upload! */ diff --git a/src/com/owncloud/android/ui/activity/UploadListActivity.java b/src/com/owncloud/android/ui/activity/UploadListActivity.java index 403716ac64f..77c7ecb1b54 100755 --- a/src/com/owncloud/android/ui/activity/UploadListActivity.java +++ b/src/com/owncloud/android/ui/activity/UploadListActivity.java @@ -2,17 +2,23 @@ import java.io.File; +import android.content.Intent; import android.os.Bundle; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import com.owncloud.android.R; +import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.db.UploadDbHandler; import com.owncloud.android.db.UploadDbObject; +import com.owncloud.android.db.UploadDbHandler.UploadStatus; import com.owncloud.android.files.services.FileUploadService; +import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.ui.errorhandling.ExceptionHandler; import com.owncloud.android.ui.fragment.UploadListFragment; +import com.owncloud.android.utils.FileStorageUtils; /** * Activity listing pending, active, and completed uploads. User can delete @@ -36,9 +42,39 @@ protected void onCreate(Bundle savedInstanceState) { // //////////////////////////////////////// // UploadListFragment.ContainerActivity // //////////////////////////////////////// + /** + * TODO Without a menu this is a little un-intuitive. + */ @Override public void onUploadItemClick(UploadDbObject file) { - // TODO Auto-generated method stub + OCFile ocFile = FileStorageUtils.fillOCFile(file); + switch (file.getUploadStatus()) { + case UPLOAD_IN_PROGRESS: + if (ocFile != null) { + getFileOperationsHelper().cancelTransference(ocFile); + } else { + Log_OC.e(TAG, "Could not get OCFile for " + file.getRemotePath() + ". Cannot cancel."); + } + break; + case UPLOAD_SUCCEEDED: + Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class); + showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, ocFile); + showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, file.getAccount(this)); + startActivity(showDetailsIntent); + break; + case UPLOAD_CANCELLED: + case UPLOAD_PAUSED: + UploadDbHandler db = UploadDbHandler.getInstance(this.getBaseContext()); + file.setUploadStatus(UploadStatus.UPLOAD_LATER); + db.updateUpload(file); + // no break; to start upload immediately. + case UPLOAD_LATER: + case UPLOAD_FAILED_RETRY: + FileUploadService.retry(this); + break; + default: + break; + } } diff --git a/src/com/owncloud/android/utils/FileStorageUtils.java b/src/com/owncloud/android/utils/FileStorageUtils.java index cf487ccf0a0..c28d86e89ae 100644 --- a/src/com/owncloud/android/utils/FileStorageUtils.java +++ b/src/com/owncloud/android/utils/FileStorageUtils.java @@ -22,6 +22,7 @@ import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.db.UploadDbObject; import com.owncloud.android.lib.resources.files.RemoteFile; import android.annotation.SuppressLint; @@ -135,6 +136,15 @@ public static OCFile fillOCFile(RemoteFile remote) { return file; } + public static OCFile fillOCFile(UploadDbObject o) { + OCFile file = new OCFile(o.getRemotePath()); + File localFile = new File(o.getLocalPath()); + file.setFileLength(localFile.length()); + file.setMimetype(o.getMimeType()); + file.setStoragePath(o.getLocalPath()); + return file; + } + /** * Creates and populates a new {@link RemoteFile} object with the data read from an {@link OCFile}. * From d3be204240d60f9a848966221c4fac3616c17429 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Wed, 26 Nov 2014 10:03:51 +0100 Subject: [PATCH 27/76] use FileStorageUtils.fillOCFile in SynchronizeFolderOperation --- .../android/operations/SynchronizeFolderOperation.java | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/com/owncloud/android/operations/SynchronizeFolderOperation.java b/src/com/owncloud/android/operations/SynchronizeFolderOperation.java index d61e678416b..48c8257765a 100644 --- a/src/com/owncloud/android/operations/SynchronizeFolderOperation.java +++ b/src/com/owncloud/android/operations/SynchronizeFolderOperation.java @@ -460,15 +460,7 @@ public boolean isMultiStatus(int status) { * @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; + return FileStorageUtils.fillOCFile(remote); } From 8e9cf480ab2e7c35cdce9c72e0362c787ca07ccf Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Wed, 26 Nov 2014 12:28:29 +0100 Subject: [PATCH 28/76] added log output fix multiple calls of ConnectivityActionReceiver --- .../services/ConnectivityActionReceiver.java | 105 +++++++++++------- .../files/services/FileUploadService.java | 15 ++- .../operations/UploadFileOperation.java | 4 + .../ui/activity/FileDisplayActivity.java | 2 + .../ui/activity/UploadListActivity.java | 2 + 5 files changed, 85 insertions(+), 43 deletions(-) diff --git a/src/com/owncloud/android/files/services/ConnectivityActionReceiver.java b/src/com/owncloud/android/files/services/ConnectivityActionReceiver.java index da3e5758711..b9be0f3f3e6 100755 --- a/src/com/owncloud/android/files/services/ConnectivityActionReceiver.java +++ b/src/com/owncloud/android/files/services/ConnectivityActionReceiver.java @@ -6,65 +6,90 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.NetworkInfo.State; +import android.net.wifi.WifiManager; import android.os.Bundle; import android.util.Log; import com.owncloud.android.files.InstantUploadBroadcastReceiver; +import com.owncloud.android.lib.common.utils.Log_OC; /** - * Receives all connectivity action from Android OS at all times and performs required OC actions. - * For now that are: - * - Signal connectivity to {@link FileUploadService}. - * - * Later can be added: - * - Signal connectivity to download service, deletion service, ... - * - Handle offline mode (cf. https://github.com/owncloud/android/issues/162) - * + * Receives all connectivity action from Android OS at all times and performs + * required OC actions. For now that are: - Signal connectivity to + * {@link FileUploadService}. + * + * Later can be added: - Signal connectivity to download service, deletion + * service, ... - Handle offline mode (cf. + * https://github.com/owncloud/android/issues/162) + * * @author LukeOwncloud - * + * */ public class ConnectivityActionReceiver extends BroadcastReceiver { private static final String TAG = "ConnectivityActionReceiver"; @Override public void onReceive(final Context context, Intent intent) { -// if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) { - Log.v(TAG, "action: " + intent.getAction()); - Log.v(TAG, "component: " + intent.getComponent()); - Bundle extras = intent.getExtras(); - if (extras != null) { - for (String key : extras.keySet()) { - Log.v(TAG, "key [" + key + "]: " + extras.get(key)); - } - } else { - Log.v(TAG, "no extras"); + // LOG ALL EVENTS: + Log.v(TAG, "action: " + intent.getAction()); + Log.v(TAG, "component: " + intent.getComponent()); + Bundle extras = intent.getExtras(); + if (extras != null) { + for (String key : extras.keySet()) { + Log.v(TAG, "key [" + key + "]: " + extras.get(key)); } + } else { + Log.v(TAG, "no extras"); + } - if (InstantUploadBroadcastReceiver.isOnline(context)) { - FileUploadService.retry(context); + + /** + * Just checking for State.CONNECTED will is not good enough, as it ends here multiple times. + * Work around from: + * http://stackoverflow.com/questions/17287178/connectivitymanager-getactivenetworkinfo-returning-true-when-internet-is-off + */ + if(intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { + NetworkInfo networkInfo = + intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); + if(networkInfo.isConnected()) { + Log.d(TAG, "Wifi is connected: " + String.valueOf(networkInfo)); + wifiConnected(context); } -// } + } else if(intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) { + ConnectivityManager cm = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = cm.getActiveNetworkInfo(); + if(networkInfo.getType() == ConnectivityManager.TYPE_WIFI && + ! networkInfo.isConnected()) { + Log.d(TAG, "Wifi is disconnected: " + String.valueOf(networkInfo)); + wifiDisconnected(context); + } + } + + } + + private void wifiConnected(Context context) { + Log_OC.d(TAG, "FileUploadService.retry() called by onReceive()"); + FileUploadService.retry(context); } - - static public void enable(Context context) { + + private void wifiDisconnected(Context context) { + + } + + static public void enableActionReceiver(Context context) { PackageManager pm = context.getPackageManager(); - ComponentName compName = - new ComponentName(context.getApplicationContext(), - ConnectivityActionReceiver.class); - pm.setComponentEnabledSetting( - compName, - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, - PackageManager.DONT_KILL_APP); + ComponentName compName = new ComponentName(context.getApplicationContext(), ConnectivityActionReceiver.class); + pm.setComponentEnabledSetting(compName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, + PackageManager.DONT_KILL_APP); } - - static public void disable(Context context) { + + static public void disableActionReceiver(Context context) { PackageManager pm = context.getPackageManager(); - ComponentName compName = - new ComponentName(context.getApplicationContext(), - ConnectivityActionReceiver.class); - pm.setComponentEnabledSetting( - compName, - PackageManager.COMPONENT_ENABLED_STATE_DISABLED, - PackageManager.DONT_KILL_APP); + ComponentName compName = new ComponentName(context.getApplicationContext(), ConnectivityActionReceiver.class); + pm.setComponentEnabledSetting(compName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP); } } \ No newline at end of file diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index ecb6a8bd125..cd7320c1e2e 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -254,6 +254,7 @@ public void onCreate() { } if(InstantUploadBroadcastReceiver.isOnline(getApplicationContext())) { + Log_OC.d(TAG, "FileUploadService.retry() called by onCreate()"); FileUploadService.retry(getApplicationContext()); } } @@ -281,7 +282,10 @@ public void onCreate() { @Override protected void onHandleIntent(Intent intent) { + Log_OC.i(TAG, "onHandleIntent start"); + Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size() + " - before adding new uploads."); if (intent == null || intent.hasExtra(KEY_RETRY)) { + Log_OC.d(TAG, "Receive null intent."); // service was restarted by OS (after return START_STICKY and kill // service) or connectivity change was detected. ==> check persistent upload // list. @@ -301,7 +305,7 @@ protected void onHandleIntent(Intent intent) { } } } else { - + Log_OC.d(TAG, "Receive upload intent."); UploadSingleMulti uploadType = (UploadSingleMulti) intent.getSerializableExtra(KEY_UPLOAD_TYPE); if (uploadType == null) { Log_OC.e(TAG, "Incorrect or no upload type provided"); @@ -406,17 +410,21 @@ protected void onHandleIntent(Intent intent) { // at this point mPendingUploads is filled. - Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size()); + Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size() + " - before uploading."); try { Iterator it = mPendingUploads.keySet().iterator(); while (it.hasNext()) { - UploadDbObject uploadDbObject = mPendingUploads.get(it.next()); + String up = it.next(); + Log_OC.d(TAG, "Calling uploadFile for " + up); + UploadDbObject uploadDbObject = mPendingUploads.get(up); boolean uploadSuccessful = uploadFile(uploadDbObject); } } catch (ConcurrentModificationException e) { // for now: ignore. TODO: fix this. } + Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size() + " - after uploading."); + Log_OC.i(TAG, "onHandleIntent end"); } /** @@ -951,6 +959,7 @@ private boolean isPdfFileFromContentProviderWithoutExtension(String localPath, S } public static void retry(Context context) { + Log_OC.d(TAG, "FileUploadService.retry()"); Intent i = new Intent(context, FileUploadService.class); i.putExtra(FileUploadService.KEY_RETRY, true); context.startService(i); diff --git a/src/com/owncloud/android/operations/UploadFileOperation.java b/src/com/owncloud/android/operations/UploadFileOperation.java index f4720bce054..b858527f18b 100644 --- a/src/com/owncloud/android/operations/UploadFileOperation.java +++ b/src/com/owncloud/android/operations/UploadFileOperation.java @@ -459,6 +459,10 @@ private boolean existsFile(OwnCloudClient client, String remotePath){ } public void cancel() { + if (mUploadOperation == null) { + Log_OC.e(TAG, "UploadFileOperation.cancel(): mUploadOperation == null for file: " + mFile + ". Fix that."); + return; + } mUploadOperation.cancel(); } } diff --git a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java index 57614ffbc8b..ce4cadd412e 100644 --- a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -1221,6 +1221,8 @@ public void onReceive(Context context, Intent intent) { if (sameAccount && sameFile && detailFragmentIsShown) { if (uploadWasFine) { setFile(getStorageManager().getFileByPath(uploadedRemotePath)); + } else { + //TODO remove upload progress bar after upload failed. } if (renamedInUpload) { String newName = (new File(uploadedRemotePath)).getName(); diff --git a/src/com/owncloud/android/ui/activity/UploadListActivity.java b/src/com/owncloud/android/ui/activity/UploadListActivity.java index 77c7ecb1b54..9017d780b8c 100755 --- a/src/com/owncloud/android/ui/activity/UploadListActivity.java +++ b/src/com/owncloud/android/ui/activity/UploadListActivity.java @@ -70,6 +70,7 @@ public void onUploadItemClick(UploadDbObject file) { // no break; to start upload immediately. case UPLOAD_LATER: case UPLOAD_FAILED_RETRY: + Log_OC.d(TAG, "FileUploadService.retry() called by onUploadItemClick()"); FileUploadService.retry(this); break; default: @@ -89,6 +90,7 @@ public boolean onMenuItemSelected(int featureId, MenuItem item) { boolean retval = true; switch (item.getItemId()) { case R.id.action_retry_uploads: { + Log_OC.d(TAG, "FileUploadService.retry() called by onMenuItemSelected()"); FileUploadService.retry(this); break; } From 39abd71e82ec8e1fc0a3d75ecbd45a7d1c6586cb Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Wed, 26 Nov 2014 12:34:54 +0100 Subject: [PATCH 29/76] fixed multiple calls of ConnectivityActionReceiver --- .../android/files/services/ConnectivityActionReceiver.java | 2 +- src/com/owncloud/android/files/services/FileUploadService.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/com/owncloud/android/files/services/ConnectivityActionReceiver.java b/src/com/owncloud/android/files/services/ConnectivityActionReceiver.java index b9be0f3f3e6..23ac7a527ed 100755 --- a/src/com/owncloud/android/files/services/ConnectivityActionReceiver.java +++ b/src/com/owncloud/android/files/services/ConnectivityActionReceiver.java @@ -61,7 +61,7 @@ public void onReceive(final Context context, Intent intent) { ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = cm.getActiveNetworkInfo(); - if(networkInfo.getType() == ConnectivityManager.TYPE_WIFI && + if(networkInfo == null || networkInfo.getType() == ConnectivityManager.TYPE_WIFI && ! networkInfo.isConnected()) { Log.d(TAG, "Wifi is disconnected: " + String.valueOf(networkInfo)); wifiDisconnected(context); diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index cd7320c1e2e..2a4e31a7fef 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -253,6 +253,7 @@ public void onCreate() { mDb.updateUpload(uploadDbObject); } + //TODO This service can be instantiated at any time. Move this retry call to start of app. if(InstantUploadBroadcastReceiver.isOnline(getApplicationContext())) { Log_OC.d(TAG, "FileUploadService.retry() called by onCreate()"); FileUploadService.retry(getApplicationContext()); From d1386ea14074714b6f7a48482dd2b295d3045b0b Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Wed, 26 Nov 2014 13:56:55 +0100 Subject: [PATCH 30/76] Use OCFile inside UploadDbObject, made OCFile serializable (requires casts to make invocation of Intent.putExtra() unambiguous) --- .../owncloud/android/datamodel/OCFile.java | 12 +++- .../owncloud/android/db/UploadDbHandler.java | 15 ++--- .../owncloud/android/db/UploadDbObject.java | 64 ++++++++----------- .../files/services/FileDownloader.java | 4 +- .../files/services/FileUploadService.java | 11 ++-- .../owncloud/android/media/MediaService.java | 5 +- .../android/media/MediaServiceBinder.java | 3 +- .../operations/SynchronizeFileOperation.java | 5 +- .../observer/FileObserverService.java | 3 +- .../services/observer/FolderObserver.java | 3 +- .../ui/activity/ConflictsResolveActivity.java | 3 +- .../ui/activity/FileDisplayActivity.java | 9 +-- .../android/ui/activity/MoveActivity.java | 5 +- .../ui/activity/UploadListActivity.java | 5 +- .../ui/fragment/OCFileListFragment.java | 3 +- .../ui/preview/PreviewImageActivity.java | 5 +- .../ui/preview/PreviewMediaFragment.java | 3 +- .../android/utils/FileStorageUtils.java | 9 --- 18 files changed, 83 insertions(+), 84 deletions(-) diff --git a/src/com/owncloud/android/datamodel/OCFile.java b/src/com/owncloud/android/datamodel/OCFile.java index 09687cb3eff..11ab00f03e0 100644 --- a/src/com/owncloud/android/datamodel/OCFile.java +++ b/src/com/owncloud/android/datamodel/OCFile.java @@ -19,15 +19,23 @@ package com.owncloud.android.datamodel; import java.io.File; +import java.io.Serializable; import com.owncloud.android.lib.common.utils.Log_OC; -import third_parties.daveKoeller.AlphanumComparator; +import third_parties.daveKoeller.AlphanumComparator; import android.os.Parcel; import android.os.Parcelable; import android.webkit.MimeTypeMap; -public class OCFile implements Parcelable, Comparable { +// OCFile needs to be Serializable because it is stored persistently inside UploadDbObject. +// (Parcelable is not suitable for persistent storage.) +public class OCFile implements Parcelable, Comparable, Serializable { + + /** + * Should be changed whenever any property of OCFile changes. + */ + private static final long serialVersionUID = 5604080482686390078L; public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { @Override diff --git a/src/com/owncloud/android/db/UploadDbHandler.java b/src/com/owncloud/android/db/UploadDbHandler.java index 144f6efe898..7aa47c6f50e 100644 --- a/src/com/owncloud/android/db/UploadDbHandler.java +++ b/src/com/owncloud/android/db/UploadDbHandler.java @@ -50,14 +50,13 @@ public class UploadDbHandler extends Observable { // for testing only public void recreateDb() { - // mDB.beginTransaction(); - // try { - // mHelper.onUpgrade(mDB, 0, mDatabaseVersion); - // mDB.setTransactionSuccessful(); - // } finally { - // mDB.endTransaction(); - // } - +// getDB().beginTransaction(); +// try { +// mHelper.onUpgrade(getDB(), 0, mDatabaseVersion); +// getDB().setTransactionSuccessful(); +// } finally { +// getDB().endTransaction(); +// } } public enum UploadStatus { diff --git a/src/com/owncloud/android/db/UploadDbObject.java b/src/com/owncloud/android/db/UploadDbObject.java index 5e55ae65ce3..9fce216f3d4 100755 --- a/src/com/owncloud/android/db/UploadDbObject.java +++ b/src/com/owncloud/android/db/UploadDbObject.java @@ -13,6 +13,7 @@ import android.util.Base64; import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.db.UploadDbHandler.UploadStatus; import com.owncloud.android.files.services.FileUploadService.LocalBehaviour; import com.owncloud.android.lib.common.operations.RemoteOperationResult; @@ -32,19 +33,30 @@ public class UploadDbObject implements Serializable { private static final long serialVersionUID = -2306246191385279928L; private static final String TAG = "UploadDbObject"; - /** - * Local path to file which is to be uploaded. - */ - String localPath; - /** - * Remote path where file is to be uploaded to. - */ - String remotePath; - - /** - * Mime type of upload file. - */ - String mimeType; + + public UploadDbObject(OCFile ocFile) { + this.ocFile = ocFile; + } +// /** +// * Local path to file which is to be uploaded. +// */ +// String localPath; +// /** +// * Remote path where file is to be uploaded to. +// */ +// String remotePath; +// +// /** +// * Mime type of upload file. +// */ +// String mimeType; + OCFile ocFile; + + public OCFile getOCFile() { + return ocFile; + } + + /** * Local action for upload. */ @@ -120,43 +132,23 @@ public void setLastResult(RemoteOperationResult lastResult) { * @return the localPath */ public String getLocalPath() { - return localPath; - } - - /** - * @param localPath the localPath to set - */ - public void setLocalPath(String localPath) { - this.localPath = localPath; + return ocFile.getStoragePath(); } /** * @return the remotePath */ public String getRemotePath() { - return remotePath; - } - - /** - * @param remotePath the remotePath to set - */ - public void setRemotePath(String remotePath) { - this.remotePath = remotePath; + return ocFile.getRemotePath(); } /** * @return the mimeType */ public String getMimeType() { - return mimeType; + return ocFile.getMimetype(); } - /** - * @param mimeType the mimeType to set - */ - public void setMimeType(String mimeType) { - this.mimeType = mimeType; - } /** * @return the localAction diff --git a/src/com/owncloud/android/files/services/FileDownloader.java b/src/com/owncloud/android/files/services/FileDownloader.java index 88c821392a7..179c821ac35 100644 --- a/src/com/owncloud/android/files/services/FileDownloader.java +++ b/src/com/owncloud/android/files/services/FileDownloader.java @@ -32,7 +32,6 @@ import com.owncloud.android.authentication.AuthenticatorActivity; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; - import com.owncloud.android.lib.common.network.OnDatatransferProgressListener; import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.OwnCloudClient; @@ -62,6 +61,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Parcelable; import android.os.Process; import android.support.v4.app.NotificationCompat; @@ -431,7 +431,7 @@ private void notifyDownloadStart(DownloadFileOperation download) { } else { showDetailsIntent = new Intent(this, FileDisplayActivity.class); } - showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, download.getFile()); + showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, (Parcelable)download.getFile()); showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, download.getAccount()); showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index 2a4e31a7fef..714b7597ec2 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -42,6 +42,7 @@ import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; +import android.os.Parcelable; import android.os.Process; import android.support.v4.app.NotificationCompat; import android.webkit.MimeTypeMap; @@ -377,10 +378,7 @@ protected void onHandleIntent(Intent intent) { // save always persistently path of upload, so it can be retried if // failed. for (int i = 0; i < files.length; i++) { - UploadDbObject uploadObject = new UploadDbObject(); - uploadObject.setRemotePath(files[i].getRemotePath()); - uploadObject.setLocalPath(files[i].getStoragePath()); - uploadObject.setMimeType(files[i].getMimetype()); + UploadDbObject uploadObject = new UploadDbObject(files[i]); uploadObject.setAccountName(account.name); uploadObject.setForceOverwrite(forceOverwrite); uploadObject.setCreateRemoteFolder(isCreateRemoteFolder); @@ -608,8 +606,7 @@ private boolean uploadFile(UploadDbObject uploadDbObject) { String uploadKey = null; uploadKey = buildRemoteName(account, uploadDbObject.getRemotePath()); - OCFile file = obtainNewOCFileToUpload(uploadDbObject.getRemotePath(), uploadDbObject.getLocalPath(), - uploadDbObject.getMimeType()); + OCFile file = uploadDbObject.getOCFile(); mCurrentUpload = new UploadFileOperation(account, file, chunked, uploadDbObject.isForceOverwrite(), uploadDbObject.getLocalAction(), getApplicationContext()); if (uploadDbObject.isCreateRemoteFolder()) { @@ -840,7 +837,7 @@ private void notifyUploadStart(UploadFileOperation upload) { // / 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_FILE, (Parcelable)upload.getFile()); showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, upload.getAccount()); showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); mNotificationBuilder.setContentIntent(PendingIntent.getActivity(this, (int) System.currentTimeMillis(), diff --git a/src/com/owncloud/android/media/MediaService.java b/src/com/owncloud/android/media/MediaService.java index 52daa04c2ab..f6d335d8a7f 100644 --- a/src/com/owncloud/android/media/MediaService.java +++ b/src/com/owncloud/android/media/MediaService.java @@ -32,6 +32,7 @@ import android.net.wifi.WifiManager; import android.net.wifi.WifiManager.WifiLock; import android.os.IBinder; +import android.os.Parcelable; import android.os.PowerManager; import android.widget.Toast; @@ -535,7 +536,7 @@ public void onPrepared(MediaPlayer player) { private void updateNotification(String content) { // TODO check if updating the Intent is really necessary Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class); - showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, mFile); + showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, (Parcelable)mFile); showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, mAccount); showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), @@ -572,7 +573,7 @@ private void setUpAsForeground(String content) { /// 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, mFile); + showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, (Parcelable)mFile); showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, mAccount); showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), diff --git a/src/com/owncloud/android/media/MediaServiceBinder.java b/src/com/owncloud/android/media/MediaServiceBinder.java index 1b56ec016a7..2bb582f6574 100644 --- a/src/com/owncloud/android/media/MediaServiceBinder.java +++ b/src/com/owncloud/android/media/MediaServiceBinder.java @@ -26,6 +26,7 @@ import android.content.Intent; import android.media.MediaPlayer; import android.os.Binder; +import android.os.Parcelable; import android.widget.MediaController; @@ -152,7 +153,7 @@ public void start(Account account, OCFile file, boolean playImmediately, int pos Log_OC.d(TAG, "Loading and starting through binder..."); Intent i = new Intent(mService, MediaService.class); i.putExtra(MediaService.EXTRA_ACCOUNT, account); - i.putExtra(MediaService.EXTRA_FILE, file); + i.putExtra(MediaService.EXTRA_FILE, (Parcelable)file); i.putExtra(MediaService.EXTRA_PLAY_ON_LOAD, playImmediately); i.putExtra(MediaService.EXTRA_START_POSITION, position); i.setAction(MediaService.ACTION_PLAY_FILE); diff --git a/src/com/owncloud/android/operations/SynchronizeFileOperation.java b/src/com/owncloud/android/operations/SynchronizeFileOperation.java index ffa31868fb7..21b81d8a7d7 100644 --- a/src/com/owncloud/android/operations/SynchronizeFileOperation.java +++ b/src/com/owncloud/android/operations/SynchronizeFileOperation.java @@ -33,6 +33,7 @@ import android.accounts.Account; import android.content.Context; import android.content.Intent; +import android.os.Parcelable; /** * Remote operation performing the read of remote file in the ownCloud server. @@ -209,7 +210,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { private void requestForUpload(OCFile file) { Intent i = new Intent(mContext, FileUploadService.class); i.putExtra(FileUploadService.KEY_ACCOUNT, mAccount); - i.putExtra(FileUploadService.KEY_FILE, file); + i.putExtra(FileUploadService.KEY_FILE, (Parcelable)file); /*i.putExtra(FileUploader.KEY_REMOTE_FILE, mRemotePath); // doing this we would lose the value of keepInSync in the road, and maybe it's not updated in the database when the FileUploader service gets it! i.putExtra(FileUploader.KEY_LOCAL_FILE, localFile.getStoragePath());*/ i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UploadSingleMulti.UPLOAD_SINGLE_FILE); @@ -227,7 +228,7 @@ private void requestForUpload(OCFile file) { private void requestForDownload(OCFile file) { Intent i = new Intent(mContext, FileDownloader.class); i.putExtra(FileDownloader.EXTRA_ACCOUNT, mAccount); - i.putExtra(FileDownloader.EXTRA_FILE, file); + i.putExtra(FileDownloader.EXTRA_FILE, (Parcelable)file); mContext.startService(i); mTransferWasRequested = true; } diff --git a/src/com/owncloud/android/services/observer/FileObserverService.java b/src/com/owncloud/android/services/observer/FileObserverService.java index 114f0e426fc..82e768c7060 100644 --- a/src/com/owncloud/android/services/observer/FileObserverService.java +++ b/src/com/owncloud/android/services/observer/FileObserverService.java @@ -31,6 +31,7 @@ import android.content.IntentFilter; import android.database.Cursor; import android.os.IBinder; +import android.os.Parcelable; import com.owncloud.android.MainApp; import com.owncloud.android.authentication.AccountUtils; @@ -101,7 +102,7 @@ public static Intent makeObservedFileIntent( Intent intent = new Intent(context, FileObserverService.class); intent.setAction(watchIt ? FileObserverService.ACTION_ADD_OBSERVED_FILE : FileObserverService.ACTION_DEL_OBSERVED_FILE); - intent.putExtra(FileObserverService.ARG_FILE, file); + intent.putExtra(FileObserverService.ARG_FILE, (Parcelable)file); intent.putExtra(FileObserverService.ARG_ACCOUNT, account); return intent; } diff --git a/src/com/owncloud/android/services/observer/FolderObserver.java b/src/com/owncloud/android/services/observer/FolderObserver.java index 67b41a13840..939128c0ad0 100644 --- a/src/com/owncloud/android/services/observer/FolderObserver.java +++ b/src/com/owncloud/android/services/observer/FolderObserver.java @@ -25,6 +25,7 @@ import android.content.Context; import android.content.Intent; import android.os.FileObserver; +import android.os.Parcelable; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; @@ -203,7 +204,7 @@ private void startSyncOperation(String fileName) { // this can be very intrusive; a notification should be preferred Intent i = new Intent(mContext, ConflictsResolveActivity.class); i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); - i.putExtra(ConflictsResolveActivity.EXTRA_FILE, file); + i.putExtra(ConflictsResolveActivity.EXTRA_FILE, (Parcelable)file); i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, mAccount); mContext.startActivity(i); } diff --git a/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java b/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java index ff269c2883b..ff72c60f209 100644 --- a/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java +++ b/src/com/owncloud/android/ui/activity/ConflictsResolveActivity.java @@ -30,6 +30,7 @@ import android.content.Intent; import android.os.Bundle; +import android.os.Parcelable; /** * Wrapper activity which will be launched if keep-in-sync file will be modified by external @@ -68,7 +69,7 @@ public void conflictDecisionMade(Decision decision) { return; } i.putExtra(FileUploadService.KEY_ACCOUNT, getAccount()); - i.putExtra(FileUploadService.KEY_FILE, getFile()); + i.putExtra(FileUploadService.KEY_FILE, (Parcelable)getFile()); i.putExtra(FileUploadService.KEY_UPLOAD_TYPE, FileUploadService.UploadSingleMulti.UPLOAD_SINGLE_FILE); startService(i); diff --git a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java index ce4cadd412e..c77e1cb80cc 100644 --- a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -46,6 +46,7 @@ import android.os.Bundle; import android.os.Environment; import android.os.IBinder; +import android.os.Parcelable; import android.preference.PreferenceManager; import android.provider.DocumentsContract; import android.provider.MediaStore; @@ -1661,7 +1662,7 @@ private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation if (!result.isSuccess()) { if (result.getCode() == ResultCode.SYNC_CONFLICT) { Intent i = new Intent(this, ConflictsResolveActivity.class); - i.putExtra(ConflictsResolveActivity.EXTRA_FILE, syncedFile); + i.putExtra(ConflictsResolveActivity.EXTRA_FILE, (Parcelable)syncedFile); i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, getAccount()); startActivity(i); @@ -1731,7 +1732,7 @@ private void requestForDownload() { if (!mDownloaderBinder.isDownloading(account, mWaitingToPreview)) { Intent i = new Intent(this, FileDownloader.class); i.putExtra(FileDownloader.EXTRA_ACCOUNT, account); - i.putExtra(FileDownloader.EXTRA_FILE, mWaitingToPreview); + i.putExtra(FileDownloader.EXTRA_FILE, (Parcelable)mWaitingToPreview); startService(i); } } @@ -1788,7 +1789,7 @@ private void requestForDownload(OCFile file) { if (!mDownloaderBinder.isDownloading(account, file)) { Intent i = new Intent(this, FileDownloader.class); i.putExtra(FileDownloader.EXTRA_ACCOUNT, account); - i.putExtra(FileDownloader.EXTRA_FILE, file); + i.putExtra(FileDownloader.EXTRA_FILE, (Parcelable)file); startService(i); } } @@ -1820,7 +1821,7 @@ public void startDownloadForSending(OCFile file) { */ public void startImagePreview(OCFile file) { Intent showDetailsIntent = new Intent(this, PreviewImageActivity.class); - showDetailsIntent.putExtra(EXTRA_FILE, file); + showDetailsIntent.putExtra(EXTRA_FILE, (Parcelable)file); showDetailsIntent.putExtra(EXTRA_ACCOUNT, getAccount()); startActivity(showDetailsIntent); diff --git a/src/com/owncloud/android/ui/activity/MoveActivity.java b/src/com/owncloud/android/ui/activity/MoveActivity.java index 8a254705b9c..4e00a17acd8 100644 --- a/src/com/owncloud/android/ui/activity/MoveActivity.java +++ b/src/com/owncloud/android/ui/activity/MoveActivity.java @@ -29,6 +29,7 @@ import android.content.IntentFilter; import android.content.res.Resources.NotFoundException; import android.os.Bundle; +import android.os.Parcelable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.util.Log; @@ -373,8 +374,8 @@ public void onClick(View v) { OCFile targetFile = (OCFile) i.getParcelableExtra(MoveActivity.EXTRA_TARGET_FILE); Intent data = new Intent(); - data.putExtra(EXTRA_CURRENT_FOLDER, getCurrentFolder()); - data.putExtra(EXTRA_TARGET_FILE, targetFile); + data.putExtra(EXTRA_CURRENT_FOLDER, (Parcelable)getCurrentFolder()); + data.putExtra(EXTRA_TARGET_FILE, (Parcelable)targetFile); setResult(RESULT_OK_AND_MOVE, data); finish(); } diff --git a/src/com/owncloud/android/ui/activity/UploadListActivity.java b/src/com/owncloud/android/ui/activity/UploadListActivity.java index 9017d780b8c..9302debd98a 100755 --- a/src/com/owncloud/android/ui/activity/UploadListActivity.java +++ b/src/com/owncloud/android/ui/activity/UploadListActivity.java @@ -4,6 +4,7 @@ import android.content.Intent; import android.os.Bundle; +import android.os.Parcelable; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; @@ -47,7 +48,7 @@ protected void onCreate(Bundle savedInstanceState) { */ @Override public void onUploadItemClick(UploadDbObject file) { - OCFile ocFile = FileStorageUtils.fillOCFile(file); + OCFile ocFile = file.getOCFile(); switch (file.getUploadStatus()) { case UPLOAD_IN_PROGRESS: if (ocFile != null) { @@ -58,7 +59,7 @@ public void onUploadItemClick(UploadDbObject file) { break; case UPLOAD_SUCCEEDED: Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class); - showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, ocFile); + showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, (Parcelable)ocFile); showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, file.getAccount(this)); startActivity(showDetailsIntent); break; diff --git a/src/com/owncloud/android/ui/fragment/OCFileListFragment.java b/src/com/owncloud/android/ui/fragment/OCFileListFragment.java index 0b1059fc037..1ff8e17d308 100644 --- a/src/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/src/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -22,6 +22,7 @@ import android.app.Activity; import android.content.Intent; import android.os.Bundle; +import android.os.Parcelable; import android.support.v4.widget.SwipeRefreshLayout; import android.view.ContextMenu; import android.view.MenuInflater; @@ -325,7 +326,7 @@ public boolean onContextItemSelected (MenuItem item) { Intent action = new Intent(getActivity(), MoveActivity.class); // Pass mTargetFile that contains info of selected file/folder - action.putExtra(MoveActivity.EXTRA_TARGET_FILE, mTargetFile); + action.putExtra(MoveActivity.EXTRA_TARGET_FILE, (Parcelable)mTargetFile); getActivity().startActivityForResult(action, FileDisplayActivity.ACTION_MOVE_FILES); return true; } diff --git a/src/com/owncloud/android/ui/preview/PreviewImageActivity.java b/src/com/owncloud/android/ui/preview/PreviewImageActivity.java index 29f14f610bc..2bc74c55281 100644 --- a/src/com/owncloud/android/ui/preview/PreviewImageActivity.java +++ b/src/com/owncloud/android/ui/preview/PreviewImageActivity.java @@ -29,6 +29,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Message; +import android.os.Parcelable; import android.preference.PreferenceManager; import android.support.v4.view.ViewPager; import android.view.View; @@ -350,7 +351,7 @@ private void backToDisplayActivity() { public void showDetails(OCFile file) { Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class); showDetailsIntent.setAction(FileDisplayActivity.ACTION_DETAILS); - showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, file); + showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, (Parcelable)file); showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, AccountUtils.getCurrentOwnCloudAccount(this)); startActivity(showDetailsIntent); int pos = mPreviewImagePagerAdapter.getFilePosition(file); @@ -366,7 +367,7 @@ private void requestForDownload(OCFile file) { } else if (!mDownloaderBinder.isDownloading(getAccount(), file)) { Intent i = new Intent(this, FileDownloader.class); i.putExtra(FileDownloader.EXTRA_ACCOUNT, getAccount()); - i.putExtra(FileDownloader.EXTRA_FILE, file); + i.putExtra(FileDownloader.EXTRA_FILE, (Parcelable)file); startService(i); } } diff --git a/src/com/owncloud/android/ui/preview/PreviewMediaFragment.java b/src/com/owncloud/android/ui/preview/PreviewMediaFragment.java index 7d6489b2813..4e76e0cd7b2 100644 --- a/src/com/owncloud/android/ui/preview/PreviewMediaFragment.java +++ b/src/com/owncloud/android/ui/preview/PreviewMediaFragment.java @@ -33,6 +33,7 @@ import android.os.Build; import android.os.Bundle; import android.os.IBinder; +import android.os.Parcelable; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -516,7 +517,7 @@ public boolean onTouch(View v, MotionEvent event) { private void startFullScreenVideo() { Intent i = new Intent(getSherlockActivity(), PreviewVideoActivity.class); i.putExtra(FileActivity.EXTRA_ACCOUNT, mAccount); - i.putExtra(FileActivity.EXTRA_FILE, getFile()); + i.putExtra(FileActivity.EXTRA_FILE, (Parcelable)getFile()); i.putExtra(PreviewVideoActivity.EXTRA_AUTOPLAY, mVideoPreview.isPlaying()); mVideoPreview.pause(); i.putExtra(PreviewVideoActivity.EXTRA_START_POSITION, mVideoPreview.getCurrentPosition()); diff --git a/src/com/owncloud/android/utils/FileStorageUtils.java b/src/com/owncloud/android/utils/FileStorageUtils.java index c28d86e89ae..a8b2120f681 100644 --- a/src/com/owncloud/android/utils/FileStorageUtils.java +++ b/src/com/owncloud/android/utils/FileStorageUtils.java @@ -136,15 +136,6 @@ public static OCFile fillOCFile(RemoteFile remote) { return file; } - public static OCFile fillOCFile(UploadDbObject o) { - OCFile file = new OCFile(o.getRemotePath()); - File localFile = new File(o.getLocalPath()); - file.setFileLength(localFile.length()); - file.setMimetype(o.getMimeType()); - file.setStoragePath(o.getLocalPath()); - return file; - } - /** * Creates and populates a new {@link RemoteFile} object with the data read from an {@link OCFile}. * From 6ca1c343d6018e55495a08a91ad792ae6cbfc53d Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Wed, 26 Nov 2014 14:39:17 +0100 Subject: [PATCH 31/76] remove debug tag --- AndroidManifest.xml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 15fb9442322..d4c31e2d0a6 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -163,16 +163,7 @@ - - - - - - - - - - + From d08e4369f790a125f81e51e83ba9869a5db69c3e Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Wed, 26 Nov 2014 14:46:23 +0100 Subject: [PATCH 32/76] open UploadListActivity on click on upload failure notification --- .../android/files/services/FileUploadService.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index 714b7597ec2..f210c5a21cf 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -76,6 +76,7 @@ import com.owncloud.android.operations.common.SyncOperation; import com.owncloud.android.ui.activity.FileActivity; import com.owncloud.android.ui.activity.FileDisplayActivity; +import com.owncloud.android.ui.activity.UploadListActivity; import com.owncloud.android.utils.ErrorMessageAdapter; import com.owncloud.android.utils.UriUtils; @@ -895,6 +896,15 @@ private void notifyUploadResult(RemoteOperationResult uploadResult, UploadFileOp } + if(!uploadResult.isSuccess()){ + //in case of failure, do not show details file view (because there is no file!) + Intent showUploadListIntent = new Intent(this, UploadListActivity.class); + showUploadListIntent.putExtra(FileActivity.EXTRA_FILE, (Parcelable)upload.getFile()); + showUploadListIntent.putExtra(FileActivity.EXTRA_ACCOUNT, upload.getAccount()); + mNotificationBuilder.setContentIntent(PendingIntent.getActivity(this, (int) System.currentTimeMillis(), + showUploadListIntent, 0)); + } + mNotificationBuilder.setContentText(content); mNotificationManager.notify(tickerId, mNotificationBuilder.build()); From 949c2ae3c0d3e3fcf0fb3a98678875c9420e5af7 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Thu, 27 Nov 2014 08:58:53 +0100 Subject: [PATCH 33/76] restructuring added for uploading KEY_WHILE_CHARGING_ONLY --- .../owncloud/android/db/UploadDbObject.java | 12 + .../files/services/FileUploadService.java | 208 ++++++++++++------ .../owncloud/android/utils/UploadUtils.java | 18 ++ 3 files changed, 174 insertions(+), 64 deletions(-) create mode 100755 src/com/owncloud/android/utils/UploadUtils.java diff --git a/src/com/owncloud/android/db/UploadDbObject.java b/src/com/owncloud/android/db/UploadDbObject.java index 9fce216f3d4..d353fd7462c 100755 --- a/src/com/owncloud/android/db/UploadDbObject.java +++ b/src/com/owncloud/android/db/UploadDbObject.java @@ -113,6 +113,10 @@ public void setLastResult(RemoteOperationResult lastResult) { * Upload only via wifi? */ boolean isUseWifiOnly; + /** + * Upload only if phone being charged? + */ + boolean isWhileChargingOnly; /** * Name of Owncloud account to upload file to. */ @@ -270,4 +274,12 @@ public Account getAccount(Context context) { return AccountUtils.getOwnCloudAccountByName(context, getAccountName()); } + public void setWhileChargingOnly(boolean isWhileChargingOnly) { + this.isWhileChargingOnly = isWhileChargingOnly; + } + + public boolean isWhileChargingOnly() { + return isWhileChargingOnly; + } + } diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index f210c5a21cf..186c4383dd1 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -78,6 +78,7 @@ import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.ui.activity.UploadListActivity; import com.owncloud.android.utils.ErrorMessageAdapter; +import com.owncloud.android.utils.UploadUtils; import com.owncloud.android.utils.UriUtils; /** @@ -122,6 +123,7 @@ public FileUploadService() { public static final String KEY_FORCE_OVERWRITE = "KEY_FORCE_OVERWRITE"; public static final String KEY_CREATE_REMOTE_FOLDER = "CREATE_REMOTE_FOLDER"; public static final String KEY_WIFI_ONLY = "WIFI_ONLY"; + public static final String KEY_WHILE_CHARGING_ONLY = "KEY_WHILE_CHARGING_ONLY"; public static final String KEY_LOCAL_BEHAVIOUR = "BEHAVIOUR"; /** @@ -255,7 +257,7 @@ public void onCreate() { mDb.updateUpload(uploadDbObject); } - //TODO This service can be instantiated at any time. Move this retry call to start of app. + //TODO This service can be instantiated at any time. Better move this retry call to start of app. if(InstantUploadBroadcastReceiver.isOnline(getApplicationContext())) { Log_OC.d(TAG, "FileUploadService.retry() called by onCreate()"); FileUploadService.retry(getApplicationContext()); @@ -289,24 +291,26 @@ protected void onHandleIntent(Intent intent) { Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size() + " - before adding new uploads."); if (intent == null || intent.hasExtra(KEY_RETRY)) { Log_OC.d(TAG, "Receive null intent."); - // service was restarted by OS (after return START_STICKY and kill - // service) or connectivity change was detected. ==> check persistent upload - // list. - // + // service was restarted by OS or connectivity change was detected or + // retry of pending upload was requested. + // ==> First check persistent uploads, then perform upload. + int countAddedEntries = 0; List list = mDb.getAllPendingUploads(); for (UploadDbObject uploadDbObject : list) { // store locally. String uploadKey = buildRemoteName(uploadDbObject); - UploadDbObject previous = mPendingUploads.putIfAbsent(uploadKey, uploadDbObject); - + UploadDbObject previous = mPendingUploads.putIfAbsent(uploadKey, uploadDbObject); if(previous == null) { // and store persistently. uploadDbObject.setUploadStatus(UploadStatus.UPLOAD_LATER); mDb.updateUpload(uploadDbObject); + countAddedEntries++; } else { //upload already pending. ignore. } } + Log_OC.d(TAG, "added " + countAddedEntries + + " entrie(s) to mPendingUploads (this should be 0 except for the first time)."); } else { Log_OC.d(TAG, "Receive upload intent."); UploadSingleMulti uploadType = (UploadSingleMulti) intent.getSerializableExtra(KEY_UPLOAD_TYPE); @@ -372,6 +376,7 @@ protected void onHandleIntent(Intent intent) { boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false); boolean isCreateRemoteFolder = intent.getBooleanExtra(KEY_CREATE_REMOTE_FOLDER, false); boolean isUseWifiOnly = intent.getBooleanExtra(KEY_WIFI_ONLY, true); + boolean isWhileChargingOnly = intent.getBooleanExtra(KEY_WHILE_CHARGING_ONLY, true); LocalBehaviour localAction = (LocalBehaviour) intent.getSerializableExtra(KEY_LOCAL_BEHAVIOUR); if (localAction == null) localAction = LocalBehaviour.LOCAL_BEHAVIOUR_COPY; @@ -385,6 +390,7 @@ protected void onHandleIntent(Intent intent) { uploadObject.setCreateRemoteFolder(isCreateRemoteFolder); uploadObject.setLocalAction(localAction); uploadObject.setUseWifiOnly(isUseWifiOnly); + uploadObject.setWhileChargingOnly(isWhileChargingOnly); uploadObject.setUploadStatus(UploadStatus.UPLOAD_LATER); String uploadKey = buildRemoteName(uploadObject); @@ -402,27 +408,46 @@ protected void onHandleIntent(Intent intent) { // upload already pending. ignore. } } - - // TODO check if would be clever to read entries from - // UploadDbHandler and add to requestedUploads at this point - } // at this point mPendingUploads is filled. Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size() + " - before uploading."); - try { - Iterator it = mPendingUploads.keySet().iterator(); - while (it.hasNext()) { - String up = it.next(); - Log_OC.d(TAG, "Calling uploadFile for " + up); - UploadDbObject uploadDbObject = mPendingUploads.get(up); - boolean uploadSuccessful = uploadFile(uploadDbObject); + Iterator it = mPendingUploads.keySet().iterator(); + while (it.hasNext()) { + String upload = it.next(); + UploadDbObject uploadDbObject = mPendingUploads.get(upload); + + switch (canUploadFileNow(uploadDbObject)) { + case NOW: + Log_OC.d(TAG, "Calling uploadFile for " + upload); + RemoteOperationResult uploadResult = uploadFile(uploadDbObject); + + updateDataseUploadResult(uploadResult, mCurrentUpload); + notifyUploadResult(uploadResult, mCurrentUpload); + sendFinalBroadcast(uploadResult, mCurrentUpload); + if (!shouldRetryFailedUpload(uploadResult)) { + Log_OC.d(TAG, "Upload with result " + uploadResult.getCode() + ": " + uploadResult.getLogMessage() + + " will be abandoned."); + mPendingUploads.remove(buildRemoteName(uploadDbObject)); + } + mCurrentUpload = null; + break; + case LATER: + //TODO schedule retry for later upload. + break; + case FILE_GONE: + mDb.updateUpload(uploadDbObject.getLocalPath(), UploadStatus.UPLOAD_FAILED_GIVE_UP, + new RemoteOperationResult(ResultCode.FILE_NOT_FOUND)); + it.remove(); + break; + case ERROR: + Log_OC.e(TAG, "canUploadFileNow() returned ERROR. Fix that!"); + break; } - } catch (ConcurrentModificationException e) { - // for now: ignore. TODO: fix this. } + Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size() + " - after uploading."); Log_OC.i(TAG, "onHandleIntent end"); } @@ -563,41 +588,70 @@ public void onTransferProgress(long progressRate, long totalTransferredSoFar, lo } } } - + + enum CanUploadFileNowStatus {NOW, LATER, FILE_GONE, ERROR}; + /** - * Core upload method: sends the file(s) to upload. This function blocks - * until upload succeeded or failed. + * Returns true when the file may be uploaded now. This methods checks all + * restraints of the passed {@link UploadDbObject}, these include + * isUseWifiOnly(), check if local file exists, check if file was already + * uploaded... + * + * If return value is CanUploadFileNowStatus.NOW, uploadFile() may be + * called. + * + * @return CanUploadFileNowStatus.NOW is upload may proceed,
+ * CanUploadFileNowStatus.LATER if upload should be performed at a + * later time,
+ * CanUploadFileNowStatus.ERROR if a severe error happened, calling + * entity should remove upload from queue. * - * @param uploadDbObject Key to access the upload to perform, contained in - * mPendingUploads - * @return true on success. */ - private boolean uploadFile(UploadDbObject uploadDbObject) { - - if(uploadDbObject.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED) { - Log_OC.w(TAG, "Already succeeded uploadObject was again scheduled for upload. Fix that!"); - return false; - } + private CanUploadFileNowStatus canUploadFileNow(UploadDbObject uploadDbObject) { - if (mCurrentUpload != null) { - Log_OC.e(TAG, - "mCurrentUpload != null. Meaning there is another upload in progress, cannot start a new one. Fix that!"); - return false; + if (uploadDbObject.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED) { + Log_OC.w(TAG, "Already succeeded uploadObject was again scheduled for upload. Fix that!"); + return CanUploadFileNowStatus.ERROR; } if (uploadDbObject.isUseWifiOnly() && !InstantUploadBroadcastReceiver.isConnectedViaWiFi(getApplicationContext())) { Log_OC.d(TAG, "Do not start upload because it is wifi-only."); - return false; + return CanUploadFileNowStatus.LATER; + } + + if(uploadDbObject.isWhileChargingOnly() && !UploadUtils.isCharging(getApplicationContext())) { + Log_OC.d(TAG, "Do not start upload because it is while charging only."); + return CanUploadFileNowStatus.LATER; } if (!new File(uploadDbObject.getLocalPath()).exists()) { - mDb.updateUpload(uploadDbObject.getLocalPath(), UploadStatus.UPLOAD_FAILED_GIVE_UP, - new RemoteOperationResult(ResultCode.FILE_NOT_FOUND)); Log_OC.d(TAG, "Do not start upload because local file does not exist."); - return false; + return CanUploadFileNowStatus.FILE_GONE; } + return CanUploadFileNowStatus.NOW; + } + + + /** + * Core upload method: sends the file(s) to upload. This function blocks + * until upload succeeded or failed. + * + * Before uploading starts mCurrentUpload is set. + * + * @param uploadDbObject Key to access the upload to perform, contained in + * mPendingUploads + * @return RemoteOperationResult of upload operation. + */ + private RemoteOperationResult uploadFile(UploadDbObject uploadDbObject) { + + if (mCurrentUpload != null) { + Log_OC.e(TAG, + "mCurrentUpload != null. Meaning there is another upload in progress, cannot start a new one. Fix that!"); + return new RemoteOperationResult(new IllegalStateException("mCurrentUpload != null when calling uploadFile()")); + } + AccountManager aMgr = AccountManager.get(this); Account account = uploadDbObject.getAccount(getApplicationContext()); String version = aMgr.getUserData(account, Constants.KEY_OC_VERSION); @@ -615,7 +669,7 @@ private boolean uploadFile(UploadDbObject uploadDbObject) { } mCurrentUpload.addDatatransferProgressListener((FileUploaderBinder) mBinder); - notifyUploadStart(mCurrentUpload); + notifyUploadStart(mCurrentUpload); RemoteOperationResult uploadResult = null, grantResult = null; try { @@ -656,7 +710,6 @@ private boolean uploadFile(UploadDbObject uploadDbObject) { uploadResult = new RemoteOperationResult(e); } finally { - mPendingUploads.remove(buildRemoteName(uploadDbObject)); Log_OC.i(TAG, "Remove CurrentUploadItem from pending upload Item Map."); if (uploadResult.isException()) { // enforce the creation of a new client object for next @@ -666,14 +719,7 @@ private boolean uploadFile(UploadDbObject uploadDbObject) { mUploadClient = null; } } - - // notify result - notifyUploadResult(uploadResult, mCurrentUpload); - sendFinalBroadcast(mCurrentUpload, uploadResult); - - mCurrentUpload = null; - - return uploadResult.isSuccess(); + return uploadResult; } @@ -846,7 +892,14 @@ private void notifyUploadStart(UploadFileOperation upload) { mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotificationBuilder.build()); - mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_IN_PROGRESS, null); + updateDatabaseUploadStart(mCurrentUpload); + } + + /** + * Updates the persistent upload database that upload is in progress. + */ + private void updateDatabaseUploadStart(UploadFileOperation upload) { + mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_IN_PROGRESS, null); } /** @@ -910,34 +963,61 @@ private void notifyUploadResult(RemoteOperationResult uploadResult, UploadFileOp if (uploadResult.isSuccess()) { - mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_SUCCEEDED, uploadResult); - - // remove success notification, with a delay of 2 seconds + // remove success notification, with a delay of 2 seconds NotificationDelayer.cancelWithDelay(mNotificationManager, R.string.uploader_upload_succeeded_ticker, 2000); + } + } + } + + /** + * Updates the persistent upload database with upload result. + */ + private void updateDataseUploadResult(RemoteOperationResult uploadResult, UploadFileOperation upload) { + // result: success or fail notification + if (uploadResult.isCancelled()) { + mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_CANCELLED, uploadResult); + } else { + + if (uploadResult.isSuccess()) { + mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_SUCCEEDED, uploadResult); } else { - // TODO: add other cases in which upload attempt is to be - // abandoned. - if (uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED) { + if (shouldRetryFailedUpload(uploadResult)) { + mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_FAILED_RETRY, uploadResult); + } else { mDb.updateUpload(upload.getOriginalStoragePath(), UploadDbHandler.UploadStatus.UPLOAD_FAILED_GIVE_UP, uploadResult); - } else { - mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_FAILED_RETRY, uploadResult); } } - } else { - mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_FAILED_RETRY, uploadResult); + } + } + + /** + * Determines whether with given uploadResult the upload should be retried later. + * @param uploadResult + * @return true if upload should be retried later, false if is should be abandoned. + */ + private boolean shouldRetryFailedUpload(RemoteOperationResult uploadResult) { + if (uploadResult.isSuccess()) { + return false; + } + switch (uploadResult.getCode()) { + case HOST_NOT_AVAILABLE: + case NO_NETWORK_CONNECTION: + case TIMEOUT: + return true; + default: + return false; } } /** * 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 */ - private void sendFinalBroadcast(UploadFileOperation upload, RemoteOperationResult uploadResult) { + private void sendFinalBroadcast(RemoteOperationResult uploadResult, UploadFileOperation upload) { Intent end = new Intent(getUploadFinishMessage()); end.putExtra(EXTRA_REMOTE_PATH, upload.getRemotePath()); // real remote // path, after diff --git a/src/com/owncloud/android/utils/UploadUtils.java b/src/com/owncloud/android/utils/UploadUtils.java new file mode 100755 index 00000000000..d7de06caf1e --- /dev/null +++ b/src/com/owncloud/android/utils/UploadUtils.java @@ -0,0 +1,18 @@ +package com.owncloud.android.utils; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; + + +public class UploadUtils { + + public static boolean isCharging(Context context) { + IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); + Intent batteryStatus = context.registerReceiver(null, ifilter); + + int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1); + return status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL; + } +} From d7499fdd0d6024890695e66c12b9ec2c25f7246d Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Thu, 27 Nov 2014 09:02:12 +0100 Subject: [PATCH 34/76] moved upload helpers to UploadUtils --- .../files/InstantUploadBroadcastReceiver.java | 14 -------------- .../android/files/services/FileUploadService.java | 5 ++--- src/com/owncloud/android/utils/UploadUtils.java | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java index 27a5b860559..5a948413cbe 100644 --- a/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java +++ b/src/com/owncloud/android/files/InstantUploadBroadcastReceiver.java @@ -25,8 +25,6 @@ import android.content.Context; import android.content.Intent; import android.database.Cursor; -import android.net.ConnectivityManager; -import android.net.NetworkInfo.State; import android.preference.PreferenceManager; import android.provider.MediaStore.Images; import android.provider.MediaStore.Video; @@ -214,18 +212,6 @@ private void handleNewVideoAction(Context context, Intent intent) { // // } - public static boolean isOnline(Context context) { - ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - return cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnected(); - } - - public static boolean isConnectedViaWiFi(Context context) { - ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - return cm != null && cm.getActiveNetworkInfo() != null - && cm.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI - && cm.getActiveNetworkInfo().getState() == State.CONNECTED; - } - public static boolean instantPictureUploadEnabled(Context context) { return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("instant_uploading", false); } diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index 186c4383dd1..f0710ccec38 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -55,7 +55,6 @@ import com.owncloud.android.db.UploadDbHandler; import com.owncloud.android.db.UploadDbHandler.UploadStatus; import com.owncloud.android.db.UploadDbObject; -import com.owncloud.android.files.InstantUploadBroadcastReceiver; import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; @@ -258,7 +257,7 @@ public void onCreate() { } //TODO This service can be instantiated at any time. Better move this retry call to start of app. - if(InstantUploadBroadcastReceiver.isOnline(getApplicationContext())) { + if(UploadUtils.isOnline(getApplicationContext())) { Log_OC.d(TAG, "FileUploadService.retry() called by onCreate()"); FileUploadService.retry(getApplicationContext()); } @@ -615,7 +614,7 @@ private CanUploadFileNowStatus canUploadFileNow(UploadDbObject uploadDbObject) { } if (uploadDbObject.isUseWifiOnly() - && !InstantUploadBroadcastReceiver.isConnectedViaWiFi(getApplicationContext())) { + && !UploadUtils.isConnectedViaWiFi(getApplicationContext())) { Log_OC.d(TAG, "Do not start upload because it is wifi-only."); return CanUploadFileNowStatus.LATER; } diff --git a/src/com/owncloud/android/utils/UploadUtils.java b/src/com/owncloud/android/utils/UploadUtils.java index d7de06caf1e..b5da42bee45 100755 --- a/src/com/owncloud/android/utils/UploadUtils.java +++ b/src/com/owncloud/android/utils/UploadUtils.java @@ -3,6 +3,8 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.net.NetworkInfo.State; import android.os.BatteryManager; @@ -15,4 +17,16 @@ public static boolean isCharging(Context context) { int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1); return status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL; } + + public static boolean isOnline(Context context) { + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + return cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnected(); + } + + public static boolean isConnectedViaWiFi(Context context) { + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + return cm != null && cm.getActiveNetworkInfo() != null + && cm.getActiveNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI + && cm.getActiveNetworkInfo().getState() == State.CONNECTED; + } } From 28de7023987cde2e80680dcd33ebb55320298570 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Thu, 27 Nov 2014 10:00:10 +0100 Subject: [PATCH 35/76] prepare delayed uploads commenting --- .../owncloud/android/db/UploadDbObject.java | 20 ++++++++ .../files/services/FileUploadService.java | 46 +++++++++++++++++-- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/com/owncloud/android/db/UploadDbObject.java b/src/com/owncloud/android/db/UploadDbObject.java index d353fd7462c..8e29c9b2a7b 100755 --- a/src/com/owncloud/android/db/UploadDbObject.java +++ b/src/com/owncloud/android/db/UploadDbObject.java @@ -117,6 +117,11 @@ public void setLastResult(RemoteOperationResult lastResult) { * Upload only if phone being charged? */ boolean isWhileChargingOnly; + /** + * Earliest time when upload may be started. Negative if not set. + */ + long uploadTimestamp; + /** * Name of Owncloud account to upload file to. */ @@ -282,4 +287,19 @@ public boolean isWhileChargingOnly() { return isWhileChargingOnly; } + /** + * Earliest time when upload may be started. Negative if not set. + * @return the uploadTimestamp + */ + public long getUploadTimestamp() { + return uploadTimestamp; + } + + /** + * Earliest time when upload may be started. Set to negative value for immediate upload. + * @param uploadTimestamp the uploadTimestamp to set + */ + public void setUploadTimestamp(long uploadTimestamp) { + this.uploadTimestamp = uploadTimestamp; + } } diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index f0710ccec38..7f0ca8399a5 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.IOException; import java.util.ConcurrentModificationException; +import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -45,6 +46,7 @@ import android.os.Parcelable; import android.os.Process; import android.support.v4.app.NotificationCompat; +import android.text.format.DateUtils; import android.webkit.MimeTypeMap; import com.owncloud.android.R; @@ -76,6 +78,7 @@ import com.owncloud.android.ui.activity.FileActivity; import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.ui.activity.UploadListActivity; +import com.owncloud.android.utils.DisplayUtils; import com.owncloud.android.utils.ErrorMessageAdapter; import com.owncloud.android.utils.UploadUtils; import com.owncloud.android.utils.UriUtils; @@ -113,16 +116,39 @@ public FileUploadService() { public static final String KEY_REMOTE_FILE = "REMOTE_FILE"; public static final String KEY_MIME_TYPE = "MIME_TYPE"; - public static final String KEY_RETRY = "KEY_RETRY"; - - + /** + * Call this Service with only this Intent key if all pending uploads are to be retried. + */ + private static final String KEY_RETRY = "KEY_RETRY"; + /** + * {@link Account} to which file is to be uploaded. + */ public static final String KEY_ACCOUNT = "ACCOUNT"; - + /** + * Set whether single file or multiple files are to be uploaded. Value must be of type {@link UploadSingleMulti}. + */ public static final String KEY_UPLOAD_TYPE = "UPLOAD_TYPE"; + /** + * Set to true if remote file is to be overwritten. Default action is to upload with different name. + */ public static final String KEY_FORCE_OVERWRITE = "KEY_FORCE_OVERWRITE"; + /** + * Set to true if remote folder is to be created if it does not exist. + */ public static final String KEY_CREATE_REMOTE_FOLDER = "CREATE_REMOTE_FOLDER"; + /** + * Set to true if upload is to performed only when connected via wifi. + */ public static final String KEY_WIFI_ONLY = "WIFI_ONLY"; + /** + * Set to true if upload is to performed only when phone is being charged. + */ public static final String KEY_WHILE_CHARGING_ONLY = "KEY_WHILE_CHARGING_ONLY"; + /** + * Set to future UNIX timestamp. Upload will not be performed before this timestamp. + */ + public static final String KEY_UPLOAD_TIMESTAMP= "KEY_UPLOAD_TIMESTAMP"; + public static final String KEY_LOCAL_BEHAVIOUR = "BEHAVIOUR"; /** @@ -376,6 +402,8 @@ protected void onHandleIntent(Intent intent) { boolean isCreateRemoteFolder = intent.getBooleanExtra(KEY_CREATE_REMOTE_FOLDER, false); boolean isUseWifiOnly = intent.getBooleanExtra(KEY_WIFI_ONLY, true); boolean isWhileChargingOnly = intent.getBooleanExtra(KEY_WHILE_CHARGING_ONLY, true); + long uploadTimestamp = intent.getLongExtra(KEY_UPLOAD_TIMESTAMP, -1); + LocalBehaviour localAction = (LocalBehaviour) intent.getSerializableExtra(KEY_LOCAL_BEHAVIOUR); if (localAction == null) localAction = LocalBehaviour.LOCAL_BEHAVIOUR_COPY; @@ -390,6 +418,7 @@ protected void onHandleIntent(Intent intent) { uploadObject.setLocalAction(localAction); uploadObject.setUseWifiOnly(isUseWifiOnly); uploadObject.setWhileChargingOnly(isWhileChargingOnly); + uploadObject.setUploadTimestamp(uploadTimestamp); uploadObject.setUploadStatus(UploadStatus.UPLOAD_LATER); String uploadKey = buildRemoteName(uploadObject); @@ -623,6 +652,15 @@ private CanUploadFileNowStatus canUploadFileNow(UploadDbObject uploadDbObject) { Log_OC.d(TAG, "Do not start upload because it is while charging only."); return CanUploadFileNowStatus.LATER; } + Date now = new Date(); + if (now.getTime() < uploadDbObject.getUploadTimestamp()) { + Log_OC.d( + TAG, + "Do not start upload because it is schedule for " + + DisplayUtils.unixTimeToHumanReadable(uploadDbObject.getUploadTimestamp())); + return CanUploadFileNowStatus.LATER; + } + if (!new File(uploadDbObject.getLocalPath()).exists()) { Log_OC.d(TAG, "Do not start upload because local file does not exist."); From 4018af13732fa31a10fd5087e735cf4cafc67ddd Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Thu, 27 Nov 2014 10:07:04 +0100 Subject: [PATCH 36/76] commenting --- .../android/files/services/FileUploadService.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index 7f0ca8399a5..9d2151c5d77 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -463,7 +463,10 @@ protected void onHandleIntent(Intent intent) { mCurrentUpload = null; break; case LATER: - //TODO schedule retry for later upload. + // Schedule retry for later upload. Delay can be due to: + // KEY_WIFI_ONLY - done by ConnectivityActionReceiver + // KEY_WHILE_CHARGING_ONLY - TODO add PowerConnectionReceiver + // KEY_UPLOAD_TIMESTAMP - TODO use AlarmManager to wake up this service break; case FILE_GONE: mDb.updateUpload(uploadDbObject.getLocalPath(), UploadStatus.UPLOAD_FAILED_GIVE_UP, @@ -1083,6 +1086,9 @@ private boolean isPdfFileFromContentProviderWithoutExtension(String localPath, S !localPath.endsWith(FILE_EXTENSION_PDF); } + /** + * Call if all pending uploads are to be retried. + */ public static void retry(Context context) { Log_OC.d(TAG, "FileUploadService.retry()"); Intent i = new Intent(context, FileUploadService.class); From 37d3a72960c1d6e93900e347c3797376c5e47928 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Thu, 27 Nov 2014 11:56:24 +0100 Subject: [PATCH 37/76] add ExpandableListFragment which is inherits from ExtendedListFragment Only difference: it uses an ExpandableListView which supports dividing list into groups --- res/layout/list_fragment_expandable.xml | 45 ++++++++++ .../ui/fragment/ExpandableListFragment.java | 89 +++++++++++++++++++ .../ui/fragment/ExtendedListFragment.java | 12 +-- 3 files changed, 140 insertions(+), 6 deletions(-) create mode 100755 res/layout/list_fragment_expandable.xml create mode 100755 src/com/owncloud/android/ui/fragment/ExpandableListFragment.java diff --git a/res/layout/list_fragment_expandable.xml b/res/layout/list_fragment_expandable.xml new file mode 100755 index 00000000000..838e48efdbb --- /dev/null +++ b/res/layout/list_fragment_expandable.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/com/owncloud/android/ui/fragment/ExpandableListFragment.java b/src/com/owncloud/android/ui/fragment/ExpandableListFragment.java new file mode 100755 index 00000000000..cc7f06ac587 --- /dev/null +++ b/src/com/owncloud/android/ui/fragment/ExpandableListFragment.java @@ -0,0 +1,89 @@ +/* ownCloud Android client application + * Copyright (C) 2012 Bartek Przybylski + * Copyright (C) 2012-2013 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 . + * + */ + +package com.owncloud.android.ui.fragment; + +import java.util.ArrayList; + +import android.os.Bundle; +import android.support.v4.widget.SwipeRefreshLayout; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ExpandableListAdapter; +import android.widget.ExpandableListView; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import com.actionbarsherlock.app.SherlockFragment; +import com.owncloud.android.R; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.ui.ExtendedListView; +import com.owncloud.android.ui.activity.OnEnforceableRefreshListener; + +/** + * Extending ExtendedListFragment. This allows dividing list in groups. + */ +public class ExpandableListFragment extends ExtendedListFragment + { + + protected ExpandableListView mList; + + public void setListAdapter(ExpandableListAdapter listAdapter) { + mList.setAdapter(listAdapter); + mList.invalidate(); + } + + public ExpandableListView getListView() { + return mList; + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + Log_OC.e(TAG, "onCreateView"); + + View v = inflater.inflate(R.layout.list_fragment_expandable, null); + mEmptyListMessage = (TextView) v.findViewById(R.id.empty_list_view); + mList = (ExpandableListView)(v.findViewById(R.id.list_root)); + mList.setOnItemClickListener(this); + + mList.setDivider(getResources().getDrawable(R.drawable.uploader_list_separator)); + mList.setDividerHeight(1); + + if (savedInstanceState != null) { + int referencePosition = savedInstanceState.getInt(KEY_SAVED_LIST_POSITION); + setReferencePosition(referencePosition); + } + + // Pull down refresh + mRefreshLayout = (SwipeRefreshLayout) v.findViewById(R.id.swipe_refresh_files); + mRefreshEmptyLayout = (SwipeRefreshLayout) v.findViewById(R.id.swipe_refresh_files_emptyView); + + onCreateSwipeToRefresh(mRefreshLayout); + onCreateSwipeToRefresh(mRefreshEmptyLayout); + + mList.setEmptyView(mRefreshEmptyLayout); + + return v; + } + + } diff --git a/src/com/owncloud/android/ui/fragment/ExtendedListFragment.java b/src/com/owncloud/android/ui/fragment/ExtendedListFragment.java index 5d3f7ace6f8..51a60ad641d 100644 --- a/src/com/owncloud/android/ui/fragment/ExtendedListFragment.java +++ b/src/com/owncloud/android/ui/fragment/ExtendedListFragment.java @@ -43,9 +43,9 @@ public class ExtendedListFragment extends SherlockFragment implements OnItemClickListener, OnEnforceableRefreshListener { - private static final String TAG = ExtendedListFragment.class.getSimpleName(); + protected static final String TAG = ExtendedListFragment.class.getSimpleName(); - private static final String KEY_SAVED_LIST_POSITION = "SAVED_LIST_POSITION"; + protected static final String KEY_SAVED_LIST_POSITION = "SAVED_LIST_POSITION"; private static final String KEY_INDEXES = "INDEXES"; private static final String KEY_FIRST_POSITIONS= "FIRST_POSITIONS"; private static final String KEY_TOPS = "TOPS"; @@ -54,9 +54,9 @@ public class ExtendedListFragment extends SherlockFragment protected ExtendedListView mList; - private SwipeRefreshLayout mRefreshLayout; - private SwipeRefreshLayout mRefreshEmptyLayout; - private TextView mEmptyListMessage; + protected SwipeRefreshLayout mRefreshLayout; + protected SwipeRefreshLayout mRefreshEmptyLayout; + protected TextView mEmptyListMessage; // Save the state of the scroll in browsing private ArrayList mIndexes; @@ -293,7 +293,7 @@ public String getEmptyViewText() { return (mEmptyListMessage != null) ? mEmptyListMessage.getText().toString() : ""; } - private void onCreateSwipeToRefresh(SwipeRefreshLayout refreshLayout) { + protected void onCreateSwipeToRefresh(SwipeRefreshLayout refreshLayout) { // Colors in animations: background refreshLayout.setColorScheme(R.color.background_color, R.color.background_color, R.color.background_color, R.color.background_color); From eaab185720d0a94472ea602a416c40beaabf5022 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Thu, 27 Nov 2014 14:26:31 +0100 Subject: [PATCH 38/76] introduce divisions in upload list view --- res/drawable/upload_failed.png | Bin 0 -> 4325 bytes res/drawable/upload_finished.png | Bin 0 -> 4653 bytes res/drawable/upload_in_progress.png | Bin 0 -> 2764 bytes res/layout/upload_list_group.xml | 28 +++ .../owncloud/android/db/UploadDbHandler.java | 45 +++- .../files/services/FileUploadService.java | 4 +- ....java => ExpandableUploadListAdapter.java} | 220 ++++++++++++------ .../ui/fragment/UploadListFragment.java | 190 --------------- 8 files changed, 215 insertions(+), 272 deletions(-) create mode 100755 res/drawable/upload_failed.png create mode 100755 res/drawable/upload_finished.png create mode 100755 res/drawable/upload_in_progress.png create mode 100755 res/layout/upload_list_group.xml rename src/com/owncloud/android/ui/adapter/{UploadListAdapter.java => ExpandableUploadListAdapter.java} (53%) delete mode 100755 src/com/owncloud/android/ui/fragment/UploadListFragment.java diff --git a/res/drawable/upload_failed.png b/res/drawable/upload_failed.png new file mode 100755 index 0000000000000000000000000000000000000000..94a056e5af19fd598d3742900adff8147cb28c28 GIT binary patch literal 4325 zcmeHK_fwM#vwriEkPte70MbJ52^gA63895vLKh@7kxuARgH)BKpCVWgL=Pwk2uB2@ z1VP~l0xE)qLlKlBQUs(3d9VHp-%oeu-ksUmeV*N)o}Jy9J?~&=!HqtG1^|HDl49xv z0I=UF3_!8{4xx4K&VNvhlLZ;59~NKw4d6k>w#ESPsEG5x7XbilfP;;**`FpD3xrOUuZ}$jZvf$;rvf%PS}- zC@Lx{DJdx{E32resH&=}si~=}tK;!_0)e2Rp`od%dGzQ}B9W-2rKPQ{t)ru(tE;Q0 zr+57LaeaM#0|Ns?Lqj7YBNB;3CX-D}OiWEp&CJZq&CM+=EGQI;rKP2nm6f%%wXLnK zot>S%y}g5jgQKIPlamvbN_BR2c5!iWb#--fb8~lh_wexW^z`)d^78ifrqO7=zP^5b ze*XUcfq{XiPoEA73OaMl+W_hg zQvUa#IHD*XF#y2+?T^E3G|R1ilSg9B++(Scr(+X*qXPj4-{6Q?yq%ddE}n?j#%pS5 zY(-ZB0GGa{sj+k7sqeQ!{U`f`a=z4ccEOJg&9*alT4(Dq|ckU3%%hu}aB3D}IY-hwBliQfCW@jzFuxWW+hGToqfnaZSx$S^mBuH+95naFM5#eyl?MNS*7 z1@ouINQ~~`3Q`ISz)0r%Fs?a@=o)4V1-(#bo>a;s5_*bN=Y(c3SDszu7K!n%18zD0u{PYs30*u<-(gNdYZ!$JG8Z(D3MjbKV%eMvM2m6PHiW&z4y}*HgS;vr#Ljg7* z?(LnG9-X2`sC7(}+7NcAStJ*LNIB2x8~pt^rrSUSATB7<2~6Xfjc1GyRlwXrBwwKccc3f1;7qx>J6~N95D4kdD9itTJ?tdN%tGZ@G z0X|oZ50F|=d8jCfYY%z1y8K|ULT5rJ-9(bGEv95l3!I0DaK+drq;R*2M6jo6k*GgEKzVzdsc>2@TprK zwRu4TUh@#528wy;%;120doX683DyN@Z$s~JBkb=%@t&;!nvZV}wxaORCn*9qk<1Z@jykevRh(z6#NH8X&mKgml4O68*z&L5WL zf(;5wk+bhJPu1J5!z-S^gp)Rm;#_gbnbXgGR^2DWe!v+nC%olamug6cO`DaG22m&z zPj_%K(?zvGI-?d`C_e581bKbPl~7lM)yz#lzg9Fzb+GBa7*pu3Xx#{BIEopWY~>p@ z3yDPZtj*Pl-Dtd%Eepi4kard)eOr((CG#f?8hK9zxCcp)wtO0%M zn~Yt-#Uzc@nFO8jN3a|~BiOH%MIhHAx9nHy_ivzG>sT7zjB1Tw=6uSOv_4{i-!0qU zVEx$T&`Qomj{hXFe>W2P`IB+Bu=a+}I^pg(S(y1zD4;efJe+)zSD{V%6g+r)6u~f4 zFN1=ftq3oED-sv(1}(SqIEwcnou6Ft4xP~8q^TD><-Ua#ki2m60!w)p;a57lQ;e&W zYyygW9ShnIoy;aI0+LxVtFnf-PEhV=sxdrLOq*g(evoX`asRaW<+9?4_6KqqB{R`)qP9WGszs{7P&g-$iEs)oL1dx5InCvn4eC^De~*4MKuv(uFhd zm*uZ5Z&t4tTJK>Zq*H~OfI*FWzAVwt{H;dL=#ax}e*a`;JfT&B9t^zN(jrbW+s~l* zCqz9f5$rX(=MexqbxvAc;)OxubUNCGWnWfXxRajq)z$<%WBdL!aij(rJ(72{KzSkI z)E9dPyn-xeBabS339FzL&U#N}Y#uc1mG~G~kp&s!(nI%=5ei?9W&5Lp=-dPrk3T{Q zOzC25xtUAWZfEhA=#k?<}N$SMdfO=a-aziqY#n+fkT=%ZK5ts?}p_M1{pGYGV zp?;>z`&Xmr-&cfC7!=U|4tl*Q<-S^pVbaBAGUr4;pfrf7H8Ree>%yjS*>7cz_U?bw zxca%Y&55CI8e2WLANN$?&4|&iz*mejc+*%*3d`c~Kjpq4Ks#0N0hOK{qdhW`m9ZF&M+30~VJDGEEiH4t~ihY8jkEMga zjLU&qW>Jvb0#xJiEG`GJtDScB&L)`i0hJ!>+LQ1hI|tr+ZhF4-yPJ+aUOJUDgT*2e z8Moz@+^f&jBofvSr|0NvPG+$6Of`J@r4Fb{rWSz);Ylm)EB3c;)Vs^8~RbH0o9HWh92nfiiax%csoV{0_6q z1~`!CPGwc2SxF{H{0_YJLU$Iu7Gj^2Fix&`iviM6CO4pPKSA|7QDD^9?!`ntmNEsg zE`f6P^MQ;rx~4|2Iy1Dztv~-dSV$4vSKgK82XZgBuBEyv#2TzCg*|O#(N{1% z$K+U@YKuo!IImaH=ZPq#oUzT@m@#7e73a)n4iomTIhKU$&U4~6SOknihFLJ|%r|6P zRx-N%85mU#spe}|(NaxSEv-p>c6;__%~|$oUu!1B_M0VBGh{jRcG8bSSBa+uYs~%@ zJ*IBu)_Wgn+K?JfG8w++zPMt#Haj3SjGwu-0ksX3+6wm(KNh8B%SP&rKr~!ZhGqoi zP=GVH_Y8+cSJXZehNEH#R9J5bJ(-Fa7IrpJmr%kr7< z?W`1`%QYe87WSaPFNR<1bg!(c7s<{wC93Qc8-Nd3)ja09+1j^<0F%yj|FjEmQQP=nr;n}lh8b{`fy zU@z06c|s;0kxETztrVxTobA2i$xN^5j|Npw_^E~AjtSbL$#ygMHptrV_8p;g9U_lzb^<|tO5<6PO@l@&qUWDLF^%ex_Q2~MQc)LJ zO+VMqBns?6`!istLsXO;65VRpy_aMX45gF0v=bEIcIcxMROVM`@gMyvhixBnh@t!m zRAaW^vglqNWNje($oPiJ;rG2txSjQzwzVg$hNogthwC&EUQnZD^g6o9sd_$lw^BN+ zVvM5=z_b*UOFj4g=a+78K=|E73%XVlff*2fZ|V8yDm8PNc1MT0?VE^8z>%oSJ${H{ z2R_<49p|5wTJraBOnCVH?>#W|4XPk5TE`_Jh}Y~pQI2^xtafb}i#|?sX9PE(gv27R z!V@z}-<}VddvqDt!1UcRf3TBI_F(J+kE)BT4kxN)m}66HUv{Kw&>FY4nfVb`^<+kH zirBJZv|%CGvnxkM19>l>!${cM1~o%q!)81Cv@;?oDW+ztDiP?wKbEXN$4@1OWgE z;7_>P!bLm)1Ro?4DZociP*8{u3WXBpBO)Rq%12C0YzH4S8ZFL8LPA24&(57YrT9oo zOYh<#BO@aVBPTDf@EeSxl9G}#j0&IM_wL=R3Ztf`roInGLqkJT3uZqLZEbBGn1g(D zb#?XhVGMW}!W`mbWDH|sVse;|shOFXg@uKsrR9+$M=%%+7K^pEwzjdcv9+~524i>p z_;CjZ2S-OoCnqNy4o@HuTwGjSU0vPW+}z#WJv=--Jw3g=yu4w2e0tK6co%SBqSu1&-wG`!@|PC!^0!^L`FtNMMXtNN5}Aqjg2LdNEa?# zh~pC)1!Zn%zylY6#PA<^@Z2*^blsfN8 zi$opwj0FIp1^5S^(9d(=C3ll-JxDH*fuwl9m;eCp7ZO1_=w$1v5@&eO=%Buy-fBz% z?>lta-p0x`;SBT3_8(tt6w-gz6nne~=;ncEjDWDDE4XExkNo5Am1Ho*9QP z*VU-!QNsq+b#jfNc16Myw=dK?d^4>W>k>vJgj`4eyKQ=uB#EIm4gIax>i^f-YGlIS zXpuM#WZKo=+9HRqI%Vm3{9OW$_UxI0R<^6=917_nw^2&O4d)^H)CU*QFL@W6Q^wvlLXx>=lmCvb0ym ziP+@)P9QHEqDb_p{G|B+Orn)0#SA1F%tVKU&C8v!QYJm;^L%xrW4j^e_b3c6&K5F3A4 z`>T^twh2HaKB$&x{<p2#e5%nJ}?9oq>>U%j8_+=rLfhIBjXmu|$k$@wAoSKC?GVTq zhpaeCF%mudMykzDEIHi=}nJ`H4jv`UJ17KozhJ2J*a=GHX2`ZtPs zL#_orC2Jyv&b^aj#aoZHA7#5AHFtly^cP;>eS*X;R(ia-k{YIVObi~h4zvY!Jh$ew zr(_B_W zri>SX{n?Nk?*2Mrq8;r+~N(9n3zpC|EnaDpujIE`6XXQn8=% zsMGUa9NXLO*lTdK3(FfHVav($4;TrVJbY-c_yqW-G(Q!JD0nn}`hHVe4(kfA|VXgb9v$OSrXA_ro39YAEX!W<5^J`^@;L%n#n4DWS z%uDWCGjB?-i>+0y>5~`q1^T0u=X#7liyGC~)NruPl5*T71PVrcrgpk@pD z;+D*HrOmq=N5ux$$RBR+W0oXmS^Wl%_K8X{eDx!s=M94Eh+!tA^zCV89O)`fihWE?BOqZavp2473%bVF3;~06KSuLVeN0|~`vm2YTWxF?W$JctxnKtI3COYZ5 zNZ}1@&G{a#s&7kLo#V4_lyxpHGj8?Hv>e#n6|kET-+HTKxzDFO%juUglKEW+a{@0Q zxe~s_z(Kl|23J3+4+`DgdJfALx#6xt{nlucwxu;3#_cbsO&YYWuyDE~BFjq#)cnfr z$K0GBXQE1*<=#d>ccv-EwbBzFJE>nSqY#Y#u#A@e4Ff~f;f3)$ST$nQOQ5P<(2UW~ zl%@$Sg|!&8Zqn}VAr#RhZ2|TZo0PYUf;~k<%XgF=wbGXW@tIe7C`EU!c_aU!%-KDeVKeQKZ9l4{MnCeIWoiNKT9puEkf?^oie_70_?(L zoU0uA3@4uTB5=D&pz6CR)n}nF^THS2sq7W$b&7M=YQV`px42ORC` z4`rBi<)Kp!K8b*|MnqPub_1AsLTex=#aQhI35raMyA-yrOpOYh*_&z<&WoC>j&s|x zlNgcTV{{wKUveG?YrOq%ZRR|?#E$bQ1tP{Wc5PD*)Jo6XT=mnFXX&hQ?xLNOT-MpB z5s{cBJy2jw%#~k2^v?$_8>)U7u3|3jY8xv+4AuSAHFo?__a@r^cV4aD7ir;RDJbSU z&E++EAx$M+jxzZeV8joPZyC9}y?`I{rLcpc! zf{ra+v4G@w@=7~QHnjkj<-`6fk?G^SeIz~eZpt`S6q}+v{7y5NKS^&#y6_)_aYq9~ zsb=n+t+`cB+E>+TXU6*ZR=Lak*QDl2I*d|dovyW%BQ?X*KK`sKS^Lb@t?NceYfQ)iJkqF+M{164li7Rp^@i+cBhC@B_(OViJE;E zyiC{4*|KJl+sP>w+<;&W?8uvcR*Vvz8Fvln=%!v?pG6H8=>C6JY%`r1a%Fvgu2w>g z16gA$HYbqH1~c{d6)w==eH=M#zlB>CDJreRkBXV~!|m>*Oor~pPA|k1msH;D|MLYu zSsxalfSne*#mhb8X0CGRF||dv={KSkka8si-yn)EEHn7);~8%u$)tai=MOL!HX+Pm zYS?xSU>%8oJ;7gxbs}Z46Tbl1lN5*j5>Z+1*a=$?vyRf|zf>CWnXtWZs{c~TPOoyw zfc*tQ3)hd6@-9Eha|gW7;s}V)b1G1W9g3am_j1tbvaXXbc0=HoB%FCtZkqPKk$nR0(r~+5$a6cYE84kU28fB;EoXRcYFW<; z4g>-(P}cB-5O<^_Kk{6jku5x8Z4E7PsXVbxxo@U@ zAMK>h8%L>@VxR&A>SFZ7vFh2avkmG1vu5&o8|S3&AW%!TDf(u*U1dpaD|)C}H@nTw zQ3W{ff}8huwrYEW0x1oH@^!ShK_zUA5u52UP1pm){}hmndq{G<`l;2T85C$aqbJ+U zUHBx`c6~d!>P{f~MJ`=WQj8T^#N5w_7xSKlve!GeTZop8e?(U86#_tje28lG_2dIE5#!(qfk58PyK?vxO0xG$1OWD}3Wg9e*%ZY$B9gGa zNgi>8q?C|^FhEL5if&|VRN~nXQkZUBLimlflX3unu#KCuC-&E`Uwr+)?cWOg-xUZk zl-=Yn?nyf3;sFp627*NPfgw=Q{Rd!T;u4Yv4@pVO$jZqpC?b`VRgS7@XlfnP*3mUI zGDex2pR};T*w|w2>>V7Pobj&iUcP<-fv3-eo;?>v2#<&)M#aR&krEP?7Vl$AI_CdlYS;$Z3H83EKQaYIb8gh zSWL)aI+Z)kQ(w7Yn~wFB|3NDIx;L?zB{G{xp3R?IulfGfS_1Tsy_cH$fvNX7^JB^4 z1;;1pVZ@B2rtkK4U)wv6YKYQef4B?;-AynFcV)#ip{>(*8CK4TXP8ecR)*cL*AkHg zoSIlGu9v+2rhEjG;DZt!-24zuTm5@rP5$hd%G}cSf$S`GOF4?twq9IX^l~zC@*RBved(?6GbuI z!Bw-(h9#Be8o>iHs=EH%e$J^r%tGVVk;iZYw@s~X2VLye9u*1G#Z6{boKe)XW1&XI z?!ExkuO5`)1{_nNvj`q%f8P81^1TA4Q~2!KlG!A0w}Jtvq`m6e!fCe-kZQWCv}>))U1{Zr>Zh|_lS?@JLlKgE;pMk7|J zK1acb_$6Yh=lJ0BcOo<~Ltgnt`0>SHRK|3#uPMN_eMDbcZD=05O@7vw_QTDjr{gV9 z>6?KGk7Es7RP_={mTE+(%F|=@7Jq}Wy&1CV>h!k!dkNA*?S_O(v{&eL0}rs*4aD(x zbmGPMi39lU;+tX-%Y;=0N`2Tw@y#_CYPO+T-J-bh1U8f$TA zV%9WR3guWHt!tq0KyM%xW=n&BtW+p zr3>9=Z%-6*DOysick+gGM6;0W09F(4xu21DqA1JpIa|fz0vc?Rkpzd zWnGGHD>KlNJm<_E_PKoZdq!NjKgdraEn)k_xsc&GCaz4XPTi*#*HD{Ukp}XEr)}P% zsj4calB@abVA`fP!f>N1UJL#K%^jq1rc$pKf=)>C;*((tNODOv=meZ+;9o%Yf}N^R z4#!w!73{QyEeImp03O1{o7FbaAFVv&jvXYTGtK_?;Gr*|O8EpxsYz8p0G zv~1BeroDjSYXOr=Mxi317|O9_bB)K&Os z52e8}S3V1EPNFkqNT;X$*c2dM@vr0{`eDwhtC|C6Yoz2#UB)VrcBup`^8wO!k||IBjmk6x!iv%l5b1~{rZhB@doP(*0mr!1U_~il+DlJ zbx3hL2OZ#~j|t$X{s3(i(@SY^0SxdrGk(2~C5S)-j@k2_3{R2Y3;0^6OaM_3iUmeT z7B=wtO8lqpl)=+38 z620VU$4i2A)u7gly1~f8cn&x^%pIZGpPlAwJ0A6=%u7?6COs0WSA<(v8L3||F6w)% zHPTG!Kin;u_;#Rvtnl#_*zb@eHT}lruiB{t!v-`RBW+T4k-=R>?dU-U>Fp?{G7#FB ztsR){H19n=h)K>cFmz8S%Vj=LK(I%Juf*b&&zci_BVNSVnzYx1hb*;W&NqX7(c?Nr zWo`E}wPjVKE*IrZXhSOSjOY0N*+Yx#B&mc)&G`l?_mMl(W+SrZonmCCXk_q7M%Oa^ z_LKDLdlfTYFpJ^ywV^f63cj!y!PRPNzx~-SNK8*l{+l>MHtc^d%tapt>W17q+BU5i u?`)vb@XRhhjh18_*AhTghWvA%-c@R + + + + + + + +
\ No newline at end of file diff --git a/src/com/owncloud/android/db/UploadDbHandler.java b/src/com/owncloud/android/db/UploadDbHandler.java index 7aa47c6f50e..223092eeff3 100644 --- a/src/com/owncloud/android/db/UploadDbHandler.java +++ b/src/com/owncloud/android/db/UploadDbHandler.java @@ -273,13 +273,13 @@ public boolean removeUpload(String localPath) { return result != 0; } - public List getAllStoredUploads() { + public UploadDbObject[] getAllStoredUploads() { return getUploads(null, null); } - private List getUploads(String selection, String[] selectionArgs) { + private UploadDbObject[] getUploads(String selection, String[] selectionArgs) { Cursor c = getDB().query(TABLE_UPLOAD, null, selection, selectionArgs, null, null, null); - List list = new ArrayList(); + UploadDbObject[] list = new UploadDbObject[c.getCount()]; if (c.moveToFirst()) { do { String uploadObjectString = c.getString(c.getColumnIndex("uploadObject")); @@ -287,20 +287,51 @@ private List getUploads(String selection, String[] selectionArgs if (uploadObject == null) { Log_OC.e(TAG, "Could not deserialize UploadDbObject " + uploadObjectString); } else { - list.add(uploadObject); + list[c.getPosition()] = uploadObject; } } while (c.moveToNext()); } return list; } - public List getAllPendingUploads() { + /** + * Get all uploads which are pending, i.e., queued for upload but not currently being uploaded + * @return + */ + public UploadDbObject[] getPendingUploads() { return getUploads("uploadStatus==" + UploadStatus.UPLOAD_LATER.value + " OR uploadStatus==" - + UploadStatus.UPLOAD_FAILED_RETRY.value, null); + + UploadStatus.UPLOAD_FAILED_RETRY.value+ " OR uploadStatus==" + + UploadStatus.UPLOAD_PAUSED.value, null); } - public List getCurrentUpload() { + /** + * Get all uploads which are currently being uploaded. There should only be one. No guarantee though. + */ + public UploadDbObject[] getCurrentUpload() { return getUploads("uploadStatus==" + UploadStatus.UPLOAD_IN_PROGRESS.value, null); } + + /** + * Get all current and pending uploads. + */ + public UploadDbObject[] getCurrentAndPendingUploads() { + return getUploads("uploadStatus==" + UploadStatus.UPLOAD_IN_PROGRESS.value + " OR uploadStatus==" + UploadStatus.UPLOAD_LATER.value + " OR uploadStatus==" + + UploadStatus.UPLOAD_FAILED_RETRY.value+ " OR uploadStatus==" + + UploadStatus.UPLOAD_PAUSED.value, null); + } + + /** + * Get all unrecoverably failed. Upload of these should/must/will not be retried. + */ + public UploadDbObject[] getFailedUploads() { + return getUploads("uploadStatus==" + UploadStatus.UPLOAD_FAILED_GIVE_UP.value + " OR uploadStatus==" + + UploadStatus.UPLOAD_CANCELLED.value, null); + } + /** + * Get all uploads which where successfully completed. + */ + public UploadDbObject[] getFinishedUploads() { + return getUploads("uploadStatus==" + UploadStatus.UPLOAD_SUCCEEDED.value, null); + } private SQLiteDatabase getDB() { if (mDB == null) { diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index 9d2151c5d77..9ae4640c675 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -276,7 +276,7 @@ public void onCreate() { mDb.recreateDb(); //for testing only //when this service starts there is no upload in progress. if db says so, app probably crashed before. - List current = mDb.getCurrentUpload(); + UploadDbObject[] current = mDb.getCurrentUpload(); for (UploadDbObject uploadDbObject : current) { uploadDbObject.setUploadStatus(UploadStatus.UPLOAD_LATER); mDb.updateUpload(uploadDbObject); @@ -320,7 +320,7 @@ protected void onHandleIntent(Intent intent) { // retry of pending upload was requested. // ==> First check persistent uploads, then perform upload. int countAddedEntries = 0; - List list = mDb.getAllPendingUploads(); + UploadDbObject[] list = mDb.getPendingUploads(); for (UploadDbObject uploadDbObject : list) { // store locally. String uploadKey = buildRemoteName(uploadDbObject); diff --git a/src/com/owncloud/android/ui/adapter/UploadListAdapter.java b/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java similarity index 53% rename from src/com/owncloud/android/ui/adapter/UploadListAdapter.java rename to src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java index 34385f7f84b..3258254ef0a 100755 --- a/src/com/owncloud/android/ui/adapter/UploadListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java @@ -2,23 +2,24 @@ import java.util.Arrays; import java.util.Comparator; -import java.util.List; import java.util.Observable; import java.util.Observer; import android.app.Activity; import android.content.Context; import android.database.DataSetObserver; +import android.graphics.Bitmap; import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.BaseAdapter; +import android.widget.BaseExpandableListAdapter; +import android.widget.ExpandableListView; import android.widget.ImageView; -import android.widget.ListAdapter; import android.widget.TextView; import com.owncloud.android.R; +import com.owncloud.android.datamodel.ThumbnailsCacheManager; import com.owncloud.android.db.UploadDbHandler; import com.owncloud.android.db.UploadDbObject; import com.owncloud.android.lib.common.utils.Log_OC; @@ -29,17 +30,78 @@ * active, completed. Filtering possible. * */ -public class UploadListAdapter extends BaseAdapter implements ListAdapter, Observer { +public class ExpandableUploadListAdapter extends BaseExpandableListAdapter implements Observer { - private static final String TAG = "UploadListAdapter"; + private static final String TAG = "ExpandableUploadListAdapter"; private Activity mActivity; - private UploadDbObject[] mUploads = null; + + interface Refresh { + public void refresh(); + } + abstract class UploadGroup implements Refresh { + UploadDbObject[] items; + String name; + public UploadGroup(String groupName) { + this.name = groupName; + items = new UploadDbObject[0]; + } + public String getGroupName() { + return name; + } + public Comparator comparator = new Comparator() { + @Override + public int compare(UploadDbObject lhs, UploadDbObject rhs) { + return compareUploadTime(lhs, rhs); + } + private int compareUploadTime(UploadDbObject lhs, UploadDbObject rhs) { + return rhs.getUploadTime().compareTo(lhs.getUploadTime()); + } + }; + abstract public int getGroupIcon(); + } + private UploadGroup[] mUploadGroups = null; UploadDbHandler mDb; - public UploadListAdapter(Activity context) { + public ExpandableUploadListAdapter(Activity context) { Log_OC.d(TAG, "UploadListAdapter"); mActivity = context; mDb = UploadDbHandler.getInstance(mActivity); + mUploadGroups = new UploadGroup[3]; + mUploadGroups[0] = new UploadGroup("Current Uploads") { + @Override + public void refresh() { + items = mDb.getCurrentAndPendingUploads(); + Arrays.sort(items, comparator); + } + @Override + public int getGroupIcon() { + return R.drawable.upload_in_progress; + } + }; + mUploadGroups[1] = new UploadGroup("Failed Uploads"){ + @Override + public void refresh() { + items = mDb.getFailedUploads(); + Arrays.sort(items, comparator); + } + @Override + public int getGroupIcon() { + return R.drawable.upload_failed; + } + + }; + mUploadGroups[2] = new UploadGroup("Finished Uploads"){ + @Override + public void refresh() { + items = mDb.getFinishedUploads(); + Arrays.sort(items, comparator); + } + @Override + public int getGroupIcon() { + return R.drawable.upload_finished; + } + + }; loadUploadItemsFromDb(); } @@ -62,42 +124,14 @@ public boolean areAllItemsEnabled() { return true; } - @Override - public boolean isEnabled(int position) { - return true; - } - - @Override - public int getCount() { - return mUploads != null ? mUploads.length : 0; - } - - @Override - public Object getItem(int position) { - if (mUploads == null || mUploads.length <= position) - return null; - return mUploads[position]; - } - - @Override - public long getItemId(int position) { - return mUploads != null && mUploads.length <= position ? position : -1; - } - - @Override - public int getItemViewType(int position) { - return 0; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { + private View getView(UploadDbObject[] uploadsItems, int position, View convertView, ViewGroup parent) { View view = convertView; if (view == null) { LayoutInflater inflator = (LayoutInflater) mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); view = inflator.inflate(R.layout.upload_list_item, null); } - if (mUploads != null && mUploads.length > position) { - UploadDbObject uploadObject = mUploads[position]; + if (uploadsItems != null && uploadsItems.length > position) { + UploadDbObject uploadObject = uploadsItems[position]; TextView fileName = (TextView) view.findViewById(R.id.upload_name); fileName.setText(uploadObject.getLocalPath()); @@ -110,11 +144,15 @@ public View getView(int position, View convertView, ViewGroup parent) { statusView.setText(status); ImageView fileIcon = (ImageView) view.findViewById(R.id.imageView1); - // if (!file.isDirectory()) { fileIcon.setImageResource(R.drawable.file); - // } else { - // fileIcon.setImageResource(R.drawable.ic_menu_archive); - // } + try { + //?? TODO RemoteID is not set yet. How to get thumbnail? + Bitmap b = ThumbnailsCacheManager.getBitmapFromDiskCache(uploadObject.getOCFile().getRemoteId()); + if (b != null) { + fileIcon.setImageBitmap(b); + } + } catch (NullPointerException e) { + } TextView fileSizeV = (TextView) view.findViewById(R.id.file_size); CharSequence dateString = DisplayUtils.getRelativeDateTimeString(mActivity, uploadObject.getUploadTime() @@ -161,49 +199,23 @@ public View getView(int position, View convertView, ViewGroup parent) { return view; } - @Override - public int getViewTypeCount() { - return 1; - } + @Override public boolean hasStableIds() { return false; } - @Override - public boolean isEmpty() { - return (mUploads == null || mUploads.length == 0); - } + /** * Load upload items from {@link UploadDbHandler}. */ private void loadUploadItemsFromDb() { Log_OC.d(TAG, "loadUploadItemsFromDb"); - List list = mDb.getAllStoredUploads(); - mUploads = list.toArray(new UploadDbObject[list.size()]); - if (mUploads != null) { - Arrays.sort(mUploads, new Comparator() { - @Override - public int compare(UploadDbObject lhs, UploadDbObject rhs) { - // if (lhs.isDirectory() && !rhs.isDirectory()) { - // return -1; - // } else if (!lhs.isDirectory() && rhs.isDirectory()) { - // return 1; - // } - return compareUploadTime(lhs, rhs); - } - - private int compareNames(UploadDbObject lhs, UploadDbObject rhs) { - return lhs.getLocalPath().toLowerCase().compareTo(rhs.getLocalPath().toLowerCase()); - } - - private int compareUploadTime(UploadDbObject lhs, UploadDbObject rhs) { - return rhs.getUploadTime().compareTo(lhs.getUploadTime()); - } - - }); + + for (UploadGroup group : mUploadGroups) { + group.refresh(); } mActivity.runOnUiThread(new Runnable() { @Override @@ -219,4 +231,66 @@ public void update(Observable arg0, Object arg1) { Log_OC.d(TAG, "update"); loadUploadItemsFromDb(); } + + @Override + public Object getChild(int groupPosition, int childPosition) { + return mUploadGroups[groupPosition].items[childPosition]; + } + + @Override + public long getChildId(int groupPosition, int childPosition) { + return groupPosition * 1000l + childPosition; + } + + @Override + public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, + ViewGroup parent) { + return getView(mUploadGroups[groupPosition].items, childPosition, convertView, parent); + } + + @Override + public int getChildrenCount(int groupPosition) { + return mUploadGroups[groupPosition].items.length; + } + + @Override + public Object getGroup(int groupPosition) { + return mUploadGroups[groupPosition]; + } + + @Override + public int getGroupCount() { + return mUploadGroups.length; + } + + @Override + public long getGroupId(int groupPosition) { + return groupPosition; + } + + @Override + public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { + //force group to stay unfolded + ExpandableListView listView = (ExpandableListView) parent; + listView.expandGroup(groupPosition); + + listView.setGroupIndicator(null); + + UploadGroup group = (UploadGroup) getGroup(groupPosition); + if (convertView == null) { + LayoutInflater infalInflater = (LayoutInflater) mActivity + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + convertView = infalInflater.inflate(R.layout.upload_list_group, null); + } + TextView tv = (TextView) convertView.findViewById(R.id.uploadListGroupName); + tv.setText(group.getGroupName()); + ImageView icon = (ImageView) convertView.findViewById(R.id.uploadListGroupIcon); + icon.setImageResource(group.getGroupIcon()); + return convertView; + } + + @Override + public boolean isChildSelectable(int groupPosition, int childPosition) { + return false; + } } diff --git a/src/com/owncloud/android/ui/fragment/UploadListFragment.java b/src/com/owncloud/android/ui/fragment/UploadListFragment.java deleted file mode 100755 index 99828160dda..00000000000 --- a/src/com/owncloud/android/ui/fragment/UploadListFragment.java +++ /dev/null @@ -1,190 +0,0 @@ -/* ownCloud Android client application - * Copyright (C) 2011 Bartek Przybylski - * Copyright (C) 2012-2013 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 . - * - */ -package com.owncloud.android.ui.fragment; - -import java.io.File; -import java.util.ArrayList; - -import android.app.Activity; -import android.os.Bundle; -import android.util.SparseBooleanArray; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ImageView; -import android.widget.ListView; - -import com.owncloud.android.R; -import com.owncloud.android.db.UploadDbObject; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.ui.adapter.UploadListAdapter; - -/** - * A Fragment that lists all files and folders in a given LOCAL path. - * - * @author LukeOwncloud - * - */ -public class UploadListFragment extends ExtendedListFragment { - private static final String TAG = "UploadListFragment"; - - /** - * Reference to the Activity which this fragment is attached to. For - * callbacks - */ - private UploadListFragment.ContainerActivity mContainerActivity; - - /** Adapter to connect the data from the directory with the View object */ - private UploadListAdapter mAdapter = null; - - /** - * {@inheritDoc} - */ - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - try { - mContainerActivity = (ContainerActivity) activity; - } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() + " must implement " - + UploadListFragment.ContainerActivity.class.getSimpleName()); - } - } - - /** - * {@inheritDoc} - */ - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - Log_OC.i(TAG, "onCreateView() start"); - View v = super.onCreateView(inflater, container, savedInstanceState); - getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); - disableSwipe(); // Disable pull refresh -// setMessageForEmptyList(getString(R.string.local_file_list_empty)); - setMessageForEmptyList("No uploads available."); - Log_OC.i(TAG, "onCreateView() end"); - return v; - } - - /** - * {@inheritDoc} - */ - @Override - public void onActivityCreated(Bundle savedInstanceState) { - Log_OC.i(TAG, "onActivityCreated() start"); - - super.onActivityCreated(savedInstanceState); - mAdapter = new UploadListAdapter(getActivity()); - setListAdapter(mAdapter); - - } - - public void selectAll() { - int numberOfFiles = mAdapter.getCount(); - for (int i = 0; i < numberOfFiles; i++) { - UploadDbObject file = (UploadDbObject) mAdapter.getItem(i); - if (file != null) { - // / Click on a file - getListView().setItemChecked(i, true); - // notify the change to the container Activity - mContainerActivity.onUploadItemClick(file); - } - } - } - - public void deselectAll() { - mAdapter = new UploadListAdapter(getActivity()); - setListAdapter(mAdapter); - } - - /** - * Checks the file clicked over. Browses inside if it is a directory. - * Notifies the container activity in any case. - */ - @Override - public void onItemClick(AdapterView l, View v, int position, long id) { - UploadDbObject uploadDbObject = (UploadDbObject) mAdapter.getItem(position); - - if (uploadDbObject != null) { - - // notify the click to container Activity - mContainerActivity.onUploadItemClick(uploadDbObject); - - ImageView checkBoxV = (ImageView) v.findViewById(R.id.custom_checkbox); - if (checkBoxV != null) { - if (getListView().isItemChecked(position)) { - checkBoxV.setImageResource(android.R.drawable.checkbox_on_background); - } else { - checkBoxV.setImageResource(android.R.drawable.checkbox_off_background); - } - } - - } else { - Log_OC.w(TAG, "Null object in ListAdapter!!"); - } - } - - /** - * Returns the fule paths to the files checked by the user - * - * @return File paths to the files checked by the user. - */ - public String[] getCheckedFilePaths() { - ArrayList result = new ArrayList(); - SparseBooleanArray positions = mList.getCheckedItemPositions(); - if (positions.size() > 0) { - for (int i = 0; i < positions.size(); i++) { - if (positions.get(positions.keyAt(i)) == true) { - result.add(((File) mList.getItemAtPosition(positions.keyAt(i))).getAbsolutePath()); - } - } - - Log_OC.d(TAG, "Returning " + result.size() + " selected files"); - } - return result.toArray(new String[result.size()]); - } - - /** - * Interface to implement by any Activity that includes some instance of - * UploadListFragment - * - * @author LukeOwncloud - */ - public interface ContainerActivity { - - /** - * Callback method invoked when an upload item is clicked by the user on - * the upload list - * - * @param file - */ - public void onUploadItemClick(UploadDbObject file); - - /** - * Callback method invoked when the parent activity is fully created to - * get the filter which is to be applied to the upload list. - * - * @return Filter to be applied. Can be null, then all uploads are - * shown. - */ - public File getInitialFilter(); - - } - -} From 397dd7b5b29d229cf133277d809ed537da3655cd Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Thu, 27 Nov 2014 15:56:09 +0100 Subject: [PATCH 39/76] fix on click handler added on long click handler --- .../ui/activity/UploadListActivity.java | 22 ++- .../adapter/ExpandableUploadListAdapter.java | 4 +- .../ui/fragment/ExpandableListFragment.java | 32 ++-- .../ui/fragment/UploadListFragment.java | 144 ++++++++++++++++++ 4 files changed, 176 insertions(+), 26 deletions(-) create mode 100755 src/com/owncloud/android/ui/fragment/UploadListFragment.java diff --git a/src/com/owncloud/android/ui/activity/UploadListActivity.java b/src/com/owncloud/android/ui/activity/UploadListActivity.java index 9302debd98a..28ee5d03f04 100755 --- a/src/com/owncloud/android/ui/activity/UploadListActivity.java +++ b/src/com/owncloud/android/ui/activity/UploadListActivity.java @@ -1,7 +1,5 @@ package com.owncloud.android.ui.activity; -import java.io.File; - import android.content.Intent; import android.os.Bundle; import android.os.Parcelable; @@ -10,16 +8,14 @@ import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import com.owncloud.android.R; -import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.db.UploadDbHandler; -import com.owncloud.android.db.UploadDbObject; import com.owncloud.android.db.UploadDbHandler.UploadStatus; +import com.owncloud.android.db.UploadDbObject; import com.owncloud.android.files.services.FileUploadService; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.ui.errorhandling.ExceptionHandler; import com.owncloud.android.ui.fragment.UploadListFragment; -import com.owncloud.android.utils.FileStorageUtils; /** * Activity listing pending, active, and completed uploads. User can delete @@ -47,7 +43,7 @@ protected void onCreate(Bundle savedInstanceState) { * TODO Without a menu this is a little un-intuitive. */ @Override - public void onUploadItemClick(UploadDbObject file) { + public boolean onUploadItemClick(UploadDbObject file) { OCFile ocFile = file.getOCFile(); switch (file.getUploadStatus()) { case UPLOAD_IN_PROGRESS: @@ -77,13 +73,7 @@ public void onUploadItemClick(UploadDbObject file) { default: break; } - - } - - @Override - public File getInitialFilter() { - // TODO Auto-generated method stub - return null; + return true; } @Override @@ -112,6 +102,12 @@ public boolean onCreateOptionsMenu(Menu menu) { inflater.inflate(R.menu.upload_list_menu, menu); return true; } + + @Override + public boolean onUploadItemLongClick(UploadDbObject file) { + // TODO Auto-generated method stub + return false; + } diff --git a/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java b/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java index 3258254ef0a..479d56f0109 100755 --- a/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java @@ -239,7 +239,7 @@ public Object getChild(int groupPosition, int childPosition) { @Override public long getChildId(int groupPosition, int childPosition) { - return groupPosition * 1000l + childPosition; + return childPosition; } @Override @@ -291,6 +291,6 @@ public View getGroupView(int groupPosition, boolean isExpanded, View convertView @Override public boolean isChildSelectable(int groupPosition, int childPosition) { - return false; + return true; } } diff --git a/src/com/owncloud/android/ui/fragment/ExpandableListFragment.java b/src/com/owncloud/android/ui/fragment/ExpandableListFragment.java index cc7f06ac587..dfa577da976 100755 --- a/src/com/owncloud/android/ui/fragment/ExpandableListFragment.java +++ b/src/com/owncloud/android/ui/fragment/ExpandableListFragment.java @@ -18,32 +18,28 @@ package com.owncloud.android.ui.fragment; -import java.util.ArrayList; - import android.os.Bundle; import android.support.v4.widget.SwipeRefreshLayout; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemLongClickListener; import android.widget.ExpandableListAdapter; import android.widget.ExpandableListView; -import android.widget.ListAdapter; -import android.widget.ListView; +import android.widget.ExpandableListView.OnChildClickListener; import android.widget.TextView; -import com.actionbarsherlock.app.SherlockFragment; import com.owncloud.android.R; import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.ui.ExtendedListView; -import com.owncloud.android.ui.activity.OnEnforceableRefreshListener; /** * Extending ExtendedListFragment. This allows dividing list in groups. */ -public class ExpandableListFragment extends ExtendedListFragment +public class ExpandableListFragment extends ExtendedListFragment implements OnChildClickListener, OnItemLongClickListener { + protected static final String TAG = ExpandableListFragment.class.getSimpleName(); protected ExpandableListView mList; @@ -56,7 +52,6 @@ public ExpandableListView getListView() { return mList; } - @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Log_OC.e(TAG, "onCreateView"); @@ -64,7 +59,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa View v = inflater.inflate(R.layout.list_fragment_expandable, null); mEmptyListMessage = (TextView) v.findViewById(R.id.empty_list_view); mList = (ExpandableListView)(v.findViewById(R.id.list_root)); - mList.setOnItemClickListener(this); + mList.setOnChildClickListener(this); + mList.setOnItemLongClickListener(this); mList.setDivider(getResources().getDrawable(R.drawable.uploader_list_separator)); mList.setDividerHeight(1); @@ -86,4 +82,18 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa return v; } + @Override + public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { + // to be @overriden + Log_OC.e(TAG, "onChildClick(). This method should be overriden!"); + return false; } + + @Override + public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { + // to be @overriden + Log_OC.e(TAG, "onItemLongClick(). This method should be overriden!"); + return false; + } + +} diff --git a/src/com/owncloud/android/ui/fragment/UploadListFragment.java b/src/com/owncloud/android/ui/fragment/UploadListFragment.java new file mode 100755 index 00000000000..8ce91874687 --- /dev/null +++ b/src/com/owncloud/android/ui/fragment/UploadListFragment.java @@ -0,0 +1,144 @@ +/* ownCloud Android client application + * Copyright (C) 2011 Bartek Przybylski + * Copyright (C) 2012-2013 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 . + * + */ +package com.owncloud.android.ui.fragment; + +import android.app.Activity; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.BaseExpandableListAdapter; +import android.widget.ExpandableListView; +import android.widget.ListView; + +import com.owncloud.android.db.UploadDbObject; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.ui.adapter.ExpandableUploadListAdapter; + +/** + * A Fragment that lists all files and folders in a given LOCAL path. + * + * @author LukeOwncloud + * + */ +public class UploadListFragment extends ExpandableListFragment { + static private String TAG = "UploadListFragment"; + + /** + * Reference to the Activity which this fragment is attached to. For + * callbacks + */ + private UploadListFragment.ContainerActivity mContainerActivity; + + BaseExpandableListAdapter mAdapter; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View v = super.onCreateView(inflater, container, savedInstanceState); + getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + return v; + } + + + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + mContainerActivity = (ContainerActivity) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + " must implement " + + UploadListFragment.ContainerActivity.class.getSimpleName()); + } + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + Log_OC.d(TAG, "onActivityCreated() start"); + super.onActivityCreated(savedInstanceState); + mAdapter = new ExpandableUploadListAdapter(getActivity()); + setListAdapter(mAdapter); + } + + @Override + public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { + boolean handled = false; + UploadDbObject uploadDbObject = (UploadDbObject) mAdapter.getChild(groupPosition, childPosition); + if (uploadDbObject != null) { + // notify the click to container Activity + handled = mContainerActivity.onUploadItemClick(uploadDbObject); + } else { + Log_OC.w(TAG, "Null object in ListAdapter!!"); + } + return handled; + } + + @Override + public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { + Log_OC.d(TAG, "onItemLongClick() position: " + position + " id: " + id); + int itemType = ExpandableListView.getPackedPositionType(id); + + if (itemType == ExpandableListView.PACKED_POSITION_TYPE_CHILD) { + int childPosition = ExpandableListView.getPackedPositionChild(id); + int groupPosition = ExpandableListView.getPackedPositionGroup(id); + UploadDbObject uploadDbObject = (UploadDbObject) mAdapter.getChild(groupPosition, childPosition); + if (uploadDbObject != null) { + return mContainerActivity.onUploadItemLongClick(uploadDbObject); + } else { + Log_OC.w(TAG, "Null object in ListAdapter!!"); + } + } else if (itemType == ExpandableListView.PACKED_POSITION_TYPE_GROUP) { + // clicked on group header. ignore. + return false; + } + + return false; + + } + + /** + * Interface to implement by any Activity that includes some instance of + * UploadListFragment + * + * @author LukeOwncloud + */ + public interface ContainerActivity { + + /** + * Callback method invoked when an upload item is clicked by the user on + * the upload list + * + * @param file + * @return return true if click was handled. + */ + public boolean onUploadItemClick(UploadDbObject file); + + /** + * Callback method invoked when an upload item is long clicked by the user on + * the upload list + * + * @param file + * @return return true if click was handled. + */ + public boolean onUploadItemLongClick(UploadDbObject file); + + } + +} \ No newline at end of file From cf63d7ce0e4f2b8b909b405440042edb75f4cc0a Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Thu, 27 Nov 2014 16:40:52 +0100 Subject: [PATCH 40/76] remove on long click, add context menu instead --- res/menu/upload_actions_menu.xml | 8 ++ .../android/files/FileOperationsHelper.java | 33 ++++--- .../ui/activity/UploadListActivity.java | 7 -- .../ui/fragment/ExpandableListFragment.java | 10 +-- .../ui/fragment/UploadListFragment.java | 90 +++++++++++++------ 5 files changed, 93 insertions(+), 55 deletions(-) create mode 100755 res/menu/upload_actions_menu.xml diff --git a/res/menu/upload_actions_menu.xml b/res/menu/upload_actions_menu.xml new file mode 100755 index 00000000000..c6ed455a1af --- /dev/null +++ b/res/menu/upload_actions_menu.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/com/owncloud/android/files/FileOperationsHelper.java b/src/com/owncloud/android/files/FileOperationsHelper.java index 626595c2c7a..2c1d025e2cc 100644 --- a/src/com/owncloud/android/files/FileOperationsHelper.java +++ b/src/com/owncloud/android/files/FileOperationsHelper.java @@ -251,19 +251,28 @@ public void createFolder(String remotePath, boolean createFullPath) { 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); + FileUploaderBinder uploaderBinder = mFileActivity.getFileUploaderBinder(); + if (downloaderBinder != null) { + if (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 { + Log_OC.w(TAG, "Download for " + file + " not in progress. Cannot cancel."); } - - downloaderBinder.cancel(account, file); - - } else if (uploaderBinder != null && uploaderBinder.isUploading(account, file)) { - uploaderBinder.cancel(account, file); + } else if (uploaderBinder != null) { + if (uploaderBinder.isUploading(account, file)) { + uploaderBinder.cancel(account, file); + } else { + Log_OC.w(TAG, "Upload for " + file + " not in progress. Cannot cancel."); + } + } else { + Log_OC.w(TAG, "Neither downloaderBinder nor uploaderBinder set. Cannot cancel."); } } diff --git a/src/com/owncloud/android/ui/activity/UploadListActivity.java b/src/com/owncloud/android/ui/activity/UploadListActivity.java index 28ee5d03f04..b8031fe10e7 100755 --- a/src/com/owncloud/android/ui/activity/UploadListActivity.java +++ b/src/com/owncloud/android/ui/activity/UploadListActivity.java @@ -103,12 +103,5 @@ public boolean onCreateOptionsMenu(Menu menu) { return true; } - @Override - public boolean onUploadItemLongClick(UploadDbObject file) { - // TODO Auto-generated method stub - return false; - } - - } diff --git a/src/com/owncloud/android/ui/fragment/ExpandableListFragment.java b/src/com/owncloud/android/ui/fragment/ExpandableListFragment.java index dfa577da976..9aef1cc3da4 100755 --- a/src/com/owncloud/android/ui/fragment/ExpandableListFragment.java +++ b/src/com/owncloud/android/ui/fragment/ExpandableListFragment.java @@ -37,7 +37,7 @@ /** * Extending ExtendedListFragment. This allows dividing list in groups. */ -public class ExpandableListFragment extends ExtendedListFragment implements OnChildClickListener, OnItemLongClickListener +public class ExpandableListFragment extends ExtendedListFragment implements OnChildClickListener { protected static final String TAG = ExpandableListFragment.class.getSimpleName(); @@ -60,7 +60,6 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa mEmptyListMessage = (TextView) v.findViewById(R.id.empty_list_view); mList = (ExpandableListView)(v.findViewById(R.id.list_root)); mList.setOnChildClickListener(this); - mList.setOnItemLongClickListener(this); mList.setDivider(getResources().getDrawable(R.drawable.uploader_list_separator)); mList.setDividerHeight(1); @@ -89,11 +88,4 @@ public boolean onChildClick(ExpandableListView parent, View v, int groupPosition return false; } - @Override - public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { - // to be @overriden - Log_OC.e(TAG, "onItemLongClick(). This method should be overriden!"); - return false; - } - } diff --git a/src/com/owncloud/android/ui/fragment/UploadListFragment.java b/src/com/owncloud/android/ui/fragment/UploadListFragment.java index 8ce91874687..64202623edb 100755 --- a/src/com/owncloud/android/ui/fragment/UploadListFragment.java +++ b/src/com/owncloud/android/ui/fragment/UploadListFragment.java @@ -18,19 +18,37 @@ package com.owncloud.android.ui.fragment; import android.app.Activity; +import android.content.Intent; import android.os.Bundle; +import android.os.Parcelable; import android.util.Log; +import android.view.ContextMenu; import android.view.LayoutInflater; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.view.ContextMenu.ContextMenuInfo; import android.widget.AdapterView; import android.widget.BaseExpandableListAdapter; import android.widget.ExpandableListView; +import android.widget.ExpandableListView.ExpandableListContextMenuInfo; import android.widget.ListView; +import android.widget.AdapterView.AdapterContextMenuInfo; +import com.owncloud.android.R; +import com.owncloud.android.datamodel.OCFile; +import com.owncloud.android.db.UploadDbHandler.UploadStatus; import com.owncloud.android.db.UploadDbObject; +import com.owncloud.android.files.FileMenuFilter; import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.ui.activity.FileActivity; +import com.owncloud.android.ui.activity.FileDisplayActivity; +import com.owncloud.android.ui.activity.MoveActivity; import com.owncloud.android.ui.adapter.ExpandableUploadListAdapter; +import com.owncloud.android.ui.dialog.ConfirmationDialogFragment; +import com.owncloud.android.ui.dialog.RemoveFileDialogFragment; +import com.owncloud.android.ui.dialog.RenameFileDialogFragment; /** * A Fragment that lists all files and folders in a given LOCAL path. @@ -75,6 +93,9 @@ public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mAdapter = new ExpandableUploadListAdapter(getActivity()); setListAdapter(mAdapter); + + registerForContextMenu(getListView()); + getListView().setOnCreateContextMenuListener(this); } @Override @@ -91,26 +112,50 @@ public boolean onChildClick(ExpandableListView parent, View v, int groupPosition } @Override - public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { - Log_OC.d(TAG, "onItemLongClick() position: " + position + " id: " + id); - int itemType = ExpandableListView.getPackedPositionType(id); - - if (itemType == ExpandableListView.PACKED_POSITION_TYPE_CHILD) { - int childPosition = ExpandableListView.getPackedPositionChild(id); - int groupPosition = ExpandableListView.getPackedPositionGroup(id); - UploadDbObject uploadDbObject = (UploadDbObject) mAdapter.getChild(groupPosition, childPosition); - if (uploadDbObject != null) { - return mContainerActivity.onUploadItemLongClick(uploadDbObject); - } else { - Log_OC.w(TAG, "Null object in ListAdapter!!"); + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + MenuInflater inflater = getSherlockActivity().getMenuInflater(); + inflater.inflate(R.menu.upload_actions_menu, menu); + + ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) menuInfo; + int childPosition = ExpandableListView.getPackedPositionChild(info.packedPosition); + int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition); + UploadDbObject uploadFile = (UploadDbObject) mAdapter.getChild(groupPosition, childPosition); + if (uploadFile.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED + || uploadFile.getUploadStatus() == UploadStatus.UPLOAD_FAILED_GIVE_UP) { + MenuItem item = menu.findItem(R.id.action_cancel_upload); + if (item != null) { + item.setVisible(false); + item.setEnabled(false); } - } else if (itemType == ExpandableListView.PACKED_POSITION_TYPE_GROUP) { - // clicked on group header. ignore. - return false; + } + } + + @Override + public boolean onContextItemSelected (MenuItem item) { + ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) item.getMenuInfo(); + int childPosition = ExpandableListView.getPackedPositionChild(info.packedPosition); + int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition); + UploadDbObject uploadFile = (UploadDbObject) mAdapter.getChild(groupPosition, childPosition); + switch (item.getItemId()) { + case R.id.action_cancel_upload: { + ((FileActivity) getActivity()).getFileOperationsHelper().cancelTransference(uploadFile.getOCFile()); + return true; + } + case R.id.action_see_details: { + Intent showDetailsIntent = new Intent(getActivity(), FileDisplayActivity.class); + showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, (Parcelable) uploadFile.getOCFile()); + showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, uploadFile.getAccount(getActivity())); + startActivity(showDetailsIntent); + return true; + } + case R.id.action_open_file_with: { + ((FileActivity) getActivity()).getFileOperationsHelper().openFile(uploadFile.getOCFile()); + return true; + } + default: + return super.onContextItemSelected(item); } - - return false; - } /** @@ -129,15 +174,6 @@ public interface ContainerActivity { * @return return true if click was handled. */ public boolean onUploadItemClick(UploadDbObject file); - - /** - * Callback method invoked when an upload item is long clicked by the user on - * the upload list - * - * @param file - * @return return true if click was handled. - */ - public boolean onUploadItemLongClick(UploadDbObject file); } From 86eccab46479a570cdfbf8339b298336e54e9c69 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Thu, 27 Nov 2014 16:52:17 +0100 Subject: [PATCH 41/76] show details on simple click --- .../ui/activity/UploadListActivity.java | 32 +++---------------- .../ui/fragment/UploadListFragment.java | 1 + 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/src/com/owncloud/android/ui/activity/UploadListActivity.java b/src/com/owncloud/android/ui/activity/UploadListActivity.java index b8031fe10e7..da426d6311e 100755 --- a/src/com/owncloud/android/ui/activity/UploadListActivity.java +++ b/src/com/owncloud/android/ui/activity/UploadListActivity.java @@ -45,34 +45,10 @@ protected void onCreate(Bundle savedInstanceState) { @Override public boolean onUploadItemClick(UploadDbObject file) { OCFile ocFile = file.getOCFile(); - switch (file.getUploadStatus()) { - case UPLOAD_IN_PROGRESS: - if (ocFile != null) { - getFileOperationsHelper().cancelTransference(ocFile); - } else { - Log_OC.e(TAG, "Could not get OCFile for " + file.getRemotePath() + ". Cannot cancel."); - } - break; - case UPLOAD_SUCCEEDED: - Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class); - showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, (Parcelable)ocFile); - showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, file.getAccount(this)); - startActivity(showDetailsIntent); - break; - case UPLOAD_CANCELLED: - case UPLOAD_PAUSED: - UploadDbHandler db = UploadDbHandler.getInstance(this.getBaseContext()); - file.setUploadStatus(UploadStatus.UPLOAD_LATER); - db.updateUpload(file); - // no break; to start upload immediately. - case UPLOAD_LATER: - case UPLOAD_FAILED_RETRY: - Log_OC.d(TAG, "FileUploadService.retry() called by onUploadItemClick()"); - FileUploadService.retry(this); - break; - default: - break; - } + Intent showDetailsIntent = new Intent(this, FileDisplayActivity.class); + showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, (Parcelable) ocFile); + showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, file.getAccount(this)); + startActivity(showDetailsIntent); return true; } diff --git a/src/com/owncloud/android/ui/fragment/UploadListFragment.java b/src/com/owncloud/android/ui/fragment/UploadListFragment.java index 64202623edb..ab94f629590 100755 --- a/src/com/owncloud/android/ui/fragment/UploadListFragment.java +++ b/src/com/owncloud/android/ui/fragment/UploadListFragment.java @@ -139,6 +139,7 @@ public boolean onContextItemSelected (MenuItem item) { UploadDbObject uploadFile = (UploadDbObject) mAdapter.getChild(groupPosition, childPosition); switch (item.getItemId()) { case R.id.action_cancel_upload: { + //TODO OCFile does not have UploadBinder. :( ((FileActivity) getActivity()).getFileOperationsHelper().cancelTransference(uploadFile.getOCFile()); return true; } From 7d2f573f6d55ad6f2b8e127bfaa57f9826af1d87 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Thu, 27 Nov 2014 17:03:48 +0100 Subject: [PATCH 42/76] added debug output --- .../owncloud/android/files/FileOperationsHelper.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/com/owncloud/android/files/FileOperationsHelper.java b/src/com/owncloud/android/files/FileOperationsHelper.java index 2c1d025e2cc..690b5e5d526 100644 --- a/src/com/owncloud/android/files/FileOperationsHelper.java +++ b/src/com/owncloud/android/files/FileOperationsHelper.java @@ -263,15 +263,17 @@ public void cancelTransference(OCFile file) { downloaderBinder.cancel(account, file); } else { - Log_OC.w(TAG, "Download for " + file + " not in progress. Cannot cancel."); + Log_OC.d(TAG, "Download for " + file + " not in progress. Cannot cancel."); } - } else if (uploaderBinder != null) { + } + if (uploaderBinder != null) { if (uploaderBinder.isUploading(account, file)) { uploaderBinder.cancel(account, file); } else { - Log_OC.w(TAG, "Upload for " + file + " not in progress. Cannot cancel."); + Log_OC.d(TAG, "Upload for " + file + " not in progress. Cannot cancel."); } - } else { + } + if(downloaderBinder == null && uploaderBinder == null) { Log_OC.w(TAG, "Neither downloaderBinder nor uploaderBinder set. Cannot cancel."); } } From 4518da5d03cc4c5dbb0e8603fba2f1c2093ad00a Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Fri, 28 Nov 2014 11:20:11 +0100 Subject: [PATCH 43/76] fix progress notifications fix handling multiple uploads with same filepath key --- .../owncloud/android/db/UploadDbHandler.java | 127 ++++++++++++------ .../owncloud/android/db/UploadDbObject.java | 7 + .../android/files/FileOperationsHelper.java | 2 +- .../files/services/FileUploadService.java | 114 +++++++++++----- .../operations/UploadFileOperation.java | 6 +- .../adapter/ExpandableUploadListAdapter.java | 10 +- .../ui/fragment/FileDetailFragment.java | 8 ++ 7 files changed, 194 insertions(+), 80 deletions(-) diff --git a/src/com/owncloud/android/db/UploadDbHandler.java b/src/com/owncloud/android/db/UploadDbHandler.java index 223092eeff3..2a9228738c8 100644 --- a/src/com/owncloud/android/db/UploadDbHandler.java +++ b/src/com/owncloud/android/db/UploadDbHandler.java @@ -50,13 +50,13 @@ public class UploadDbHandler extends Observable { // for testing only public void recreateDb() { -// getDB().beginTransaction(); -// try { -// mHelper.onUpgrade(getDB(), 0, mDatabaseVersion); -// getDB().setTransactionSuccessful(); -// } finally { -// getDB().endTransaction(); -// } +// getDB().beginTransaction(); +// try { +// mHelper.onUpgrade(getDB(), 0, mDatabaseVersion); +// getDB().setTransactionSuccessful(); +// } finally { +// getDB().endTransaction(); +// } } public enum UploadStatus { @@ -93,6 +93,7 @@ public enum UploadStatus { private UploadStatus(int value) { this.value = value; } + public int getValue() { return value; } @@ -142,7 +143,6 @@ public boolean storeFile(String filepath, String account, String message) { // return result != -1; } - // ununsed until now. uncomment if needed. // public Cursor getFailedFiles() { // return mDB.query(TABLE_INSTANT_UPLOAD, null, "attempt>" + @@ -191,47 +191,50 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { * @return true on success. */ public boolean storeUpload(UploadDbObject uploadObject) { + Log_OC.e(TAG, "Inserting "+uploadObject.getLocalPath()+" with uploadStatus="+uploadObject.getUploadStatus()); + ContentValues cv = new ContentValues(); cv.put("path", uploadObject.getLocalPath()); cv.put("uploadStatus", uploadObject.getUploadStatus().value); cv.put("uploadObject", uploadObject.toString()); long result = getDB().insert(TABLE_UPLOAD, null, cv); + Log_OC.d(TAG, "putFileForLater returns with: " + result + " for file: " + uploadObject.getLocalPath()); if (result == 1) { notifyObserversNow(); } else { - Log_OC.e(TAG, "Failed to insert item into upload db."); + Log_OC.e(TAG, "Failed to insert item "+uploadObject.getLocalPath()+" into upload db."); } return result != -1; } /** * Update upload status of file in DB. + * * @return 1 if file status was updated, else 0. */ public int updateUpload(UploadDbObject uploadDbObject) { - return updateUpload(uploadDbObject.getLocalPath(), uploadDbObject.getUploadStatus(), uploadDbObject.getLastResult()); + return updateUpload(uploadDbObject.getLocalPath(), uploadDbObject.getUploadStatus(), + uploadDbObject.getLastResult()); } - /** - * Update upload status of file. - * - * @param filepath filepath local file path to file. used as identifier. - * @param status new status. - * @param result new result of upload operation - * @return 1 if file status was updated, else 0. - */ - public int updateUpload(String filepath, UploadStatus status, RemoteOperationResult result) { - Cursor c = getDB().query(TABLE_UPLOAD, null, "path=?", new String[] { filepath }, null, null, null); - if (c.getCount() != 1) { - Log_OC.e(TAG, c.getCount() + " items for path=" + filepath + " available in UploadDb. Expected 1."); - return 0; - } - if (c.moveToFirst()) { + public int updateUploadInternal(Cursor c, UploadStatus status, RemoteOperationResult result) { + + while(c.moveToNext()) { // read upload object and update + + String uploadObjectString = c.getString(c.getColumnIndex("uploadObject")); UploadDbObject uploadObject = UploadDbObject.fromString(uploadObjectString); + + String path = c.getString(c.getColumnIndex("path")); + Log_OC.e( + TAG, + "Updating " + path + ": " + uploadObject.getLocalPath() + " with uploadStatus=" + status + "(old:" + + uploadObject.getUploadStatus() + ") and result=" + result + "(old:" + + uploadObject.getLastResult()); + uploadObject.setUploadStatus(status); uploadObject.setLastResult(result); uploadObjectString = uploadObject.toString(); @@ -239,7 +242,10 @@ public int updateUpload(String filepath, UploadStatus status, RemoteOperationRes ContentValues cv = new ContentValues(); cv.put("uploadStatus", status.value); cv.put("uploadObject", uploadObjectString); - int r = getDB().update(TABLE_UPLOAD, cv, "path=?", new String[] { filepath }); + + + int r = getDB().update(TABLE_UPLOAD, cv, "path=?", new String[] { path }); + if (r == 1) { notifyObserversNow(); } else { @@ -249,7 +255,28 @@ public int updateUpload(String filepath, UploadStatus status, RemoteOperationRes } return 0; } + /** + * Update upload status of file uniquely referenced by filepath. + * + * @param filepath filepath local file path to file. used as identifier. + * @param status new status. + * @param result new result of upload operation + * @return 1 if file status was updated, else 0. + */ + public int updateUpload(String filepath, UploadStatus status, RemoteOperationResult result) { + +// Log_OC.e(TAG, "Updating "+filepath+" with uploadStatus="+status +" and result="+result); + + Cursor c = getDB().query(TABLE_UPLOAD, null, "path=?", new String[] { filepath }, null, null, null); + if (c.getCount() != 1) { + Log_OC.e(TAG, c.getCount() + " items for path=" + filepath + + " available in UploadDb. Expected 1. Failed to update upload db."); + return 0; + } + return updateUploadInternal(c, status, result); + } + /** * Should be called when some value of this DB was changed. All observers * are informed. @@ -267,10 +294,10 @@ public void notifyObserversNow() { * @param localPath * @return true when one or more upload entries were removed */ - public boolean removeUpload(String localPath) { - long result = getDB().delete(TABLE_UPLOAD, "path = ?", new String[] { localPath }); + public int removeUpload(String localPath) { + int result = getDB().delete(TABLE_UPLOAD, "path = ?", new String[] { localPath }); Log_OC.d(TABLE_UPLOAD, "delete returns with: " + result + " for file: " + localPath); - return result != 0; + return result; } public UploadDbObject[] getAllStoredUploads() { @@ -295,37 +322,42 @@ private UploadDbObject[] getUploads(String selection, String[] selectionArgs) { } /** - * Get all uploads which are pending, i.e., queued for upload but not currently being uploaded + * Get all uploads which are pending, i.e., queued for upload but not + * currently being uploaded + * * @return */ public UploadDbObject[] getPendingUploads() { return getUploads("uploadStatus==" + UploadStatus.UPLOAD_LATER.value + " OR uploadStatus==" - + UploadStatus.UPLOAD_FAILED_RETRY.value+ " OR uploadStatus==" - + UploadStatus.UPLOAD_PAUSED.value, null); + + UploadStatus.UPLOAD_FAILED_RETRY.value, null); } + /** - * Get all uploads which are currently being uploaded. There should only be one. No guarantee though. + * Get all uploads which are currently being uploaded. There should only be + * one. No guarantee though. */ public UploadDbObject[] getCurrentUpload() { return getUploads("uploadStatus==" + UploadStatus.UPLOAD_IN_PROGRESS.value, null); } - + /** * Get all current and pending uploads. */ public UploadDbObject[] getCurrentAndPendingUploads() { - return getUploads("uploadStatus==" + UploadStatus.UPLOAD_IN_PROGRESS.value + " OR uploadStatus==" + UploadStatus.UPLOAD_LATER.value + " OR uploadStatus==" - + UploadStatus.UPLOAD_FAILED_RETRY.value+ " OR uploadStatus==" - + UploadStatus.UPLOAD_PAUSED.value, null); + return getUploads("uploadStatus==" + UploadStatus.UPLOAD_IN_PROGRESS.value + " OR uploadStatus==" + + UploadStatus.UPLOAD_LATER.value + " OR uploadStatus==" + UploadStatus.UPLOAD_FAILED_RETRY.value + + " OR uploadStatus==" + UploadStatus.UPLOAD_PAUSED.value, null); } - + /** - * Get all unrecoverably failed. Upload of these should/must/will not be retried. + * Get all unrecoverably failed. Upload of these should/must/will not be + * retried. */ public UploadDbObject[] getFailedUploads() { return getUploads("uploadStatus==" + UploadStatus.UPLOAD_FAILED_GIVE_UP.value + " OR uploadStatus==" + UploadStatus.UPLOAD_CANCELLED.value, null); - } + } + /** * Get all uploads which where successfully completed. */ @@ -346,15 +378,22 @@ private void setDB(SQLiteDatabase mDB) { public long cleanDoneUploads() { String[] where = new String[3]; - where[0] = String.valueOf(UploadStatus.UPLOAD_CANCELLED.value); - where[1] = String.valueOf(UploadStatus.UPLOAD_FAILED_GIVE_UP.value); - where[2] = String.valueOf(UploadStatus.UPLOAD_SUCCEEDED.value); + where[0] = String.valueOf(UploadStatus.UPLOAD_CANCELLED.value); + where[1] = String.valueOf(UploadStatus.UPLOAD_FAILED_GIVE_UP.value); + where[2] = String.valueOf(UploadStatus.UPLOAD_SUCCEEDED.value); long result = getDB().delete(TABLE_UPLOAD, "uploadStatus = ? OR uploadStatus = ? OR uploadStatus = ?", where); Log_OC.d(TABLE_UPLOAD, "delete all done uploads"); - if(result>0) { + if (result > 0) { notifyObserversNow(); } return result; } + public void setAllCurrentToUploadLater() { + + Cursor c = getDB().query(TABLE_UPLOAD, null, "uploadStatus==" + UploadStatus.UPLOAD_IN_PROGRESS.value, null, null, null, null); + + updateUploadInternal(c, UploadStatus.UPLOAD_LATER, null); + } + } diff --git a/src/com/owncloud/android/db/UploadDbObject.java b/src/com/owncloud/android/db/UploadDbObject.java index 8e29c9b2a7b..a77716a54d2 100755 --- a/src/com/owncloud/android/db/UploadDbObject.java +++ b/src/com/owncloud/android/db/UploadDbObject.java @@ -302,4 +302,11 @@ public long getUploadTimestamp() { public void setUploadTimestamp(long uploadTimestamp) { this.uploadTimestamp = uploadTimestamp; } + + /** + * For debugging purposes only. + */ + public String toFormattedString() { + return getLocalPath() + " status:"+getUploadStatus() + " result:" + getLastResult(); + } } diff --git a/src/com/owncloud/android/files/FileOperationsHelper.java b/src/com/owncloud/android/files/FileOperationsHelper.java index 690b5e5d526..3429d177d15 100644 --- a/src/com/owncloud/android/files/FileOperationsHelper.java +++ b/src/com/owncloud/android/files/FileOperationsHelper.java @@ -47,7 +47,7 @@ */ public class FileOperationsHelper { - private static final String TAG = FileOperationsHelper.class.getName(); + private static final String TAG = FileOperationsHelper.class.getSimpleName(); private static final String FTAG_CHOOSER_DIALOG = "CHOOSER_DIALOG"; diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index 9ae4640c675..e3881f867d6 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -67,6 +67,7 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.resources.files.ExistenceCheckRemoteOperation; +import com.owncloud.android.lib.resources.files.FileUtils; import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation; import com.owncloud.android.lib.resources.files.RemoteFile; import com.owncloud.android.lib.resources.status.OwnCloudVersion; @@ -98,7 +99,7 @@ * */ @SuppressWarnings("unused") -public class FileUploadService extends IntentService { +public class FileUploadService extends IntentService implements OnDatatransferProgressListener { public FileUploadService() { super("FileUploadService"); @@ -221,6 +222,7 @@ public int getValue() { private NotificationManager mNotificationManager; private NotificationCompat.Builder mNotificationBuilder; + private int mLastPercent; private static final String MIME_TYPE_PDF = "application/pdf"; private static final String FILE_EXTENSION_PDF = ".pdf"; @@ -266,7 +268,7 @@ private static boolean chunkedUploadIsSupported(OwnCloudVersion version) { @Override public void onCreate() { super.onCreate(); - Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size()); + Log_OC.d(TAG, "mPendingUploads size:" + mPendingUploads.size()); mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); HandlerThread thread = new HandlerThread("FileUploaderThread", Process.THREAD_PRIORITY_BACKGROUND); thread.start(); @@ -276,11 +278,7 @@ public void onCreate() { mDb.recreateDb(); //for testing only //when this service starts there is no upload in progress. if db says so, app probably crashed before. - UploadDbObject[] current = mDb.getCurrentUpload(); - for (UploadDbObject uploadDbObject : current) { - uploadDbObject.setUploadStatus(UploadStatus.UPLOAD_LATER); - mDb.updateUpload(uploadDbObject); - } + mDb.setAllCurrentToUploadLater(); //TODO This service can be instantiated at any time. Better move this retry call to start of app. if(UploadUtils.isOnline(getApplicationContext())) { @@ -312,26 +310,25 @@ public void onCreate() { @Override protected void onHandleIntent(Intent intent) { - Log_OC.i(TAG, "onHandleIntent start"); - Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size() + " - before adding new uploads."); + Log_OC.d(TAG, "onHandleIntent start"); + Log_OC.d(TAG, "mPendingUploads size:" + mPendingUploads.size() + " - before adding new uploads."); if (intent == null || intent.hasExtra(KEY_RETRY)) { - Log_OC.d(TAG, "Receive null intent."); + Log_OC.d(TAG, "Received null intent."); // service was restarted by OS or connectivity change was detected or // retry of pending upload was requested. // ==> First check persistent uploads, then perform upload. int countAddedEntries = 0; UploadDbObject[] list = mDb.getPendingUploads(); for (UploadDbObject uploadDbObject : list) { - // store locally. + Log_OC.d(TAG, "Retrieved from DB: " + uploadDbObject.toFormattedString()); + String uploadKey = buildRemoteName(uploadDbObject); - UploadDbObject previous = mPendingUploads.putIfAbsent(uploadKey, uploadDbObject); + UploadDbObject previous = mPendingUploads.putIfAbsent(uploadKey, uploadDbObject); if(previous == null) { - // and store persistently. - uploadDbObject.setUploadStatus(UploadStatus.UPLOAD_LATER); - mDb.updateUpload(uploadDbObject); + Log_OC.d(TAG, "mPendingUploads added: " + uploadDbObject.toFormattedString()); countAddedEntries++; } else { - //upload already pending. ignore. + //already pending. ignore. } } Log_OC.d(TAG, "added " + countAddedEntries @@ -426,9 +423,21 @@ protected void onHandleIntent(Intent intent) { if(previous == null) { + Log_OC.d(TAG, "mPendingUploads added: " + uploadObject.toFormattedString()); + + // if upload was not queued before, we can add it to + // database because the user wants the file to be uploaded. + // however, it can happened that the user uploaded the same + // file before in which case there is an old db entry. + // delete that to be sure we have the latest one. + if(mDb.removeUpload(uploadObject.getLocalPath())>0) { + Log_OC.w(TAG, "There was an old DB entry " + uploadObject.getLocalPath() + + " which had to be removed in order to add new one."); + } boolean success = mDb.storeUpload(uploadObject); if(!success) { - Log_OC.e(TAG, "Could not add upload to database. It might be a duplicate. Ignore."); + Log_OC.e(TAG, "Could not add upload " + uploadObject.getLocalPath() + + " to database. This should not happen."); } } else { Log_OC.w(TAG, "FileUploadService got upload intent for file which is already queued: " @@ -437,10 +446,9 @@ protected void onHandleIntent(Intent intent) { } } } - // at this point mPendingUploads is filled. - Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size() + " - before uploading."); + Log_OC.d(TAG, "mPendingUploads size:" + mPendingUploads.size() + " - before uploading."); Iterator it = mPendingUploads.keySet().iterator(); while (it.hasNext()) { @@ -459,7 +467,8 @@ protected void onHandleIntent(Intent intent) { Log_OC.d(TAG, "Upload with result " + uploadResult.getCode() + ": " + uploadResult.getLogMessage() + " will be abandoned."); mPendingUploads.remove(buildRemoteName(uploadDbObject)); - } + } + Log_OC.d(TAG, "mCurrentUpload = null"); mCurrentUpload = null; break; case LATER: @@ -479,8 +488,8 @@ protected void onHandleIntent(Intent intent) { } } - Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size() + " - after uploading."); - Log_OC.i(TAG, "onHandleIntent end"); + Log_OC.d(TAG, "mPendingUploads size:" + mPendingUploads.size() + " - after uploading."); + Log_OC.d(TAG, "onHandleIntent end"); } /** @@ -525,14 +534,23 @@ public class FileUploaderBinder extends Binder implements OnDatatransferProgress * @param file A file in the queue of pending uploads */ public void cancel(Account account, OCFile file) { - UploadDbObject upload = null; - //remove is atomic operation. no need for synchronize. - upload = mPendingUploads.remove(buildRemoteName(account, file)); - upload.setUploadStatus(UploadStatus.UPLOAD_CANCELLED); - mDb.updateUpload(upload); - if(mCurrentUpload != null) { + // updating current references (i.e., uploadStatus of current + // upload) is handled by updateDataseUploadResult() which is called + // after upload finishes. Following cancel call makes sure that is + // does finish right now. + if (mCurrentUpload != null) { mCurrentUpload.cancel(); - mCurrentUpload = null; + } else { + Log_OC.e(TAG, "mCurrentUpload == null. Cannot cancel upload. Fix that!"); + + // in this case we have to update the db here. this should never + // happen though! + UploadDbObject upload = mPendingUploads.remove(buildRemoteName(account, file)); + upload.setUploadStatus(UploadStatus.UPLOAD_CANCELLED); + // storagePath inside upload is the temporary path. file + // contains the correct path used as db reference. + upload.getOCFile().setStoragePath(file.getStoragePath()); + mDb.updateUpload(upload); } } @@ -547,6 +565,10 @@ public void clearListeners() { * If 'file' is a directory, returns 'true' if some of its descendant * files is uploading or waiting to upload. * + * Warning: If remote file exists and !forceOverwrite the original file + * is being returned here. That is, it seems as if the original file is + * being updated when actually a new file is being uploaded. + * * @param account Owncloud account where the remote file will be stored. * @param file A file that could be in the queue of pending uploads */ @@ -702,18 +724,20 @@ private RemoteOperationResult uploadFile(UploadDbObject uploadDbObject) { uploadKey = buildRemoteName(account, uploadDbObject.getRemotePath()); OCFile file = uploadDbObject.getOCFile(); + Log_OC.d(TAG, "mCurrentUpload = new UploadFileOperation"); mCurrentUpload = new UploadFileOperation(account, file, chunked, uploadDbObject.isForceOverwrite(), uploadDbObject.getLocalAction(), getApplicationContext()); if (uploadDbObject.isCreateRemoteFolder()) { mCurrentUpload.setRemoteFolderToBeCreated(); } mCurrentUpload.addDatatransferProgressListener((FileUploaderBinder) mBinder); - + mCurrentUpload.addDatatransferProgressListener(this); + notifyUploadStart(mCurrentUpload); RemoteOperationResult uploadResult = null, grantResult = null; try { - // / prepare client object to send requests to the ownCloud + // prepare client object to send requests to the ownCloud // server if (mUploadClient == null || !mLastAccount.equals(mCurrentUpload.getAccount())) { mLastAccount = mCurrentUpload.getAccount(); @@ -722,7 +746,7 @@ private RemoteOperationResult uploadFile(UploadDbObject uploadDbObject) { mUploadClient = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, this); } - // / check the existence of the parent folder for the file to + // 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 @@ -733,6 +757,11 @@ private RemoteOperationResult uploadFile(UploadDbObject uploadDbObject) { if (grantResult.isSuccess()) { OCFile parent = mStorageManager.getFileByPath(remoteParentPath); mCurrentUpload.getFile().setParentId(parent.getFileId()); + // inside this call the remote path may be changed (if remote + // file exists and !forceOverwrite) in this case the map from + // mPendingUploads is wrong. This results in an upload + // indication in the GUI showing that the original file is being + // uploaded (instead that a new one is created) uploadResult = mCurrentUpload.execute(mUploadClient); if (uploadResult.isSuccess()) { saveUploadedFile(mCurrentUpload); @@ -750,7 +779,7 @@ private RemoteOperationResult uploadFile(UploadDbObject uploadDbObject) { uploadResult = new RemoteOperationResult(e); } finally { - Log_OC.i(TAG, "Remove CurrentUploadItem from pending upload Item Map."); + Log_OC.d(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 @@ -911,6 +940,7 @@ private OCFile obtainNewOCFileToUpload(String remotePath, String localPath, Stri */ private void notifyUploadStart(UploadFileOperation upload) { // / create status notification with a progress bar + mLastPercent = 0; mNotificationBuilder = NotificationBuilderWithProgressBar.newNotificationBuilderWithProgressBar(this); mNotificationBuilder .setOngoing(true) @@ -942,6 +972,22 @@ private void updateDatabaseUploadStart(UploadFileOperation upload) { mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_IN_PROGRESS, null); } + /** + * Callback method to update the progress bar in the status notification + */ + @Override + 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 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()); + } + mLastPercent = percent; + } + /** * Updates the status notification with the result of an upload operation. * @@ -1015,6 +1061,7 @@ private void notifyUploadResult(RemoteOperationResult uploadResult, UploadFileOp */ private void updateDataseUploadResult(RemoteOperationResult uploadResult, UploadFileOperation upload) { // result: success or fail notification + Log_OC.d(TAG, "updateDataseUploadResult uploadResult: " + uploadResult + " upload: " + upload); if (uploadResult.isCancelled()) { mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_CANCELLED, uploadResult); } else { @@ -1096,4 +1143,5 @@ public static void retry(Context context) { context.startService(i); } + } diff --git a/src/com/owncloud/android/operations/UploadFileOperation.java b/src/com/owncloud/android/operations/UploadFileOperation.java index b858527f18b..f0531362b38 100644 --- a/src/com/owncloud/android/operations/UploadFileOperation.java +++ b/src/com/owncloud/android/operations/UploadFileOperation.java @@ -380,7 +380,11 @@ protected RemoteOperationResult run(OwnCloudClient client) { complement = " (while copying local file to " + FileStorageUtils.getSavePath(mAccount.name) + ")"; } - Log_OC.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage() + complement, result.getException()); + if(result.isCancelled()){ + Log_OC.w(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage()); + } else { + Log_OC.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage() + complement, result.getException()); + } } else { Log_OC.e(TAG, "Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage()); } diff --git a/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java b/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java index 479d56f0109..b34fe25e72f 100755 --- a/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java @@ -22,6 +22,7 @@ import com.owncloud.android.datamodel.ThumbnailsCacheManager; import com.owncloud.android.db.UploadDbHandler; import com.owncloud.android.db.UploadDbObject; +import com.owncloud.android.lib.common.network.OnDatatransferProgressListener; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.utils.DisplayUtils; @@ -30,7 +31,7 @@ * active, completed. Filtering possible. * */ -public class ExpandableUploadListAdapter extends BaseExpandableListAdapter implements Observer { +public class ExpandableUploadListAdapter extends BaseExpandableListAdapter implements Observer, OnDatatransferProgressListener { private static final String TAG = "ExpandableUploadListAdapter"; private Activity mActivity; @@ -293,4 +294,11 @@ public View getGroupView(int groupPosition, boolean isExpanded, View convertView public boolean isChildSelectable(int groupPosition, int childPosition) { return true; } + + @Override + public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, + String fileAbsoluteName) { + // TODO Auto-generated method stub + + } } diff --git a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java index 1e144879b1b..627784336c1 100644 --- a/src/com/owncloud/android/ui/fragment/FileDetailFragment.java +++ b/src/com/owncloud/android/ui/fragment/FileDetailFragment.java @@ -509,11 +509,19 @@ private boolean ocVersionSupportsTimeCreated(){ public void listenForTransferProgress() { if (mProgressListener != null) { if (mContainerActivity.getFileDownloaderBinder() != null) { + Log_OC.d(TAG, "registering download progress listener"); mContainerActivity.getFileDownloaderBinder().addDatatransferProgressListener(mProgressListener, mAccount, getFile()); + }else { + Log_OC.d(TAG, "mContainerActivity.getFileDownloaderBinder() == null"); } if (mContainerActivity.getFileUploaderBinder() != null) { + Log_OC.d(TAG, "registering upload progress listener"); mContainerActivity.getFileUploaderBinder().addDatatransferProgressListener(mProgressListener, mAccount, getFile()); + }else { + Log_OC.d(TAG, "mContainerActivity.getFileUploaderBinder() == null"); } + } else { + Log_OC.d(TAG, "mProgressListener == null"); } } From 9445692878ddf6d693e04c2f40d840d8c1647447 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Fri, 28 Nov 2014 14:01:19 +0100 Subject: [PATCH 44/76] fix cancel upload add remove single upload from list add retry upload --- res/menu/upload_actions_menu.xml | 8 +- res/values/strings.xml | 2 + .../owncloud/android/db/UploadDbHandler.java | 8 ++ .../android/files/FileOperationsHelper.java | 26 +++++- .../files/services/FileUploadService.java | 84 ++++++++++++++++++- .../operations/UploadFileOperation.java | 37 +++++++- .../ui/activity/UploadListActivity.java | 36 +++++++- .../ui/fragment/UploadListFragment.java | 30 +++++-- 8 files changed, 208 insertions(+), 23 deletions(-) diff --git a/res/menu/upload_actions_menu.xml b/res/menu/upload_actions_menu.xml index c6ed455a1af..e23804afc38 100755 --- a/res/menu/upload_actions_menu.xml +++ b/res/menu/upload_actions_menu.xml @@ -1,8 +1,10 @@ - - - + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index d8f1e930c0a..85a953823c0 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -81,6 +81,8 @@ OK Cancel download Cancel upload + Remove upload + Retry upload Cancel Save & Exit Error diff --git a/src/com/owncloud/android/db/UploadDbHandler.java b/src/com/owncloud/android/db/UploadDbHandler.java index 2a9228738c8..ec0d987a8f3 100644 --- a/src/com/owncloud/android/db/UploadDbHandler.java +++ b/src/com/owncloud/android/db/UploadDbHandler.java @@ -297,12 +297,20 @@ public void notifyObserversNow() { public int removeUpload(String localPath) { int result = getDB().delete(TABLE_UPLOAD, "path = ?", new String[] { localPath }); Log_OC.d(TABLE_UPLOAD, "delete returns with: " + result + " for file: " + localPath); + if(result > 0) { + notifyObserversNow(); + } return result; } public UploadDbObject[] getAllStoredUploads() { return getUploads(null, null); } + + public UploadDbObject[] getUploadByLocalPath(String localPath) { + return getUploads("path = ?", new String[] { localPath }); + } + private UploadDbObject[] getUploads(String selection, String[] selectionArgs) { Cursor c = getDB().query(TABLE_UPLOAD, null, selection, selectionArgs, null, null, null); diff --git a/src/com/owncloud/android/files/FileOperationsHelper.java b/src/com/owncloud/android/files/FileOperationsHelper.java index 3429d177d15..75341d3a772 100644 --- a/src/com/owncloud/android/files/FileOperationsHelper.java +++ b/src/com/owncloud/android/files/FileOperationsHelper.java @@ -247,7 +247,31 @@ public void createFolder(String remotePath, boolean createFullPath) { mFileActivity.showLoadingDialog(); } + /** + * Retry downloading a failed or cancelled upload + */ + public void retryUpload(OCFile file) { + Account account = mFileActivity.getAccount(); + FileUploaderBinder uploaderBinder = mFileActivity.getFileUploaderBinder(); + if (uploaderBinder != null) { + uploaderBinder.retry(account, file); + } else { + Log_OC.w(TAG, "uploaderBinder not set. Cannot remove " + file); + } + } + /** + * Remove upload from upload list. + */ + public void removeUploadFromList(OCFile file) { + Account account = mFileActivity.getAccount(); + FileUploaderBinder uploaderBinder = mFileActivity.getFileUploaderBinder(); + if (uploaderBinder != null) { + uploaderBinder.remove(account, file); + } else { + Log_OC.w(TAG, "uploaderBinder not set. Cannot remove " + file); + } + } public void cancelTransference(OCFile file) { Account account = mFileActivity.getAccount(); FileDownloaderBinder downloaderBinder = mFileActivity.getFileDownloaderBinder(); @@ -263,7 +287,7 @@ public void cancelTransference(OCFile file) { downloaderBinder.cancel(account, file); } else { - Log_OC.d(TAG, "Download for " + file + " not in progress. Cannot cancel."); + Log_OC.d(TAG, "Download for " + file + " not in progress. Cannot cancel " + file); } } if (uploaderBinder != null) { diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index e3881f867d6..b4bf18c8c84 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -20,6 +20,7 @@ import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.ConcurrentModificationException; import java.util.Date; import java.util.HashMap; @@ -30,6 +31,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicBoolean; import android.accounts.Account; import android.accounts.AccountManager; @@ -62,6 +64,7 @@ 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.OperationCancelledException; 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; @@ -121,6 +124,11 @@ public FileUploadService() { * Call this Service with only this Intent key if all pending uploads are to be retried. */ private static final String KEY_RETRY = "KEY_RETRY"; + /** + * Call this Service with KEY_RETRY and KEY_RETRY_REMOTE_PATH to retry + * download of file identified by KEY_RETRY_REMOTE_PATH. + */ + private static final String KEY_RETRY_REMOTE_PATH = "KEY_RETRY_REMOTE_PATH"; /** * {@link Account} to which file is to be uploaded. */ @@ -208,6 +216,9 @@ public int getValue() { private FileDataStorageManager mStorageManager; //since there can be only one instance of an Android service, there also just one db connection. private UploadDbHandler mDb = null; + + private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false); + private final AtomicBoolean mCancellationPossible = new AtomicBoolean(false); /** * List of uploads that are currently pending. Maps from remotePath to where file @@ -450,16 +461,33 @@ protected void onHandleIntent(Intent intent) { Log_OC.d(TAG, "mPendingUploads size:" + mPendingUploads.size() + " - before uploading."); - Iterator it = mPendingUploads.keySet().iterator(); + Iterator it; + if (intent != null && intent.getStringExtra(KEY_RETRY_REMOTE_PATH) != null) { + ArrayList list = new ArrayList(1); + String remotePath = intent.getStringExtra(KEY_RETRY_REMOTE_PATH); + list.add(remotePath); + it = list.iterator(); + Log_OC.d(TAG, "Start uploading " + remotePath); + } else { + it = mPendingUploads.keySet().iterator(); + } + while (it.hasNext()) { String upload = it.next(); UploadDbObject uploadDbObject = mPendingUploads.get(upload); + + if(uploadDbObject == null) { + Log_OC.e(TAG, "Cannot upload null. Fix that!"); + continue; + } switch (canUploadFileNow(uploadDbObject)) { case NOW: Log_OC.d(TAG, "Calling uploadFile for " + upload); RemoteOperationResult uploadResult = uploadFile(uploadDbObject); + //TODO check if cancelled by user + updateDataseUploadResult(uploadResult, mCurrentUpload); notifyUploadResult(uploadResult, mCurrentUpload); sendFinalBroadcast(uploadResult, mCurrentUpload); @@ -538,10 +566,13 @@ public void cancel(Account account, OCFile file) { // upload) is handled by updateDataseUploadResult() which is called // after upload finishes. Following cancel call makes sure that is // does finish right now. - if (mCurrentUpload != null) { + if (mCurrentUpload != null && mCurrentUpload.isUploadInProgress()) { mCurrentUpload.cancel(); + } else if(mCancellationPossible.get()){ + mCancellationRequested.set(true); } else { - Log_OC.e(TAG, "mCurrentUpload == null. Cannot cancel upload. Fix that!"); + Log_OC.e(TAG, + "Cannot cancel upload because not in progress. Instead remove from pending upload list. This should not happen."); // in this case we have to update the db here. this should never // happen though! @@ -553,6 +584,29 @@ public void cancel(Account account, OCFile file) { mDb.updateUpload(upload); } } + + public void remove(Account account, OCFile file) { + UploadDbObject upload = mPendingUploads.remove(buildRemoteName(account, file)); + if(upload == null) { + Log_OC.e(TAG, "Could not delete upload "+file+" from mPendingUploads."); + } + int d = mDb.removeUpload(file.getStoragePath()); + if(d == 0) { + Log_OC.e(TAG, "Could not delete upload "+file.getStoragePath()+" from database."); + } + } + + public void retry(Account account, OCFile file) { + //get upload from db and add to mPendingUploads. Then start upload. + UploadDbObject[] list = mDb.getUploadByLocalPath(file.getStoragePath()); + if(list.length == 1) { + String uploadKey = buildRemoteName(list[0]); + mPendingUploads.putIfAbsent(uploadKey, list[0]); + FileUploadService.retry(getApplicationContext(), uploadKey); + } else { + Log_OC.e(TAG, "Upload file " + file + " not found. Cannot upload."); + } + } public void clearListeners() { mBoundListeners.clear(); @@ -640,6 +694,8 @@ public void onTransferProgress(long progressRate, long totalTransferredSoFar, lo boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, localFileName); } } + + } enum CanUploadFileNowStatus {NOW, LATER, FILE_GONE, ERROR}; @@ -733,6 +789,8 @@ private RemoteOperationResult uploadFile(UploadDbObject uploadDbObject) { mCurrentUpload.addDatatransferProgressListener((FileUploaderBinder) mBinder); mCurrentUpload.addDatatransferProgressListener(this); + mCancellationRequested.set(false); + mCancellationPossible.set(true); notifyUploadStart(mCurrentUpload); RemoteOperationResult uploadResult = null, grantResult = null; @@ -751,9 +809,15 @@ private RemoteOperationResult uploadFile(UploadDbObject uploadDbObject) { String remoteParentPath = new File(mCurrentUpload.getRemotePath()).getParent(); remoteParentPath = remoteParentPath.endsWith(OCFile.PATH_SEPARATOR) ? remoteParentPath : remoteParentPath + OCFile.PATH_SEPARATOR; + //TODO this might take a moment and should thus be cancelable, for now: cancel afterwards. grantResult = grantFolderExistence(mCurrentUpload, remoteParentPath); + + if(mCancellationRequested.get()) { + throw new OperationCancelledException(); + } + mCancellationPossible.set(false); - // / perform the upload + // perform the upload if (grantResult.isSuccess()) { OCFile parent = mStorageManager.getFileByPath(remoteParentPath); mCurrentUpload.getFile().setParentId(parent.getFileId()); @@ -778,6 +842,8 @@ private RemoteOperationResult uploadFile(UploadDbObject uploadDbObject) { Log_OC.e(TAG, "Error while trying to get autorization for " + mLastAccount.name, e); uploadResult = new RemoteOperationResult(e); + } catch (OperationCancelledException e) { + uploadResult = new RemoteOperationResult(e); } finally { Log_OC.d(TAG, "Remove CurrentUploadItem from pending upload Item Map."); if (uploadResult.isException()) { @@ -1137,9 +1203,19 @@ private boolean isPdfFileFromContentProviderWithoutExtension(String localPath, S * Call if all pending uploads are to be retried. */ public static void retry(Context context) { + retry(context, null); + } + + /** + * Call to retry upload identified by remotePath + */ + private static void retry(Context context, String remotePath) { Log_OC.d(TAG, "FileUploadService.retry()"); Intent i = new Intent(context, FileUploadService.class); i.putExtra(FileUploadService.KEY_RETRY, true); + if(remotePath != null) { + i.putExtra(FileUploadService.KEY_RETRY_REMOTE_PATH, remotePath); + } context.startService(i); } diff --git a/src/com/owncloud/android/operations/UploadFileOperation.java b/src/com/owncloud/android/operations/UploadFileOperation.java index f0531362b38..811f7b013d6 100644 --- a/src/com/owncloud/android/operations/UploadFileOperation.java +++ b/src/com/owncloud/android/operations/UploadFileOperation.java @@ -90,13 +90,13 @@ public class UploadFileOperation extends RemoteOperation { PutMethod mPutMethod = null; private Set mDataTransferListeners = new HashSet(); private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false); + private final AtomicBoolean mUploadStarted = new AtomicBoolean(false); private Context mContext; private UploadRemoteFileOperation mUploadOperation; protected RequestEntity mEntity = null; - public UploadFileOperation( Account account, OCFile file, boolean chunked, @@ -200,6 +200,8 @@ public void removeDatatransferProgressListener(OnDatatransferProgressListener li @Override protected RemoteOperationResult run(OwnCloudClient client) { + mCancellationRequested.set(false); + mUploadStarted.set(true); RemoteOperationResult result = null; boolean localCopyPassed = false, nameCheckPassed = false; File temporalFile = null, originalFile = new File(mOriginalStoragePath), expectedFile = null; @@ -220,6 +222,10 @@ protected RemoteOperationResult run(OwnCloudClient client) { // getAvailableRemotePath() // !!! expectedFile = new File(expectedPath); + + if (mCancellationRequested.get()) { + throw new OperationCancelledException(); + } // check location of local file; if not the expected, copy to a // temporal file before upload (if COPY is the expected behaviour) @@ -307,6 +313,10 @@ protected RemoteOperationResult run(OwnCloudClient client) { } } localCopyPassed = true; + + if (mCancellationRequested.get()) { + throw new OperationCancelledException(); + } /// perform the upload if ( mChunked && (new File(mFile.getStoragePath())).length() > ChunkedUploadRemoteFileOperation.CHUNK_SIZE ) { @@ -366,6 +376,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { } } finally { + mUploadStarted.set(false); if (temporalFile != null && !originalFile.equals(temporalFile)) { temporalFile.delete(); } @@ -462,11 +473,29 @@ private boolean existsFile(OwnCloudClient client, String remotePath){ return result.isSuccess(); } + /** + * Allows to cancel the actual upload operation. If actual upload operating + * is in progress it is cancelled, if upload preparation is being performed + * upload will not take place. + */ public void cancel() { if (mUploadOperation == null) { - Log_OC.e(TAG, "UploadFileOperation.cancel(): mUploadOperation == null for file: " + mFile + ". Fix that."); - return; + if (mUploadStarted.get()) { + Log_OC.d(TAG, "Cancelling upload during upload preparations."); + mCancellationRequested.set(true); + } else { + Log_OC.e(TAG, "No upload in progress. This should not happen."); + } + } else { + Log_OC.d(TAG, "Cancelling upload during actual upload operation."); + mUploadOperation.cancel(); } - mUploadOperation.cancel(); + } + + /** + * As soon as this method return true, upload can be cancel via cancel(). + */ + public boolean isUploadInProgress() { + return mUploadStarted.get(); } } diff --git a/src/com/owncloud/android/ui/activity/UploadListActivity.java b/src/com/owncloud/android/ui/activity/UploadListActivity.java index da426d6311e..c47284ce6a5 100755 --- a/src/com/owncloud/android/ui/activity/UploadListActivity.java +++ b/src/com/owncloud/android/ui/activity/UploadListActivity.java @@ -1,7 +1,10 @@ package com.owncloud.android.ui.activity; +import android.content.ComponentName; import android.content.Intent; +import android.content.ServiceConnection; import android.os.Bundle; +import android.os.IBinder; import android.os.Parcelable; import com.actionbarsherlock.view.Menu; @@ -10,9 +13,9 @@ import com.owncloud.android.R; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.db.UploadDbHandler; -import com.owncloud.android.db.UploadDbHandler.UploadStatus; import com.owncloud.android.db.UploadDbObject; import com.owncloud.android.files.services.FileUploadService; +import com.owncloud.android.files.services.FileUploadService.FileUploaderBinder; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.ui.errorhandling.ExceptionHandler; import com.owncloud.android.ui.fragment.UploadListFragment; @@ -39,9 +42,6 @@ protected void onCreate(Bundle savedInstanceState) { // //////////////////////////////////////// // UploadListFragment.ContainerActivity // //////////////////////////////////////// - /** - * TODO Without a menu this is a little un-intuitive. - */ @Override public boolean onUploadItemClick(UploadDbObject file) { OCFile ocFile = file.getOCFile(); @@ -78,6 +78,34 @@ public boolean onCreateOptionsMenu(Menu menu) { inflater.inflate(R.menu.upload_list_menu, menu); return true; } + + @Override + protected ServiceConnection newTransferenceServiceConnection() { + return new UploadListServiceConnection(); + } + + /** Defines callbacks for service binding, passed to bindService() */ + private class UploadListServiceConnection implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName component, IBinder service) { + + if (component.equals(new ComponentName(UploadListActivity.this, FileUploadService.class))) { + Log_OC.d(TAG, "UploadListActivty connected to Upload service"); + mUploaderBinder = (FileUploaderBinder) service; + } else { + return; + } + + } + + @Override + public void onServiceDisconnected(ComponentName component) { + if (component.equals(new ComponentName(UploadListActivity.this, FileUploadService.class))) { + Log_OC.d(TAG, "UploadListActivty suddenly disconnected from Upload service"); + mUploaderBinder = null; + } + } + }; } diff --git a/src/com/owncloud/android/ui/fragment/UploadListFragment.java b/src/com/owncloud/android/ui/fragment/UploadListFragment.java index ab94f629590..de3d44f46a2 100755 --- a/src/com/owncloud/android/ui/fragment/UploadListFragment.java +++ b/src/com/owncloud/android/ui/fragment/UploadListFragment.java @@ -121,14 +121,26 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuIn int childPosition = ExpandableListView.getPackedPositionChild(info.packedPosition); int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition); UploadDbObject uploadFile = (UploadDbObject) mAdapter.getChild(groupPosition, childPosition); - if (uploadFile.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED - || uploadFile.getUploadStatus() == UploadStatus.UPLOAD_FAILED_GIVE_UP) { + if (uploadFile.getUploadStatus() != UploadStatus.UPLOAD_IN_PROGRESS) { MenuItem item = menu.findItem(R.id.action_cancel_upload); if (item != null) { item.setVisible(false); item.setEnabled(false); } - } + } else { + MenuItem item = menu.findItem(R.id.action_remove_upload); + if (item != null) { + item.setVisible(false); + item.setEnabled(false); + } + } + if (!(uploadFile.getUploadStatus() == UploadStatus.UPLOAD_CANCELLED || uploadFile.getUploadStatus() == UploadStatus.UPLOAD_FAILED_RETRY)) { + MenuItem item = menu.findItem(R.id.action_retry_upload); + if (item != null) { + item.setVisible(false); + item.setEnabled(false); + } + } } @Override @@ -138,12 +150,16 @@ public boolean onContextItemSelected (MenuItem item) { int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition); UploadDbObject uploadFile = (UploadDbObject) mAdapter.getChild(groupPosition, childPosition); switch (item.getItemId()) { - case R.id.action_cancel_upload: { - //TODO OCFile does not have UploadBinder. :( + case R.id.action_cancel_upload: ((FileActivity) getActivity()).getFileOperationsHelper().cancelTransference(uploadFile.getOCFile()); return true; - } - case R.id.action_see_details: { + case R.id.action_remove_upload: { + ((FileActivity) getActivity()).getFileOperationsHelper().removeUploadFromList(uploadFile.getOCFile()); + return true; + }case R.id.action_retry_upload: { + ((FileActivity) getActivity()).getFileOperationsHelper().retryUpload(uploadFile.getOCFile()); + return true; + }case R.id.action_see_details: { Intent showDetailsIntent = new Intent(getActivity(), FileDisplayActivity.class); showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, (Parcelable) uploadFile.getOCFile()); showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, uploadFile.getAccount(getActivity())); From 9e6d8c1b58bfafebbf0db6accf599a649b3d93ab Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Sun, 30 Nov 2014 15:05:18 +0100 Subject: [PATCH 45/76] set charging only default to false add message for LATER status enable retry option for all not-succeeded uploads --- .../files/services/FileUploadService.java | 27 ++++++++++++++--- .../adapter/ExpandableUploadListAdapter.java | 5 ++++ .../ui/fragment/UploadListFragment.java | 30 ++++++++++++------- 3 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index b4bf18c8c84..de5e24367d4 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -21,11 +21,9 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.ConcurrentModificationException; import java.util.Date; import java.util.HashMap; import java.util.Iterator; -import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -48,7 +46,6 @@ import android.os.Parcelable; import android.os.Process; import android.support.v4.app.NotificationCompat; -import android.text.format.DateUtils; import android.webkit.MimeTypeMap; import com.owncloud.android.R; @@ -409,7 +406,7 @@ protected void onHandleIntent(Intent intent) { boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false); boolean isCreateRemoteFolder = intent.getBooleanExtra(KEY_CREATE_REMOTE_FOLDER, false); boolean isUseWifiOnly = intent.getBooleanExtra(KEY_WIFI_ONLY, true); - boolean isWhileChargingOnly = intent.getBooleanExtra(KEY_WHILE_CHARGING_ONLY, true); + boolean isWhileChargingOnly = intent.getBooleanExtra(KEY_WHILE_CHARGING_ONLY, false); long uploadTimestamp = intent.getLongExtra(KEY_UPLOAD_TIMESTAMP, -1); LocalBehaviour localAction = (LocalBehaviour) intent.getSerializableExtra(KEY_LOCAL_BEHAVIOUR); @@ -520,6 +517,27 @@ protected void onHandleIntent(Intent intent) { Log_OC.d(TAG, "onHandleIntent end"); } + /** + * Returns the reason as String why state of uploadDbObject is LATER. If + * upload state != LATER return null. + */ + static public String getUploadLaterReason(Context context, UploadDbObject uploadDbObject) { + if (uploadDbObject.isUseWifiOnly() && !UploadUtils.isConnectedViaWiFi(context)) { + return "Upload is wifi-only."; + } + if (uploadDbObject.isWhileChargingOnly() && !UploadUtils.isCharging(context)) { + return "Upload is charging-only."; + } + Date now = new Date(); + if (now.getTime() < uploadDbObject.getUploadTimestamp()) { + return "Upload scheduled for " + DisplayUtils.unixTimeToHumanReadable(uploadDbObject.getUploadTimestamp()); + } + if (uploadDbObject.getUploadStatus() == UploadStatus.UPLOAD_LATER) { + return "Upload delayed for unknown reason."; + } + return null; + } + /** * Provides a binder object that clients can use to perform operations on * the queue of uploads, excepting the addition of new files. @@ -1158,6 +1176,7 @@ private boolean shouldRetryFailedUpload(RemoteOperationResult uploadResult) { case HOST_NOT_AVAILABLE: case NO_NETWORK_CONNECTION: case TIMEOUT: + case WRONG_CONNECTION: // SocketException return true; default: return false; diff --git a/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java b/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java index b34fe25e72f..c3607631d3b 100755 --- a/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java @@ -21,7 +21,9 @@ import com.owncloud.android.R; import com.owncloud.android.datamodel.ThumbnailsCacheManager; import com.owncloud.android.db.UploadDbHandler; +import com.owncloud.android.db.UploadDbHandler.UploadStatus; import com.owncloud.android.db.UploadDbObject; +import com.owncloud.android.files.services.FileUploadService; import com.owncloud.android.lib.common.network.OnDatatransferProgressListener; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.utils.DisplayUtils; @@ -142,6 +144,9 @@ private View getView(UploadDbObject[] uploadsItems, int position, View convertVi if (uploadObject.getLastResult() != null && !uploadObject.getLastResult().isSuccess()) { status += ": " + uploadObject.getLastResult().getLogMessage(); } + if (uploadObject.getUploadStatus() == UploadStatus.UPLOAD_LATER) { + status += ": " + FileUploadService.getUploadLaterReason(mActivity, uploadObject); + } statusView.setText(status); ImageView fileIcon = (ImageView) view.findViewById(R.id.imageView1); diff --git a/src/com/owncloud/android/ui/fragment/UploadListFragment.java b/src/com/owncloud/android/ui/fragment/UploadListFragment.java index de3d44f46a2..a6ff50c2a35 100755 --- a/src/com/owncloud/android/ui/fragment/UploadListFragment.java +++ b/src/com/owncloud/android/ui/fragment/UploadListFragment.java @@ -21,34 +21,25 @@ import android.content.Intent; import android.os.Bundle; import android.os.Parcelable; -import android.util.Log; import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.view.ContextMenu.ContextMenuInfo; -import android.widget.AdapterView; import android.widget.BaseExpandableListAdapter; import android.widget.ExpandableListView; import android.widget.ExpandableListView.ExpandableListContextMenuInfo; import android.widget.ListView; -import android.widget.AdapterView.AdapterContextMenuInfo; import com.owncloud.android.R; -import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.db.UploadDbHandler.UploadStatus; import com.owncloud.android.db.UploadDbObject; -import com.owncloud.android.files.FileMenuFilter; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.ui.activity.FileActivity; import com.owncloud.android.ui.activity.FileDisplayActivity; -import com.owncloud.android.ui.activity.MoveActivity; import com.owncloud.android.ui.adapter.ExpandableUploadListAdapter; -import com.owncloud.android.ui.dialog.ConfirmationDialogFragment; -import com.owncloud.android.ui.dialog.RemoveFileDialogFragment; -import com.owncloud.android.ui.dialog.RenameFileDialogFragment; /** * A Fragment that lists all files and folders in a given LOCAL path. @@ -134,7 +125,7 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuIn item.setEnabled(false); } } - if (!(uploadFile.getUploadStatus() == UploadStatus.UPLOAD_CANCELLED || uploadFile.getUploadStatus() == UploadStatus.UPLOAD_FAILED_RETRY)) { + if (!userCanRetryUpload(uploadFile)) { MenuItem item = menu.findItem(R.id.action_retry_upload); if (item != null) { item.setVisible(false); @@ -143,6 +134,23 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuIn } } + /** + * Returns true when user can choose to retry this upload. + * + * @param uploadFile + * @return + */ + private boolean userCanRetryUpload(UploadDbObject uploadFile) { + switch (uploadFile.getUploadStatus()) { + case UPLOAD_CANCELLED: + case UPLOAD_FAILED_RETRY: + case UPLOAD_FAILED_GIVE_UP: + return true; + default: + return false; + } + } + @Override public boolean onContextItemSelected (MenuItem item) { ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) item.getMenuInfo(); From 3dacd2436dcf938044e047b28d00ed0351e3302f Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Sun, 30 Nov 2014 15:17:40 +0100 Subject: [PATCH 46/76] update db when retrying upload allow LATER upload to be cancelled --- .../files/services/FileUploadService.java | 6 ++++++ .../android/ui/fragment/UploadListFragment.java | 17 +++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index de5e24367d4..1f1f4744105 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -464,6 +464,12 @@ protected void onHandleIntent(Intent intent) { String remotePath = intent.getStringExtra(KEY_RETRY_REMOTE_PATH); list.add(remotePath); it = list.iterator(); + // update db status for upload + UploadDbObject uploadDbObject = mPendingUploads.get(remotePath); + uploadDbObject.setUploadStatus(UploadStatus.UPLOAD_LATER); + uploadDbObject.setLastResult(null); + mDb.updateUpload(uploadDbObject); + Log_OC.d(TAG, "Start uploading " + remotePath); } else { it = mPendingUploads.keySet().iterator(); diff --git a/src/com/owncloud/android/ui/fragment/UploadListFragment.java b/src/com/owncloud/android/ui/fragment/UploadListFragment.java index a6ff50c2a35..8d83a2583a7 100755 --- a/src/com/owncloud/android/ui/fragment/UploadListFragment.java +++ b/src/com/owncloud/android/ui/fragment/UploadListFragment.java @@ -34,7 +34,6 @@ import android.widget.ListView; import com.owncloud.android.R; -import com.owncloud.android.db.UploadDbHandler.UploadStatus; import com.owncloud.android.db.UploadDbObject; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.ui.activity.FileActivity; @@ -112,14 +111,14 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuIn int childPosition = ExpandableListView.getPackedPositionChild(info.packedPosition); int groupPosition = ExpandableListView.getPackedPositionGroup(info.packedPosition); UploadDbObject uploadFile = (UploadDbObject) mAdapter.getChild(groupPosition, childPosition); - if (uploadFile.getUploadStatus() != UploadStatus.UPLOAD_IN_PROGRESS) { - MenuItem item = menu.findItem(R.id.action_cancel_upload); + if (userCanCancelUpload(uploadFile)) { + MenuItem item = menu.findItem(R.id.action_remove_upload); if (item != null) { item.setVisible(false); item.setEnabled(false); } } else { - MenuItem item = menu.findItem(R.id.action_remove_upload); + MenuItem item = menu.findItem(R.id.action_cancel_upload); if (item != null) { item.setVisible(false); item.setEnabled(false); @@ -134,6 +133,16 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuIn } } + private boolean userCanCancelUpload(UploadDbObject uploadFile) { + switch (uploadFile.getUploadStatus()) { + case UPLOAD_IN_PROGRESS: + case UPLOAD_LATER: + return true; + default: + return false; + } + } + /** * Returns true when user can choose to retry this upload. * From 74284856b93bfc7e1cc819c6facab4de5afeb95f Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Sun, 30 Nov 2014 18:11:24 +0100 Subject: [PATCH 47/76] remove not needed service looper --- .../files/services/FileUploadService.java | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index 1f1f4744105..282f198e6b1 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -40,11 +40,8 @@ import android.content.Context; import android.content.Intent; import android.os.Binder; -import android.os.HandlerThread; import android.os.IBinder; -import android.os.Looper; import android.os.Parcelable; -import android.os.Process; import android.support.v4.app.NotificationCompat; import android.webkit.MimeTypeMap; @@ -201,12 +198,8 @@ public int getValue() { } }; - // public static final int UPLOAD_SINGLE_FILE = 0; - // public static final int UPLOAD_MULTIPLE_FILES = 1; - private static final String TAG = FileUploadService.class.getSimpleName(); - private Looper mServiceLooper; private IBinder mBinder; private OwnCloudClient mUploadClient = null; private Account mLastAccount = null; @@ -276,11 +269,8 @@ private static boolean chunkedUploadIsSupported(OwnCloudVersion version) { @Override public void onCreate() { super.onCreate(); - Log_OC.d(TAG, "mPendingUploads size:" + mPendingUploads.size()); + Log_OC.d(TAG, "mPendingUploads size:" + mPendingUploads.size() + " - onCreate"); mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - HandlerThread thread = new HandlerThread("FileUploaderThread", Process.THREAD_PRIORITY_BACKGROUND); - thread.start(); - mServiceLooper = thread.getLooper(); mBinder = new FileUploaderBinder(); mDb = UploadDbHandler.getInstance(this.getBaseContext()); mDb.recreateDb(); //for testing only @@ -288,11 +278,8 @@ public void onCreate() { //when this service starts there is no upload in progress. if db says so, app probably crashed before. mDb.setAllCurrentToUploadLater(); - //TODO This service can be instantiated at any time. Better move this retry call to start of app. - if(UploadUtils.isOnline(getApplicationContext())) { - Log_OC.d(TAG, "FileUploadService.retry() called by onCreate()"); - FileUploadService.retry(getApplicationContext()); - } + Log_OC.d(TAG, "FileUploadService.retry() called by onCreate()"); + FileUploadService.retry(getApplicationContext()); } /** From 142b894a7e732063c8fec06102143e8e8b697ea3 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Sun, 30 Nov 2014 19:33:21 +0100 Subject: [PATCH 48/76] display complete LATER reason --- .../files/services/FileUploadService.java | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index 282f198e6b1..277d451e63a 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -328,6 +328,10 @@ protected void onHandleIntent(Intent intent) { } Log_OC.d(TAG, "added " + countAddedEntries + " entrie(s) to mPendingUploads (this should be 0 except for the first time)."); + // null intent is received when charging or wifi state changes. + // fake a mDb change event, so that GUI can update the reason for + // LATER status of uploads. + mDb.notifyObserversNow(); } else { Log_OC.d(TAG, "Receive upload intent."); UploadSingleMulti uploadType = (UploadSingleMulti) intent.getSerializableExtra(KEY_UPLOAD_TYPE); @@ -515,18 +519,31 @@ protected void onHandleIntent(Intent intent) { * upload state != LATER return null. */ static public String getUploadLaterReason(Context context, UploadDbObject uploadDbObject) { + StringBuilder reason = new StringBuilder(); + Date now = new Date(); + if (now.getTime() < uploadDbObject.getUploadTimestamp()) { + reason.append("Waiting for " + DisplayUtils.unixTimeToHumanReadable(uploadDbObject.getUploadTimestamp())); + } if (uploadDbObject.isUseWifiOnly() && !UploadUtils.isConnectedViaWiFi(context)) { - return "Upload is wifi-only."; + if (reason.length() > 0) { + reason.append(" and wifi connectivity"); + } else { + reason.append("Waiting for wifi connectivity"); + } } if (uploadDbObject.isWhileChargingOnly() && !UploadUtils.isCharging(context)) { - return "Upload is charging-only."; + if (reason.length() > 0) { + reason.append(" and charging"); + } else { + reason.append("Waiting for charging"); + } } - Date now = new Date(); - if (now.getTime() < uploadDbObject.getUploadTimestamp()) { - return "Upload scheduled for " + DisplayUtils.unixTimeToHumanReadable(uploadDbObject.getUploadTimestamp()); + reason.append("."); + if (reason.length() > 0) { + return reason.toString(); } if (uploadDbObject.getUploadStatus() == UploadStatus.UPLOAD_LATER) { - return "Upload delayed for unknown reason."; + return "Upload delayed for unknown reason. Fix that!"; } return null; } @@ -589,6 +606,7 @@ public void cancel(Account account, OCFile file) { // happen though! UploadDbObject upload = mPendingUploads.remove(buildRemoteName(account, file)); upload.setUploadStatus(UploadStatus.UPLOAD_CANCELLED); + upload.setLastResult(new RemoteOperationResult(ResultCode.CANCELLED)); // storagePath inside upload is the temporary path. file // contains the correct path used as db reference. upload.getOCFile().setStoragePath(file.getStoragePath()); From 0ff69216651a0e6cd1bae39e8d2c7ba4a636a310 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Sun, 30 Nov 2014 20:18:27 +0100 Subject: [PATCH 49/76] git added filesize improving status messages --- res/layout/upload_list_group.xml | 1 - res/layout/upload_list_item.xml | 31 ++++++++- .../adapter/ExpandableUploadListAdapter.java | 65 ++++++++++++++++--- 3 files changed, 85 insertions(+), 12 deletions(-) diff --git a/res/layout/upload_list_group.xml b/res/layout/upload_list_group.xml index fd673a23ea0..d3e95e0975d 100755 --- a/res/layout/upload_list_group.xml +++ b/res/layout/upload_list_group.xml @@ -17,7 +17,6 @@ android:id="@+id/uploadListGroupName" android:layout_width="fill_parent" android:layout_height="25dip" - android:gravity="center_vertical|end" android:paddingLeft="5dip" android:paddingRight="5dip" android:textColor="#ffffffff" diff --git a/res/layout/upload_list_item.xml b/res/layout/upload_list_item.xml index b2d5c836e66..f9c3beb2305 100755 --- a/res/layout/upload_list_item.xml +++ b/res/layout/upload_list_item.xml @@ -58,6 +58,35 @@ android:textColor="#303030" android:textSize="16dip" /> + + + + + + + + + Date: Sun, 30 Nov 2014 21:21:17 +0100 Subject: [PATCH 50/76] fix store upload debug msg --- src/com/owncloud/android/db/UploadDbHandler.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/com/owncloud/android/db/UploadDbHandler.java b/src/com/owncloud/android/db/UploadDbHandler.java index ec0d987a8f3..83499e10670 100644 --- a/src/com/owncloud/android/db/UploadDbHandler.java +++ b/src/com/owncloud/android/db/UploadDbHandler.java @@ -17,8 +17,6 @@ */ package com.owncloud.android.db; -import java.util.ArrayList; -import java.util.List; import java.util.Observable; import android.content.ContentValues; @@ -201,12 +199,13 @@ public boolean storeUpload(UploadDbObject uploadObject) { long result = getDB().insert(TABLE_UPLOAD, null, cv); Log_OC.d(TAG, "putFileForLater returns with: " + result + " for file: " + uploadObject.getLocalPath()); - if (result == 1) { - notifyObserversNow(); + if (result == -1) { + Log_OC.e(TAG, "Failed to insert item " + uploadObject.getLocalPath() + " into upload db."); + return false; } else { - Log_OC.e(TAG, "Failed to insert item "+uploadObject.getLocalPath()+" into upload db."); + notifyObserversNow(); + return true; } - return result != -1; } /** From 982befe542b3a17336c16489197d4f3022d453de Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Sun, 30 Nov 2014 21:22:51 +0100 Subject: [PATCH 51/76] fix GUI output --- .../files/services/FileUploadService.java | 11 +++-------- .../adapter/ExpandableUploadListAdapter.java | 19 +++++++------------ 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index 277d451e63a..3f32bff1466 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -480,8 +480,6 @@ protected void onHandleIntent(Intent intent) { Log_OC.d(TAG, "Calling uploadFile for " + upload); RemoteOperationResult uploadResult = uploadFile(uploadDbObject); - //TODO check if cancelled by user - updateDataseUploadResult(uploadResult, mCurrentUpload); notifyUploadResult(uploadResult, mCurrentUpload); sendFinalBroadcast(uploadResult, mCurrentUpload); @@ -539,7 +537,7 @@ static public String getUploadLaterReason(Context context, UploadDbObject upload } } reason.append("."); - if (reason.length() > 0) { + if (reason.length() > 1) { return reason.toString(); } if (uploadDbObject.getUploadStatus() == UploadStatus.UPLOAD_LATER) { @@ -599,11 +597,8 @@ public void cancel(Account account, OCFile file) { } else if(mCancellationPossible.get()){ mCancellationRequested.set(true); } else { - Log_OC.e(TAG, - "Cannot cancel upload because not in progress. Instead remove from pending upload list. This should not happen."); - - // in this case we have to update the db here. this should never - // happen though! + // upload not in progress, but pending. + // in this case we have to update the db here. UploadDbObject upload = mPendingUploads.remove(buildRemoteName(account, file)); upload.setUploadStatus(UploadStatus.UPLOAD_CANCELLED); upload.setLastResult(new RemoteOperationResult(ResultCode.CANCELLED)); diff --git a/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java b/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java index 0c1e015b0dc..aab23381d24 100755 --- a/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java @@ -142,7 +142,7 @@ private View getView(UploadDbObject[] uploadsItems, int position, View convertVi TextView localPath = (TextView) view.findViewById(R.id.upload_local_path); String path = uploadObject.getOCFile().getStoragePath(); path = path.substring(0, path.length() - file.length() - 1); - localPath.setText(path); + localPath.setText("Path: " + path); TextView fileSize = (TextView) view.findViewById(R.id.upload_file_size); fileSize.setText(DisplayUtils.bytesToHumanReadable(uploadObject.getOCFile().getFileLength())); @@ -154,10 +154,10 @@ private View getView(UploadDbObject[] uploadsItems, int position, View convertVi status = mActivity.getResources().getString(R.string.uploader_upload_in_progress_ticker); break; case UPLOAD_FAILED_GIVE_UP: - if(uploadObject.getLastResult() != null) { - status = "Upload failed: " + uploadObject.getLastResult().getLogMessage(); - } else { - status = "Upload failed."; + if (uploadObject.getLastResult() != null) { + status = "Upload failed: " + uploadObject.getLastResult().getLogMessage(); + } else { + status = "Upload failed."; } break; case UPLOAD_FAILED_RETRY: @@ -172,15 +172,10 @@ private View getView(UploadDbObject[] uploadsItems, int position, View convertVi status = FileUploadService.getUploadLaterReason(mActivity, uploadObject); break; case UPLOAD_SUCCEEDED: - status = ""; + status = "Completed."; break; case UPLOAD_CANCELLED: - if(uploadObject.getLastResult() == null){ - status = "Upload cancelled."; - } else { - status = uploadObject.getLastResult() - .getLogMessage(); - } + status = "Upload cancelled."; break; case UPLOAD_PAUSED: status = "Upload paused."; From 6ce811228ae7157d93a2d055a7f08c16e79a9356 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Mon, 1 Dec 2014 07:56:04 +0100 Subject: [PATCH 52/76] fix GUI options --- src/com/owncloud/android/db/UploadDbHandler.java | 2 +- src/com/owncloud/android/ui/fragment/UploadListFragment.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/com/owncloud/android/db/UploadDbHandler.java b/src/com/owncloud/android/db/UploadDbHandler.java index 83499e10670..eb6fababd50 100644 --- a/src/com/owncloud/android/db/UploadDbHandler.java +++ b/src/com/owncloud/android/db/UploadDbHandler.java @@ -228,7 +228,7 @@ public int updateUploadInternal(Cursor c, UploadStatus status, RemoteOperationRe UploadDbObject uploadObject = UploadDbObject.fromString(uploadObjectString); String path = c.getString(c.getColumnIndex("path")); - Log_OC.e( + Log_OC.d( TAG, "Updating " + path + ": " + uploadObject.getLocalPath() + " with uploadStatus=" + status + "(old:" + uploadObject.getUploadStatus() + ") and result=" + result + "(old:" diff --git a/src/com/owncloud/android/ui/fragment/UploadListFragment.java b/src/com/owncloud/android/ui/fragment/UploadListFragment.java index 8d83a2583a7..f9ba6964058 100755 --- a/src/com/owncloud/android/ui/fragment/UploadListFragment.java +++ b/src/com/owncloud/android/ui/fragment/UploadListFragment.java @@ -137,6 +137,7 @@ private boolean userCanCancelUpload(UploadDbObject uploadFile) { switch (uploadFile.getUploadStatus()) { case UPLOAD_IN_PROGRESS: case UPLOAD_LATER: + case UPLOAD_FAILED_RETRY: return true; default: return false; @@ -152,7 +153,7 @@ private boolean userCanCancelUpload(UploadDbObject uploadFile) { private boolean userCanRetryUpload(UploadDbObject uploadFile) { switch (uploadFile.getUploadStatus()) { case UPLOAD_CANCELLED: - case UPLOAD_FAILED_RETRY: + //case UPLOAD_FAILED_RETRY://automatically retried. no need for user option. case UPLOAD_FAILED_GIVE_UP: return true; default: From 82c7771fc788aa2756f663c07675ff2446441d11 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Mon, 1 Dec 2014 08:17:40 +0100 Subject: [PATCH 53/76] comments and less debug output --- src/com/owncloud/android/db/UploadDbHandler.java | 10 +++++----- src/com/owncloud/android/db/UploadDbObject.java | 2 +- .../android/files/services/FileUploadService.java | 4 +++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/com/owncloud/android/db/UploadDbHandler.java b/src/com/owncloud/android/db/UploadDbHandler.java index eb6fababd50..f94f1d954c5 100644 --- a/src/com/owncloud/android/db/UploadDbHandler.java +++ b/src/com/owncloud/android/db/UploadDbHandler.java @@ -228,12 +228,12 @@ public int updateUploadInternal(Cursor c, UploadStatus status, RemoteOperationRe UploadDbObject uploadObject = UploadDbObject.fromString(uploadObjectString); String path = c.getString(c.getColumnIndex("path")); - Log_OC.d( + Log_OC.v( TAG, - "Updating " + path + ": " + uploadObject.getLocalPath() + " with uploadStatus=" + status + "(old:" - + uploadObject.getUploadStatus() + ") and result=" + result + "(old:" - + uploadObject.getLastResult()); - + "Updating " + path + " with status:" + status + " and result:" + + (result == null ? "null" : result.getCode()) + " (old:" + + uploadObject.toFormattedString() + ")"); + uploadObject.setUploadStatus(status); uploadObject.setLastResult(result); uploadObjectString = uploadObject.toString(); diff --git a/src/com/owncloud/android/db/UploadDbObject.java b/src/com/owncloud/android/db/UploadDbObject.java index a77716a54d2..98b2c16c8a2 100755 --- a/src/com/owncloud/android/db/UploadDbObject.java +++ b/src/com/owncloud/android/db/UploadDbObject.java @@ -307,6 +307,6 @@ public void setUploadTimestamp(long uploadTimestamp) { * For debugging purposes only. */ public String toFormattedString() { - return getLocalPath() + " status:"+getUploadStatus() + " result:" + getLastResult(); + return getLocalPath() + " status:"+getUploadStatus() + " result:" + (getLastResult()==null?"null":getLastResult().getCode()); } } diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index 3f32bff1466..2fb8cf9bfda 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -289,7 +289,9 @@ public void onCreate() { * * Note: We use an IntentService here. It does not provide simultaneous * requests, but instead internally queues them and gets them to this - * onHandleIntent method one after another. + * onHandleIntent method one after another. This makes implementation less + * error-prone but prevents files to be added to list while another upload + * is active. If everything else works fine, fixing this should be a TODO. * * Entry point to add one or several files to the queue of uploads. * From 30f9abb65708d7060fa4078b55f9ab55f30d199a Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Mon, 1 Dec 2014 08:24:26 +0100 Subject: [PATCH 54/76] comment --- .../android/ui/adapter/ExpandableUploadListAdapter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java b/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java index aab23381d24..f4f87673b47 100755 --- a/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java @@ -192,7 +192,7 @@ private View getView(UploadDbObject[] uploadsItems, int position, View convertVi ImageView fileIcon = (ImageView) view.findViewById(R.id.imageView1); fileIcon.setImageResource(R.drawable.file); try { - //?? TODO RemoteID is not set yet. How to get thumbnail? + //TODO Wait for https://github.com/owncloud/android/pull/746 and add thumbnail. Bitmap b = ThumbnailsCacheManager.getBitmapFromDiskCache(uploadObject.getOCFile().getRemoteId()); if (b != null) { fileIcon.setImageBitmap(b); From 0c0ef5402d3432d35d2f482eca9d95316a6a5afb Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Mon, 8 Dec 2014 17:03:46 +0100 Subject: [PATCH 55/76] change upload list layout according to https://github.com/owncloud/android/issues/765#issuecomment-65076426 --- res/drawable/upload_failed.png | Bin 4325 -> 461 bytes res/drawable/upload_finished.png | Bin 4653 -> 322 bytes res/drawable/upload_in_progress.png | Bin 2764 -> 658 bytes res/layout/upload_list_group.xml | 63 ++++++++++++++++++---------- 4 files changed, 41 insertions(+), 22 deletions(-) diff --git a/res/drawable/upload_failed.png b/res/drawable/upload_failed.png index 94a056e5af19fd598d3742900adff8147cb28c28..86d00c586f5d3db0046830ba978ecac6232457eb 100755 GIT binary patch literal 461 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmSQK*5Dp-y;YjHK@;M7UB8wRq zq}PKmW1Q4P8K9tKiEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$QVa}?UY;(F zArY-_r#bo^HV|;Vzg$LHy+PS^5r^Ut-b>mAtPu-zc1)Zr{6!+S-F327$i`%I7f}u6 z%BeGd>VJE3+{k#HqB5iw@PWuV86fn^1pCGlH@7jm4ZVEPLcn``3PJ%3Yzx zQIP(Pbxp9M)XTsRg?fBzZpSq*G}O*J`YNnu!^Y`y4d?%HFYQog>~WNFX!<4eF{MF} zBWd^NYzD;+`Ljkx6)$tPvlVw$m>rOr$SPpnBr<;ni-6Rjn9aL56uwDLN(^%@w07Mi zy~E9EuT1mQK7k(I)DrffBZ~L8O=bu>y4;C{k*6!Bo!e1DWZ|rxdMZoXV^6iFT|7C4NS*{&bgkLp|pQ-t32KlgruDD}X`I;OXk;vd$@?2>|51 Bt!DrL literal 4325 zcmeHK_fwM#vwriEkPte70MbJ52^gA63895vLKh@7kxuARgH)BKpCVWgL=Pwk2uB2@ z1VP~l0xE)qLlKlBQUs(3d9VHp-%oeu-ksUmeV*N)o}Jy9J?~&=!HqtG1^|HDl49xv z0I=UF3_!8{4xx4K&VNvhlLZ;59~NKw4d6k>w#ESPsEG5x7XbilfP;;**`FpD3xrOUuZ}$jZvf$;rvf%PS}- zC@Lx{DJdx{E32resH&=}si~=}tK;!_0)e2Rp`od%dGzQ}B9W-2rKPQ{t)ru(tE;Q0 zr+57LaeaM#0|Ns?Lqj7YBNB;3CX-D}OiWEp&CJZq&CM+=EGQI;rKP2nm6f%%wXLnK zot>S%y}g5jgQKIPlamvbN_BR2c5!iWb#--fb8~lh_wexW^z`)d^78ifrqO7=zP^5b ze*XUcfq{XiPoEA73OaMl+W_hg zQvUa#IHD*XF#y2+?T^E3G|R1ilSg9B++(Scr(+X*qXPj4-{6Q?yq%ddE}n?j#%pS5 zY(-ZB0GGa{sj+k7sqeQ!{U`f`a=z4ccEOJg&9*alT4(Dq|ckU3%%hu}aB3D}IY-hwBliQfCW@jzFuxWW+hGToqfnaZSx$S^mBuH+95naFM5#eyl?MNS*7 z1@ouINQ~~`3Q`ISz)0r%Fs?a@=o)4V1-(#bo>a;s5_*bN=Y(c3SDszu7K!n%18zD0u{PYs30*u<-(gNdYZ!$JG8Z(D3MjbKV%eMvM2m6PHiW&z4y}*HgS;vr#Ljg7* z?(LnG9-X2`sC7(}+7NcAStJ*LNIB2x8~pt^rrSUSATB7<2~6Xfjc1GyRlwXrBwwKccc3f1;7qx>J6~N95D4kdD9itTJ?tdN%tGZ@G z0X|oZ50F|=d8jCfYY%z1y8K|ULT5rJ-9(bGEv95l3!I0DaK+drq;R*2M6jo6k*GgEKzVzdsc>2@TprK zwRu4TUh@#528wy;%;120doX683DyN@Z$s~JBkb=%@t&;!nvZV}wxaORCn*9qk<1Z@jykevRh(z6#NH8X&mKgml4O68*z&L5WL zf(;5wk+bhJPu1J5!z-S^gp)Rm;#_gbnbXgGR^2DWe!v+nC%olamug6cO`DaG22m&z zPj_%K(?zvGI-?d`C_e581bKbPl~7lM)yz#lzg9Fzb+GBa7*pu3Xx#{BIEopWY~>p@ z3yDPZtj*Pl-Dtd%Eepi4kard)eOr((CG#f?8hK9zxCcp)wtO0%M zn~Yt-#Uzc@nFO8jN3a|~BiOH%MIhHAx9nHy_ivzG>sT7zjB1Tw=6uSOv_4{i-!0qU zVEx$T&`Qomj{hXFe>W2P`IB+Bu=a+}I^pg(S(y1zD4;efJe+)zSD{V%6g+r)6u~f4 zFN1=ftq3oED-sv(1}(SqIEwcnou6Ft4xP~8q^TD><-Ua#ki2m60!w)p;a57lQ;e&W zYyygW9ShnIoy;aI0+LxVtFnf-PEhV=sxdrLOq*g(evoX`asRaW<+9?4_6KqqB{R`)qP9WGszs{7P&g-$iEs)oL1dx5InCvn4eC^De~*4MKuv(uFhd zm*uZ5Z&t4tTJK>Zq*H~OfI*FWzAVwt{H;dL=#ax}e*a`;JfT&B9t^zN(jrbW+s~l* zCqz9f5$rX(=MexqbxvAc;)OxubUNCGWnWfXxRajq)z$<%WBdL!aij(rJ(72{KzSkI z)E9dPyn-xeBabS339FzL&U#N}Y#uc1mG~G~kp&s!(nI%=5ei?9W&5Lp=-dPrk3T{Q zOzC25xtUAWZfEhA=#k?<}N$SMdfO=a-aziqY#n+fkT=%ZK5ts?}p_M1{pGYGV zp?;>z`&Xmr-&cfC7!=U|4tl*Q<-S^pVbaBAGUr4;pfrf7H8Ree>%yjS*>7cz_U?bw zxca%Y&55CI8e2WLANN$?&4|&iz*mejc+*%*3d`c~Kjpq4Ks#0N0hOK{qdhW`m9ZF&M+30~VJDGEEiH4t~ihY8jkEMga zjLU&qW>Jvb0#xJiEG`GJtDScB&L)`i0hJ!>+LQ1hI|tr+ZhF4-yPJ+aUOJUDgT*2e z8Moz@+^f&jBofvSr|0NvPG+$6Of`J@r4Fb{rWSz);Ylm)EB3c;)Vs^8~RbH0o9HWh92nfiiax%csoV{0_6q z1~`!CPGwc2SxF{H{0_YJLU$Iu7Gj^2Fix&`iviM6CO4pPKSA|7QDD^9?!`ntmNEsg zE`f6P^MQ;rx~4|2Iy1Dztv~-dSV$4vSKgK82XZgBuBEyv#2TzCg*|O#(N{1% z$K+U@YKuo!IImaH=ZPq#oUzT@m@#7e73a)n4iomTIhKU$&U4~6SOknihFLJ|%r|6P zRx-N%85mU#spe}|(NaxSEv-p>c6;__%~|$oUu!1B_M0VBGh{jRcG8bSSBa+uYs~%@ zJ*IBu)_Wgn+K?JfG8w++zPMt#Haj3SjGwu-0ksX3+6wm(KNh8B%SP&rKr~!ZhGqoi zP=GVH_Y8+cSJXZehNEH#R9J5bJ(-Fa7IrpJmr%kr7< z?W`1`%QYe87WSaPFNR<1bg!(c7s<{wC93Qc8-Nd3)ja09+1j^<0F%yj|FjEmQQP=nr;n}lh8b{`fy zU@z06c|s;0kxETztrVxTobA2i$xN^5j|Npw_^E~AjtSbL$#ygMHptrV_8p;g9U_lzb^<|tO5<6PO@l@&qUWDLF^%ex_Q2~MQc)LJ zO+VMqBns?6`!istLsXO;65VRpy_aMX45gF0v=bEIcIcxMROVM`@gMyvhixBnh@t!m zRAaW^vglqNWNje($oPiJ;rG2txSjQzwzVg$hNogthwC&EUQnZD^g6o9sd_$lw^BN+ zVvM5=z_b*UOFj4g=a+78K=|E73%XVlff*2fZ|V8yDm8PNc1MT0?VE^8z>%oSJ${H{ z2R_<49p|5wTJraBOnCVH?>#W|4XPk5TE`_Jh}Y~pQI2^xtafb}i#|?sX9PE(gv27R z!V@z}-<}VddvqDt!1UcRf3TBI_F(J+kE)BT4kxN)m}66HUv{Kw&>FY4nfVb`^<+kH zirBJZv|%CGvnxkM19>l>!${cM1~o%q!)81Cv@;?oDW+ztDiP?wKb0cjP+cz~dU9lr0#3?*Mbf|Jgf=A6}c=l(c&4j!l;|PX!#|c=kTca3;I`pOhnK zK1|uQ?;khA(Hq^HCU!AZ&dJUE8qSzuaUr18A$n$SW5IR>1||iDJB%u_ZkdPnUg@;8 zY3|VG&Wq6y)S9AviY2jY5l8wIp{-Nj-eX>~^quIxEziCPUB1gy-tf>sDC<+%lq<{B zUWeUYZ}*}xhPO)fqgnVnF%|~~mImtsKepQVo-s4Fzijl;{7-d*_3}>^Y^gq1P6NHd N;OXk;vd$@?2>|(?d#eBd literal 4653 zcmeH~do)yQ+{gEx!MHSt+&aVwp~x-d)(nwQX2vZEXN$4@1OWgE z;7_>P!bLm)1Ro?4DZociP*8{u3WXBpBO)Rq%12C0YzH4S8ZFL8LPA24&(57YrT9oo zOYh<#BO@aVBPTDf@EeSxl9G}#j0&IM_wL=R3Ztf`roInGLqkJT3uZqLZEbBGn1g(D zb#?XhVGMW}!W`mbWDH|sVse;|shOFXg@uKsrR9+$M=%%+7K^pEwzjdcv9+~524i>p z_;CjZ2S-OoCnqNy4o@HuTwGjSU0vPW+}z#WJv=--Jw3g=yu4w2e0tK6co%SBqSu1&-wG`!@|PC!^0!^L`FtNMMXtNN5}Aqjg2LdNEa?# zh~pC)1!Zn%zylY6#PA<^@Z2*^blsfN8 zi$opwj0FIp1^5S^(9d(=C3ll-JxDH*fuwl9m;eCp7ZO1_=w$1v5@&eO=%Buy-fBz% z?>lta-p0x`;SBT3_8(tt6w-gz6nne~=;ncEjDWDDE4XExkNo5Am1Ho*9QP z*VU-!QNsq+b#jfNc16Myw=dK?d^4>W>k>vJgj`4eyKQ=uB#EIm4gIax>i^f-YGlIS zXpuM#WZKo=+9HRqI%Vm3{9OW$_UxI0R<^6=917_nw^2&O4d)^H)CU*QFL@W6Q^wvlLXx>=lmCvb0ym ziP+@)P9QHEqDb_p{G|B+Orn)0#SA1F%tVKU&C8v!QYJm;^L%xrW4j^e_b3c6&K5F3A4 z`>T^twh2HaKB$&x{<p2#e5%nJ}?9oq>>U%j8_+=rLfhIBjXmu|$k$@wAoSKC?GVTq zhpaeCF%mudMykzDEIHi=}nJ`H4jv`UJ17KozhJ2J*a=GHX2`ZtPs zL#_orC2Jyv&b^aj#aoZHA7#5AHFtly^cP;>eS*X;R(ia-k{YIVObi~h4zvY!Jh$ew zr(_B_W zri>SX{n?Nk?*2Mrq8;r+~N(9n3zpC|EnaDpujIE`6XXQn8=% zsMGUa9NXLO*lTdK3(FfHVav($4;TrVJbY-c_yqW-G(Q!JD0nn}`hHVe4(kfA|VXgb9v$OSrXA_ro39YAEX!W<5^J`^@;L%n#n4DWS z%uDWCGjB?-i>+0y>5~`q1^T0u=X#7liyGC~)NruPl5*T71PVrcrgpk@pD z;+D*HrOmq=N5ux$$RBR+W0oXmS^Wl%_K8X{eDx!s=M94Eh+!tA^zCV89O)`fihWE?BOqZavp2473%bVF3;~06KSuLVeN0|~`vm2YTWxF?W$JctxnKtI3COYZ5 zNZ}1@&G{a#s&7kLo#V4_lyxpHGj8?Hv>e#n6|kET-+HTKxzDFO%juUglKEW+a{@0Q zxe~s_z(Kl|23J3+4+`DgdJfALx#6xt{nlucwxu;3#_cbsO&YYWuyDE~BFjq#)cnfr z$K0GBXQE1*<=#d>ccv-EwbBzFJE>nSqY#Y#u#A@e4Ff~f;f3)$ST$nQOQ5P<(2UW~ zl%@$Sg|!&8Zqn}VAr#RhZ2|TZo0PYUf;~k<%XgF=wbGXW@tIe7C`EU!c_aU!%-KDeVKeQKZ9l4{MnCeIWoiNKT9puEkf?^oie_70_?(L zoU0uA3@4uTB5=D&pz6CR)n}nF^THS2sq7W$b&7M=YQV`px42ORC` z4`rBi<)Kp!K8b*|MnqPub_1AsLTex=#aQhI35raMyA-yrOpOYh*_&z<&WoC>j&s|x zlNgcTV{{wKUveG?YrOq%ZRR|?#E$bQ1tP{Wc5PD*)Jo6XT=mnFXX&hQ?xLNOT-MpB z5s{cBJy2jw%#~k2^v?$_8>)U7u3|3jY8xv+4AuSAHFo?__a@r^cV4aD7ir;RDJbSU z&E++EAx$M+jxzZeV8joPZyC9}y?`I{rLcpc! zf{ra+v4G@w@=7~QHnjkj<-`6fk?G^SeIz~eZpt`S6q}+v{7y5NKS^&#y6_)_aYq9~ zsb=n+t+`cB+E>+TXU6*ZR=Lak*QDl2I*d|dovyW%BQ?X*KK`sKS^Lb@t?NceYfQ)iJkqF+M{164li7Rp^@i+cBhC@B_(OViJE;E zyiC{4*|KJl+sP>w+<;&W?8uvcR*Vvz8Fvln=%!v?pG6H8=>C6JY%`r1a%Fvgu2w>g z16gA$HYbqH1~c{d6)w==eH=M#zlB>CDJreRkBXV~!|m>*Oor~pPA|k1msH;D|MLYu zSsxalfSne*#mhb8X0CGRF||dv={KSkka8si-yn)EEHn7);~8%u$)tai=MOL!HX+Pm zYS?xSU>%8oJ;7gxbs}Z46Tbl1lN5*j5>Z+1*a=$?vyRf|zf>CWnXtWZs{c~TPOoyw zfc*tQ3)hd6@-9Eha|gW7;s}V)b1G1W9g3am_j1tbvaXXbc0=HoB%FCtZkqPKk$nR0(r~+5$a6cYE84kU28fB;EoXRcYFW<; z4g>-(P}cB-5O<^_Kk{6jku5x8Z4E7PsXVbxxo@U@ zAMK>h8%L>@VxR&A>SFZ7vFh2avkmG1vu5&o8|S3&AW%!TDf(u*U1dpaD|)C}H@nTw zQ3W{ff}8huwrYEW0x1oH@^!ShK_zUA5u52UP1pm){}hmndq{G<`l;2T85C$aqbJ+U zUHBx`c6~d!>P{f~MJ`=WQj8T^#N5w_7xSKlve!GeTZop8e?(Xl2g&!*3 ze_m z+WmzM)W5=HLe`AqadK3OhxW41l7rU3;dKK33cOqgwrlLzR{DPd|Ac@LLTq5!W*{Mi zR^9#7l*u-kmQ(VgHjn8p_Xi3em6!9fuVs1JZ>0)&+DB2Et?_%iNd#^+1P{x9;cGqo zwSRHAiGV0?QpXeElk(=J&ezS(`cO=yj#WIAw_;0$t;k!ki2^>Oz9n14nHXccGtn(P z$v=~Z_DK=?URjXgI6e#j{spJ%(l9Gcj^X7N!k<@nUKQ|1o?|zz;YT0fHf~^~=A*4I zGV+ts-G<*r8~1wcx@^e}NeE#V&f>iM5GZm02NC2a=Z|U86#_tje28lG_2dIE5#!(qfk58PyK?vxO0xG$1OWD}3Wg9e*%ZY$B9gGa zNgi>8q?C|^FhEL5if&|VRN~nXQkZUBLimlflX3unu#KCuC-&E`Uwr+)?cWOg-xUZk zl-=Yn?nyf3;sFp627*NPfgw=Q{Rd!T;u4Yv4@pVO$jZqpC?b`VRgS7@XlfnP*3mUI zGDex2pR};T*w|w2>>V7Pobj&iUcP<-fv3-eo;?>v2#<&)M#aR&krEP?7Vl$AI_CdlYS;$Z3H83EKQaYIb8gh zSWL)aI+Z)kQ(w7Yn~wFB|3NDIx;L?zB{G{xp3R?IulfGfS_1Tsy_cH$fvNX7^JB^4 z1;;1pVZ@B2rtkK4U)wv6YKYQef4B?;-AynFcV)#ip{>(*8CK4TXP8ecR)*cL*AkHg zoSIlGu9v+2rhEjG;DZt!-24zuTm5@rP5$hd%G}cSf$S`GOF4?twq9IX^l~zC@*RBved(?6GbuI z!Bw-(h9#Be8o>iHs=EH%e$J^r%tGVVk;iZYw@s~X2VLye9u*1G#Z6{boKe)XW1&XI z?!ExkuO5`)1{_nNvj`q%f8P81^1TA4Q~2!KlG!A0w}Jtvq`m6e!fCe-kZQWCv}>))U1{Zr>Zh|_lS?@JLlKgE;pMk7|J zK1acb_$6Yh=lJ0BcOo<~Ltgnt`0>SHRK|3#uPMN_eMDbcZD=05O@7vw_QTDjr{gV9 z>6?KGk7Es7RP_={mTE+(%F|=@7Jq}Wy&1CV>h!k!dkNA*?S_O(v{&eL0}rs*4aD(x zbmGPMi39lU;+tX-%Y;=0N`2Tw@y#_CYPO+T-J-bh1U8f$TA zV%9WR3guWHt!tq0KyM%xW=n&BtW+p zr3>9=Z%-6*DOysick+gGM6;0W09F(4xu21DqA1JpIa|fz0vc?Rkpzd zWnGGHD>KlNJm<_E_PKoZdq!NjKgdraEn)k_xsc&GCaz4XPTi*#*HD{Ukp}XEr)}P% zsj4calB@abVA`fP!f>N1UJL#K%^jq1rc$pKf=)>C;*((tNODOv=meZ+;9o%Yf}N^R z4#!w!73{QyEeImp03O1{o7FbaAFVv&jvXYTGtK_?;Gr*|O8EpxsYz8p0G zv~1BeroDjSYXOr=Mxi317|O9_bB)K&Os z52e8}S3V1EPNFkqNT;X$*c2dM@vr0{`eDwhtC|C6Yoz2#UB)VrcBup`^8wO!k||IBjmk6x!iv%l5b1~{rZhB@doP(*0mr!1U_~il+DlJ zbx3hL2OZ#~j|t$X{s3(i(@SY^0SxdrGk(2~C5S)-j@k2_3{R2Y3;0^6OaM_3iUmeT z7B=wtO8lqpl)=+38 z620VU$4i2A)u7gly1~f8cn&x^%pIZGpPlAwJ0A6=%u7?6COs0WSA<(v8L3||F6w)% zHPTG!Kin;u_;#Rvtnl#_*zb@eHT}lruiB{t!v-`RBW+T4k-=R>?dU-U>Fp?{G7#FB ztsR){H19n=h)K>cFmz8S%Vj=LK(I%Juf*b&&zci_BVNSVnzYx1hb*;W&NqX7(c?Nr zWo`E}wPjVKE*IrZXhSOSjOY0N*+Yx#B&mc)&G`l?_mMl(W+SrZonmCCXk_q7M%Oa^ z_LKDLdlfTYFpJ^ywV^f63cj!y!PRPNzx~-SNK8*l{+l>MHtc^d%tapt>W17q+BU5i u?`)vb@XRhhjh18_*AhTghWvA%-c@R + android:background="@drawable/abs__ab_bottom_solid_light_holo" + android:orientation="horizontal" + android:layout_height="40dp"> - - + - - + + - \ No newline at end of file + + + + + + + From fd6d57fa75b3b6544b690e880a9815716655a053 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Wed, 10 Dec 2014 08:18:12 +0100 Subject: [PATCH 56/76] removed unnecessary items from upload_list_item --- res/layout/upload_list_item.xml | 61 +------------------ .../adapter/ExpandableUploadListAdapter.java | 40 +----------- 2 files changed, 3 insertions(+), 98 deletions(-) diff --git a/res/layout/upload_list_item.xml b/res/layout/upload_list_item.xml index f9c3beb2305..1e9291eaf27 100755 --- a/res/layout/upload_list_item.xml +++ b/res/layout/upload_list_item.xml @@ -12,14 +12,6 @@ android:focusable="false" android:focusableInTouchMode="false"> - - - - + @@ -70,7 +53,6 @@ android:id="@+id/upload_local_path" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="TextView" android:layout_weight=".5" android:textColor="@color/list_item_lastmod_and_filesize_text" android:textSize="12dip"/> @@ -80,7 +62,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="right" - android:text="TextView" android:textColor="@color/list_item_lastmod_and_filesize_text" android:layout_weight=".5" android:textSize="12dip"/> @@ -98,7 +79,6 @@ android:id="@+id/upload_status" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="TextView" android:layout_weight=".5" android:textColor="@color/list_item_lastmod_and_filesize_text" android:textSize="12dip"/> @@ -108,7 +88,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="right" - android:text="TextView" android:textColor="@color/list_item_lastmod_and_filesize_text" android:layout_weight=".5" android:textSize="12dip"/> @@ -117,42 +96,4 @@ - - - - - - - - - -
diff --git a/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java b/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java index f4f87673b47..7b1b65edc97 100755 --- a/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java @@ -200,46 +200,10 @@ private View getView(UploadDbObject[] uploadsItems, int position, View convertVi } catch (NullPointerException e) { } - TextView uploadDateV = (TextView) view.findViewById(R.id.upload_date); + TextView uploadDate = (TextView) view.findViewById(R.id.upload_date); CharSequence dateString = DisplayUtils.getRelativeDateTimeString(mActivity, uploadObject.getUploadTime() .getTimeInMillis(), DateUtils.SECOND_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, 0); - uploadDateV.setText(dateString); - ImageView checkBoxV = (ImageView) view.findViewById(R.id.custom_checkbox); - // if (!file.isDirectory()) { - // fileSizeV.setVisibility(View.VISIBLE); - // fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.length())); - // lastModV.setVisibility(View.VISIBLE); - // lastModV.setText(DisplayUtils.unixTimeToHumanReadable(file.lastModified())); - // ListView parentList = (ListView)parent; - // if (parentList.getChoiceMode() == ListView.CHOICE_MODE_NONE) { - // checkBoxV.setVisibility(View.GONE); - // } else { - // if (parentList.isItemChecked(position)) { - // checkBoxV.setImageResource(android.R.drawable.checkbox_on_background); - // } else { - // checkBoxV.setImageResource(android.R.drawable.checkbox_off_background); - // } - // checkBoxV.setVisibility(View.VISIBLE); - // } - // - // } else { - checkBoxV.setVisibility(View.GONE); - // } - - view.findViewById(R.id.imageView2).setVisibility(View.INVISIBLE); // not - // GONE; - // the - // alignment - // changes; - // ugly - // way - // to - // keep - // it - view.findViewById(R.id.imageView3).setVisibility(View.GONE); - - view.findViewById(R.id.sharedIcon).setVisibility(View.GONE); - view.findViewById(R.id.sharedWithMeIcon).setVisibility(View.GONE); + uploadDate.setText(dateString); } return view; From 11efd022f9073e586d70ab3eaef899f14d29dfdb Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Wed, 10 Dec 2014 13:44:20 +0100 Subject: [PATCH 57/76] use settings layout for upload list (remove group header background, add underline, remove group icons) --- res/layout/upload_list_group.xml | 23 ++++++++++++++----- res/layout/upload_list_item.xml | 10 +------- res/layout/upload_list_layout.xml | 4 ++++ .../adapter/ExpandableUploadListAdapter.java | 4 ++-- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/res/layout/upload_list_group.xml b/res/layout/upload_list_group.xml index e7eae69c09c..8f5e95c15ae 100755 --- a/res/layout/upload_list_group.xml +++ b/res/layout/upload_list_group.xml @@ -1,14 +1,13 @@ + - + + + diff --git a/res/layout/upload_list_item.xml b/res/layout/upload_list_item.xml index 1e9291eaf27..59c934e4611 100755 --- a/res/layout/upload_list_item.xml +++ b/res/layout/upload_list_item.xml @@ -2,7 +2,6 @@ @@ -16,8 +15,7 @@ android:id="@+id/imageView1" android:layout_width="@dimen/file_icon_size" android:layout_height="@dimen/file_icon_size" - android:layout_gravity="center_vertical" - android:layout_marginLeft="9dp" + android:layout_gravity="center" android:src="@drawable/ic_menu_archive" /> @@ -34,8 +32,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" - android:layout_marginLeft="4dp" - android:layout_marginRight="4dp" android:ellipsize="middle" android:singleLine="true" android:textColor="#303030" @@ -45,8 +41,6 @@ \ No newline at end of file diff --git a/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java b/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java index 7b1b65edc97..717c92edb94 100755 --- a/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java @@ -294,8 +294,8 @@ public View getGroupView(int groupPosition, boolean isExpanded, View convertView } TextView tv = (TextView) convertView.findViewById(R.id.uploadListGroupName); tv.setText(group.getGroupName()); - ImageView icon = (ImageView) convertView.findViewById(R.id.uploadListGroupIcon); - icon.setImageResource(group.getGroupIcon()); +// ImageView icon = (ImageView) convertView.findViewById(R.id.uploadListGroupIcon); +// icon.setImageResource(group.getGroupIcon()); return convertView; } From 61de793925bbf7da6087b7683208826b14a41ed8 Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Wed, 10 Dec 2014 14:33:47 +0100 Subject: [PATCH 58/76] hide empty upload groups --- res/values/strings.xml | 1 + .../adapter/ExpandableUploadListAdapter.java | 29 +++++++++++++++---- .../ui/fragment/UploadListFragment.java | 3 +- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index e777fe0c8da..a29f0326832 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -66,6 +66,7 @@ Nothing in here. Upload something! Loading... There are no files in this folder. + No uploads available. folder folders file diff --git a/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java b/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java index 717c92edb94..59c106d8e5b 100755 --- a/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/ExpandableUploadListAdapter.java @@ -244,7 +244,7 @@ public void update(Observable arg0, Object arg1) { @Override public Object getChild(int groupPosition, int childPosition) { - return mUploadGroups[groupPosition].items[childPosition]; + return mUploadGroups[(int) getGroupId(groupPosition)].items[childPosition]; } @Override @@ -255,27 +255,44 @@ public long getChildId(int groupPosition, int childPosition) { @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { - return getView(mUploadGroups[groupPosition].items, childPosition, convertView, parent); + return getView(mUploadGroups[(int) getGroupId(groupPosition)].items, childPosition, convertView, parent); } @Override public int getChildrenCount(int groupPosition) { - return mUploadGroups[groupPosition].items.length; + return mUploadGroups[(int) getGroupId(groupPosition)].items.length; } @Override public Object getGroup(int groupPosition) { - return mUploadGroups[groupPosition]; + return mUploadGroups[(int) getGroupId(groupPosition)]; } @Override public int getGroupCount() { - return mUploadGroups.length; + int size = 0; + for (UploadGroup uploadGroup : mUploadGroups) { + if(uploadGroup.items.length > 0) { + size++; + } + } + return size; } + /** + * Returns the groupId (that is, index in mUploadGroups) for group at position groupPosition (0-based). + * Could probably be done more intuitive but this tested methods works as intended. + */ @Override public long getGroupId(int groupPosition) { - return groupPosition; + int id = -1; + for (int i = 0; i <= groupPosition; ) { + id++; + if(mUploadGroups[id].items.length > 0){ + i++; + } + } + return id; } @Override diff --git a/src/com/owncloud/android/ui/fragment/UploadListFragment.java b/src/com/owncloud/android/ui/fragment/UploadListFragment.java index f9ba6964058..45c6201ce33 100755 --- a/src/com/owncloud/android/ui/fragment/UploadListFragment.java +++ b/src/com/owncloud/android/ui/fragment/UploadListFragment.java @@ -32,6 +32,7 @@ import android.widget.ExpandableListView; import android.widget.ExpandableListView.ExpandableListContextMenuInfo; import android.widget.ListView; +import android.widget.TextView; import com.owncloud.android.R; import com.owncloud.android.db.UploadDbObject; @@ -61,10 +62,10 @@ public class UploadListFragment extends ExpandableListFragment { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = super.onCreateView(inflater, container, savedInstanceState); getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); + setMessageForEmptyList(getString(R.string.upload_list_empty)); return v; } - @Override public void onAttach(Activity activity) { super.onAttach(activity); From 3fdb78d0c96428105ee7e110c2cc9915c85cd28b Mon Sep 17 00:00:00 2001 From: Luke Owncloud Date: Fri, 12 Dec 2014 10:59:44 +0100 Subject: [PATCH 59/76] added right button for items in upload list (for cancel, retry, and remove) --- res/drawable/btn_small_round.xml | 46 +++++++++++++++++++ res/layout/upload_list_item.xml | 22 +++++++++ .../ui/activity/UploadListActivity.java | 27 ++++++++++- .../adapter/ExpandableUploadListAdapter.java | 39 +++++++++++++++- .../ui/fragment/UploadListFragment.java | 36 ++------------- .../owncloud/android/utils/UploadUtils.java | 37 +++++++++++++++ 6 files changed, 174 insertions(+), 33 deletions(-) create mode 100755 res/drawable/btn_small_round.xml diff --git a/res/drawable/btn_small_round.xml b/res/drawable/btn_small_round.xml new file mode 100755 index 00000000000..6638ed9e25d --- /dev/null +++ b/res/drawable/btn_small_round.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/upload_list_item.xml b/res/layout/upload_list_item.xml index 59c934e4611..6f745068b5a 100755 --- a/res/layout/upload_list_item.xml +++ b/res/layout/upload_list_item.xml @@ -88,4 +88,26 @@ + + + +