From 6914dad6b9bc51c2988acc0332bff43fddf78656 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Mon, 25 Mar 2024 09:29:36 +0100 Subject: [PATCH 01/16] chore: remove WorkerUtils.kts --- .../foundation/e/drive/utils/WorkerUtils.kt | 56 ------------------- 1 file changed, 56 deletions(-) delete mode 100644 app/src/main/java/foundation/e/drive/utils/WorkerUtils.kt diff --git a/app/src/main/java/foundation/e/drive/utils/WorkerUtils.kt b/app/src/main/java/foundation/e/drive/utils/WorkerUtils.kt deleted file mode 100644 index 8dcd6c59..00000000 --- a/app/src/main/java/foundation/e/drive/utils/WorkerUtils.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright © MURENA SAS 2024. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Public License v3.0 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/gpl.html - */ - -package foundation.e.drive.utils - -import android.content.Context -import androidx.work.OneTimeWorkRequest -import androidx.work.WorkManager -import foundation.e.drive.models.SyncedFolder -import foundation.e.drive.work.WorkRequestFactory - -object WorkerUtils { - fun registerSetupWorkers(context: Context) { - val rootFolderSetupWorkers = generateRootFolderSetupWorkers(context) - if (rootFolderSetupWorkers.isEmpty()) { - return - } - - val getUserInfoRequest = WorkRequestFactory.getOneTimeWorkRequest( - WorkRequestFactory.WorkType.ONE_TIME_USER_INFO, - null - ) - val finishSetupRequest = WorkRequestFactory.getOneTimeWorkRequest( - WorkRequestFactory.WorkType.ONE_TIME_FINISH_SETUP, - null - ) - - var workContinuation = WorkManager.getInstance(context) - .beginWith(getUserInfoRequest) - - rootFolderSetupWorkers.forEach { - workContinuation = workContinuation.then(it) - } - - workContinuation - .then(finishSetupRequest) - .enqueue() - } - - private fun generateRootFolderSetupWorkers(context: Context): List { - val rootSyncedFolderList: List = - RootSyncedFolderProvider.getSyncedFolderRoots(context) - - return rootSyncedFolderList.map { - WorkRequestFactory.getOneTimeWorkRequest( - WorkRequestFactory.WorkType.ONE_TIME_ROOT_FOLDER_SETUP, - it - ) - } - } -} -- GitLab From 1f289e8fad3ce6376571cd9bf07bcc6ba2a9aa3f Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Mon, 25 Mar 2024 09:33:49 +0100 Subject: [PATCH 02/16] refactor(WorkLauncher.kt): include method to register Remote Folders creation worker --- .../foundation/e/drive/work/WorkLauncher.kt | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/foundation/e/drive/work/WorkLauncher.kt b/app/src/main/java/foundation/e/drive/work/WorkLauncher.kt index 77e4133a..19897d89 100644 --- a/app/src/main/java/foundation/e/drive/work/WorkLauncher.kt +++ b/app/src/main/java/foundation/e/drive/work/WorkLauncher.kt @@ -1,5 +1,5 @@ /* - * Copyright © MURENA SAS 2023. + * Copyright © MURENA SAS 2023-2024. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at @@ -10,11 +10,14 @@ package foundation.e.drive.work import android.content.Context import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingWorkPolicy +import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager import foundation.e.drive.account.AccountUserInfoWorker +import foundation.e.drive.models.SyncedFolder import foundation.e.drive.periodicScan.FullScanWorker import foundation.e.drive.periodicScan.PeriodicScanWorker import foundation.e.drive.synchronization.SyncWorker +import foundation.e.drive.utils.RootSyncedFolderProvider import foundation.e.drive.work.WorkRequestFactory.WorkType /** @@ -79,4 +82,46 @@ class WorkLauncher private constructor(context: Context) { val request = WorkRequestFactory.getOneTimeWorkRequest(WorkType.ONE_TIME_SYNC, null) workManager.enqueueUniqueWork(SyncWorker.UNIQUE_WORK_NAME, ExistingWorkPolicy.KEEP, request) } + + + fun enqueueSetupWorkers(context: Context): Boolean { + val rootFolderSetupWorkers = generateRootFolderSetupWorkers(context) + if (rootFolderSetupWorkers.isEmpty()) { + return false + } + + val getUserInfoRequest = WorkRequestFactory.getOneTimeWorkRequest( + WorkType.ONE_TIME_USER_INFO, + null + ) + val finishSetupRequest = WorkRequestFactory.getOneTimeWorkRequest( + WorkType.ONE_TIME_FINISH_SETUP, + null + ) + + var workContinuation = WorkManager.getInstance(context) + .beginWith(getUserInfoRequest) + + rootFolderSetupWorkers.forEach { + workContinuation = workContinuation.then(it) + } + + workContinuation + .then(finishSetupRequest) + .enqueue() + + return true + } + + private fun generateRootFolderSetupWorkers(context: Context): List { + val rootSyncedFolderList: List = + RootSyncedFolderProvider.getSyncedFolderRoots(context) + + return rootSyncedFolderList.map { + WorkRequestFactory.getOneTimeWorkRequest( + WorkType.ONE_TIME_ROOT_FOLDER_SETUP, + it + ) + } + } } -- GitLab From 32149d21933fd03fe0e2b924c72d081d9191ca0a Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Mon, 25 Mar 2024 10:58:15 +0100 Subject: [PATCH 03/16] refactor(AccoutnAddedReceiver.kt): remove method to trigger remote folders worker - remove useless import - update licence years - use WorkLauncher --- .../account/receivers/AccountAddedReceiver.kt | 47 +++---------------- 1 file changed, 6 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/account/receivers/AccountAddedReceiver.kt b/app/src/main/java/foundation/e/drive/account/receivers/AccountAddedReceiver.kt index b0c784e3..b2c51e5b 100644 --- a/app/src/main/java/foundation/e/drive/account/receivers/AccountAddedReceiver.kt +++ b/app/src/main/java/foundation/e/drive/account/receivers/AccountAddedReceiver.kt @@ -1,5 +1,5 @@ /* - * Copyright © MURENA SAS 2023. + * Copyright © MURENA SAS 2023-2024. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at @@ -14,11 +14,8 @@ import android.content.Intent import android.content.SharedPreferences import foundation.e.drive.R import foundation.e.drive.account.AccountUtils -import foundation.e.drive.models.SyncedFolder import foundation.e.drive.utils.AppConstants import foundation.e.drive.utils.DavClientProvider -import foundation.e.drive.utils.RootSyncedFolderProvider -import foundation.e.drive.work.WorkRequestFactory.* import foundation.e.drive.work.WorkLauncher import timber.log.Timber @@ -47,10 +44,10 @@ class AccountAddedReceiver() : BroadcastReceiver() { .putString(AccountManager.KEY_ACCOUNT_NAME, accountName) .apply() - if (registerSetupWorkers(context)) { + val workLauncher = WorkLauncher.getInstance(context) + if (workLauncher.enqueueSetupWorkers(context)) { DavClientProvider.getInstance().cleanUp() - - WorkLauncher.getInstance(context).enqueuePeriodicUserInfoFetching() + workLauncher.enqueuePeriodicUserInfoFetching() } } @@ -67,7 +64,7 @@ class AccountAddedReceiver() : BroadcastReceiver() { prefs: SharedPreferences, context: Context ): Boolean { - if (AccountUtils.isSetupAlreadyDone(prefs)) { + if (isSetupAlreadyDone(prefs)) { return false } @@ -98,36 +95,4 @@ class AccountAddedReceiver() : BroadcastReceiver() { private fun isExistingAccount(accountName: String, context: Context): Boolean { return AccountUtils.getAccount(accountName, context) != null } - - private fun registerSetupWorkers(context: Context): Boolean { - val rootFolderSetupWorkers = generateRootFolderSetupWorkers(context) ?: return false - val getUserInfoRequest = getOneTimeWorkRequest(WorkType.ONE_TIME_USER_INFO, null) - val finishSetupRequest = getOneTimeWorkRequest(WorkType.ONE_TIME_FINISH_SETUP, null) - val workManager = WorkManager.getInstance(context) - - workManager.beginWith(getUserInfoRequest) - .then(rootFolderSetupWorkers) - .then(finishSetupRequest) - .enqueue() - return true - } - - private fun generateRootFolderSetupWorkers(context: Context): MutableList? { - val rootSyncedFolderList: List = - RootSyncedFolderProvider.getSyncedFolderRoots(context) - - if (rootSyncedFolderList.isEmpty()) { - return null - } - - val workRequests: MutableList = ArrayList() - for (folder in rootSyncedFolderList) { - val rootFolderSetupWorkRequest = getOneTimeWorkRequest( - WorkType.ONE_TIME_ROOT_FOLDER_SETUP, - folder - ) - workRequests.add(rootFolderSetupWorkRequest) - } - return workRequests - } -} \ No newline at end of file +} -- GitLab From 457d85ce73fde9f011c3b9a61c9c0c3ae84aba85 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Mon, 25 Mar 2024 12:23:58 +0100 Subject: [PATCH 04/16] fix (BootCompleteReceiver): fix error introduced by merging main branch into epic branch also rename a method in AccountUtils --- .../e/drive/account/AccountUtils.kt | 17 +++- .../receivers/BootCompletedReceiver.java | 88 ++++++++++--------- 2 files changed, 62 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/account/AccountUtils.kt b/app/src/main/java/foundation/e/drive/account/AccountUtils.kt index 7d73f23a..583add0d 100644 --- a/app/src/main/java/foundation/e/drive/account/AccountUtils.kt +++ b/app/src/main/java/foundation/e/drive/account/AccountUtils.kt @@ -16,12 +16,15 @@ package foundation.e.drive.account + import android.accounts.Account import android.accounts.AccountManager import android.content.Context -import foundation.e.drive.R -import foundation.e.drive.utils.AppConstants.SHARED_PREFERENCE_NAME +import android.content.SharedPreferences +import foundation.e.drive.utils.AppConstants import foundation.e.drive.utils.AppConstants.ACCOUNT_DATA_GROUPS +import foundation.e.drive.utils.AppConstants.SHARED_PREFERENCE_NAME + object AccountUtils { @@ -63,9 +66,17 @@ object AccountUtils { if (accountName.isEmpty()) return null val accountManager = AccountManager.get(context) - val accountType = context.getString(R.string.eelo_account_type) + val accountType = context.getString(foundation.e.drive.R.string.eelo_account_type) return accountManager.getAccountsByType(accountType) .firstOrNull { account -> account.name == accountName } } + + @JvmStatic + fun isNoAccountPresent(context: Context): Boolean { + val accountManager = AccountManager.get(context.applicationContext) + val accountList = + accountManager.getAccountsByType(context.getString(foundation.e.drive.R.string.eelo_account_type)) + return accountList.isEmpty() + } } diff --git a/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java b/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java index 16655f25..1201984e 100644 --- a/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java +++ b/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java @@ -1,5 +1,5 @@ /* - * Copyright © MURENA SAS 2022-2023. + * Copyright © MURENA SAS 2022-2024. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at @@ -24,6 +24,7 @@ import foundation.e.drive.synchronization.SyncProxy; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.work.WorkLauncher; +import foundation.e.drive.account.AccountUtils; import timber.log.Timber; /** @@ -42,38 +43,40 @@ public class BootCompletedReceiver extends BroadcastReceiver { public void onReceive(@NonNull Context context, @NonNull Intent intent) { final String action = intent.getAction(); Timber.v("onReceive(...)"); - final SharedPreferences prefs = context.getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); - - if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { - final String currentDateProp = CommonUtils.getProp(DATE_SYSTEM_PROPERTY); - if (isOsUpdated(prefs, currentDateProp)) { // App is persistent so can only be updated (by replacement) on OS update - handleOsUpdate(context); - } - changeSetupCompletedPreferenceKey(prefs); + if (!Intent.ACTION_BOOT_COMPLETED.equals(action) + || AccountUtils.isNoAccountPresent(context)) { + return; + } - if (!isSetupCompleted(prefs)) return; + final SharedPreferences prefs = context.getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); - final int lastSavedVersionCode = prefs.getInt(PREF_VERSION_CODE, VERSION_CODE_FOR_UPDATE_1); - if (isEdriveUpdated(lastSavedVersionCode)) { - onEdriveUpdate(lastSavedVersionCode, context); - prefs.edit().putInt(PREF_VERSION_CODE, BuildConfig.VERSION_CODE).apply(); - } + migrateSetupCompletedPrefsKey(prefs); + if (!isSetupCompleted(prefs)){ + WorkLauncher.getInstance(context).enqueueSetupWorkers(context); + return; //set up workers are scheduled. Just wait that they run + } - if (isAccountPresent) { - WorkerUtils.INSTANCE.registerSetupWorkers(context.getApplicationContext()); - } + /* + * App was persistent so can only be updated (by replacement) on OS update + * note: still needs the below check and call when migrating for persistent eDrive to not persistent eDrive. + * But on "not persistent eDrive" update "to not persistent eDrive" update+1, this won't be required again + */ + final String currentDateProp = CommonUtils.getProp(DATE_SYSTEM_PROPERTY); + if (isOsUpdated(prefs, currentDateProp)) { + handleOsUpdate(context); + } - SyncProxy.INSTANCE.startListeningFiles((Application) context.getApplicationContext()); + final int lastSavedVersionCode = prefs.getInt(PREF_VERSION_CODE, VERSION_CODE_FOR_UPDATE_1); + if (isEdriveUpdated(lastSavedVersionCode)) { + onEdriveUpdate(lastSavedVersionCode, context); + prefs.edit().putInt(PREF_VERSION_CODE, BuildConfig.VERSION_CODE).apply(); } - } - private void forceDBUpdate(@NonNull Context context) { - final DbHelper dbHelper = new DbHelper(context); - dbHelper.getWritableDatabase().close(); + SyncProxy.INSTANCE.startListeningFiles((Application) context.getApplicationContext()); } - private void changeSetupCompletedPreferenceKey(@NonNull SharedPreferences prefs) { + private void migrateSetupCompletedPrefsKey(@NonNull SharedPreferences prefs) { if (prefs.getBoolean(OLD_SETUP_COMPLETED_PREF_KEY, false)) { Timber.i("Update setup complete preferences"); prefs @@ -81,28 +84,15 @@ public class BootCompletedReceiver extends BroadcastReceiver { .remove(OLD_SETUP_COMPLETED_PREF_KEY) .putBoolean(AppConstants.SETUP_COMPLETED, true) .apply(); - } } - /** - * Force reinitialization, upgrade of DB in case of OS update - * @param context Context used to start InitializationService - */ - private void handleOsUpdate(@NonNull Context context) { - forceDBUpdate(context); - } - /** @noinspection SpellCheckingInspection*/ private boolean isEdriveUpdated(int oldVersionCode) { Timber.d("isEdriveUpdated (%s > %s) ?", BuildConfig.VERSION_CODE, oldVersionCode); return BuildConfig.VERSION_CODE > oldVersionCode; } - private boolean isSetupCompleted(@NonNull SharedPreferences prefs) { - return prefs.getBoolean(AppConstants.SETUP_COMPLETED, false); - } - private void onEdriveUpdate(int oldVersionCode, @NonNull Context context) { if (oldVersionCode <= VERSION_CODE_FOR_UPDATE_1) { try { @@ -117,8 +107,9 @@ public class BootCompletedReceiver extends BroadcastReceiver { final WorkManager workManager= WorkManager.getInstance(context); workManager.cancelAllWork(); - WorkLauncher.getInstance(context).enqueuePeriodicFullScan(); - WorkLauncher.getInstance(context).enqueuePeriodicUserInfoFetching(); + final WorkLauncher workLauncher = WorkLauncher.getInstance(context); + workLauncher.enqueuePeriodicFullScan(); + workLauncher.enqueuePeriodicUserInfoFetching(); } } @@ -126,4 +117,21 @@ public class BootCompletedReceiver extends BroadcastReceiver { final String lastKnownDateProp = prefs.getString(DATE_SYSTEM_PROPERTY, ""); return !currentDateProp.equals(lastKnownDateProp); } -} \ No newline at end of file + + /** + * Force reinitialization, upgrade of DB in case of OS update + * @param context Context used to start InitializationService + */ + private void handleOsUpdate(@NonNull Context context) { + forceDBUpdate(context); + } + + private void forceDBUpdate(@NonNull Context context) { + final DbHelper dbHelper = new DbHelper(context); + dbHelper.getWritableDatabase().close(); + } + + private boolean isSetupCompleted(@NonNull SharedPreferences prefs) { + return prefs.getBoolean(AppConstants.SETUP_COMPLETED, false); + } +} -- GitLab From 8075e65875add33bedafbe8d4f825c8f6f3ab4a9 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Mon, 25 Mar 2024 12:28:36 +0100 Subject: [PATCH 05/16] fix (upload): remove timestamp conversion (/1000) --- .../tasks/UploadFileOperation.java | 13 ++------- .../operations/UploadFileOperationTest.java | 28 +------------------ 2 files changed, 3 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java b/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java index d3fbd198..697813bf 100644 --- a/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java @@ -285,7 +285,7 @@ public class UploadFileOperation extends RemoteOperation { @VisibleForTesting() @NonNull public RemoteOperationResult uploadChunkedFile(@NonNull final File file, @NonNull final OwnCloudClient client) { - final long timeStamp = file.lastModified() / 1000; + final long timeStamp = file.lastModified(); final String mimeType = getMimeType(file); final ChunkedFileUploadRemoteOperation uploadOperation = new ChunkedFileUploadRemoteOperation(syncedState.getLocalPath(), syncedState.getRemotePath(), @@ -342,7 +342,7 @@ public class UploadFileOperation extends RemoteOperation { @VisibleForTesting() @NonNull public RemoteOperationResult uploadFile(@NonNull final File file, @NonNull final OwnCloudClient client, boolean checkEtag) { - final long timeStamp = file.lastModified() / 1000; + final long timeStamp = file.lastModified(); final String eTag = checkEtag ? syncedState.getLastEtag() : null; final UploadFileRemoteOperation uploadOperation = new UploadFileRemoteOperation(syncedState.getLocalPath(), @@ -374,13 +374,4 @@ public class UploadFileOperation extends RemoteOperation { return syncedState; } - /** - * Convert local file timestamp to the format expected by the cloud - * On Android, file timestamp is a ms value, while nextcloud expect value in second - * @return String timestamp in second - */ - @VisibleForTesting() - public @NonNull String formatTimestampToMatchCloud(long timestamp) { - return String.valueOf(timestamp/1000); - } } \ No newline at end of file diff --git a/app/src/test/java/foundation/e/drive/operations/UploadFileOperationTest.java b/app/src/test/java/foundation/e/drive/operations/UploadFileOperationTest.java index 8bcb28ef..e210f569 100644 --- a/app/src/test/java/foundation/e/drive/operations/UploadFileOperationTest.java +++ b/app/src/test/java/foundation/e/drive/operations/UploadFileOperationTest.java @@ -320,32 +320,6 @@ public class UploadFileOperationTest { assertTrue("Expected result for ifMatchETagRequired(client) is true but got: false", addIfMatchHeader); } - @Test - public void formatTimeStampToMatchCloud_validTimestamp() { - final long initialTimestamp = 1683017095074L; - final String expectedOutput = "1683017095"; - final SyncedFileState mockedSyncedState = Mockito.mock(SyncedFileState.class); - final UploadFileOperation operationUnderTest = new UploadFileOperation(mockedSyncedState, account, context); - - final String output = operationUnderTest.formatTimestampToMatchCloud(initialTimestamp); - assertNotNull("Output shouldn't be null", output); - assertFalse("Output shouldn't not be empty.", output.isEmpty()); - assertEquals("Output doesn't match expectation.", expectedOutput, output); - } - - @Test - public void formatTimeStampToMatchCloud_invalidTimestamp() { - final long initialTimestamp = 0; - final String expectedOutput = "0"; - final SyncedFileState mockedSyncedState = Mockito.mock(SyncedFileState.class); - final UploadFileOperation operationUnderTest = new UploadFileOperation(mockedSyncedState, account, context); - - final String output = operationUnderTest.formatTimestampToMatchCloud(initialTimestamp); - assertNotNull("Output shouldn't be null", output); - assertFalse("Output shouldn't not be empty.", output.isEmpty()); - assertEquals("Output doesn't match expectation.", expectedOutput, output); - } - private void mockUserInfoReading(UploadFileOperation instanceUnderTest) { final long userTotalQuota = 100; final long userUsedQuota = 50; @@ -373,4 +347,4 @@ public class UploadFileOperationTest { private void mockCreateRemoteDir(boolean successExpected, final UploadFileOperation instanceUnderTest, final String remotePath) { Mockito.doReturn(successExpected).when(instanceUnderTest).createRemoteFolder(remotePath, ocClient); } -} \ No newline at end of file +} -- GitLab From 225f08dbc32f504e58b83f4c9652f76b808774fc Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Mon, 25 Mar 2024 13:54:42 +0100 Subject: [PATCH 06/16] chore: remove comment in manifest --- app/src/main/AndroidManifest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6d5f4569..46a24d6c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -12,7 +12,7 @@ - + -- GitLab From 53628d52239c0f96c924afc745b937ea7a3bb215 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Mon, 25 Mar 2024 16:02:40 +0100 Subject: [PATCH 07/16] fix (upload): REVERT remove timestamp conversion (/1000) This reverts commit 8075e65875add33bedafbe8d4f825c8f6f3ab4a9. --- .../tasks/UploadFileOperation.java | 13 +++++++-- .../operations/UploadFileOperationTest.java | 28 ++++++++++++++++++- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java b/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java index 697813bf..d3fbd198 100644 --- a/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java @@ -285,7 +285,7 @@ public class UploadFileOperation extends RemoteOperation { @VisibleForTesting() @NonNull public RemoteOperationResult uploadChunkedFile(@NonNull final File file, @NonNull final OwnCloudClient client) { - final long timeStamp = file.lastModified(); + final long timeStamp = file.lastModified() / 1000; final String mimeType = getMimeType(file); final ChunkedFileUploadRemoteOperation uploadOperation = new ChunkedFileUploadRemoteOperation(syncedState.getLocalPath(), syncedState.getRemotePath(), @@ -342,7 +342,7 @@ public class UploadFileOperation extends RemoteOperation { @VisibleForTesting() @NonNull public RemoteOperationResult uploadFile(@NonNull final File file, @NonNull final OwnCloudClient client, boolean checkEtag) { - final long timeStamp = file.lastModified(); + final long timeStamp = file.lastModified() / 1000; final String eTag = checkEtag ? syncedState.getLastEtag() : null; final UploadFileRemoteOperation uploadOperation = new UploadFileRemoteOperation(syncedState.getLocalPath(), @@ -374,4 +374,13 @@ public class UploadFileOperation extends RemoteOperation { return syncedState; } + /** + * Convert local file timestamp to the format expected by the cloud + * On Android, file timestamp is a ms value, while nextcloud expect value in second + * @return String timestamp in second + */ + @VisibleForTesting() + public @NonNull String formatTimestampToMatchCloud(long timestamp) { + return String.valueOf(timestamp/1000); + } } \ No newline at end of file diff --git a/app/src/test/java/foundation/e/drive/operations/UploadFileOperationTest.java b/app/src/test/java/foundation/e/drive/operations/UploadFileOperationTest.java index e210f569..8bcb28ef 100644 --- a/app/src/test/java/foundation/e/drive/operations/UploadFileOperationTest.java +++ b/app/src/test/java/foundation/e/drive/operations/UploadFileOperationTest.java @@ -320,6 +320,32 @@ public class UploadFileOperationTest { assertTrue("Expected result for ifMatchETagRequired(client) is true but got: false", addIfMatchHeader); } + @Test + public void formatTimeStampToMatchCloud_validTimestamp() { + final long initialTimestamp = 1683017095074L; + final String expectedOutput = "1683017095"; + final SyncedFileState mockedSyncedState = Mockito.mock(SyncedFileState.class); + final UploadFileOperation operationUnderTest = new UploadFileOperation(mockedSyncedState, account, context); + + final String output = operationUnderTest.formatTimestampToMatchCloud(initialTimestamp); + assertNotNull("Output shouldn't be null", output); + assertFalse("Output shouldn't not be empty.", output.isEmpty()); + assertEquals("Output doesn't match expectation.", expectedOutput, output); + } + + @Test + public void formatTimeStampToMatchCloud_invalidTimestamp() { + final long initialTimestamp = 0; + final String expectedOutput = "0"; + final SyncedFileState mockedSyncedState = Mockito.mock(SyncedFileState.class); + final UploadFileOperation operationUnderTest = new UploadFileOperation(mockedSyncedState, account, context); + + final String output = operationUnderTest.formatTimestampToMatchCloud(initialTimestamp); + assertNotNull("Output shouldn't be null", output); + assertFalse("Output shouldn't not be empty.", output.isEmpty()); + assertEquals("Output doesn't match expectation.", expectedOutput, output); + } + private void mockUserInfoReading(UploadFileOperation instanceUnderTest) { final long userTotalQuota = 100; final long userUsedQuota = 50; @@ -347,4 +373,4 @@ public class UploadFileOperationTest { private void mockCreateRemoteDir(boolean successExpected, final UploadFileOperation instanceUnderTest, final String remotePath) { Mockito.doReturn(successExpected).when(instanceUnderTest).createRemoteFolder(remotePath, ocClient); } -} +} \ No newline at end of file -- GitLab From 8466bb363e1551bc8aa5ea0a9afbe3f03d0d7528 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Mon, 25 Mar 2024 16:12:38 +0100 Subject: [PATCH 08/16] fix: solves timestamp issues again --- .../synchronization/tasks/UploadFileOperation.java | 8 ++++---- .../drive/operations/UploadFileOperationTest.java | 14 +++++--------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java b/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java index d3fbd198..96df4a7d 100644 --- a/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java @@ -285,7 +285,7 @@ public class UploadFileOperation extends RemoteOperation { @VisibleForTesting() @NonNull public RemoteOperationResult uploadChunkedFile(@NonNull final File file, @NonNull final OwnCloudClient client) { - final long timeStamp = file.lastModified() / 1000; + final long timeStamp = formatTimestampToMatchCloud(file.lastModified()); final String mimeType = getMimeType(file); final ChunkedFileUploadRemoteOperation uploadOperation = new ChunkedFileUploadRemoteOperation(syncedState.getLocalPath(), syncedState.getRemotePath(), @@ -342,7 +342,7 @@ public class UploadFileOperation extends RemoteOperation { @VisibleForTesting() @NonNull public RemoteOperationResult uploadFile(@NonNull final File file, @NonNull final OwnCloudClient client, boolean checkEtag) { - final long timeStamp = file.lastModified() / 1000; + final long timeStamp = formatTimestampToMatchCloud(file.lastModified()); final String eTag = checkEtag ? syncedState.getLastEtag() : null; final UploadFileRemoteOperation uploadOperation = new UploadFileRemoteOperation(syncedState.getLocalPath(), @@ -380,7 +380,7 @@ public class UploadFileOperation extends RemoteOperation { * @return String timestamp in second */ @VisibleForTesting() - public @NonNull String formatTimestampToMatchCloud(long timestamp) { - return String.valueOf(timestamp/1000); + public @NonNull long formatTimestampToMatchCloud(long timestamp) { + return timestamp/1000; } } \ No newline at end of file diff --git a/app/src/test/java/foundation/e/drive/operations/UploadFileOperationTest.java b/app/src/test/java/foundation/e/drive/operations/UploadFileOperationTest.java index 8bcb28ef..cb4adbf1 100644 --- a/app/src/test/java/foundation/e/drive/operations/UploadFileOperationTest.java +++ b/app/src/test/java/foundation/e/drive/operations/UploadFileOperationTest.java @@ -323,26 +323,22 @@ public class UploadFileOperationTest { @Test public void formatTimeStampToMatchCloud_validTimestamp() { final long initialTimestamp = 1683017095074L; - final String expectedOutput = "1683017095"; + final long expectedOutput = 1683017095L; final SyncedFileState mockedSyncedState = Mockito.mock(SyncedFileState.class); final UploadFileOperation operationUnderTest = new UploadFileOperation(mockedSyncedState, account, context); - final String output = operationUnderTest.formatTimestampToMatchCloud(initialTimestamp); - assertNotNull("Output shouldn't be null", output); - assertFalse("Output shouldn't not be empty.", output.isEmpty()); + final long output = operationUnderTest.formatTimestampToMatchCloud(initialTimestamp); assertEquals("Output doesn't match expectation.", expectedOutput, output); } @Test public void formatTimeStampToMatchCloud_invalidTimestamp() { - final long initialTimestamp = 0; - final String expectedOutput = "0"; + final long initialTimestamp = 0L; + final long expectedOutput = 0L; final SyncedFileState mockedSyncedState = Mockito.mock(SyncedFileState.class); final UploadFileOperation operationUnderTest = new UploadFileOperation(mockedSyncedState, account, context); - final String output = operationUnderTest.formatTimestampToMatchCloud(initialTimestamp); - assertNotNull("Output shouldn't be null", output); - assertFalse("Output shouldn't not be empty.", output.isEmpty()); + final long output = operationUnderTest.formatTimestampToMatchCloud(initialTimestamp); assertEquals("Output doesn't match expectation.", expectedOutput, output); } -- GitLab From fe54cf3ef76ca6f6612da5d3ac73853327bf3d68 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Mon, 25 Mar 2024 17:38:51 +0100 Subject: [PATCH 09/16] style: fix review on AccountUtils --- .../java/foundation/e/drive/account/AccountUtils.kt | 13 +++++-------- .../e/drive/receivers/BootCompletedReceiver.java | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/account/AccountUtils.kt b/app/src/main/java/foundation/e/drive/account/AccountUtils.kt index 583add0d..407e9513 100644 --- a/app/src/main/java/foundation/e/drive/account/AccountUtils.kt +++ b/app/src/main/java/foundation/e/drive/account/AccountUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright MURENA SAS 2022-2023 + * Copyright MURENA SAS 2022-2024 * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -16,16 +16,13 @@ package foundation.e.drive.account - import android.accounts.Account import android.accounts.AccountManager import android.content.Context -import android.content.SharedPreferences -import foundation.e.drive.utils.AppConstants +import foundation.e.drive.R.* import foundation.e.drive.utils.AppConstants.ACCOUNT_DATA_GROUPS import foundation.e.drive.utils.AppConstants.SHARED_PREFERENCE_NAME - object AccountUtils { @JvmStatic @@ -66,17 +63,17 @@ object AccountUtils { if (accountName.isEmpty()) return null val accountManager = AccountManager.get(context) - val accountType = context.getString(foundation.e.drive.R.string.eelo_account_type) + val accountType = context.getString(string.eelo_account_type) return accountManager.getAccountsByType(accountType) .firstOrNull { account -> account.name == accountName } } @JvmStatic - fun isNoAccountPresent(context: Context): Boolean { + fun noAccountIsPresent(context: Context): Boolean { val accountManager = AccountManager.get(context.applicationContext) val accountList = - accountManager.getAccountsByType(context.getString(foundation.e.drive.R.string.eelo_account_type)) + accountManager.getAccountsByType(context.getString(string.eelo_account_type)) return accountList.isEmpty() } } diff --git a/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java b/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java index 1201984e..91488f96 100644 --- a/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java +++ b/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java @@ -45,7 +45,7 @@ public class BootCompletedReceiver extends BroadcastReceiver { Timber.v("onReceive(...)"); if (!Intent.ACTION_BOOT_COMPLETED.equals(action) - || AccountUtils.isNoAccountPresent(context)) { + || AccountUtils.noAccountIsPresent(context)) { return; } -- GitLab From 552fc3aa44d16e8ff91674bb96df58b5fe7a0a27 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Mon, 25 Mar 2024 17:40:27 +0100 Subject: [PATCH 10/16] style: fix review on WorkLauncher --- app/src/main/java/foundation/e/drive/work/WorkLauncher.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/foundation/e/drive/work/WorkLauncher.kt b/app/src/main/java/foundation/e/drive/work/WorkLauncher.kt index 19897d89..ab0324f8 100644 --- a/app/src/main/java/foundation/e/drive/work/WorkLauncher.kt +++ b/app/src/main/java/foundation/e/drive/work/WorkLauncher.kt @@ -94,6 +94,7 @@ class WorkLauncher private constructor(context: Context) { WorkType.ONE_TIME_USER_INFO, null ) + val finishSetupRequest = WorkRequestFactory.getOneTimeWorkRequest( WorkType.ONE_TIME_FINISH_SETUP, null -- GitLab From 56c6da1e19a5cf8499c1cf2015923881ef9b3244 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Mon, 25 Mar 2024 17:46:05 +0100 Subject: [PATCH 11/16] style: fix review on UploadFileOperation.java --- .../e/drive/synchronization/tasks/UploadFileOperation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java b/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java index 96df4a7d..cff10fdf 100644 --- a/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java @@ -380,7 +380,7 @@ public class UploadFileOperation extends RemoteOperation { * @return String timestamp in second */ @VisibleForTesting() - public @NonNull long formatTimestampToMatchCloud(long timestamp) { + public long formatTimestampToMatchCloud(long timestamp) { return timestamp/1000; } } \ No newline at end of file -- GitLab From 8f888fd374e367c0c07a8384538698e2c7984833 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Mon, 25 Mar 2024 18:01:40 +0100 Subject: [PATCH 12/16] fix (BootCompleteReceiver) : re-add triggering of periodic work --- .../foundation/e/drive/receivers/BootCompletedReceiver.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java b/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java index 91488f96..d940ec50 100644 --- a/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java +++ b/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java @@ -50,10 +50,11 @@ public class BootCompletedReceiver extends BroadcastReceiver { } final SharedPreferences prefs = context.getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); + final WorkLauncher workLauncher = WorkLauncher.getInstance(context); migrateSetupCompletedPrefsKey(prefs); if (!isSetupCompleted(prefs)){ - WorkLauncher.getInstance(context).enqueueSetupWorkers(context); + workLauncher.enqueueSetupWorkers(context); return; //set up workers are scheduled. Just wait that they run } @@ -74,6 +75,9 @@ public class BootCompletedReceiver extends BroadcastReceiver { } SyncProxy.INSTANCE.startListeningFiles((Application) context.getApplicationContext()); + + workLauncher.enqueuePeriodicFullScan(); + workLauncher.enqueuePeriodicUserInfoFetching(); } private void migrateSetupCompletedPrefsKey(@NonNull SharedPreferences prefs) { -- GitLab From e2c3875f4a2601aace11d7b6579e1693466b4750 Mon Sep 17 00:00:00 2001 From: Sayantan Roychowdhury Date: Tue, 26 Mar 2024 08:34:38 +0000 Subject: [PATCH 13/16] style: add space around division sign --- .../e/drive/synchronization/tasks/UploadFileOperation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java b/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java index cff10fdf..9993d2ad 100644 --- a/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/synchronization/tasks/UploadFileOperation.java @@ -381,6 +381,6 @@ public class UploadFileOperation extends RemoteOperation { */ @VisibleForTesting() public long formatTimestampToMatchCloud(long timestamp) { - return timestamp/1000; + return timestamp / 1000; } } \ No newline at end of file -- GitLab From b110f6c621c743917e657679214a65ba16471f2c Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Tue, 26 Mar 2024 09:44:50 +0100 Subject: [PATCH 14/16] chore: remove wildcart in import --- .../main/java/foundation/e/drive/account/AccountUtils.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/account/AccountUtils.kt b/app/src/main/java/foundation/e/drive/account/AccountUtils.kt index 407e9513..c73a0da8 100644 --- a/app/src/main/java/foundation/e/drive/account/AccountUtils.kt +++ b/app/src/main/java/foundation/e/drive/account/AccountUtils.kt @@ -19,7 +19,7 @@ package foundation.e.drive.account import android.accounts.Account import android.accounts.AccountManager import android.content.Context -import foundation.e.drive.R.* +import foundation.e.drive.R import foundation.e.drive.utils.AppConstants.ACCOUNT_DATA_GROUPS import foundation.e.drive.utils.AppConstants.SHARED_PREFERENCE_NAME @@ -63,7 +63,7 @@ object AccountUtils { if (accountName.isEmpty()) return null val accountManager = AccountManager.get(context) - val accountType = context.getString(string.eelo_account_type) + val accountType = context.getString(R.string.eelo_account_type) return accountManager.getAccountsByType(accountType) .firstOrNull { account -> account.name == accountName } @@ -73,7 +73,7 @@ object AccountUtils { fun noAccountIsPresent(context: Context): Boolean { val accountManager = AccountManager.get(context.applicationContext) val accountList = - accountManager.getAccountsByType(context.getString(string.eelo_account_type)) + accountManager.getAccountsByType(context.getString(R.string.eelo_account_type)) return accountList.isEmpty() } } -- GitLab From 52c317e5c720dd971cc7da1e59df354ced914666 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Tue, 26 Mar 2024 10:06:51 +0100 Subject: [PATCH 15/16] style: rename method from AccountUtils --- .../main/java/foundation/e/drive/account/AccountUtils.kt | 4 ++-- .../foundation/e/drive/receivers/BootCompletedReceiver.java | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/account/AccountUtils.kt b/app/src/main/java/foundation/e/drive/account/AccountUtils.kt index c73a0da8..ae3c1118 100644 --- a/app/src/main/java/foundation/e/drive/account/AccountUtils.kt +++ b/app/src/main/java/foundation/e/drive/account/AccountUtils.kt @@ -70,10 +70,10 @@ object AccountUtils { } @JvmStatic - fun noAccountIsPresent(context: Context): Boolean { + fun isAccountAvailable(context: Context): Boolean { val accountManager = AccountManager.get(context.applicationContext) val accountList = accountManager.getAccountsByType(context.getString(R.string.eelo_account_type)) - return accountList.isEmpty() + return accountList.isNotEmpty() } } diff --git a/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java b/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java index d940ec50..d7da47a4 100644 --- a/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java +++ b/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java @@ -39,13 +39,15 @@ public class BootCompletedReceiver extends BroadcastReceiver { private static final int VERSION_CODE_FOR_UPDATE_1 = 1002000; private static final int VERSION_CODE_FOR_UPDATE_2 = 1003017; + + @Override public void onReceive(@NonNull Context context, @NonNull Intent intent) { final String action = intent.getAction(); Timber.v("onReceive(...)"); if (!Intent.ACTION_BOOT_COMPLETED.equals(action) - || AccountUtils.noAccountIsPresent(context)) { + || !AccountUtils.isAccountAvailable(context)) { return; } @@ -55,7 +57,7 @@ public class BootCompletedReceiver extends BroadcastReceiver { migrateSetupCompletedPrefsKey(prefs); if (!isSetupCompleted(prefs)){ workLauncher.enqueueSetupWorkers(context); - return; //set up workers are scheduled. Just wait that they run + return; } /* -- GitLab From 6e2ba6908bb6f7cf0c7838d919250f1011308ec9 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Tue, 26 Mar 2024 10:09:15 +0100 Subject: [PATCH 16/16] style: remove useless method --- .../foundation/e/drive/receivers/BootCompletedReceiver.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java b/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java index d7da47a4..5001e7f5 100644 --- a/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java +++ b/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java @@ -129,12 +129,8 @@ public class BootCompletedReceiver extends BroadcastReceiver { * @param context Context used to start InitializationService */ private void handleOsUpdate(@NonNull Context context) { - forceDBUpdate(context); - } - - private void forceDBUpdate(@NonNull Context context) { final DbHelper dbHelper = new DbHelper(context); - dbHelper.getWritableDatabase().close(); + dbHelper.getWritableDatabase().close(); //force DB update } private boolean isSetupCompleted(@NonNull SharedPreferences prefs) { -- GitLab