From f48bcbf0256819e20efeeb6dbc8543b8f1d8fe85 Mon Sep 17 00:00:00 2001 From: Vincent Bourgmayer Date: Mon, 18 Dec 2023 13:34:27 +0000 Subject: [PATCH 01/24] 299 step 6 remove persistent flag --- README.md | 46 +++++++- app/build.gradle | 18 ++-- app/src/main/AndroidManifest.xml | 9 +- .../e/drive/account/AccountUtils.kt | 71 +++++++++++++ .../account/receivers/AccountAddedReceiver.kt | 14 +-- .../account/setup/FinishSetupWorker.java | 33 +----- .../account/setup/RootFolderSetupWorker.java | 17 +-- .../e/drive/activity/AccountsActivity.java | 9 +- .../foundation/e/drive/database/DbHelper.java | 1 - .../e/drive/periodicScan/FullScanWorker.kt | 12 +-- .../receivers/BootCompletedReceiver.java | 74 ++++++++++--- .../e/drive/receivers/DebugCmdReceiver.java | 76 ++++++++++--- .../e/drive/synchronization/SyncProxy.kt | 11 +- .../e/drive/synchronization/SyncTask.kt | 6 +- .../e/drive/synchronization/SyncWorker.kt | 23 ++-- .../tasks/UploadFileOperation.java | 53 +++++++--- .../e/drive/utils/AccountUtils.java | 51 --------- .../foundation/e/drive/utils/CommonUtils.java | 65 +----------- .../e/drive/widgets/EDriveWidget.java | 11 +- .../foundation/e/drive/work/WorkerUtils.kt | 70 ++++++++++++ detekt.yml | 3 + dev_tools.sh | 100 ++++++++++++++++++ registerAccount.sh | 55 ++++++++++ 23 files changed, 561 insertions(+), 267 deletions(-) create mode 100644 app/src/main/java/foundation/e/drive/account/AccountUtils.kt delete mode 100644 app/src/main/java/foundation/e/drive/utils/AccountUtils.java create mode 100644 app/src/main/java/foundation/e/drive/work/WorkerUtils.kt create mode 100755 dev_tools.sh create mode 100755 registerAccount.sh diff --git a/README.md b/README.md index 215c0051..c7d430fa 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ eDrive can also receive some broadcast intent for different purprose: **Force the synchronization.** ```bash -adb shell am broadcast -a foundation.e.drive.action.FORCE_SYNC --receiver-include-background +adb shell am broadcast -a foundation.e.drive.action.FORCE_SCAN --receiver-include-background ``` **Generate a database dump accessible by the user** @@ -58,14 +58,52 @@ adb shell am broadcast -a foundation.e.drive.action.FORCE_SYNC --receiver-includ adb shell am broadcast -a foundation.e.drive.action.DUMP_DATABASE --receiver-include-background ``` +or Download database directly: + +```bash +adb pull /data/data/foundation.e.drive/databases +``` + **Disable log limit on release build** ```bash - adb shell am broadcast -a foundation.e.drive.action.FULL_LOG_ON_PROD --receiver-include-background --ez full_log_enable true +adb shell am broadcast -a foundation.e.drive.action.FULL_LOG_ON_PROD --receiver-include-background --ez full_log_enable true ``` **Limit log output on release build** *(after previous command)* ```bash - adb shell am broadcast -a foundation.e.drive.action.FULL_LOG_ON_PROD --receiver-include-background --ez full_log_enable false -``` \ No newline at end of file +adb shell am broadcast -a foundation.e.drive.action.FULL_LOG_ON_PROD --receiver-include-background --ez full_log_enable false +``` + +**Test SyncWorker: upload 10 empty files** + +Does not rely on FileObserver or FullScanWorker + +```bash +adb shell am broadcast -a foundation.e.drive.action.TEST_SYNC --receiver-include-background +``` + +**Display logat** + +```bash +adb logcat --pid=$(adb shell pidof -s foundation.e.drive) +``` + +You can also use the script `dev-tools.sh` to run those command. Use : `./dev-tools.sh -h` to display options + +### local NC for testing + +Use following documentation to set up a local NC instance + +https://gitlab.e.foundation/internal/wiki/-/wikis/qa-team/how-to-run-local-nextcloud + +`docker run -d -p 8080:80 -e NEXTCLOUD_TRUSTED_DOMAINS=":8080" -e SQLITE_DATABASE=nc -e NEXTCLOUD_ADMIN_USER=admin -e NEXTCLOUD_ADMIN_PASSWORD=admin nextcloud: +` + +Then you can use Wireshark by example to collect network packet. +Example of filter for wireshark: +`(ip.src == && ip.dst == ) || (ip.dst == && ip.src == ) && http` + + +*note: replace , and by values* \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 2073fe13..eaff7661 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,10 +3,9 @@ plugins { id 'org.jetbrains.kotlin.android' } - def versionMajor = 1 -def versionMinor = 3 -def versionPatch = 16 +def versionMinor = 4 +def versionPatch = 0 def getTestProp(String propName) { def result = "" @@ -97,16 +96,15 @@ dependencies { implementation 'com.github.nextcloud:android-library:2.13.0' implementation "commons-httpclient:commons-httpclient:3.1@jar" implementation fileTree(include: ['*.jar'], dir: 'libs') - api 'androidx.annotation:annotation:1.6.0' - implementation 'androidx.core:core:1.10.1' + api 'androidx.annotation:annotation:1.7.0' implementation 'androidx.appcompat:appcompat:1.6.1' implementation "androidx.constraintlayout:constraintlayout:2.1.4" - implementation 'com.google.android.material:material:1.9.0' + implementation 'com.google.android.material:material:1.10.0' implementation 'com.github.bumptech.glide:glide:4.15.1' implementation 'com.github.bumptech.glide:annotations:4.15.1' - implementation 'androidx.core:core-ktx:1.10.1' + implementation 'androidx.core:core-ktx:1.12.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.15.1' - implementation "androidx.work:work-runtime:2.8.1" + implementation "androidx.work:work-runtime:2.9.0" implementation 'androidx.test:core:1.5.0' implementation 'com.jakewharton.timber:timber:5.0.1' implementation 'foundation.e:elib:0.0.1-alpha11' @@ -114,7 +112,7 @@ dependencies { androidTestImplementation 'androidx.test:runner:1.5.2' androidTestImplementation 'androidx.test:rules:1.5.0' - androidTestImplementation 'androidx.annotation:annotation:1.6.0' + androidTestImplementation 'androidx.annotation:annotation:1.7.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation 'junit:junit:4.13.2' @@ -124,5 +122,5 @@ dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.robolectric:robolectric:4.7.3' testImplementation 'org.mockito:mockito-core:5.0.0' - testImplementation 'androidx.work:work-testing:2.8.1' + testImplementation 'androidx.work:work-testing:2.9.0' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ac42e944..e2caa58f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,6 +15,11 @@ + + + + - + + diff --git a/app/src/main/java/foundation/e/drive/account/AccountUtils.kt b/app/src/main/java/foundation/e/drive/account/AccountUtils.kt new file mode 100644 index 00000000..7d73f23a --- /dev/null +++ b/app/src/main/java/foundation/e/drive/account/AccountUtils.kt @@ -0,0 +1,71 @@ +/* + * Copyright MURENA SAS 2022-2023 + * 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 + * (at your option) any later version. + * + * 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 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 foundation.e.drive.utils.AppConstants.ACCOUNT_DATA_GROUPS + +object AccountUtils { + + @JvmStatic + fun getPremiumPlan(accountManager: AccountManager, account: Account?): String? { + if (account == null) return null + val groupData = accountManager.getUserData(account, ACCOUNT_DATA_GROUPS) + val premiumGroup = extractPremiumGroup(groupData) + return extractPremiumPlan(premiumGroup) + } + + @JvmStatic + private fun extractPremiumPlan(premiumGroup: String?): String? { + if (premiumGroup.isNullOrEmpty()) return null + + val splitPremiumGroup = premiumGroup.split("-") + return if (splitPremiumGroup.size < 2) null + else splitPremiumGroup[1] + } + + @JvmStatic + private fun extractPremiumGroup(groupData: String?): String? { + if (groupData.isNullOrEmpty()) return null + + val groups = groupData.split(",") + return groups.firstOrNull { group: String -> group.contains("premium-") } + } + + @JvmStatic + fun getAccount(context: Context): Account? { + val prefs = context.getSharedPreferences(SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE) + val accountName = prefs.getString(AccountManager.KEY_ACCOUNT_NAME, "") + + return getAccount(accountName!!, context) + } + + @JvmStatic + fun getAccount(accountName: String, context: Context): Account? { + if (accountName.isEmpty()) return null + + val accountManager = AccountManager.get(context) + val accountType = context.getString(R.string.eelo_account_type) + + return accountManager.getAccountsByType(accountType) + .firstOrNull { account -> account.name == accountName } + } +} 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 4e0b9248..2d0eb8d9 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 @@ -15,12 +15,13 @@ import android.content.SharedPreferences import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager 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.CommonUtils import foundation.e.drive.utils.DavClientProvider import foundation.e.drive.utils.RootSyncedFolderProvider import foundation.e.drive.work.WorkRequestFactory.* +import foundation.e.drive.work.WorkerUtils import timber.log.Timber /** @@ -51,7 +52,7 @@ class AccountAddedReceiver() : BroadcastReceiver() { if (registerSetupWorkers(context)) { DavClientProvider.getInstance().cleanUp() - CommonUtils.registerPeriodicUserInfoChecking(WorkManager.getInstance(context)) + WorkerUtils.enqueuePeriodicUserInfoFetching(WorkManager.getInstance(context)) } } @@ -80,7 +81,7 @@ class AccountAddedReceiver() : BroadcastReceiver() { return false } - if (!isExistingAccount(accountName, accountType, context)) { + if (!isExistingAccount(accountName, context)) { Timber.w("No account exist for username: %s ", accountType, accountName) return false } @@ -96,9 +97,8 @@ class AccountAddedReceiver() : BroadcastReceiver() { return prefs.getBoolean(AppConstants.SETUP_COMPLETED, false) } - private fun isExistingAccount(accountName: String, accountType: String, context: Context): Boolean { - val account = CommonUtils.getAccount(accountName, accountType, AccountManager.get(context)) - return account != null + private fun isExistingAccount(accountName: String, context: Context): Boolean { + return AccountUtils.getAccount(accountName, context) != null } private fun registerSetupWorkers(context: Context): Boolean { @@ -118,7 +118,7 @@ class AccountAddedReceiver() : BroadcastReceiver() { val rootSyncedFolderList: List = RootSyncedFolderProvider.getSyncedFolderRoots(context) - if (rootSyncedFolderList.isNullOrEmpty()) { + if (rootSyncedFolderList.isEmpty()) { return null } diff --git a/app/src/main/java/foundation/e/drive/account/setup/FinishSetupWorker.java b/app/src/main/java/foundation/e/drive/account/setup/FinishSetupWorker.java index 1563cf01..3a847973 100644 --- a/app/src/main/java/foundation/e/drive/account/setup/FinishSetupWorker.java +++ b/app/src/main/java/foundation/e/drive/account/setup/FinishSetupWorker.java @@ -11,23 +11,16 @@ package foundation.e.drive.account.setup; import static android.content.Context.MODE_PRIVATE; import static foundation.e.drive.utils.AppConstants.INITIAL_FOLDER_NUMBER; import static foundation.e.drive.utils.AppConstants.SHARED_PREFERENCE_NAME; -import static foundation.e.drive.work.WorkRequestFactory.WorkType.ONE_TIME_APP_LIST; -import static foundation.e.drive.work.WorkRequestFactory.WorkType.ONE_TIME_FORCED_FULL_SCAN; -import static foundation.e.drive.work.WorkRequestFactory.WorkType.PERIODIC_SCAN; import android.content.Context; import androidx.annotation.NonNull; -import androidx.work.ExistingPeriodicWorkPolicy; -import androidx.work.ExistingWorkPolicy; import androidx.work.WorkManager; import androidx.work.Worker; import androidx.work.WorkerParameters; -import foundation.e.drive.periodicScan.FullScanWorker; -import foundation.e.drive.periodicScan.PeriodicScanWorker; import foundation.e.drive.utils.AppConstants; -import foundation.e.drive.work.WorkRequestFactory; +import foundation.e.drive.work.WorkerUtils; import timber.log.Timber; /** @@ -68,26 +61,8 @@ public class FinishSetupWorker extends Worker { private void enqueueWorkers(@NonNull final Context appContext) { final WorkManager workManager = WorkManager.getInstance(appContext); - enqueueAppListGenerationWorker(workManager); - enqueueFullScanWorker(workManager); - enqueuePeriodicFileScanWorker(workManager); - } - - private void enqueueFullScanWorker(@NonNull final WorkManager workManager) { - workManager.enqueueUniqueWork( - FullScanWorker.UNIQUE_WORK_NAME, - ExistingWorkPolicy.KEEP, - WorkRequestFactory.getOneTimeWorkRequest(ONE_TIME_FORCED_FULL_SCAN, null) - ); - } - - private void enqueueAppListGenerationWorker(@NonNull final WorkManager workManager) { - workManager.enqueue(WorkRequestFactory.getOneTimeWorkRequest(ONE_TIME_APP_LIST, null)); - } - - private void enqueuePeriodicFileScanWorker(@NonNull final WorkManager workManager) { - workManager.enqueueUniquePeriodicWork(PeriodicScanWorker.UNIQUE_WORK_NAME, - ExistingPeriodicWorkPolicy.KEEP, - WorkRequestFactory.getPeriodicWorkRequest(PERIODIC_SCAN)); + WorkerUtils.enqueueOneTimeAppListGenerator(workManager); + WorkerUtils.enqueueOneTimeFullScan(workManager, false); + WorkerUtils.enqueuePeriodicFullScan(workManager); } } \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/account/setup/RootFolderSetupWorker.java b/app/src/main/java/foundation/e/drive/account/setup/RootFolderSetupWorker.java index 6488eeb5..99082e5e 100644 --- a/app/src/main/java/foundation/e/drive/account/setup/RootFolderSetupWorker.java +++ b/app/src/main/java/foundation/e/drive/account/setup/RootFolderSetupWorker.java @@ -9,9 +9,7 @@ package foundation.e.drive.account.setup; import android.accounts.Account; -import android.accounts.AccountManager; import android.content.Context; -import android.content.SharedPreferences; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -25,10 +23,9 @@ import com.owncloud.android.lib.resources.files.CreateFolderRemoteOperation; import java.io.File; -import foundation.e.drive.R; +import foundation.e.drive.account.AccountUtils; import foundation.e.drive.database.DbHelper; import foundation.e.drive.models.SyncedFolder; -import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.DavClientProvider; import timber.log.Timber; @@ -62,7 +59,7 @@ public class RootFolderSetupWorker extends Worker { public Result doWork() { try { final Context context = getApplicationContext(); - final Account account = getAccount(); + final Account account = AccountUtils.getAccount(getApplicationContext()); if (account == null) { Timber.d("doWork(): Can't get valid account"); return Result.failure(); @@ -129,14 +126,4 @@ public class RootFolderSetupWorker extends Worker { return result; } - - @Nullable - private Account getAccount() { - final SharedPreferences prefs = getApplicationContext().getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); - final String accountName = prefs.getString(AccountManager.KEY_ACCOUNT_NAME, ""); - final String accountType = getApplicationContext().getString(R.string.eelo_account_type); - - if (accountName.isEmpty()) return null; - return CommonUtils.getAccount(accountName, accountType, AccountManager.get(getApplicationContext())); - } } diff --git a/app/src/main/java/foundation/e/drive/activity/AccountsActivity.java b/app/src/main/java/foundation/e/drive/activity/AccountsActivity.java index 5929f370..a0aea0e5 100644 --- a/app/src/main/java/foundation/e/drive/activity/AccountsActivity.java +++ b/app/src/main/java/foundation/e/drive/activity/AccountsActivity.java @@ -33,8 +33,8 @@ import com.bumptech.glide.Glide; import com.owncloud.android.lib.common.OwnCloudClient; import foundation.e.drive.R; +import foundation.e.drive.account.AccountUtils; import foundation.e.drive.databinding.ActivityAccountsBinding; -import foundation.e.drive.utils.AccountUtils; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.DavClientProvider; import foundation.e.drive.widgets.EDriveWidget; @@ -113,9 +113,10 @@ public class AccountsActivity extends AppCompatActivity { binding.plan.setText(getString(R.string.free_plan, totalShownQuota)); - AccountUtils.getPremiumGroup(accountManager, account) - .ifPresent(group -> binding.plan.setText(getString(R.string.premium_plan, - group.split("-")[1]))); + final String premiumPlan = AccountUtils.getPremiumPlan(accountManager, account); + if (premiumPlan != null) { + binding.plan.setText(getString(R.string.premium_plan, premiumPlan)); + } binding.myPlan.setVisibility(View.VISIBLE); binding.plan.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/foundation/e/drive/database/DbHelper.java b/app/src/main/java/foundation/e/drive/database/DbHelper.java index a71ca586..5a4ac077 100644 --- a/app/src/main/java/foundation/e/drive/database/DbHelper.java +++ b/app/src/main/java/foundation/e/drive/database/DbHelper.java @@ -47,7 +47,6 @@ public final class DbHelper extends SQLiteOpenHelper { */ public DbHelper(@NonNull Context context){ super(context, DATABASE_NAME, null, DATABASE_VERSION); - Timber.tag(DbHelper.class.getSimpleName()); } /** diff --git a/app/src/main/java/foundation/e/drive/periodicScan/FullScanWorker.kt b/app/src/main/java/foundation/e/drive/periodicScan/FullScanWorker.kt index 49cc9c53..855f71ba 100644 --- a/app/src/main/java/foundation/e/drive/periodicScan/FullScanWorker.kt +++ b/app/src/main/java/foundation/e/drive/periodicScan/FullScanWorker.kt @@ -8,7 +8,6 @@ package foundation.e.drive.periodicScan import android.accounts.Account -import android.accounts.AccountManager import android.app.Application import android.content.Context import android.content.SharedPreferences @@ -16,7 +15,7 @@ import androidx.work.Worker import androidx.work.WorkerParameters import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.resources.files.model.RemoteFile -import foundation.e.drive.R +import foundation.e.drive.account.AccountUtils import foundation.e.drive.database.DbHelper import foundation.e.drive.models.SyncRequest import foundation.e.drive.models.SyncedFolder @@ -51,7 +50,7 @@ class FullScanWorker(private val context: Context, private val workerParams: Wor AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE ) - val account = loadAccount(prefs) + val account = AccountUtils.getAccount(applicationContext) val startAllowed = checkStartConditions(account, prefs, requestCollector) if (!startAllowed) { @@ -115,13 +114,6 @@ class FullScanWorker(private val context: Context, private val workerParams: Wor return true } - private fun loadAccount(prefs: SharedPreferences): Account? { - val accountName = prefs.getString(AccountManager.KEY_ACCOUNT_NAME, "") ?: return null - val accountType = context.getString(R.string.eelo_account_type) - - return CommonUtils.getAccount(accountName, accountType, AccountManager.get(context)) - } - /** * indicate if minimum delay between two periodic scan is respected */ 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 1f67e5a7..ae1c037b 100644 --- a/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java +++ b/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java @@ -16,12 +16,14 @@ import android.content.SharedPreferences; import android.database.sqlite.SQLiteException; import androidx.annotation.NonNull; +import androidx.work.WorkManager; import foundation.e.drive.BuildConfig; import foundation.e.drive.database.DbHelper; import foundation.e.drive.synchronization.SyncProxy; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; +import foundation.e.drive.work.WorkerUtils; import timber.log.Timber; /** @@ -31,27 +33,31 @@ import timber.log.Timber; public class BootCompletedReceiver extends BroadcastReceiver { private static final String DATE_SYSTEM_PROPERTY = "ro.build.date"; private static final String PREF_VERSION_CODE = "VERSION_CODE"; + private static final String OLD_SETUP_COMPLETED_PREF_KEY ="initService_has_run"; + + 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.tag(BootCompletedReceiver.class.getSimpleName()).v("onReceive(...)"); - final SharedPreferences pref = context.getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); + 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(pref, currentDateProp)) { // App is persistent so can only be updated (by replacement) on OS update + if (isOsUpdated(prefs, currentDateProp)) { // App is persistent so can only be updated (by replacement) on OS update handleOsUpdate(context); } + changeSetupCompletedPreferenceKey(prefs); + + if (!isSetupCompleted(prefs)) return; - if (pref.getBoolean(AppConstants.SETUP_COMPLETED, false) - && BuildConfig.VERSION_CODE > pref.getInt(PREF_VERSION_CODE, 1002000)) { - pref.edit().putInt(PREF_VERSION_CODE, BuildConfig.VERSION_CODE).apply(); - try { - DbHelper.cleanSyncedFileStateTableAfterUpdate(context); - } catch (SQLiteException exception) { - Timber.e(exception); - } + 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(); } SyncProxy.INSTANCE.startListeningFiles((Application) context.getApplicationContext()); @@ -60,20 +66,60 @@ public class BootCompletedReceiver extends BroadcastReceiver { private void forceDBUpdate(@NonNull Context context) { final DbHelper dbHelper = new DbHelper(context); - dbHelper.getWritableDatabase().close(); //Force upgrade of db. + dbHelper.getWritableDatabase().close(); + } + + private void changeSetupCompletedPreferenceKey(@NonNull SharedPreferences prefs) { + if (prefs.getBoolean(OLD_SETUP_COMPLETED_PREF_KEY, false)) { + Timber.i("Update setup complete preferences"); + prefs + .edit() + .remove(OLD_SETUP_COMPLETED_PREF_KEY) + .putBoolean(AppConstants.SETUP_COMPLETED, true) + .apply(); + + } } /** * Force reinitialization, upgrade of DB in case of OS update - * todo remove when setPersistentFlag=true will be removed * @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 { + DbHelper.cleanSyncedFileStateTableAfterUpdate(context); + } catch (SQLiteException exception) { + Timber.e(exception); + } + } + + if (oldVersionCode <= VERSION_CODE_FOR_UPDATE_2) { + Timber.d("Triggered the update 2 from: %s", oldVersionCode); + final WorkManager workManager= WorkManager.getInstance(context); + workManager.cancelAllWork(); + + WorkerUtils.enqueuePeriodicFullScan(workManager); + WorkerUtils.enqueuePeriodicUserInfoFetching(workManager); + } + } + private boolean isOsUpdated(@NonNull SharedPreferences prefs, @NonNull String currentDateProp) { final String lastKnownDateProp = prefs.getString(DATE_SYSTEM_PROPERTY, ""); return !currentDateProp.equals(lastKnownDateProp); } -} +} \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/receivers/DebugCmdReceiver.java b/app/src/main/java/foundation/e/drive/receivers/DebugCmdReceiver.java index b4b6cf29..d1c743c2 100644 --- a/app/src/main/java/foundation/e/drive/receivers/DebugCmdReceiver.java +++ b/app/src/main/java/foundation/e/drive/receivers/DebugCmdReceiver.java @@ -7,21 +7,28 @@ */ package foundation.e.drive.receivers; -import static foundation.e.drive.work.WorkRequestFactory.WorkType.ONE_TIME_FORCED_FULL_SCAN; - import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.os.Environment; import androidx.annotation.NonNull; -import androidx.work.ExistingWorkPolicy; -import androidx.work.OneTimeWorkRequest; import androidx.work.WorkManager; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import foundation.e.drive.EdriveApplication; import foundation.e.drive.database.DbHelper; -import foundation.e.drive.periodicScan.FullScanWorker; +import foundation.e.drive.models.SyncRequest; +import foundation.e.drive.models.SyncedFileState; +import foundation.e.drive.models.SyncedFolder; +import foundation.e.drive.synchronization.SyncProxy; +import foundation.e.drive.synchronization.SyncRequestCollector; import foundation.e.drive.utils.ReleaseTree; -import foundation.e.drive.work.WorkRequestFactory; +import foundation.e.drive.work.WorkerUtils; import timber.log.Timber; /** @@ -30,21 +37,21 @@ import timber.log.Timber; */ public class DebugCmdReceiver extends BroadcastReceiver { - public static final String ACTION_FORCE_SYNC = "foundation.e.drive.action.FORCE_SYNC"; + public static final String ACTION_TEST_SYNC ="foundation.e.drive.action.TEST_SYNC"; + public static final String ACTION_FORCE_SCAN = "foundation.e.drive.action.FORCE_SCAN"; public static final String ACTION_DUMP_DATABASE = "foundation.e.drive.action.DUMP_DATABASE"; public static final String ACTION_FULL_LOG_ON_PROD = "foundation.e.drive.action.FULL_LOG_ON_PROD"; private static final String FULL_LOG_ENABLE_KEY = "full_log_enable"; + @Override public void onReceive(@NonNull Context context, @NonNull Intent intent) { Timber.tag(DebugCmdReceiver.class.getSimpleName()).v("onReceive"); + final WorkManager workManager = WorkManager.getInstance(context); + switch (intent.getAction()) { - case ACTION_FORCE_SYNC: + case ACTION_FORCE_SCAN: Timber.d("Force Sync intent received"); - final WorkManager workManager = WorkManager.getInstance(context); - final OneTimeWorkRequest fullScanWorkRequest = WorkRequestFactory.getOneTimeWorkRequest(ONE_TIME_FORCED_FULL_SCAN, null); - workManager.enqueueUniqueWork(FullScanWorker.UNIQUE_WORK_NAME, - ExistingWorkPolicy.KEEP, - fullScanWorkRequest); + WorkerUtils.enqueueOneTimeFullScan(workManager, true); break; case ACTION_DUMP_DATABASE: Timber.d("Dump database intent received"); @@ -55,8 +62,51 @@ public class DebugCmdReceiver extends BroadcastReceiver { ReleaseTree.allowDebugLogOnProd(allow_full_log); Timber.d("Allow full log on prod: %s", allow_full_log); break; + case ACTION_TEST_SYNC: + Timber.d("Test SyncWorker.kt started"); + final SyncRequestCollector collector = (SyncRequestCollector) SyncProxy.INSTANCE; + collector.onPeriodicScanStart((EdriveApplication) context.getApplicationContext()); + + final File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS); + final List requests = generateDummyFileToSync(dir, context); + + if (requests.isEmpty()) return; + + collector.queueSyncRequests(requests, context.getApplicationContext()); + + collector.startSynchronization(context.getApplicationContext()); + break; default: break; } } + + private List generateDummyFileToSync(File dir, Context context) { + final List result = new ArrayList<>(); + final int fileAmount = 10; + final SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(dir.getAbsolutePath(), context); + + if (parentFolder == null || !parentFolder.isEnabled()) { + Timber.d("Won't send sync request: no parent are known for n %s", dir.getAbsolutePath()); + return result; + } + + for (int index = 0; index < fileAmount; index++) { + final String fileName = "a" + System.currentTimeMillis()/1200 + ".txt"; + File file = new File(dir.getAbsolutePath(), fileName); + try { + file.createNewFile(); + } catch (IOException exception) { + Timber.d("can't create file"); + continue; + } + + final String remotePath = parentFolder.getRemoteFolder() + fileName; + final SyncedFileState fileState = new SyncedFileState(-1, fileName, file.getAbsolutePath(), remotePath, "", 0L, parentFolder.getId(), true, 3); + + final SyncRequest request = new SyncRequest(fileState, SyncRequest.Type.UPLOAD); + result.add(request); + } + return result; + } } \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/synchronization/SyncProxy.kt b/app/src/main/java/foundation/e/drive/synchronization/SyncProxy.kt index ef829bda..bd2df430 100644 --- a/app/src/main/java/foundation/e/drive/synchronization/SyncProxy.kt +++ b/app/src/main/java/foundation/e/drive/synchronization/SyncProxy.kt @@ -9,14 +9,12 @@ package foundation.e.drive.synchronization import android.app.Application import android.content.Context -import androidx.work.ExistingWorkPolicy import androidx.work.WorkManager import foundation.e.drive.EdriveApplication import foundation.e.drive.database.FailedSyncPrefsManager import foundation.e.drive.models.SyncRequest import foundation.e.drive.models.SyncWrapper -import foundation.e.drive.work.WorkRequestFactory -import foundation.e.drive.work.WorkRequestFactory.WorkType.ONE_TIME_SYNC +import foundation.e.drive.work.WorkerUtils import timber.log.Timber import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentLinkedQueue @@ -115,11 +113,13 @@ object SyncProxy: SyncRequestCollector, SyncManager { override fun startSynchronization(context: Context) { if (context !is EdriveApplication) { Timber.d("Invalid parameter: startSynchronization(context)") + StateMachine.changeState(SyncState.LISTENING_FILES) return } if (syncRequestQueue.isEmpty()) { Timber.d("Request queue is empty") + StateMachine.changeState(SyncState.LISTENING_FILES) return } @@ -133,10 +133,7 @@ object SyncProxy: SyncRequestCollector, SyncManager { } if (previousSyncState != SyncState.SYNCHRONIZING) { - val workManager = WorkManager.getInstance(context) - val workRequest = WorkRequestFactory.getOneTimeWorkRequest(ONE_TIME_SYNC, null) - - workManager.enqueueUniqueWork(SyncWorker.UNIQUE_WORK_NAME, ExistingWorkPolicy.KEEP, workRequest) + WorkerUtils.enqueueOneTimeSync(WorkManager.getInstance(context)) } } diff --git a/app/src/main/java/foundation/e/drive/synchronization/SyncTask.kt b/app/src/main/java/foundation/e/drive/synchronization/SyncTask.kt index d37317e8..497d547b 100644 --- a/app/src/main/java/foundation/e/drive/synchronization/SyncTask.kt +++ b/app/src/main/java/foundation/e/drive/synchronization/SyncTask.kt @@ -17,7 +17,9 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCo import foundation.e.drive.database.DbHelper import foundation.e.drive.database.FailedSyncPrefsManager import foundation.e.drive.models.SyncRequest -import foundation.e.drive.models.SyncRequest.Type.* +import foundation.e.drive.models.SyncRequest.Type.UPLOAD +import foundation.e.drive.models.SyncRequest.Type.DISABLE_SYNCING +import foundation.e.drive.models.SyncRequest.Type.DOWNLOAD import foundation.e.drive.models.SyncWrapper import foundation.e.drive.synchronization.tasks.UploadFileOperation import foundation.e.drive.utils.CommonUtils @@ -143,7 +145,7 @@ class SyncTask( if (!success) { if (request.operationType == SyncRequest.Type.UPLOAD) { val filePath = fileState.localPath - if (filePath.isEmpty()) return; + if (filePath.isEmpty()) return val file = File(filePath) if (file.length() >= UploadFileOperation.FILE_SIZE_FLOOR_FOR_CHUNKED) return } diff --git a/app/src/main/java/foundation/e/drive/synchronization/SyncWorker.kt b/app/src/main/java/foundation/e/drive/synchronization/SyncWorker.kt index eda410b2..861d7b7c 100644 --- a/app/src/main/java/foundation/e/drive/synchronization/SyncWorker.kt +++ b/app/src/main/java/foundation/e/drive/synchronization/SyncWorker.kt @@ -8,11 +8,12 @@ package foundation.e.drive.synchronization import android.accounts.Account -import android.accounts.AccountManager import android.app.NotificationChannel import android.app.NotificationManager import android.content.Context import android.content.Context.NOTIFICATION_SERVICE +import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC +import android.os.Build import androidx.core.app.NotificationCompat import androidx.work.ForegroundInfo @@ -21,8 +22,7 @@ import androidx.work.WorkerParameters import com.owncloud.android.lib.common.OwnCloudClient import foundation.e.drive.EdriveApplication import foundation.e.drive.R -import foundation.e.drive.utils.AppConstants -import foundation.e.drive.utils.CommonUtils +import foundation.e.drive.account.AccountUtils import foundation.e.drive.utils.DavClientProvider import timber.log.Timber import java.util.concurrent.Executors @@ -57,7 +57,7 @@ class SyncWorker( override fun doWork(): Result { try { - account = loadAccount() + account = AccountUtils.getAccount(applicationContext) if (account == null) { Timber.d("Warning : account is null") syncManager.startListeningFiles(applicationContext as EdriveApplication) @@ -87,15 +87,6 @@ class SyncWorker( return Result.success() } - private fun loadAccount(): Account? { - val context = applicationContext - val prefs = context.getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE) - val accountName = prefs.getString(AccountManager.KEY_ACCOUNT_NAME, "") - val accountType = context.getString(R.string.eelo_account_type) - //for above: AS complaint about accountName as String? while it can't be... - return CommonUtils.getAccount(accountName!!, accountType, AccountManager.get(context)) - } - private fun getOcClient(account: Account): OwnCloudClient? { return DavClientProvider.getInstance().getClientInstance( account, @@ -144,7 +135,11 @@ class SyncWorker( .build() - return ForegroundInfo(NOTIFICATION_ID, notification) + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ForegroundInfo(NOTIFICATION_ID, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC) + } else { + ForegroundInfo(NOTIFICATION_ID, notification) + } } private fun createNotificationChannel() { 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 e141198c..d86508bc 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 @@ -99,13 +99,13 @@ public class UploadFileOperation extends RemoteOperation { final ResultCode resultCode; if (uploadResult.isSuccess()) { - updateSyncedFileState(uploadResult, file.lastModified(), client); + final String latestEtag = getLatestEtag(uploadResult, client); + updateSyncedFileState(latestEtag, file.lastModified()); resultCode = uploadResult.getCode(); } else { resultCode = onUploadFailure(uploadResult.getCode(), file.getName()); } - DbHelper.manageSyncedFileStateDB(syncedState, "UPDATE", context); return new RemoteOperationResult(resultCode); } @@ -180,30 +180,49 @@ public class UploadFileOperation extends RemoteOperation { /** * Update syncedFileState (etag & last modified) in case of successful upload - * @param uploadResult The Upload's result instance + * @param latestEtag new eTag of the remoteFile * @param fileLastModified value of local file's last modified - * @param client The client used to check etag if missing */ - private void updateSyncedFileState(final RemoteOperationResult uploadResult, final long fileLastModified, final OwnCloudClient client) { - //The below if statement should only be called for chunked upload. But + private void updateSyncedFileState(@Nullable final String latestEtag, final long fileLastModified) { + if (latestEtag != null) { + syncedState.setLastEtag(latestEtag); + } + + syncedState.setLastModified(fileLastModified); + DbHelper.manageSyncedFileStateDB(syncedState, "UPDATE", context); + } + + private @Nullable String getLatestEtag(@NonNull final RemoteOperationResult uploadResult, @NonNull final OwnCloudClient client) { + + if (uploadResult.getResultData() != null) { + return uploadResult.getResultData(); + } + + //The below code should only be called for chunked upload. But //for some unknown reason, the simple file upload doesn't give the eTag in the result // so, I moved the code here as a security - if (uploadResult.getResultData() == null) { - final RemoteOperationResult result = readRemoteFile(syncedState.getRemotePath(), client); - final ArrayList resultData = result.getData(); - if (result.isSuccess() && resultData != null && !resultData.isEmpty()) { - final String latestETag = ((RemoteFile) resultData.get(0)).getEtag(); - uploadResult.setResultData(latestETag); - } + try { + return readLatestEtagFromCloud(client); + } catch (ClassCastException exception) { + Timber.w("Impossible to read eTag from cloud: %s", exception.getMessage()); + return null; } - final String etag = uploadResult.getResultData(); + } + + private @Nullable String readLatestEtagFromCloud(@NonNull final OwnCloudClient client) throws ClassCastException{ + final RemoteOperationResult result = readRemoteFile(syncedState.getRemotePath(), client); + if (!result.isSuccess()) return null; - if (etag != null) { - syncedState.setLastEtag(etag); + final ArrayList resultData = result.getData(); + + if (resultData == null || resultData.isEmpty()) { + return null; } - syncedState.setLastModified(fileLastModified); + + return ((RemoteFile) resultData.get(0)).getEtag(); } + /** * Perform an operation to check available space on server before to upload * @param client OwnCloudClient diff --git a/app/src/main/java/foundation/e/drive/utils/AccountUtils.java b/app/src/main/java/foundation/e/drive/utils/AccountUtils.java deleted file mode 100644 index d9b944d9..00000000 --- a/app/src/main/java/foundation/e/drive/utils/AccountUtils.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright MURENA SAS 2022 - * 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 - * (at your option) any later version. - * - * 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 foundation.e.drive.utils; - -import static foundation.e.drive.utils.AppConstants.ACCOUNT_DATA_GROUPS; - -import android.accounts.Account; -import android.accounts.AccountManager; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.Arrays; -import java.util.Optional; - -public class AccountUtils { - - - @NonNull - public static Optional getPremiumGroup(@NonNull AccountManager accountManager, @Nullable Account account) { - if (account == null) { - return Optional.empty(); - } - - final String groupData = accountManager.getUserData(account, ACCOUNT_DATA_GROUPS); - - if (groupData == null || groupData.isEmpty()) { - return Optional.empty(); - } - - final String[] groups = groupData.split(","); - - return Arrays.stream(groups) - .filter(group -> group.contains("premium-")) - .findFirst(); - } -} diff --git a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java index a9fd0c00..5a605ef4 100644 --- a/app/src/main/java/foundation/e/drive/utils/CommonUtils.java +++ b/app/src/main/java/foundation/e/drive/utils/CommonUtils.java @@ -1,6 +1,6 @@ /* * Copyright © CLEUS SAS 2018-2019. - * Copyright © ECORP SAS 2022-2023. + * Copyright © MURENA SAS 2022-2023. * 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,7 +14,6 @@ import android.accounts.AccountManager; import android.annotation.SuppressLint; import android.app.NotificationChannel; import android.app.NotificationManager; -import android.app.Service; import android.content.ClipData; import android.content.ClipboardManager; import android.content.ContentResolver; @@ -31,9 +30,6 @@ import java.text.StringCharacterIterator; import java.util.Locale; import foundation.e.drive.R; -import foundation.e.drive.account.AccountUserInfoWorker; -import foundation.e.drive.work.WorkRequestFactory; -import timber.log.Timber; import static foundation.e.drive.utils.AppConstants.MEDIA_SYNC_PROVIDER_AUTHORITY; import static foundation.e.drive.utils.AppConstants.METERED_NETWORK_ALLOWED_AUTHORITY; @@ -41,10 +37,6 @@ import static foundation.e.drive.utils.AppConstants.SETTINGS_SYNC_PROVIDER_AUTHO import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.work.ExistingPeriodicWorkPolicy; -import androidx.work.PeriodicWorkRequest; -import androidx.work.WorkManager; - /** * @author Vincent Bourgmayer @@ -54,41 +46,6 @@ import androidx.work.WorkManager; */ public abstract class CommonUtils { - /** - * Set ServiceUncaughtExceptionHandler to be the MainThread Exception Handler - * Or update the service which use it - * todo: check if the ServiceExceptionHandler could be remove - * @param service current service - */ - public static void setServiceUnCaughtExceptionHandler(@NonNull Service service) { - Thread.UncaughtExceptionHandler defaultUEH = Thread.getDefaultUncaughtExceptionHandler(); - if (defaultUEH != null && ServiceExceptionHandler.class.getSimpleName().equals(defaultUEH.getClass().getSimpleName())) { - ((ServiceExceptionHandler) defaultUEH).setService(service); - } else { - Thread.setDefaultUncaughtExceptionHandler(new ServiceExceptionHandler(service)); - } - } - - - /** - * This method retrieve Account corresponding to account's name and type - * - * @param accountName Account Name, shouldn't be null - * @param accountType account type - * @param am Account Manager - * @return Account or null if not found - */ - @Nullable - public static Account getAccount(@NonNull String accountName, @NonNull String accountType, @NonNull AccountManager am) { - Account[] accounts = am.getAccountsByType(accountType); - for (int i = -1, size = accounts.length; ++i < size; ) { - if (accounts[i].name.equals(accountName)) { - return accounts[i]; - } - } - return null; - } - /** * This method retrieve Account corresponding to account's type * @@ -148,11 +105,7 @@ public abstract class CommonUtils { public static boolean isMeteredNetworkAllowed(@NonNull Account account) { return ContentResolver.getSyncAutomatically(account, METERED_NETWORK_ALLOWED_AUTHORITY); } - - /* methods relative to file */ - - - + /** * Tell if there is internet connection * @@ -161,7 +114,6 @@ public abstract class CommonUtils { * @return True if there is connection, false either */ public static boolean haveNetworkConnection(@NonNull Context context, boolean meteredNetworkAllowed) { - Timber.v("haveNetworkConnection()"); final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class); final NetworkCapabilities capabilities = cm.getNetworkCapabilities(cm.getActiveNetwork()); @@ -217,17 +169,6 @@ public abstract class CommonUtils { Toast.LENGTH_SHORT).show(); } - - /** - * Job for Widget & notification about quota - * - * @param workManager component used to register worker - */ - public static void registerPeriodicUserInfoChecking(@NonNull WorkManager workManager) { - final PeriodicWorkRequest workRequest = WorkRequestFactory.getPeriodicWorkRequest(WorkRequestFactory.WorkType.PERIODIC_USER_INFO); - workManager.enqueueUniquePeriodicWork(AccountUserInfoWorker.UNIQUE_WORK_NAME, ExistingPeriodicWorkPolicy.UPDATE, workRequest); - } - /** * Function for get build props through reflection * @@ -247,4 +188,4 @@ public abstract class CommonUtils { } return value == null ? "" : value; } -} +} \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/widgets/EDriveWidget.java b/app/src/main/java/foundation/e/drive/widgets/EDriveWidget.java index 8c01a51e..ddc61ff4 100644 --- a/app/src/main/java/foundation/e/drive/widgets/EDriveWidget.java +++ b/app/src/main/java/foundation/e/drive/widgets/EDriveWidget.java @@ -39,7 +39,7 @@ import java.util.Calendar; import java.util.Locale; import foundation.e.drive.R; -import foundation.e.drive.utils.AccountUtils; +import foundation.e.drive.account.AccountUtils; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import timber.log.Timber; @@ -256,10 +256,11 @@ public class EDriveWidget extends AppWidgetProvider { views.setTextViewText(R.id.planName, context.getString(R.string.free_plan, totalShownQuota)); - AccountUtils.getPremiumGroup(accountManager, account) - .ifPresent(group -> views.setTextViewText(R.id.planName, - context.getString(R.string.premium_plan, - group.split("-")[1]))); + final String premiumPlan = AccountUtils.getPremiumPlan(accountManager, account); + if (premiumPlan != null) { + views.setTextViewText(R.id.planName, + context.getString(R.string.premium_plan, premiumPlan)); + } views.setTextViewText(R.id.status, context.getString(R.string.progress_status, usedShownQuota, totalShownQuota)); diff --git a/app/src/main/java/foundation/e/drive/work/WorkerUtils.kt b/app/src/main/java/foundation/e/drive/work/WorkerUtils.kt new file mode 100644 index 00000000..0575de17 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/work/WorkerUtils.kt @@ -0,0 +1,70 @@ +/* + * Copyright © MURENA SAS 2023. + * 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.work + +import androidx.work.ExistingPeriodicWorkPolicy +import androidx.work.ExistingWorkPolicy +import androidx.work.WorkManager +import foundation.e.drive.account.AccountUserInfoWorker +import foundation.e.drive.periodicScan.FullScanWorker +import foundation.e.drive.periodicScan.PeriodicScanWorker +import foundation.e.drive.synchronization.SyncWorker +import foundation.e.drive.work.WorkRequestFactory.WorkType + +/** + * @author Vincent Bourgmayer + */ +object WorkerUtils { + + @JvmStatic + fun enqueuePeriodicUserInfoFetching(workManager: WorkManager) { + val request = WorkRequestFactory.getPeriodicWorkRequest(WorkType.PERIODIC_USER_INFO) + workManager.enqueueUniquePeriodicWork(AccountUserInfoWorker.UNIQUE_WORK_NAME, + ExistingPeriodicWorkPolicy.UPDATE, + request) + } + + @JvmStatic + fun enqueuePeriodicFullScan(workManager: WorkManager) { + val request = WorkRequestFactory.getPeriodicWorkRequest(WorkType.PERIODIC_SCAN) + workManager.enqueueUniquePeriodicWork(PeriodicScanWorker.UNIQUE_WORK_NAME, + ExistingPeriodicWorkPolicy.KEEP, + request) + } + + @JvmStatic + fun enqueueOneTimeFullScan(workManager: WorkManager, isForced: Boolean) { + if (isForced) { + val request = WorkRequestFactory.getOneTimeWorkRequest(WorkType.ONE_TIME_FORCED_FULL_SCAN, + null) + workManager.enqueueUniqueWork(FullScanWorker.UNIQUE_WORK_NAME, + ExistingWorkPolicy.REPLACE, + request) + return + } + + val request = WorkRequestFactory.getOneTimeWorkRequest(WorkType.ONE_TIME_FULL_SCAN, + null) + workManager.enqueueUniqueWork(FullScanWorker.UNIQUE_WORK_NAME, + ExistingWorkPolicy.KEEP, + request) + } + + @JvmStatic + fun enqueueOneTimeAppListGenerator(workManager: WorkManager) { + val request = WorkRequestFactory.getOneTimeWorkRequest(WorkType.ONE_TIME_APP_LIST, + null) + workManager.enqueue(request) + } + + @JvmStatic + fun enqueueOneTimeSync(workManager: WorkManager) { + val request = WorkRequestFactory.getOneTimeWorkRequest(WorkType.ONE_TIME_SYNC, null) + workManager.enqueueUniqueWork(SyncWorker.UNIQUE_WORK_NAME, ExistingWorkPolicy.KEEP, request) + } +} diff --git a/detekt.yml b/detekt.yml index 4efb8651..fb4a8a38 100644 --- a/detekt.yml +++ b/detekt.yml @@ -14,6 +14,9 @@ style: ForbiddenComment: active: false + ReturnCount: + excludeGuardClauses: true + # Complexity rules complexity: diff --git a/dev_tools.sh b/dev_tools.sh new file mode 100755 index 00000000..5f7d3e60 --- /dev/null +++ b/dev_tools.sh @@ -0,0 +1,100 @@ + +# Author Vincent Bourgmayer +# 2023 November 22nd + +function printHelp() { + echo "Utilisation: ./eDrive_dev_tools.sh [opt] [(optionnal:) value] + Note: This script strongly rely on ADB, so it won't work if ADB is not installed + + Options list: + -a : add Account into the device + -c : clear eDrive's data + -C : clear adb logcat + -d : dump database (generate a database dumb on the device. The dump is reachable by the user) + -f : Trigger fullScan work (scan cloud & device then try to sync changed files) + -h : print this help + -L : (required value: true|false) : true: enable/ false: disable full log + -l : (option: --clear) : --clear: clear logcat before. Otherwise just display logcat. Default: false + -p : Pull database directory from device into current computer folder + -s : test sync : Create 10 empty files in 'Document' folder and try to upload them + "; +} + + +function registerAccount() { + echo "calling script to add account into device + + /"'!'"\\ Device need to be connecter to WI_FI + + /"'!'"\\ In order to let it work, + check that credentials & server URL + are set in registerAccount2.sh + + /i\\ please also consider, that the script will need to be updated if UI + changes + "; + + + ./registerAccount.sh +} + +function setFullLog() { + if [ -z "$1" ]; then + echo "The parameter -L require a value (true|false)" + elif [ "$1" != "true" ] && [ "$1" != "false" ]; then + echo "Invalid parameter: $1. You need to provide: (true|false)" + else + echo "Sending broacast with $1 as parameter" + adb shell am broadcast -a foundation.e.drive.action.FULL_LOG_ON_PROD --receiver-include-background --ez full_log_enable "$1" + fi +} + +function displayLogcat() { + adb logcat --pid=$(adb shell pidof -s foundation.e.drive) +} + +function clearLogcat() { + adb logcat --clear +} + +function pullDatabase() { + adb pull /data/data/foundation.e.drive/databases +} + +function clearAppData() { + echo "clear eDrive's data"; + adb shell pm clear foundation.e.drive +} + +function dumpDatabase() { + echo "Dumping database" + adb shell am broadcast -a foundation.e.drive.action.DUMP_DATABASE --receiver-include-background +} + +function forceFullScan() { + adb shell am broadcast -a foundation.e.drive.action.FORCE_SCAN --receiver-include-background +} + +function testSync() { + adb shell am broadcast -a foundation.e.drive.action.TEST_SYNC --receiver-include-background +} + +while getopts acCdfhL:lps flag +do + case "${flag}" in + a) registerAccount;; + c) clearAppData;; + C) clearLogcat;; + d) dumpDatabase;; + f) forceFullScan;; + h) printHelp;; + l) displayLogcat;; + L) setFullLog ${OPTARG};; + p) pullDatabase;; + s) testSync;; + esac +done + +echo "|----FINISHED----|" +exit 0 + diff --git a/registerAccount.sh b/registerAccount.sh new file mode 100755 index 00000000..438228d1 --- /dev/null +++ b/registerAccount.sh @@ -0,0 +1,55 @@ + +# Open Add account settings page (doesn't work if already opened and not closed) +adb shell am start -a android.settings.ADD_ACCOUNT_SETTINGS + +sleep 1 +# Select /e/ account +# number of input depend on language set on the device + +adb shell input keyevent TAB +sleep 1 + +# Open the /e/ account : add account form +adb shell input keyevent ENTER + + +# CREDENTIALS & SERVER URL + +MAIL="PUT YOUR VALUE" +PWD="PUT YOUR VALUE" +SERVER_URI="PUT YOUR VALUE" + +sleep 1 +#Insert Login +adb shell input text $MAIL +adb shell input keyevent TAB + +sleep 1 +#Insert PWD +adb shell input text $PWD + +## IF NO server URI +if [ -z $SERVER_URI ] +then + adb shell input keyevent TAB + adb shell input keyevent ENTER +else + + # Move across element + adb shell input keyevent TAB + adb shell input keyevent TAB + adb shell input keyevent TAB + + # Enter server URI + adb shell input keyevent ENTER + adb shell input keyevent TAB + adb shell input text $SERVER_URI + + # Move again to login btn + adb shell input keyevent TAB + adb shell input keyevent TAB + adb shell input keyevent TAB + adb shell input keyevent TAB + adb shell input keyevent TAB + adb shell input keyevent ENTER +fi -- GitLab From 295fb6dee144e6be9f3fc5375afc4cc5ca43e9ca Mon Sep 17 00:00:00 2001 From: Vincent Bourgmayer Date: Wed, 20 Dec 2023 10:41:37 +0000 Subject: [PATCH 02/24] introduce SyncProgressNotifier --- app/src/main/AndroidManifest.xml | 1 + .../AccountRemoveCallbackReceiver.java | 3 +- .../synchronization/SyncProgressNotifier.kt | 73 +++++++++++++++++++ .../e/drive/synchronization/SyncTask.kt | 3 + .../e/drive/synchronization/SyncWorker.kt | 56 +++----------- app/src/main/res/values/strings.xml | 2 +- 6 files changed, 89 insertions(+), 49 deletions(-) create mode 100644 app/src/main/java/foundation/e/drive/synchronization/SyncProgressNotifier.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e2caa58f..b4bdbf18 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ package="foundation.e.drive"> + diff --git a/app/src/main/java/foundation/e/drive/account/receivers/AccountRemoveCallbackReceiver.java b/app/src/main/java/foundation/e/drive/account/receivers/AccountRemoveCallbackReceiver.java index 668fb93d..23f8d392 100644 --- a/app/src/main/java/foundation/e/drive/account/receivers/AccountRemoveCallbackReceiver.java +++ b/app/src/main/java/foundation/e/drive/account/receivers/AccountRemoveCallbackReceiver.java @@ -29,6 +29,7 @@ import foundation.e.drive.EdriveApplication; import foundation.e.drive.R; import foundation.e.drive.database.DbHelper; import foundation.e.drive.database.FailedSyncPrefsManager; +import foundation.e.drive.synchronization.SyncProgressNotifier; import foundation.e.drive.synchronization.SyncWorker; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.DavClientProvider; @@ -153,7 +154,7 @@ public class AccountRemoveCallbackReceiver extends BroadcastReceiver { notificationManager.cancelAll(); try { notificationManager.deleteNotificationChannel(AppConstants.notificationChannelID); - notificationManager.deleteNotificationChannel(SyncWorker.NOTIF_CHANNEL_ID); + notificationManager.deleteNotificationChannel(SyncProgressNotifier.NOTIF_CHANNEL_ID); } catch (Exception exception) { Timber.e(exception, "Cannot delete notification Channel"); } diff --git a/app/src/main/java/foundation/e/drive/synchronization/SyncProgressNotifier.kt b/app/src/main/java/foundation/e/drive/synchronization/SyncProgressNotifier.kt new file mode 100644 index 00000000..79772ea3 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/synchronization/SyncProgressNotifier.kt @@ -0,0 +1,73 @@ +/* + * Copyright © MURENA SAS 2023. + * 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.synchronization + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import android.content.pm.ServiceInfo +import android.os.Build +import androidx.core.app.NotificationCompat +import androidx.work.ForegroundInfo +import foundation.e.drive.R + +class SyncProgressNotifier(private val context: Context) { + companion object { + const val NOTIFICATION_ID = 2003004 + const val NOTIF_CHANNEL_ID = "syncChannelId" + } + + private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + private fun createNotification(requestCounter: Int): Notification { + val text = getNotificationText(requestCounter) + val title = context.getString(R.string.notif_sync_is_running_title) + + return NotificationCompat.Builder(context, NOTIF_CHANNEL_ID) + .setOngoing(true) + .setContentTitle(title) + .setContentText(text) + .setSmallIcon(R.drawable.ic_synchronization) + .build() + } + + fun notifyTaskFinished(requestCounter: Int) { + notificationManager.notify(NOTIFICATION_ID, createNotification(requestCounter)) + } + + fun cancelAllSyncNotifications() { + notificationManager.cancel(NOTIFICATION_ID) + } + + private fun getNotificationText(requestCount: Int): String { + return context.resources + .getQuantityString(R.plurals.notif_sync_is_running_txt, requestCount, requestCount) + } + + fun createNotificationChannel() { + val channelName = context.getString(R.string.notif_sync_channel_name) + val importance = NotificationManager.IMPORTANCE_MIN + val channel = NotificationChannel(NOTIF_CHANNEL_ID, channelName, importance) + val channelDescription = context.getString(R.string.notif_sync_channel_description) + channel.description = channelDescription + + notificationManager.createNotificationChannel(channel) + } + + internal fun createForegroundInfo(requestCount: Int): ForegroundInfo { + val notification = createNotification(requestCount) + + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ForegroundInfo(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC) + } else { + ForegroundInfo(NOTIFICATION_ID, notification) + } + } +} diff --git a/app/src/main/java/foundation/e/drive/synchronization/SyncTask.kt b/app/src/main/java/foundation/e/drive/synchronization/SyncTask.kt index 497d547b..7a43489f 100644 --- a/app/src/main/java/foundation/e/drive/synchronization/SyncTask.kt +++ b/app/src/main/java/foundation/e/drive/synchronization/SyncTask.kt @@ -38,6 +38,8 @@ class SyncTask( private val fileName = request.syncedFileState.name private val fileLocalPath = request.syncedFileState.localPath + private val syncNotifier = SyncProgressNotifier(context) + override fun run() { if (!canStart()) { @@ -55,6 +57,7 @@ class SyncTask( DISABLE_SYNCING -> runSyncDisabling() } + syncNotifier.notifyTaskFinished(SyncWorker.pendingTaskCounter.decrementAndGet()) updateFailureCounter(request, succeed) syncManager.removeStartedRequest(fileLocalPath) Timber.d("${request.operationType.name} finished for $fileLocalPath") diff --git a/app/src/main/java/foundation/e/drive/synchronization/SyncWorker.kt b/app/src/main/java/foundation/e/drive/synchronization/SyncWorker.kt index 861d7b7c..c3947667 100644 --- a/app/src/main/java/foundation/e/drive/synchronization/SyncWorker.kt +++ b/app/src/main/java/foundation/e/drive/synchronization/SyncWorker.kt @@ -8,26 +8,19 @@ package foundation.e.drive.synchronization import android.accounts.Account -import android.app.NotificationChannel -import android.app.NotificationManager import android.content.Context -import android.content.Context.NOTIFICATION_SERVICE -import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC -import android.os.Build -import androidx.core.app.NotificationCompat -import androidx.work.ForegroundInfo import androidx.work.Worker import androidx.work.WorkerParameters import com.owncloud.android.lib.common.OwnCloudClient import foundation.e.drive.EdriveApplication -import foundation.e.drive.R import foundation.e.drive.account.AccountUtils import foundation.e.drive.utils.DavClientProvider import timber.log.Timber import java.util.concurrent.Executors import java.util.concurrent.Future import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger class SyncWorker( context: Context, @@ -36,20 +29,19 @@ class SyncWorker( companion object { const val UNIQUE_WORK_NAME = "syncWorker" - const val NOTIF_CHANNEL_ID = "syncChannelId" - const val NOTIFICATION_ID = 2003004 + internal lateinit var pendingTaskCounter: AtomicInteger private const val threadAmount = 2 private val syncManager = SyncProxy as SyncManager } private var account: Account? = null private var ocClient: OwnCloudClient? = null - private val notificationManager = applicationContext.getSystemService(NOTIFICATION_SERVICE) as NotificationManager private var executor = Executors.newFixedThreadPool(threadAmount) + private val syncNotifier = SyncProgressNotifier(applicationContext) override fun onStopped() { Timber.d("SyncWorker has been stopped") - notificationManager.cancel(NOTIFICATION_ID) + syncNotifier.cancelAllSyncNotifications() executor.shutdownNow() super.onStopped() @@ -70,15 +62,16 @@ class SyncWorker( syncManager.startListeningFiles(applicationContext as EdriveApplication) return Result.failure() } - - createNotificationChannel() + syncNotifier.createNotificationChannel() while (!syncManager.isQueueEmpty()) { - setForegroundAsync(createForegroundInfo()) + val requestCount = syncManager.getQueueSize() + pendingTaskCounter = AtomicInteger(requestCount) + setForegroundAsync(syncNotifier.createForegroundInfo(requestCount)) executeRequests() } - notificationManager.cancel(NOTIFICATION_ID) + syncNotifier.cancelAllSyncNotifications() syncManager.startListeningFiles(applicationContext as EdriveApplication) } catch (exception: Exception) { Timber.w(exception) @@ -120,35 +113,4 @@ class SyncWorker( executor.awaitTermination(30, TimeUnit.SECONDS) } } - - private fun createForegroundInfo(): ForegroundInfo { - val title = applicationContext.getString(R.string.notif_sync_is_running_title) - val requestCount = syncManager.getQueueSize() - val text = applicationContext.resources - .getQuantityString(R.plurals.notif_sync_is_running_txt, requestCount, requestCount) - - val notification = NotificationCompat.Builder(applicationContext, NOTIF_CHANNEL_ID) - .setOngoing(true) - .setContentTitle(title) - .setContentText(text) - .setSmallIcon(R.drawable.ic_synchronization) - - .build() - - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - ForegroundInfo(NOTIFICATION_ID, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC) - } else { - ForegroundInfo(NOTIFICATION_ID, notification) - } - } - - private fun createNotificationChannel() { - val channelName = applicationContext.getString(R.string.notif_sync_channel_name) - val importance = NotificationManager.IMPORTANCE_MIN - val channel = NotificationChannel(NOTIF_CHANNEL_ID, channelName, importance) - val channelDescription = applicationContext.getString(R.string.notif_sync_channel_description) - channel.description = channelDescription - - notificationManager.createNotificationChannel(channel) - } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fda41306..1ebbd1a2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -31,7 +31,7 @@ 99% of your allotted cloud storage is used. Please take action. You\'ve filled your allotted cloud storage up to 90%. You\'ve filled your allotted cloud storage up to 80%. - File synchronization in progress + File synchronization %d file to sync %d files to sync -- GitLab From 032a76cbe2ab6ee78af0e73977bfe683c800124d Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Thu, 21 Dec 2023 16:38:45 +0100 Subject: [PATCH 03/24] Fix bug in SyncWorker.kt --- .../java/foundation/e/drive/periodicScan/FullScanWorker.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/periodicScan/FullScanWorker.kt b/app/src/main/java/foundation/e/drive/periodicScan/FullScanWorker.kt index 855f71ba..c354123b 100644 --- a/app/src/main/java/foundation/e/drive/periodicScan/FullScanWorker.kt +++ b/app/src/main/java/foundation/e/drive/periodicScan/FullScanWorker.kt @@ -64,12 +64,12 @@ class FullScanWorker(private val context: Context, private val workerParams: Wor return Result.success() } - val remoteSyncRequests = scanRemoteFiles(account, syncFolders) + val remoteSyncRequests = scanRemoteFiles(account, syncFolders.toMutableList()) syncRequests.putAll(remoteSyncRequests) Timber.d("${remoteSyncRequests.size} request collected from cloud") - val localSyncRequests = scanLocalFiles(syncFolders) + val localSyncRequests = scanLocalFiles(syncFolders.toMutableList()) syncRequests.putAll(localSyncRequests) Timber.d("${localSyncRequests.size} request collected from device") -- GitLab From 0755c7f8376a5a35d51ab384e39e3cf7477ccbd9 Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Tue, 26 Dec 2023 08:51:00 +0000 Subject: [PATCH 04/24] 1562-Refactor_fileObserver_implementation --- .../foundation/e/drive/EdriveApplication.java | 49 ++-- .../FileObservers/FileEventListener.java | 243 ----------------- .../FileObservers/RecursiveFileObserver.java | 179 ------------- .../drive/fileObservers/DirectoryObserver.kt | 41 +++ .../e/drive/fileObservers/FileEventHandler.kt | 244 ++++++++++++++++++ .../drive/fileObservers/FileEventListener.kt | 15 ++ .../fileObservers/FileObserverManager.kt | 144 +++++++++++ .../e/drive/synchronization/SyncProxy.kt | 18 +- 8 files changed, 467 insertions(+), 466 deletions(-) delete mode 100644 app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java delete mode 100644 app/src/main/java/foundation/e/drive/FileObservers/RecursiveFileObserver.java create mode 100644 app/src/main/java/foundation/e/drive/fileObservers/DirectoryObserver.kt create mode 100644 app/src/main/java/foundation/e/drive/fileObservers/FileEventHandler.kt create mode 100644 app/src/main/java/foundation/e/drive/fileObservers/FileEventListener.kt create mode 100644 app/src/main/java/foundation/e/drive/fileObservers/FileObserverManager.kt diff --git a/app/src/main/java/foundation/e/drive/EdriveApplication.java b/app/src/main/java/foundation/e/drive/EdriveApplication.java index fe9b303f..85360735 100644 --- a/app/src/main/java/foundation/e/drive/EdriveApplication.java +++ b/app/src/main/java/foundation/e/drive/EdriveApplication.java @@ -8,40 +8,40 @@ package foundation.e.drive; +import static timber.log.Timber.DebugTree; + import android.accounts.Account; import android.accounts.AccountManager; import android.app.Application; import android.content.Context; import android.content.SharedPreferences; -import android.os.Environment; -import foundation.e.drive.FileObservers.FileEventListener; -import foundation.e.drive.FileObservers.RecursiveFileObserver; +import androidx.annotation.NonNull; + import foundation.e.drive.database.FailedSyncPrefsManager; +import foundation.e.drive.fileObservers.FileObserverManager; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.ReleaseTree; import foundation.e.lib.telemetry.Telemetry; import timber.log.Timber; -import static timber.log.Timber.DebugTree; - -import androidx.annotation.NonNull; /** * Class representing the eDrive application. * It is instantiated before any other class. + * * @author Jonathan klee * @author Vincent Bourgmayer */ public class EdriveApplication extends Application { - private RecursiveFileObserver mFileObserver = null; - private FileEventListener fileEventListener; + private FileObserverManager fileObserverManager = null; @Override public void onCreate() { super.onCreate(); setupLogging(); - instantiateFileEventListener(); + + fileObserverManager = new FileObserverManager(getApplicationContext()); CommonUtils.createNotificationChannel(getApplicationContext()); @@ -49,7 +49,9 @@ public class EdriveApplication extends Application { if (!isAccountStoredInPreferences(prefs)) { final Account account = CommonUtils.getAccount(getString(R.string.eelo_account_type), AccountManager.get(this)); - if (account == null) { return; } + if (account == null) { + return; + } prefs.edit().putString(AccountManager.KEY_ACCOUNT_NAME, account.name) .putString(AccountManager.KEY_ACCOUNT_TYPE, account.type) @@ -59,19 +61,12 @@ public class EdriveApplication extends Application { FailedSyncPrefsManager.getInstance(getApplicationContext()).clearPreferences(); } - /** - * Start Recursive FileObserver if not already watching - */ synchronized public void startRecursiveFileObserver() { - if (!mFileObserver.isWatching()) { - mFileObserver.startWatching(); - Timber.d("Started RecursiveFileObserver on root folder"); - } + fileObserverManager.initializeObserving(); } synchronized public void stopRecursiveFileObserver() { - mFileObserver.stopWatching(); - Timber.d("Stopped RecursiveFileObserver on root folder"); + fileObserverManager.stopObserving(); } @Override @@ -80,23 +75,17 @@ public class EdriveApplication extends Application { Timber.i("System is low on memory. Application might get killed by the system."); } - private void instantiateFileEventListener() { - fileEventListener = new FileEventListener(getApplicationContext()); - - final String pathForObserver = Environment.getExternalStorageDirectory().getAbsolutePath(); - mFileObserver = new RecursiveFileObserver(getApplicationContext(), pathForObserver, fileEventListener); - } - private void setupLogging() { if (BuildConfig.DEBUG) { Timber.plant(new DebugTree()); - } else { - Telemetry.init(BuildConfig.SENTRY_DSN, this, true); - Timber.plant(new ReleaseTree()); + return; } + + Telemetry.init(BuildConfig.SENTRY_DSN, this, true); + Timber.plant(new ReleaseTree()); } private boolean isAccountStoredInPreferences(@NonNull SharedPreferences prefs) { return prefs.getString(AccountManager.KEY_ACCOUNT_NAME, null) != null; } -} \ No newline at end of file +} diff --git a/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java b/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java deleted file mode 100644 index 1d5a9c50..00000000 --- a/app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright © ECORP SAS 2022. - * 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.FileObservers; - -import static foundation.e.drive.models.SyncRequest.Type.DISABLE_SYNCING; -import static foundation.e.drive.models.SyncRequest.Type.UPLOAD; -import static foundation.e.drive.models.SyncedFileStateKt.DO_NOT_SCAN; -import static foundation.e.drive.models.SyncedFileStateKt.SCAN_ON_CLOUD; -import static foundation.e.drive.models.SyncedFileStateKt.SCAN_ON_DEVICE; -import static foundation.e.drive.utils.FileUtils.getLocalPath; - -import android.content.Context; -import android.os.FileObserver; - -import androidx.annotation.NonNull; - -import static com.owncloud.android.lib.resources.files.FileUtils.PATH_SEPARATOR; - -import java.io.File; - -import foundation.e.drive.database.DbHelper; -import foundation.e.drive.models.SyncRequest; -import foundation.e.drive.models.SyncedFileState; -import foundation.e.drive.models.SyncedFolder; -import foundation.e.drive.synchronization.SyncRequestCollector; -import foundation.e.drive.synchronization.SyncProxy; -import timber.log.Timber; - -/** - * @author Narinder Rana - * @author vincent Bourgmayer - */ -public class FileEventListener { - - private final Context appContext; - - public FileEventListener(@NonNull Context applicationContext) { - Timber.tag(FileEventListener.class.getSimpleName()); - this.appContext = applicationContext; - } - - public void onEvent(int event, @NonNull File file) { - if (file.isHidden()) return; - - if (file.isDirectory()) { - handleDirectoryEvent(event, file); - } else { - handleFileEvent(event, file); - } - } - - /** - * Handle some file event for a file which is not a directory - * @param event the event mask. CLOSE_WRITE, DELETE & MOVE_SELF are handled - * @param file the file concerned by the event - */ - private void handleFileEvent(int event, @NonNull File file) { - switch(event) { - case FileObserver.CLOSE_WRITE: //todo it is called two times per file except if screenshot by example or take a picture - handleFileCloseWrite(file); - break; - case FileObserver.DELETE: - handleFileDelete(file); - break; - case FileObserver.MOVE_SELF: //todo to be able to catch that, we probably need a buffer to catch a succession (MOVE_FROM, MOVE_TO, then MOVE_SELF). - Timber.d("%s has been moved. Not handled yet", file.getAbsolutePath()); - break; - default: - break; - } - } - - /** - * Handle FileEvent for a directory - * @param event FileEvent mask. CREATE, CLOSE_WRITE, DELETE, MOVE_SELF - * @param dir directory concerned by file event - */ - private void handleDirectoryEvent(int event, @NonNull File dir) { - switch(event) { - case FileObserver.CREATE: - handleDirectoryCreate(dir); - break; - case FileObserver.CLOSE_WRITE: - handleDirectoryCloseWrite(dir); - break; - case FileObserver.DELETE: //todo #1 Fix: never used. when done on a dir, it triggers handleFileEvent. Why ?! - handleDirectoryDelete(dir); - break; - case FileObserver.MOVE_SELF: - Timber.d("%s has been moved. Not handled yet", dir.getAbsolutePath()); - break; - default: - break; - } - } - - /** - * Send syncRequest to SynchronizationService - * @param request SyncRequest that should be executed asap - */ - private void sendSyncRequestToSynchronizationService(@NonNull SyncRequest request) { - final SyncRequestCollector syncManager = SyncProxy.INSTANCE; - final boolean requestAdded = syncManager.queueSyncRequest(request, appContext.getApplicationContext()); - if (requestAdded) { - Timber.d("Sending a SyncRequest for %s", request.getSyncedFileState().getName()); - syncManager.startSynchronization(appContext); - } - } - - /** - * When a new directory is detected, it must be inserted in database - * if it's parent directory is already in the database - * @param directory Directory that has been created - */ - private void handleDirectoryCreate(@NonNull File directory) { - Timber.d("handleDirectoryCreate( %s )",directory.getAbsolutePath()); - final File parentFile = directory.getParentFile(); - if (parentFile == null) return; - - final String parentPath = getLocalPath(parentFile); - final SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, appContext); - if (parentFolder != null) { - final SyncedFolder folder = new SyncedFolder(parentFolder, directory.getName() + PATH_SEPARATOR, directory.lastModified(), ""); - DbHelper.insertSyncedFolder(folder, appContext); - } - } - - /** - * Handle CLOSE_WRITE event for a directory - * todo: check in which condition a directory can generate a close_write - * @param directory Directory that has been modified - */ - private void handleDirectoryCloseWrite(@NonNull File directory) { - final String fileLocalPath = getLocalPath(directory); - Timber.d("handleDirectoryCloseWrite( %s )",fileLocalPath ); - final SyncedFolder folder = DbHelper.getSyncedFolderByLocalPath(fileLocalPath, appContext); - if (folder == null) { - handleDirectoryCreate(directory); //todo check if really relevant - } else { //It's a directory update - folder.setLastModified(directory.lastModified()); - DbHelper.updateSyncedFolder(folder, appContext); - } - } - - /** - * Handle a file deletion event for a directory - * @param directory Directory that has been removed - */ - private void handleDirectoryDelete(@NonNull File directory) { - final String fileLocalPath = getLocalPath(directory); - Timber.d("handleDirectoryDelete( %s )", fileLocalPath); - SyncedFolder folder = DbHelper.getSyncedFolderByLocalPath(fileLocalPath, appContext); - if (folder == null) { - //look for parent - final File parentFile = directory.getParentFile(); - if (parentFile == null) return; - - final String parentPath = getLocalPath(parentFile); - final SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, appContext); - if (parentFolder == null ) { //if parent is not in the DB - return; - } - folder = new SyncedFolder(parentFolder, directory.getName() + PATH_SEPARATOR, directory.lastModified(), ""); - folder.setEnabled(false); - DbHelper.insertSyncedFolder(folder, appContext); - } else if (folder.isEnabled()) { - folder.setEnabled(false); - DbHelper.updateSyncedFolder(folder, appContext); - } - } - - /** - * handle a file close_write event for a file which is not a directory - * @param file File that has been modified - */ - private void handleFileCloseWrite(@NonNull File file) { - final String fileLocalPath = getLocalPath(file); - Timber.d("handleFileCloseWrite( %s )", fileLocalPath); - SyncRequest request = null; - SyncedFileState fileState = DbHelper.loadSyncedFile( appContext, fileLocalPath, true); - - if (fileState == null) { //New file discovered - final File parentFile = file.getParentFile(); - if (parentFile == null) return; - - final String parentPath = getLocalPath(file.getParentFile()); - SyncedFolder parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, appContext); - if (parentFolder == null || !parentFolder.isEnabled()) { - Timber.d("Won't send sync request: no parent are known for new file: %s", file.getName()); - return; - } - int scanScope = DO_NOT_SCAN; - if (parentFolder.isEnabled()) { - if (parentFolder.isScanRemote()) scanScope = SCAN_ON_CLOUD; - if (parentFolder.isScanLocal()) scanScope += SCAN_ON_DEVICE; - } - - final String remotePath = parentFolder.getRemoteFolder()+file.getName(); - fileState = new SyncedFileState(-1, file.getName(), getLocalPath(file), remotePath, "", 0L, parentFolder.getId(), parentFolder.isMediaType(), scanScope); - int storedId = DbHelper.manageSyncedFileStateDB(fileState, "INSERT", appContext); - if (storedId > 0) { - fileState.setId(storedId); - request = new SyncRequest(fileState, UPLOAD); - } else { - Timber.d("New File %s observed but impossible to insert it in DB", file.getName()); - } - } else { //File update - final boolean isWaitingForDownload = fileState.isLastEtagStored() && fileState.getLastModified() == 0L; - if (fileState.getScanScope() > SCAN_ON_CLOUD && !isWaitingForDownload) { - request = new SyncRequest(fileState, UPLOAD); - } - } - if (request != null) { - sendSyncRequestToSynchronizationService(request); - } - } - - /** - * Handle a file deletion event for a file which is not a directory - * @param file File that has been removed - */ - private void handleFileDelete(@NonNull File file) { - final String fileLocalPath = getLocalPath(file); - Timber.d("handleFileDelete( %s )",fileLocalPath); - final SyncedFileState fileState = DbHelper.loadSyncedFile( appContext, fileLocalPath, true); - if (fileState == null) { - return; //Todo #1: should we call handleDirectoryDelete before to return ? - } - - //If already in DB - if (fileState.getScanScope() > DO_NOT_SCAN) { - //todo: if file is already sync disabled, we should probably remove file from DB - final SyncRequest disableSyncingRequest = new SyncRequest(fileState, DISABLE_SYNCING); - this.sendSyncRequestToSynchronizationService(disableSyncingRequest); - } - } -} diff --git a/app/src/main/java/foundation/e/drive/FileObservers/RecursiveFileObserver.java b/app/src/main/java/foundation/e/drive/FileObservers/RecursiveFileObserver.java deleted file mode 100644 index 3095537c..00000000 --- a/app/src/main/java/foundation/e/drive/FileObservers/RecursiveFileObserver.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright © ECORP SAS 2022. - * 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.FileObservers; - -import android.content.Context; -import android.os.FileObserver; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.io.File; -import java.io.FileFilter; -import java.util.HashMap; -import java.util.List; -import java.util.Stack; - -import foundation.e.drive.database.DbHelper; -import foundation.e.drive.models.SyncedFolder; - -/** - * @author Narinder Rana - * @author Vincent Bourgmayer - */ -public class RecursiveFileObserver extends FileObserver { - private final HashMap observers = new HashMap<>(); - // protected to avoid SyntheticAccessor - protected static final FileFilter WATCHABLE_DIRECTORIES_FILTER = new FileFilter() { - @Override - public boolean accept(File file) { - return file.isDirectory() && !file.getName().startsWith("."); - } - }; - - private boolean watching = false; - private final Context applicationContext; - private String path; - private int mask; - private FileEventListener listener; - - public RecursiveFileObserver(@NonNull Context applicationContext, @NonNull String path, @Nullable FileEventListener listener) { - this(applicationContext, path, ALL_EVENTS, listener); - } - - public RecursiveFileObserver(@NonNull Context applicationContext, @NonNull String path, int mask, @Nullable FileEventListener listener) { - super(path, mask); - this.path = path; - this.mask = mask | FileObserver.CREATE | FileObserver.DELETE_SELF; - this.listener = listener; - this.applicationContext = applicationContext; - } - - - @Override - public void onEvent(int event, @Nullable String path) { - File file; - if (path == null) { - file = new File(this.path); - } else { - file = new File(this.path, path); - } - - notify(event, file); - } - - // protected to avoid SyntheticAccessor - protected void notify(int event, @NonNull File file) { - if (listener != null) { - listener.onEvent(event & FileObserver.ALL_EVENTS, file); - } - } - - @Override - public void startWatching() { - Stack stack = new Stack<>(); - - List mSyncedFolders = DbHelper.getAllSyncedFolders(applicationContext); - if (!mSyncedFolders.isEmpty()){ - for (SyncedFolder syncedFolder:mSyncedFolders){ - stack.push(syncedFolder.getLocalFolder()); - stack.push(syncedFolder.getRemoteFolder()); - } - watching = true; - } - - // Recursively watch all child directories - while (!stack.empty()) { - String parent = stack.pop(); - startWatching(parent); - - File path = new File(parent); - File[] files = path.listFiles(WATCHABLE_DIRECTORIES_FILTER); - if (files != null) { - for (File file : files) { - stack.push(file.getAbsolutePath()); - } - } - } - } - - /** - * Start watching a single file - * @param path - */ - protected void startWatching(@NonNull String path) { - synchronized (observers) { - FileObserver observer = observers.remove(path); - if (observer != null) { - observer.stopWatching(); - } - observer = new SingleFileObserver(path, mask); - observer.startWatching(); - observers.put(path, observer); - } - } - - @Override - public void stopWatching() { - for (FileObserver observer : observers.values()) { - observer.stopWatching(); - } - observers.clear(); - watching = false; - } - - /** - * Stop watching a single file - * @param path - */ - protected void stopWatching(@NonNull String path) { - synchronized (observers) { - FileObserver observer = observers.remove(path); - if (observer != null) { - observer.stopWatching(); - } - } - } - - public boolean isWatching(){ - return watching; - } - - private class SingleFileObserver extends FileObserver { - private String filePath; - - public SingleFileObserver(String path, int mask) { - super(path, mask); - filePath = path; - } - - @Override - public void onEvent(int event, @Nullable String path) { - File file; - if (path == null) { - file = new File(filePath); - } else { - file = new File(filePath, path); - } - - switch (event & FileObserver.ALL_EVENTS) { - case DELETE_SELF: - RecursiveFileObserver.this.stopWatching(filePath); - break; - case CREATE: - if (WATCHABLE_DIRECTORIES_FILTER.accept(file)) { - RecursiveFileObserver.this.startWatching(file.getAbsolutePath()); - } - break; - } - - RecursiveFileObserver.this.notify(event, file); - } - } -} diff --git a/app/src/main/java/foundation/e/drive/fileObservers/DirectoryObserver.kt b/app/src/main/java/foundation/e/drive/fileObservers/DirectoryObserver.kt new file mode 100644 index 00000000..30ef8055 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/fileObservers/DirectoryObserver.kt @@ -0,0 +1,41 @@ +/* + * Copyright © MURENA SAS 2023. + * 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.fileObservers + +import android.os.Build +import android.os.FileObserver +import androidx.annotation.RequiresApi +import foundation.e.drive.synchronization.StateMachine +import foundation.e.drive.synchronization.SyncState +import java.io.File + +@RequiresApi(Build.VERSION_CODES.Q) +class DirectoryObserver( + private val dirPath: String, + private val listener: FileEventListener +) : FileObserver(File(dirPath), EVENT_MASKS) { + + companion object { + const val EVENT_MASKS = (CLOSE_WRITE or CREATE + or MOVED_FROM or MOVED_TO or MOVE_SELF + or DELETE_SELF or DELETE) + } + + override fun onEvent(event: Int, path: String?) { + val syncState = StateMachine.currentState + if (syncState != SyncState.LISTENING_FILES) { + return + } + + val file = if (path == null) File(dirPath) else File(dirPath, path) + + // to retrieve actual event code, we need to apply conjunction operation with the all event code + listener.notify(event and ALL_EVENTS, file, dirPath) + } +} diff --git a/app/src/main/java/foundation/e/drive/fileObservers/FileEventHandler.kt b/app/src/main/java/foundation/e/drive/fileObservers/FileEventHandler.kt new file mode 100644 index 00000000..e93ea972 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/fileObservers/FileEventHandler.kt @@ -0,0 +1,244 @@ +/* + * Copyright © MURENA SAS 2023. + * Copyright © ECORP SAS 2022. + * 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.fileObservers + +import android.content.Context +import android.os.FileObserver +import com.owncloud.android.lib.resources.files.FileUtils.PATH_SEPARATOR +import foundation.e.drive.database.DbHelper +import foundation.e.drive.models.DO_NOT_SCAN +import foundation.e.drive.models.SCAN_ON_CLOUD +import foundation.e.drive.models.SCAN_ON_DEVICE +import foundation.e.drive.models.SyncRequest +import foundation.e.drive.models.SyncedFileState +import foundation.e.drive.models.SyncedFolder +import foundation.e.drive.synchronization.SyncProxy +import foundation.e.drive.utils.FileUtils.getLocalPath +import timber.log.Timber +import java.io.File + +/** + * @author Narinder Rana + * @author vincent Bourgmayer + * @author Fahim Salam Chowdhury + */ +class FileEventHandler(private val context: Context) { + + fun onEvent(event: Int, file: File) { + if (file.isDirectory) { + handleDirectoryEvent(event, file) + return + } + + handleFileEvent(event, file) + } + + /** + * Handle some file event for a file which is not a directory + * @param event the event mask. CLOSE_WRITE, DELETE & MOVE_SELF are handled + * @param file the file concerned by the event + */ + private fun handleFileEvent(event: Int, file: File) { + when (event) { + FileObserver.CLOSE_WRITE -> handleFileCloseWrite(file) + FileObserver.MOVED_TO -> handleFileMoveTo(file) + FileObserver.DELETE -> handleFileDelete(file) + } + } + + /** + * Handle FileEvent for a directory + * @param event FileEvent mask. CREATE, CLOSE_WRITE, DELETE, MOVE_SELF + * @param directory directory concerned by file event + */ + private fun handleDirectoryEvent(event: Int, directory: File) { + when (event) { + FileObserver.CREATE -> handleDirectoryCreate(directory) + FileObserver.CLOSE_WRITE -> handleDirectoryCloseWrite(directory) + FileObserver.DELETE, FileObserver.DELETE_SELF -> handleDirectoryDelete(directory) + } + } + + /** + * Send syncRequest to SynchronizationService + * @param request SyncRequest that should be executed asap + */ + private fun sendSyncRequestToSynchronizationService(request: SyncRequest) { + val requestAdded = SyncProxy.queueSyncRequest(request, context.applicationContext) + if (requestAdded) { + Timber.d("Sending a SyncRequest for ${request.syncedFileState.name}") + SyncProxy.startSynchronization(context) + } + } + + /** + * When a new directory is detected, it must be inserted in database + * if it's parent directory is already in the database + * @param directory Directory that has been created + */ + private fun handleDirectoryCreate(directory: File) { + Timber.d("handleDirectoryCreate( ${directory.absolutePath} )") + val parentFile = directory.parentFile ?: return + + val parentPath = getLocalPath(parentFile) + val parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, context) ?: return + val folder = SyncedFolder( + parentFolder, + directory.name + PATH_SEPARATOR, + directory.lastModified(), + "" + ) + DbHelper.insertSyncedFolder(folder, context) + } + + /** + * Handle CLOSE_WRITE event for a directory + * todo: check in which condition a directory can generate a close_write + * @param directory Directory that has been modified + */ + private fun handleDirectoryCloseWrite(directory: File) { + val fileLocalPath = getLocalPath(directory) + Timber.d("handleDirectoryCloseWrite( $fileLocalPath )") + val folder = DbHelper.getSyncedFolderByLocalPath(fileLocalPath, context) + if (folder == null) { + handleDirectoryCreate(directory) //todo check if really relevant + } else { //It's a directory update + folder.setLastModified(directory.lastModified()) + DbHelper.updateSyncedFolder(folder, context) + } + } + + /** + * Handle a file deletion event for a directory + * @param directory Directory that has been removed + */ + private fun handleDirectoryDelete(directory: File) { + val fileLocalPath = getLocalPath(directory) + Timber.d("handleDirectoryDelete( $fileLocalPath )") + val folder = DbHelper.getSyncedFolderByLocalPath(fileLocalPath, context) + if (folder == null) { + insertDirDeleteRecordInDB(directory) + } else if (folder.isEnabled) { + folder.setEnabled(false) + DbHelper.updateSyncedFolder(folder, context) + } + } + + private fun insertDirDeleteRecordInDB(directory: File) { + //look for parent + val parentFile = directory.parentFile ?: return + + val parentPath = getLocalPath(parentFile) + val parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, context) ?: return + + val folder = SyncedFolder( + parentFolder, + directory.name + PATH_SEPARATOR, + directory.lastModified(), + "" + ) + folder.setEnabled(false) + DbHelper.insertSyncedFolder(folder, context) + } + + /** + * handle a file close_write event for a file which is not a directory + * @param file File that has been modified + */ + private fun handleFileCloseWrite(file: File) { + val fileLocalPath = getLocalPath(file) + Timber.d("handleFileCloseWrite( $fileLocalPath )") + var request: SyncRequest? = null + val fileState = DbHelper.loadSyncedFile(context, fileLocalPath, true) + + if (fileState == null) { //New file discovered + request = handleNewFileCreation(file) + } else { //File update + val isWaitingForDownload = fileState.isLastEtagStored() && fileState.lastModified == 0L + if (fileState.scanScope > SCAN_ON_CLOUD && !isWaitingForDownload) { + request = SyncRequest(fileState, SyncRequest.Type.UPLOAD) + } + } + + request?.let { + sendSyncRequestToSynchronizationService(it) + } + } + + private fun handleFileMoveTo(file: File) { + val fileLocalPath = getLocalPath(file) + Timber.d("handleFileMoveTo( $fileLocalPath )") + var request: SyncRequest? = null + val fileState = DbHelper.loadSyncedFile(context, fileLocalPath, true) + + if (fileState == null) { //New file discovered + request = handleNewFileCreation(file) + } + + request?.let { + sendSyncRequestToSynchronizationService(it) + } + } + + private fun handleNewFileCreation(file: File): SyncRequest? { + val parentFile = file.parentFile ?: return null + + val parentPath = getLocalPath(parentFile) + val parentFolder = DbHelper.getSyncedFolderByLocalPath(parentPath, context) + if (parentFolder == null || !parentFolder.isEnabled) { + Timber.d("Won't send sync request: no parent are known for new file: %s", file.name) + return null + } + + var scanScope = DO_NOT_SCAN + if (parentFolder.isEnabled) { + if (parentFolder.isScanRemote) scanScope = SCAN_ON_CLOUD + if (parentFolder.isScanLocal) scanScope += SCAN_ON_DEVICE + } + + val remotePath = parentFolder.remoteFolder + file.name + val fileState = SyncedFileState( + -1, file.name, getLocalPath(file), remotePath, "", 0L, + parentFolder.id.toLong(), parentFolder.isMediaType, scanScope + ) + return insertNewFileStateIntoDB(fileState, file) + } + + private fun insertNewFileStateIntoDB( + fileState: SyncedFileState, + file: File + ): SyncRequest? { + val storedId = DbHelper.manageSyncedFileStateDB(fileState, "INSERT", context) + if (storedId > 0) { + fileState.id = storedId + return SyncRequest(fileState, SyncRequest.Type.UPLOAD) + } + + Timber.d("New File ${file.name} observed but impossible to insert it in DB") + return null + } + + /** + * Handle a file deletion event for a file which is not a directory + * @param file File that has been removed + */ + private fun handleFileDelete(file: File) { + val fileLocalPath = getLocalPath(file) + Timber.d("handleFileDelete( $fileLocalPath )") + val fileState = DbHelper.loadSyncedFile(context, fileLocalPath, true) ?: return + + //If already in DB + if (fileState.scanScope > DO_NOT_SCAN) { + //todo: if file is already sync disabled, we should probably remove file from DB + val disableSyncingRequest = SyncRequest(fileState, SyncRequest.Type.DISABLE_SYNCING) + sendSyncRequestToSynchronizationService(disableSyncingRequest) + } + } +} diff --git a/app/src/main/java/foundation/e/drive/fileObservers/FileEventListener.kt b/app/src/main/java/foundation/e/drive/fileObservers/FileEventListener.kt new file mode 100644 index 00000000..f75a95f7 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/fileObservers/FileEventListener.kt @@ -0,0 +1,15 @@ +/* + * Copyright © MURENA SAS 2023. + * 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.fileObservers + +import java.io.File + +interface FileEventListener { + fun notify(event: Int, file: File, dirPath: String) +} diff --git a/app/src/main/java/foundation/e/drive/fileObservers/FileObserverManager.kt b/app/src/main/java/foundation/e/drive/fileObservers/FileObserverManager.kt new file mode 100644 index 00000000..39bb91fa --- /dev/null +++ b/app/src/main/java/foundation/e/drive/fileObservers/FileObserverManager.kt @@ -0,0 +1,144 @@ +/* + * Copyright © MURENA SAS 2023. + * Copyright © ECORP SAS 2022. + * 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.fileObservers + +import android.content.Context +import android.os.Build +import android.os.FileObserver +import foundation.e.drive.database.DbHelper +import foundation.e.drive.models.SyncedFolder +import timber.log.Timber +import java.io.File +import java.io.FileFilter +import java.util.Stack + +/** + * @author Narinder Rana + * @author Vincent Bourgmayer + * @author Fahim Salam Chowdhury + */ +class FileObserverManager(private val context: Context) : FileEventListener { + + companion object { + val WATCHABLE_DIRECTORIES_FILTER = FileFilter { + it.isDirectory && !it.isHidden + } + } + + private val observers = HashMap() + private val handler = FileEventHandler(context) + + private var watching = false + + override fun notify(event: Int, file: File, dirPath: String) { + if (file.isHidden) { + return + } + + Timber.d("notified for the file ${file.absolutePath} with event: $event dirPath: $dirPath") + + when (event) { + FileObserver.DELETE_SELF -> stopWatching(dirPath) + FileObserver.CREATE -> startWatchingDirectory(file) + } + + handler.onEvent(event, file) + } + + fun initializeObserving() { + if (watching) { + return + } + + stopObserving() + + val syncedFolders = DbHelper.getAllSyncedFolders(context) + if (syncedFolders.isEmpty()) { + return + } + + val stack = Stack() + syncedFolders.map(SyncedFolder::getLocalFolder) + .forEach(stack::push) + + watching = true + + // Recursively watch all child directories + recursivelyWatchAllDirectories(stack) + + Timber.d("started observing all syncable directories") + } + + private fun recursivelyWatchAllDirectories(stack: Stack) { + while (!stack.empty()) { + val parent = stack.pop() + startWatching(parent) + + val path = File(parent) + val files = path.listFiles(WATCHABLE_DIRECTORIES_FILTER) ?: continue + + files.map(File::getAbsolutePath) + .forEach(stack::push) + } + } + + private fun startWatchingDirectory(file: File) { + if (WATCHABLE_DIRECTORIES_FILTER.accept(file)) { + startWatching(file.absolutePath) + } + } + + /** + * Start watching a single file + */ + private fun startWatching(path: String) { + synchronized(observers) { + stopObservingFile(path) + startObservingFile(path) + } + } + + private fun stopObservingFile(path: String) { + observers.remove(path)?.let { + it.stopWatching() + Timber.d("stop observing file: $path") + } + } + + private fun startObservingFile(path: String) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return + } + + val observer = DirectoryObserver(path, this) + observer.startWatching() + observers[path] = observer + Timber.d("start observing file: $path") + } + + fun stopObserving() { + observers.forEach { + it.value.stopWatching() + } + observers.clear() + watching = false + + Timber.d("stopped observing all syncable directories") + } + + /** + * Stop watching a single file + */ + private fun stopWatching(path: String) { + synchronized(observers) { + stopObservingFile(path) + } + } +} diff --git a/app/src/main/java/foundation/e/drive/synchronization/SyncProxy.kt b/app/src/main/java/foundation/e/drive/synchronization/SyncProxy.kt index bd2df430..dbd8f5f6 100644 --- a/app/src/main/java/foundation/e/drive/synchronization/SyncProxy.kt +++ b/app/src/main/java/foundation/e/drive/synchronization/SyncProxy.kt @@ -119,7 +119,7 @@ object SyncProxy: SyncRequestCollector, SyncManager { if (syncRequestQueue.isEmpty()) { Timber.d("Request queue is empty") - StateMachine.changeState(SyncState.LISTENING_FILES) + startListeningFiles(context) return } @@ -128,9 +128,7 @@ object SyncProxy: SyncRequestCollector, SyncManager { if (!isStateChanged) return - if (previousSyncState == SyncState.PERIODIC_SCAN) { - context.startRecursiveFileObserver() - } + context.startRecursiveFileObserver() if (previousSyncState != SyncState.SYNCHRONIZING) { WorkerUtils.enqueueOneTimeSync(WorkManager.getInstance(context)) @@ -151,8 +149,6 @@ object SyncProxy: SyncRequestCollector, SyncManager { val isStateChanged = StateMachine.changeState(SyncState.PERIODIC_SCAN) if (!isStateChanged) return false - application.stopRecursiveFileObserver() - return true } @@ -167,14 +163,8 @@ object SyncProxy: SyncRequestCollector, SyncManager { return } - val previousSyncState = StateMachine.currentState - val isStateChanged = StateMachine.changeState(SyncState.LISTENING_FILES) - - if (!isStateChanged) return - - if (previousSyncState == SyncState.IDLE || previousSyncState == SyncState.PERIODIC_SCAN) { - application.startRecursiveFileObserver() - } + StateMachine.changeState(SyncState.LISTENING_FILES) + application.startRecursiveFileObserver() } -- GitLab From fff64cf66fd0d148a3ee8839348756b34bbb03f1 Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Tue, 26 Dec 2023 11:41:34 +0000 Subject: [PATCH 05/24] 942-Fix_wrong_corruptTimestamp_issue --- .../periodicScan/contentScanner/FileDiffUtils.kt | 8 ++++---- .../synchronization/tasks/DownloadFileOperation.java | 10 +++++++--- .../java/foundation/e/drive/utils/AppConstants.kt | 2 +- .../periodicScan/contentScanner/FileDiffUtilsTest.kt | 11 ++++++----- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/periodicScan/contentScanner/FileDiffUtils.kt b/app/src/main/java/foundation/e/drive/periodicScan/contentScanner/FileDiffUtils.kt index 41e34693..5019cfbf 100644 --- a/app/src/main/java/foundation/e/drive/periodicScan/contentScanner/FileDiffUtils.kt +++ b/app/src/main/java/foundation/e/drive/periodicScan/contentScanner/FileDiffUtils.kt @@ -24,7 +24,7 @@ object FileDiffUtils { @JvmStatic fun getActionForFileDiff(remoteFile: RemoteFile, fileState: SyncedFileState): Action { if (!hasEtagChanged(remoteFile, fileState)) { - if (isCorruptedTimestamp(remoteFile.modifiedTimestamp / 1000)) return Action.Upload + if (isCorruptedTimestamp(remoteFile.modifiedTimestamp)) return Action.Upload if (hasAlreadyBeenDownloaded(fileState)) return Action.Skip } @@ -93,13 +93,13 @@ object FileDiffUtils { * * For yet unknown reason, some remote files have this value on cloud (DB & file system) * the only way to fix them is to force re upload of the file with correct value - * @param timestampInSecond remote file timestamp (Long) + * @param timestampInMillisecond remote file timestamp (Long) * @return true if the timestamp is equal to max of unsigned int 32 */ @VisibleForTesting @JvmStatic - fun isCorruptedTimestamp(timestampInSecond: Long): Boolean { - return timestampInSecond >= AppConstants.CORRUPTED_TIMESTAMP_IN_SECOND + fun isCorruptedTimestamp(timestampInMillisecond: Long): Boolean { + return timestampInMillisecond >= AppConstants.CORRUPTED_TIMESTAMP_IN_MILLISECOND } /** diff --git a/app/src/main/java/foundation/e/drive/synchronization/tasks/DownloadFileOperation.java b/app/src/main/java/foundation/e/drive/synchronization/tasks/DownloadFileOperation.java index 4874e460..7399d54d 100644 --- a/app/src/main/java/foundation/e/drive/synchronization/tasks/DownloadFileOperation.java +++ b/app/src/main/java/foundation/e/drive/synchronization/tasks/DownloadFileOperation.java @@ -19,7 +19,7 @@ import static com.owncloud.android.lib.common.operations.RemoteOperationResult.R import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; -import static foundation.e.drive.utils.AppConstants.CORRUPTED_TIMESTAMP_IN_SECOND; +import static foundation.e.drive.utils.AppConstants.CORRUPTED_TIMESTAMP_IN_MILLISECOND; import android.content.Context; import android.media.MediaScannerConnection; @@ -159,7 +159,7 @@ public class DownloadFileOperation extends RemoteOperation { return FORBIDDEN; } - if (remoteFile.getModifiedTimestamp() < CORRUPTED_TIMESTAMP_IN_SECOND) { + if (remoteFile.getModifiedTimestamp() < CORRUPTED_TIMESTAMP_IN_MILLISECOND) { targetFile.setLastModified(remoteFile.getModifiedTimestamp()); } syncedFileState.setLastModified(targetFile.lastModified()); @@ -185,10 +185,14 @@ public class DownloadFileOperation extends RemoteOperation { final Path tmpPath = tmpFile.toPath(); final Path copyResult = Files.copy(tmpPath, targetPath, REPLACE_EXISTING); - if (copyResult.toFile().length() == tmpFile.length()) { + final File copyResultFile = copyResult.toFile(); + + if (copyResultFile.length() == tmpFile.length()) { tmpFile.delete(); return true; } + + copyResultFile.delete(); } catch (NoSuchFileException exception) { Timber.w(exception); } catch (IOException | SecurityException | NullPointerException exception) { diff --git a/app/src/main/java/foundation/e/drive/utils/AppConstants.kt b/app/src/main/java/foundation/e/drive/utils/AppConstants.kt index f3721e60..99423f99 100644 --- a/app/src/main/java/foundation/e/drive/utils/AppConstants.kt +++ b/app/src/main/java/foundation/e/drive/utils/AppConstants.kt @@ -38,7 +38,7 @@ object AppConstants { const val notificationChannelID = "foundation.e.drive" const val WORK_GENERIC_TAG = "eDrive" const val WORK_SETUP_TAG = "eDrive-init" - const val CORRUPTED_TIMESTAMP_IN_SECOND = 4294967295L + const val CORRUPTED_TIMESTAMP_IN_MILLISECOND = 4294967295000L @JvmField val USER_AGENT = "eos(" + buildTime + ")-eDrive(" + BuildConfig.VERSION_NAME + ")" diff --git a/app/src/test/java/foundation/e/drive/periodicScan/contentScanner/FileDiffUtilsTest.kt b/app/src/test/java/foundation/e/drive/periodicScan/contentScanner/FileDiffUtilsTest.kt index f078df1d..f94fdebf 100644 --- a/app/src/test/java/foundation/e/drive/periodicScan/contentScanner/FileDiffUtilsTest.kt +++ b/app/src/test/java/foundation/e/drive/periodicScan/contentScanner/FileDiffUtilsTest.kt @@ -10,6 +10,7 @@ package foundation.e.drive.periodicScan.contentScanner import com.owncloud.android.lib.resources.files.model.RemoteFile import foundation.e.drive.models.SyncedFileState import foundation.e.drive.periodicScan.contentScanner.FileDiffUtils +import foundation.e.drive.utils.AppConstants import org.junit.Assert import org.junit.Test import org.mockito.Mockito @@ -294,21 +295,21 @@ internal class FileDiffUtilsTest { /* isCorruptedTimestamp for localFile */ @Test fun `isCorruptedTimestamp() return true with timestamp equal to max of Int32 `() { - val corruptedTimestamp = 4294967295L + val corruptedTimestamp = AppConstants.CORRUPTED_TIMESTAMP_IN_MILLISECOND val resultUnderTest = FileDiffUtils.isCorruptedTimestamp(corruptedTimestamp) - Assert.assertTrue("isCorruptedTimestamp(4294967295L) returned $resultUnderTest instead of true", resultUnderTest) + Assert.assertTrue("isCorruptedTimestamp(4294967295000L) returned $resultUnderTest instead of true", resultUnderTest) } @Test fun `isCorruptedTimestamp() return true with timestamp bigger than max of Int32 `() { - val corruptedTimestamp = 4294967295L + 1 + val corruptedTimestamp = AppConstants.CORRUPTED_TIMESTAMP_IN_MILLISECOND + 1 val resultUnderTest = FileDiffUtils.isCorruptedTimestamp(corruptedTimestamp) - Assert.assertTrue("isCorruptedTimestamp(4294967295L) returned $resultUnderTest instead of true", resultUnderTest) + Assert.assertTrue("isCorruptedTimestamp(4294967295000L) returned $resultUnderTest instead of true", resultUnderTest) } @Test fun `isCorruptedTimestamp() return false with timestamp smaller than max of Int32 `() { - val corruptedTimestamp = 4294967295L - 1 + val corruptedTimestamp = AppConstants.CORRUPTED_TIMESTAMP_IN_MILLISECOND - 1 val resultUnderTest = FileDiffUtils.isCorruptedTimestamp(corruptedTimestamp) Assert.assertFalse("isCorruptedTimestamp(4294967295000L) returned $resultUnderTest instead of false", resultUnderTest) } -- GitLab From 15a7cbf49fc397c52f6de674b0e397924baf4102 Mon Sep 17 00:00:00 2001 From: Vincent Bourgmayer Date: Thu, 28 Dec 2023 11:04:20 +0000 Subject: [PATCH 06/24] 1811 fix test error --- README.md | 15 +++++++++++++ app/src/main/AndroidManifest.xml | 1 + .../e/drive/receivers/DebugCmdReceiver.java | 19 ++++++++++++++++ dev_tools.sh | 22 ++++++++++++++++++- 4 files changed, 56 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c7d430fa..b3b3b48d 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,21 @@ adb logcat --pid=$(adb shell pidof -s foundation.e.drive) You can also use the script `dev-tools.sh` to run those command. Use : `./dev-tools.sh -h` to display options + +**Disable File Observer** + + +```bash +adb shell am broadcast -a foundation.e.drive.action.ENABLE_FILE_OBSERVER --receiver-include-background --ez file_observer_enable false +``` + +**Enable File Observer** + + +```bash +adb shell am broadcast -a foundation.e.drive.action.ENABLE_FILE_OBSERVER --receiver-include-background --ez file_observer_enable true +``` + ### local NC for testing Use following documentation to set up a local NC instance diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b4bdbf18..6d5f4569 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -105,6 +105,7 @@ + diff --git a/app/src/main/java/foundation/e/drive/receivers/DebugCmdReceiver.java b/app/src/main/java/foundation/e/drive/receivers/DebugCmdReceiver.java index d1c743c2..2235d838 100644 --- a/app/src/main/java/foundation/e/drive/receivers/DebugCmdReceiver.java +++ b/app/src/main/java/foundation/e/drive/receivers/DebugCmdReceiver.java @@ -21,7 +21,9 @@ import java.util.ArrayList; import java.util.List; import foundation.e.drive.EdriveApplication; +import foundation.e.drive.account.AccountUtils; import foundation.e.drive.database.DbHelper; +import foundation.e.drive.fileObservers.FileObserverManager; import foundation.e.drive.models.SyncRequest; import foundation.e.drive.models.SyncedFileState; import foundation.e.drive.models.SyncedFolder; @@ -41,7 +43,10 @@ public class DebugCmdReceiver extends BroadcastReceiver { public static final String ACTION_FORCE_SCAN = "foundation.e.drive.action.FORCE_SCAN"; public static final String ACTION_DUMP_DATABASE = "foundation.e.drive.action.DUMP_DATABASE"; public static final String ACTION_FULL_LOG_ON_PROD = "foundation.e.drive.action.FULL_LOG_ON_PROD"; + public static final String ACTION_ENABLE_FILE_OBSERVER = "foundation.e.drive.action.ENABLE_FILE_OBSERVER"; + private static final String FULL_LOG_ENABLE_KEY = "full_log_enable"; + private static final String FILE_OBSERVER_ENABLE_KEY = "file_observer_enable"; @Override public void onReceive(@NonNull Context context, @NonNull Intent intent) { @@ -62,6 +67,20 @@ public class DebugCmdReceiver extends BroadcastReceiver { ReleaseTree.allowDebugLogOnProd(allow_full_log); Timber.d("Allow full log on prod: %s", allow_full_log); break; + case ACTION_ENABLE_FILE_OBSERVER: + if (AccountUtils.getAccount(context) == null) { + Timber.d("Ignore intent: no account"); + } + + final EdriveApplication application = (EdriveApplication) context.getApplicationContext(); + final boolean enabled = intent.getBooleanExtra(FILE_OBSERVER_ENABLE_KEY, true); + Timber.i("Intent received: enable FileObserver: %s", enabled); + if (enabled) { + application.startRecursiveFileObserver(); + } else { + application.stopRecursiveFileObserver(); + } + break; case ACTION_TEST_SYNC: Timber.d("Test SyncWorker.kt started"); final SyncRequestCollector collector = (SyncRequestCollector) SyncProxy.INSTANCE; diff --git a/dev_tools.sh b/dev_tools.sh index 5f7d3e60..442f5de7 100755 --- a/dev_tools.sh +++ b/dev_tools.sh @@ -15,6 +15,8 @@ function printHelp() { -h : print this help -L : (required value: true|false) : true: enable/ false: disable full log -l : (option: --clear) : --clear: clear logcat before. Otherwise just display logcat. Default: false + -n : Start nextcloud docker instance + -o : (required value: true|false) true: enable File Observer / false: disable File Observer -p : Pull database directory from device into current computer folder -s : test sync : Create 10 empty files in 'Document' folder and try to upload them "; @@ -79,7 +81,23 @@ function testSync() { adb shell am broadcast -a foundation.e.drive.action.TEST_SYNC --receiver-include-background } -while getopts acCdfhL:lps flag +function startNCDocker() { + docker run -d -p 8080:80 -e NEXTCLOUD_TRUSTED_DOMAINS=":8080" -e SQLITE_DATABASE=nc -e NEXTCLOUD_ADMIN_USER=admin -e NEXTCLOUD_ADMIN_PASSWORD=admin nextcloud:26 +} + +function enableFileObserver() { + if [ -z "$1" ]; then + echo "The parameter -L require a value (true|false)" + elif [ "$1" != "true" ] && [ "$1" != "false" ]; then + echo "Invalid parameter: $1. You need to provide: (true|false)" + else + echo "Sending broacast with $1 as parameter" + adb shell am broadcast -a foundation.e.drive.action.ENABLE_FILE_OBSERVER --receiver-include-background --ez file_observer_enable "$1" + fi +} + + +while getopts acCdfhL:lnops flag do case "${flag}" in a) registerAccount;; @@ -90,6 +108,8 @@ do h) printHelp;; l) displayLogcat;; L) setFullLog ${OPTARG};; + n) startNCDocker;; + o) enableFileObserver ${OPTARG};; p) pullDatabase;; s) testSync;; esac -- GitLab From 0b5839abd4996ceb878de801794da4d5378ec8d8 Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Wed, 10 Jan 2024 04:13:54 +0000 Subject: [PATCH 07/24] fix refactor : ignore .part files from syncing refactor FileFilter implementation issue: https://gitlab.e.foundation/e/os/backlog/-/issues/1712 --- .../fileFilters/AppSettingsFileFilter.java | 27 ------- .../fileFilters/CrashlogsFileFilter.java | 56 -------------- .../drive/fileFilters/FileFilterFactory.java | 40 ---------- .../e/drive/fileFilters/FileFilterFactory.kt | 73 +++++++++++++++++++ .../e/drive/fileFilters/MediaFileFilter.java | 29 -------- .../drive/fileFilters/NoCacheFileFilter.java | 22 ------ .../e/drive/fileFilters/OnlyFileFilter.java | 25 ------- .../drive/fileFilters/SettingsFileFilter.java | 34 --------- .../e/drive/fileObservers/FileEventHandler.kt | 5 ++ .../fileObservers/FileObserverManager.kt | 3 +- .../contentScanner/LocalFileLister.java | 2 +- .../foundation/e/drive/utils/FileUtils.kt | 6 ++ 12 files changed, 87 insertions(+), 235 deletions(-) delete mode 100644 app/src/main/java/foundation/e/drive/fileFilters/AppSettingsFileFilter.java delete mode 100644 app/src/main/java/foundation/e/drive/fileFilters/CrashlogsFileFilter.java delete mode 100644 app/src/main/java/foundation/e/drive/fileFilters/FileFilterFactory.java create mode 100644 app/src/main/java/foundation/e/drive/fileFilters/FileFilterFactory.kt delete mode 100644 app/src/main/java/foundation/e/drive/fileFilters/MediaFileFilter.java delete mode 100644 app/src/main/java/foundation/e/drive/fileFilters/NoCacheFileFilter.java delete mode 100644 app/src/main/java/foundation/e/drive/fileFilters/OnlyFileFilter.java delete mode 100644 app/src/main/java/foundation/e/drive/fileFilters/SettingsFileFilter.java diff --git a/app/src/main/java/foundation/e/drive/fileFilters/AppSettingsFileFilter.java b/app/src/main/java/foundation/e/drive/fileFilters/AppSettingsFileFilter.java deleted file mode 100644 index 956efe44..00000000 --- a/app/src/main/java/foundation/e/drive/fileFilters/AppSettingsFileFilter.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright © CLEUS SAS 2018-2019. - * Copyright © ECORP SAS 2020. - * 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.fileFilters; - -import java.io.File; -import java.io.FileFilter; -import java.util.Locale; - -/** - * @author Vincent Bourgmayer - * FileFilter for application Settings - */ -class AppSettingsFileFilter implements FileFilter { - @Override - public boolean accept(File pathname) { - return pathname.isFile() - || (pathname.isDirectory() - && !pathname.getName().toLowerCase(Locale.ROOT).contains("cache")); - } -} diff --git a/app/src/main/java/foundation/e/drive/fileFilters/CrashlogsFileFilter.java b/app/src/main/java/foundation/e/drive/fileFilters/CrashlogsFileFilter.java deleted file mode 100644 index 6f28fddc..00000000 --- a/app/src/main/java/foundation/e/drive/fileFilters/CrashlogsFileFilter.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright © CLEUS SAS 2018-2019. - * Copyright © ECORP SAS 2020. - * 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.fileFilters; - -import androidx.annotation.NonNull; - -import java.io.File; -import java.io.FileFilter; - -import foundation.e.drive.utils.ServiceExceptionHandler; - -/** - * @Author Vincent Bourgmayer - */ -public class CrashlogsFileFilter implements FileFilter { - private final static long max_timestamp_delta = 864000000; //10 days in ms (240*3600*1000) - - @Override - public boolean accept(@NonNull File pathname) { - String fileTimestamp = extractTimestamp(pathname.getName(), - ServiceExceptionHandler.LOG_FILE_NAME_PREFIX, - ServiceExceptionHandler.LOG_FILE_EXTENSION); - - long timestamp; - try { - timestamp = Long.parseLong(fileTimestamp); - }catch (NumberFormatException e){ - //Can't parse the extracted timestamp - //This file has not the expected name. It must be removed - return true; - } - - //if current Date - file date >= max deta allowed - return ((System.currentTimeMillis() - timestamp ) >= max_timestamp_delta); - } - - /** - * Extract the timestamp from the name of the file - * UnitTested! - * @param fileName Filename - * @param prefix prefix to ignore - * @param extension extension to ignore - * @return the timestamp extracted from the name - */ - private String extractTimestamp(String fileName, @NonNull String prefix, @NonNull String extension){ - return fileName.substring(prefix.length(), (fileName.length() - extension.length())); - } - -} \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/fileFilters/FileFilterFactory.java b/app/src/main/java/foundation/e/drive/fileFilters/FileFilterFactory.java deleted file mode 100644 index f0153965..00000000 --- a/app/src/main/java/foundation/e/drive/fileFilters/FileFilterFactory.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright © CLEUS SAS 2018-2019. - * Copyright © ECORP SAS 2020. - * 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.fileFilters; - -import androidx.annotation.NonNull; - -import java.io.FileFilter; - -/** - * @author Vincent Bourgmayer - */ -public class FileFilterFactory { - - @NonNull - public static FileFilter getFileFilter(@NonNull String category){ - FileFilter filter; - switch (category){ - case "Rom settings": - filter = new SettingsFileFilter(); - break; - case "Applications": - filter = new AppSettingsFileFilter(); - break; - case "media": - filter = new MediaFileFilter(); - break; - default: - filter = new NoCacheFileFilter(); - break; - } - return filter; - } -} diff --git a/app/src/main/java/foundation/e/drive/fileFilters/FileFilterFactory.kt b/app/src/main/java/foundation/e/drive/fileFilters/FileFilterFactory.kt new file mode 100644 index 00000000..cf7fd21e --- /dev/null +++ b/app/src/main/java/foundation/e/drive/fileFilters/FileFilterFactory.kt @@ -0,0 +1,73 @@ +/* + * Copyright MURENA SAS 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 + * (at your option) any later version. + * + * 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 foundation.e.drive.fileFilters; + +import foundation.e.drive.utils.AppConstants +import foundation.e.drive.utils.FileUtils +import java.io.File +import java.io.FileFilter + +/** + * @author Vincent Bourgmayer + * @author Fahim Salam Chowdhury + */ +object FileFilterFactory { + + fun buildFileFilter(category: String?): FileFilter { + return when (category) { + "Rom settings" -> buildSettingsFilter() + "Applications" -> buildAppSettingsFilter() + "media" -> BasicFileFilter() + else -> buildNoCacheFileFilter() + } + } + + private fun buildSettingsFilter(): FileFilter { + return object : BasicFileFilter() { + override fun accept(pathname: File?): Boolean { + return super.accept(pathname) && pathname!!.isFile && + (pathname.name.startsWith("settings_") && pathname.name.endsWith(".xml") + || pathname.name == AppConstants.APPLICATIONS_LIST_FILE_NAME) + } + } + } + + private fun buildAppSettingsFilter(): FileFilter { + return object : BasicFileFilter() { + override fun accept(pathname: File?): Boolean { + return (super.accept(pathname) + && (pathname!!.isFile || (pathname.isDirectory + && !pathname.name.lowercase().contains("cache")))) + } + } + } + + private fun buildNoCacheFileFilter(): FileFilter { + return object : BasicFileFilter() { + override fun accept(pathname: File?): Boolean { + return super.accept(pathname) && + !pathname!!.name.lowercase().contains("cache") + } + } + } +} + +private open class BasicFileFilter : FileFilter { + override fun accept(pathname: File?): Boolean { + return pathname != null && !pathname.isHidden && FileUtils.isNotPartFile(pathname) + } +} diff --git a/app/src/main/java/foundation/e/drive/fileFilters/MediaFileFilter.java b/app/src/main/java/foundation/e/drive/fileFilters/MediaFileFilter.java deleted file mode 100644 index 6ac1740a..00000000 --- a/app/src/main/java/foundation/e/drive/fileFilters/MediaFileFilter.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright © CLEUS SAS 2018-2019. - * Copyright © ECORP SAS 2020. - * 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.fileFilters; - -import java.io.File; -import java.io.FileFilter; - -/** - * @author Vincent Bourgmayer - */ -class MediaFileFilter implements FileFilter { - /** - * Only accept not hidden files: - * Media should not be synced if they're hidden files - * @param file File to check - * @return true if file is accepted - */ - @Override - public boolean accept(File file) { - return !file.isHidden(); - } -} diff --git a/app/src/main/java/foundation/e/drive/fileFilters/NoCacheFileFilter.java b/app/src/main/java/foundation/e/drive/fileFilters/NoCacheFileFilter.java deleted file mode 100644 index 075f7d92..00000000 --- a/app/src/main/java/foundation/e/drive/fileFilters/NoCacheFileFilter.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright © CLEUS SAS 2018-2019. - * 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.fileFilters; - -import java.io.File; -import java.io.FileFilter; -import java.util.Locale; - -/** - * @author Vincent Bourgmayer - */ -class NoCacheFileFilter implements FileFilter { - @Override - public boolean accept(File pathname) { - return ( ! pathname.getName().toLowerCase(Locale.ROOT).contains("cache") ); - } -} diff --git a/app/src/main/java/foundation/e/drive/fileFilters/OnlyFileFilter.java b/app/src/main/java/foundation/e/drive/fileFilters/OnlyFileFilter.java deleted file mode 100644 index a5b4b32d..00000000 --- a/app/src/main/java/foundation/e/drive/fileFilters/OnlyFileFilter.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright © CLEUS SAS 2018-2019. - * Copyright © ECORP SAS 2020. - * 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.fileFilters; - -import androidx.annotation.NonNull; - -import java.io.File; -import java.io.FileFilter; - -/** - * @author Vincent Bourgmayer - */ -public class OnlyFileFilter implements FileFilter { - @Override - public boolean accept(@NonNull File pathname) { - return (!pathname.isDirectory()); - } -} diff --git a/app/src/main/java/foundation/e/drive/fileFilters/SettingsFileFilter.java b/app/src/main/java/foundation/e/drive/fileFilters/SettingsFileFilter.java deleted file mode 100644 index 6c142079..00000000 --- a/app/src/main/java/foundation/e/drive/fileFilters/SettingsFileFilter.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright © CLEUS SAS 2018-2019. - * Copyright © ECORP SAS 2020. - * 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.fileFilters; - -import java.io.File; -import java.io.FileFilter; -import foundation.e.drive.utils.AppConstants; - -/** - * @author Vincent Bourgmayer - * @author Narinder Rana - * Filter for Device settings synchronisation - */ - class SettingsFileFilter implements FileFilter { - /** - * Only accept file with name beginning by "settings_" and ending with ".xml". - * @param pathName path to analyze - * @return True if accepted or false - */ - @Override - public boolean accept(File pathName) { - String name = pathName.getName(); - return ( pathName.isFile() && - ( ( name.startsWith("settings_") && name.endsWith(".xml") ) - || name.equals( AppConstants.APPLICATIONS_LIST_FILE_NAME ) ) ); - } -} diff --git a/app/src/main/java/foundation/e/drive/fileObservers/FileEventHandler.kt b/app/src/main/java/foundation/e/drive/fileObservers/FileEventHandler.kt index e93ea972..480fc48b 100644 --- a/app/src/main/java/foundation/e/drive/fileObservers/FileEventHandler.kt +++ b/app/src/main/java/foundation/e/drive/fileObservers/FileEventHandler.kt @@ -20,6 +20,7 @@ import foundation.e.drive.models.SyncRequest import foundation.e.drive.models.SyncedFileState import foundation.e.drive.models.SyncedFolder import foundation.e.drive.synchronization.SyncProxy +import foundation.e.drive.utils.FileUtils import foundation.e.drive.utils.FileUtils.getLocalPath import timber.log.Timber import java.io.File @@ -32,6 +33,10 @@ import java.io.File class FileEventHandler(private val context: Context) { fun onEvent(event: Int, file: File) { + if (FileUtils.isPartFile(file)) { + return + } + if (file.isDirectory) { handleDirectoryEvent(event, file) return diff --git a/app/src/main/java/foundation/e/drive/fileObservers/FileObserverManager.kt b/app/src/main/java/foundation/e/drive/fileObservers/FileObserverManager.kt index 39bb91fa..5a7e6870 100644 --- a/app/src/main/java/foundation/e/drive/fileObservers/FileObserverManager.kt +++ b/app/src/main/java/foundation/e/drive/fileObservers/FileObserverManager.kt @@ -14,6 +14,7 @@ import android.os.Build import android.os.FileObserver import foundation.e.drive.database.DbHelper import foundation.e.drive.models.SyncedFolder +import foundation.e.drive.utils.FileUtils import timber.log.Timber import java.io.File import java.io.FileFilter @@ -28,7 +29,7 @@ class FileObserverManager(private val context: Context) : FileEventListener { companion object { val WATCHABLE_DIRECTORIES_FILTER = FileFilter { - it.isDirectory && !it.isHidden + it.isDirectory && !it.isHidden && FileUtils.isNotPartFile(it) } } diff --git a/app/src/main/java/foundation/e/drive/periodicScan/contentScanner/LocalFileLister.java b/app/src/main/java/foundation/e/drive/periodicScan/contentScanner/LocalFileLister.java index 11d2c58d..43bc0662 100644 --- a/app/src/main/java/foundation/e/drive/periodicScan/contentScanner/LocalFileLister.java +++ b/app/src/main/java/foundation/e/drive/periodicScan/contentScanner/LocalFileLister.java @@ -105,7 +105,7 @@ public class LocalFileLister extends AbstractFileLister { folder = new FolderWrapper(dir); final String category = syncedFolder.isMediaType() ? "media" : syncedFolder.getLibelle(); - final FileFilter filter = FileFilterFactory.getFileFilter(category); + final FileFilter filter = FileFilterFactory.INSTANCE.buildFileFilter(category); final File[] files = dir.listFiles(filter); if (files != null) { folder.addContent(Arrays.asList(files)); diff --git a/app/src/main/java/foundation/e/drive/utils/FileUtils.kt b/app/src/main/java/foundation/e/drive/utils/FileUtils.kt index 77185ebd..2a7c87ee 100644 --- a/app/src/main/java/foundation/e/drive/utils/FileUtils.kt +++ b/app/src/main/java/foundation/e/drive/utils/FileUtils.kt @@ -66,4 +66,10 @@ object FileUtils { return "*/*" } } + + @JvmStatic + fun isPartFile(file: File) = file.name.endsWith(".part") + + @JvmStatic + fun isNotPartFile(file: File) = !isPartFile(file) } \ No newline at end of file -- GitLab From 19b620c8a7e18eb611b39856d7365a22c7d7e588 Mon Sep 17 00:00:00 2001 From: Jonathan Klee Date: Fri, 15 Dec 2023 11:43:03 +0100 Subject: [PATCH 08/24] refactor: rename WorkerUtils in WorkLauncher --- .../account/receivers/AccountAddedReceiver.kt | 4 +- .../account/setup/FinishSetupWorker.java | 11 +++--- .../receivers/BootCompletedReceiver.java | 6 +-- .../e/drive/receivers/DebugCmdReceiver.java | 4 +- .../e/drive/synchronization/SyncProxy.kt | 5 +-- .../work/{WorkerUtils.kt => WorkLauncher.kt} | 38 ++++++++++++------- 6 files changed, 39 insertions(+), 29 deletions(-) rename app/src/main/java/foundation/e/drive/work/{WorkerUtils.kt => WorkLauncher.kt} (72%) 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 2d0eb8d9..bd09dd8c 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 @@ -21,7 +21,7 @@ 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.WorkerUtils +import foundation.e.drive.work.WorkLauncher import timber.log.Timber /** @@ -52,7 +52,7 @@ class AccountAddedReceiver() : BroadcastReceiver() { if (registerSetupWorkers(context)) { DavClientProvider.getInstance().cleanUp() - WorkerUtils.enqueuePeriodicUserInfoFetching(WorkManager.getInstance(context)) + WorkLauncher.getInstance(context).enqueuePeriodicUserInfoFetching(); } } diff --git a/app/src/main/java/foundation/e/drive/account/setup/FinishSetupWorker.java b/app/src/main/java/foundation/e/drive/account/setup/FinishSetupWorker.java index 3a847973..f1ed2985 100644 --- a/app/src/main/java/foundation/e/drive/account/setup/FinishSetupWorker.java +++ b/app/src/main/java/foundation/e/drive/account/setup/FinishSetupWorker.java @@ -15,12 +15,11 @@ import static foundation.e.drive.utils.AppConstants.SHARED_PREFERENCE_NAME; import android.content.Context; import androidx.annotation.NonNull; -import androidx.work.WorkManager; import androidx.work.Worker; import androidx.work.WorkerParameters; import foundation.e.drive.utils.AppConstants; -import foundation.e.drive.work.WorkerUtils; +import foundation.e.drive.work.WorkLauncher; import timber.log.Timber; /** @@ -60,9 +59,9 @@ public class FinishSetupWorker extends Worker { } private void enqueueWorkers(@NonNull final Context appContext) { - final WorkManager workManager = WorkManager.getInstance(appContext); - WorkerUtils.enqueueOneTimeAppListGenerator(workManager); - WorkerUtils.enqueueOneTimeFullScan(workManager, false); - WorkerUtils.enqueuePeriodicFullScan(workManager); + final WorkLauncher launcher = WorkLauncher.getInstance(appContext); + launcher.enqueueOneTimeAppListGenerator(); + launcher.enqueueOneTimeFullScan(false); + launcher.enqueuePeriodicFullScan(); } } \ No newline at end of file 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 ae1c037b..ab0a9c4a 100644 --- a/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java +++ b/app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java @@ -23,7 +23,7 @@ import foundation.e.drive.database.DbHelper; import foundation.e.drive.synchronization.SyncProxy; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; -import foundation.e.drive.work.WorkerUtils; +import foundation.e.drive.work.WorkLauncher; import timber.log.Timber; /** @@ -113,8 +113,8 @@ public class BootCompletedReceiver extends BroadcastReceiver { final WorkManager workManager= WorkManager.getInstance(context); workManager.cancelAllWork(); - WorkerUtils.enqueuePeriodicFullScan(workManager); - WorkerUtils.enqueuePeriodicUserInfoFetching(workManager); + WorkLauncher.getInstance(context).enqueuePeriodicFullScan(); + WorkLauncher.getInstance(context).enqueuePeriodicUserInfoFetching(); } } diff --git a/app/src/main/java/foundation/e/drive/receivers/DebugCmdReceiver.java b/app/src/main/java/foundation/e/drive/receivers/DebugCmdReceiver.java index 2235d838..412f0339 100644 --- a/app/src/main/java/foundation/e/drive/receivers/DebugCmdReceiver.java +++ b/app/src/main/java/foundation/e/drive/receivers/DebugCmdReceiver.java @@ -30,7 +30,7 @@ import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.synchronization.SyncProxy; import foundation.e.drive.synchronization.SyncRequestCollector; import foundation.e.drive.utils.ReleaseTree; -import foundation.e.drive.work.WorkerUtils; +import foundation.e.drive.work.WorkLauncher; import timber.log.Timber; /** @@ -56,7 +56,7 @@ public class DebugCmdReceiver extends BroadcastReceiver { switch (intent.getAction()) { case ACTION_FORCE_SCAN: Timber.d("Force Sync intent received"); - WorkerUtils.enqueueOneTimeFullScan(workManager, true); + WorkLauncher.getInstance(context).enqueueOneTimeFullScan(true); break; case ACTION_DUMP_DATABASE: Timber.d("Dump database intent received"); diff --git a/app/src/main/java/foundation/e/drive/synchronization/SyncProxy.kt b/app/src/main/java/foundation/e/drive/synchronization/SyncProxy.kt index dbd8f5f6..2c676d08 100644 --- a/app/src/main/java/foundation/e/drive/synchronization/SyncProxy.kt +++ b/app/src/main/java/foundation/e/drive/synchronization/SyncProxy.kt @@ -9,12 +9,11 @@ package foundation.e.drive.synchronization import android.app.Application import android.content.Context -import androidx.work.WorkManager import foundation.e.drive.EdriveApplication import foundation.e.drive.database.FailedSyncPrefsManager import foundation.e.drive.models.SyncRequest import foundation.e.drive.models.SyncWrapper -import foundation.e.drive.work.WorkerUtils +import foundation.e.drive.work.WorkLauncher import timber.log.Timber import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentLinkedQueue @@ -131,7 +130,7 @@ object SyncProxy: SyncRequestCollector, SyncManager { context.startRecursiveFileObserver() if (previousSyncState != SyncState.SYNCHRONIZING) { - WorkerUtils.enqueueOneTimeSync(WorkManager.getInstance(context)) + WorkLauncher.getInstance(context).enqueueOneTimeSync(); } } diff --git a/app/src/main/java/foundation/e/drive/work/WorkerUtils.kt b/app/src/main/java/foundation/e/drive/work/WorkLauncher.kt similarity index 72% rename from app/src/main/java/foundation/e/drive/work/WorkerUtils.kt rename to app/src/main/java/foundation/e/drive/work/WorkLauncher.kt index 0575de17..77e4133a 100644 --- a/app/src/main/java/foundation/e/drive/work/WorkerUtils.kt +++ b/app/src/main/java/foundation/e/drive/work/WorkLauncher.kt @@ -7,6 +7,7 @@ */ package foundation.e.drive.work +import android.content.Context import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingWorkPolicy import androidx.work.WorkManager @@ -19,26 +20,39 @@ import foundation.e.drive.work.WorkRequestFactory.WorkType /** * @author Vincent Bourgmayer */ -object WorkerUtils { +class WorkLauncher private constructor(context: Context) { + private val workManager: WorkManager - @JvmStatic - fun enqueuePeriodicUserInfoFetching(workManager: WorkManager) { + init { + workManager = WorkManager.getInstance(context) + } + + companion object { + private var instance: WorkLauncher? = null + + @JvmStatic + fun getInstance(context: Context): WorkLauncher { + return instance ?: WorkLauncher(context.applicationContext) + } + } + + fun enqueuePeriodicUserInfoFetching() { val request = WorkRequestFactory.getPeriodicWorkRequest(WorkType.PERIODIC_USER_INFO) - workManager.enqueueUniquePeriodicWork(AccountUserInfoWorker.UNIQUE_WORK_NAME, + workManager.enqueueUniquePeriodicWork( + AccountUserInfoWorker.UNIQUE_WORK_NAME, ExistingPeriodicWorkPolicy.UPDATE, - request) + request + ) } - @JvmStatic - fun enqueuePeriodicFullScan(workManager: WorkManager) { + fun enqueuePeriodicFullScan() { val request = WorkRequestFactory.getPeriodicWorkRequest(WorkType.PERIODIC_SCAN) workManager.enqueueUniquePeriodicWork(PeriodicScanWorker.UNIQUE_WORK_NAME, ExistingPeriodicWorkPolicy.KEEP, request) } - @JvmStatic - fun enqueueOneTimeFullScan(workManager: WorkManager, isForced: Boolean) { + fun enqueueOneTimeFullScan(isForced: Boolean) { if (isForced) { val request = WorkRequestFactory.getOneTimeWorkRequest(WorkType.ONE_TIME_FORCED_FULL_SCAN, null) @@ -55,15 +69,13 @@ object WorkerUtils { request) } - @JvmStatic - fun enqueueOneTimeAppListGenerator(workManager: WorkManager) { + fun enqueueOneTimeAppListGenerator() { val request = WorkRequestFactory.getOneTimeWorkRequest(WorkType.ONE_TIME_APP_LIST, null) workManager.enqueue(request) } - @JvmStatic - fun enqueueOneTimeSync(workManager: WorkManager) { + fun enqueueOneTimeSync() { val request = WorkRequestFactory.getOneTimeWorkRequest(WorkType.ONE_TIME_SYNC, null) workManager.enqueueUniqueWork(SyncWorker.UNIQUE_WORK_NAME, ExistingWorkPolicy.KEEP, request) } -- GitLab From 48e81621934779b4c2948ced1f78062e65b448aa Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Wed, 10 Jan 2024 14:33:32 +0000 Subject: [PATCH 09/24] refactor : remove unwanted semicolon --- .../e/drive/account/receivers/AccountAddedReceiver.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 bd09dd8c..62190ca9 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 @@ -52,7 +52,7 @@ class AccountAddedReceiver() : BroadcastReceiver() { if (registerSetupWorkers(context)) { DavClientProvider.getInstance().cleanUp() - WorkLauncher.getInstance(context).enqueuePeriodicUserInfoFetching(); + WorkLauncher.getInstance(context).enqueuePeriodicUserInfoFetching() } } -- GitLab From 159ccdf68b8bc1b1f74209b4acdd8da513da1798 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Tue, 16 Jan 2024 16:30:28 +0100 Subject: [PATCH 10/24] fix(List apps installed): add missing call to 'cursor.close()' when fetching installed PWA --- .../java/foundation/e/drive/periodicScan/ListAppsWorker.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/foundation/e/drive/periodicScan/ListAppsWorker.java b/app/src/main/java/foundation/e/drive/periodicScan/ListAppsWorker.java index 6c6b9062..ee492393 100644 --- a/app/src/main/java/foundation/e/drive/periodicScan/ListAppsWorker.java +++ b/app/src/main/java/foundation/e/drive/periodicScan/ListAppsWorker.java @@ -76,7 +76,10 @@ public class ListAppsWorker extends Worker { final Cursor cursor = context.getContentResolver().query( Uri.parse(PWA_PLAYER), null, null, null, null); - if (cursor.getCount() <= 0) return; + if (cursor.getCount() <= 0) { + cursor.close(); + return; + } stringBuilder.append(PWA_SECTION_SEPARATOR); cursor.moveToFirst(); -- GitLab From 55b1a4ee68cc6d1f8140728c762d11b10ca5a2c8 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Thu, 25 Jan 2024 11:33:31 +0100 Subject: [PATCH 11/24] refactor (version number): bump to 1.5.0 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index eaff7661..b5d221b9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,7 +4,7 @@ plugins { } def versionMajor = 1 -def versionMinor = 4 +def versionMinor = 5 def versionPatch = 0 def getTestProp(String propName) { -- GitLab From ffbe05a02cde0c05ff36e51688c24bf66749b85b Mon Sep 17 00:00:00 2001 From: Vincent Bourgmayer Date: Tue, 26 Mar 2024 14:04:17 +0000 Subject: [PATCH 12/24] fix: error introduced by merging main branch into epic branch --- app/src/main/AndroidManifest.xml | 2 +- .../e/drive/account/AccountUtils.kt | 12 ++- .../account/receivers/AccountAddedReceiver.kt | 47 ++-------- .../receivers/BootCompletedReceiver.java | 90 ++++++++++--------- .../tasks/UploadFileOperation.java | 8 +- .../foundation/e/drive/utils/WorkerUtils.kt | 56 ------------ .../foundation/e/drive/work/WorkLauncher.kt | 48 +++++++++- .../operations/UploadFileOperationTest.java | 14 ++- 8 files changed, 123 insertions(+), 154 deletions(-) delete mode 100644 app/src/main/java/foundation/e/drive/utils/WorkerUtils.kt 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 @@ - + 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..ae3c1118 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 @@ -20,8 +20,8 @@ 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 foundation.e.drive.utils.AppConstants.ACCOUNT_DATA_GROUPS +import foundation.e.drive.utils.AppConstants.SHARED_PREFERENCE_NAME object AccountUtils { @@ -68,4 +68,12 @@ object AccountUtils { return accountManager.getAccountsByType(accountType) .firstOrNull { account -> account.name == accountName } } + + @JvmStatic + fun isAccountAvailable(context: Context): Boolean { + val accountManager = AccountManager.get(context.applicationContext) + val accountList = + accountManager.getAccountsByType(context.getString(R.string.eelo_account_type)) + return accountList.isNotEmpty() + } } 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 +} 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..5001e7f5 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; /** @@ -38,42 +39,50 @@ 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(...)"); - 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.isAccountAvailable(context)) { + return; + } - if (!isSetupCompleted(prefs)) return; + final SharedPreferences prefs = context.getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); + final WorkLauncher workLauncher = WorkLauncher.getInstance(context); - 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.enqueueSetupWorkers(context); + return; + } - 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()); + + workLauncher.enqueuePeriodicFullScan(); + workLauncher.enqueuePeriodicUserInfoFetching(); } - 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 +90,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 +113,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 +123,17 @@ 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) { + final DbHelper dbHelper = new DbHelper(context); + dbHelper.getWritableDatabase().close(); //force DB update + } + + private boolean isSetupCompleted(@NonNull SharedPreferences prefs) { + return prefs.getBoolean(AppConstants.SETUP_COMPLETED, false); + } +} 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..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 @@ -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 long formatTimestampToMatchCloud(long timestamp) { + return timestamp / 1000; } } \ No newline at end of file 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 - ) - } - } -} 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..ab0324f8 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,47 @@ 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 + ) + } + } } 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 a22fbd1989ed761a5017395c89e18e22187d66d9 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Mon, 8 Apr 2024 17:33:48 +0200 Subject: [PATCH 13/24] refactor(convert WorkRequestFactory to kotlin): create kotlin class and implement periodic scan creation method --- .../foundation/e/drive/work/WorkLauncher.kt | 5 +- .../e/drive/work/WorkRequestFactory.java | 62 +--------------- .../e/drive/work/WorkRequestFactory.kt | 73 +++++++++++++++++++ 3 files changed, 77 insertions(+), 63 deletions(-) create mode 100644 app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt 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 ab0324f8..f79e2d68 100644 --- a/app/src/main/java/foundation/e/drive/work/WorkLauncher.kt +++ b/app/src/main/java/foundation/e/drive/work/WorkLauncher.kt @@ -18,7 +18,6 @@ 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 /** * @author Vincent Bourgmayer @@ -40,7 +39,7 @@ class WorkLauncher private constructor(context: Context) { } fun enqueuePeriodicUserInfoFetching() { - val request = WorkRequestFactory.getPeriodicWorkRequest(WorkType.PERIODIC_USER_INFO) + val request = WorkRequestFactory.getPeriodicWorkRequest(PeriodicWorkType.FETCH_USER_INFO) workManager.enqueueUniquePeriodicWork( AccountUserInfoWorker.UNIQUE_WORK_NAME, ExistingPeriodicWorkPolicy.UPDATE, @@ -49,7 +48,7 @@ class WorkLauncher private constructor(context: Context) { } fun enqueuePeriodicFullScan() { - val request = WorkRequestFactory.getPeriodicWorkRequest(WorkType.PERIODIC_SCAN) + val request = WorkRequestFactory.getPeriodicWorkRequest(PeriodicWorkType.PERIODIC_SCAN) workManager.enqueueUniquePeriodicWork(PeriodicScanWorker.UNIQUE_WORK_NAME, ExistingPeriodicWorkPolicy.KEEP, request) diff --git a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.java b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.java index 460ed576..139a949e 100644 --- a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.java +++ b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.java @@ -39,13 +39,11 @@ import foundation.e.drive.account.setup.RootFolderSetupWorker; import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.periodicScan.FullScanWorker; import foundation.e.drive.periodicScan.ListAppsWorker; -import foundation.e.drive.periodicScan.PeriodicScanWorker; + import foundation.e.drive.synchronization.SyncWorker; public class WorkRequestFactory { public enum WorkType { - PERIODIC_USER_INFO, - PERIODIC_SCAN, ONE_TIME_FULL_SCAN, ONE_TIME_FORCED_FULL_SCAN, ONE_TIME_APP_LIST, @@ -55,64 +53,7 @@ public class WorkRequestFactory { ONE_TIME_SYNC } - private final static int PERIODIC_WORK_REPEAT_INTERVAL = 30; - private final static int PERIODIC_SCAN_FLEX_TIME = 5; - - /** - * Build an instance of PeriodicWorkRequest depending of the work type specified - * @param type WorkType. Should be FULL_SCAN or PERIODIC_USER_INFO or PERIODIC_APP_LIST - * If not, it will throw an InvalidParameterException - * @return Periodic WorkRequest - */ - @NonNull - public static PeriodicWorkRequest getPeriodicWorkRequest(@NonNull WorkType type) { - switch (type) { - case PERIODIC_SCAN: - return createPeriodicScanWorkRequest(); - case PERIODIC_USER_INFO: - return createPeriodicGetUserInfoWorkRequest(); - default: - throw new InvalidParameterException("Unsupported Work Type: " + type); - } - } - - /** - * Create a PeriodicWorkRequest instance for - * a Full scan with constraints on network (should - * be unmetered) and battery (shouldn't be low) - * @return instance of PeriodicWorkRequest - */ - @NonNull - private static PeriodicWorkRequest createPeriodicScanWorkRequest() { - final Constraints constraints = createUnmeteredNetworkAndHighBatteryConstraints(); - - final PeriodicWorkRequest.Builder workRequestBuilder = new PeriodicWorkRequest.Builder( - PeriodicScanWorker.class, - PERIODIC_WORK_REPEAT_INTERVAL, MINUTES, - PERIODIC_SCAN_FLEX_TIME, MINUTES); - - return workRequestBuilder.setConstraints(constraints) - .addTag(WORK_GENERIC_TAG) - .build(); - } - - /** - * Create a periodic work request to get userInfo - * @return instance of PeriodicWorkRequest - */ - @NonNull - private static PeriodicWorkRequest createPeriodicGetUserInfoWorkRequest() { - final Constraints constraints = new Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .build(); - - final PeriodicWorkRequest.Builder workRequestBuilder = new PeriodicWorkRequest.Builder(AccountUserInfoWorker.class, - PERIODIC_WORK_REPEAT_INTERVAL, MINUTES); - return workRequestBuilder.addTag(WORK_GENERIC_TAG) - .setConstraints(constraints) - .build(); - } /** * Build an instance of OneTimeWorkRequest depending of the work type specified. @@ -257,6 +198,7 @@ public class WorkRequestFactory { .build(); } + /** * Parse SyncedFolder instance in Data, used as data for WorkRequest * @param folder SyncedFolder instance diff --git a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt new file mode 100644 index 00000000..186f4f76 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt @@ -0,0 +1,73 @@ +package foundation.e.drive.work + +import androidx.work.Constraints +import androidx.work.NetworkType +import androidx.work.PeriodicWorkRequest +import androidx.work.PeriodicWorkRequestBuilder +import foundation.e.drive.account.AccountUserInfoWorker +import foundation.e.drive.periodicScan.PeriodicScanWorker +import foundation.e.drive.utils.AppConstants.WORK_GENERIC_TAG +import java.util.concurrent.TimeUnit.MINUTES + + +enum class PeriodicWorkType { + PERIODIC_FETCH_USER_INFO, + PERIODIC_SCAN} + +enum class OneTimeWorkType { + ONE_TIME_FULL_SCAN, + ONE_TIME_FORCED_FULL_SCAN, + ONE_TIME_APP_LIST, + ONE_TIME_USER_INFO, + ONE_TIME_ROOT_FOLDER_SETUP, + ONE_TIME_FINISH_SETUP, + ONE_TIME_SYNC +} +object WorkRequestFactory { + + private val PERIODIC_WORK_REPEAT_INTERVAL = 30L + private val PERIODIC_SCAN_FLEX_TIME = 5L + + /** + * Build an instance of PeriodicWorkRequest depending of the work type specified + * @param type WorkType. Should be PERIODIC_SCAN or PERIODIC_USER_INFO + * @return Periodic WorkRequest + */ + fun getPeriodicWorkRequest(type: PeriodicWorkType): PeriodicWorkRequest { + return when(type) { + PeriodicWorkType.PERIODIC_FETCH_USER_INFO -> createPeriodicGetUserInfoWorkRequest() + PeriodicWorkType.PERIODIC_SCAN -> createPeriodicScanWorkRequest() + } + } + + /** + * Create a PeriodicWorkRequest instance for + * a Full scan with constraints on network (any) and battery (not low) + * @return instance of PeriodicWorkRequest + */ + private fun createPeriodicScanWorkRequest(): PeriodicWorkRequest { + val constraint = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .setRequiresBatteryNotLow(true) + .build() + + return PeriodicWorkRequestBuilder( + PERIODIC_WORK_REPEAT_INTERVAL, MINUTES, + PERIODIC_SCAN_FLEX_TIME,MINUTES) + .setConstraints(constraint) + .addTag(WORK_GENERIC_TAG) + .build() + } + + private fun createPeriodicGetUserInfoWorkRequest(): PeriodicWorkRequest { + val constraint = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + + return PeriodicWorkRequestBuilder( + PERIODIC_WORK_REPEAT_INTERVAL, MINUTES) + .setConstraints(constraint) + .addTag(WORK_GENERIC_TAG) + .build() + } +} \ No newline at end of file -- GitLab From cbf32b416804057f8235ceff2d64d54634656c16 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Mon, 8 Apr 2024 18:28:51 +0200 Subject: [PATCH 14/24] refactor (WorkRequestFactory conversion to kotlin): convert code about OneTime workRequest --- .../e/drive/work/WorkRequestFactory.java | 209 ------------------ .../e/drive/work/WorkRequestFactory.kt | 200 +++++++++++++++-- 2 files changed, 187 insertions(+), 222 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.java b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.java index 139a949e..74226df2 100644 --- a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.java +++ b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.java @@ -8,215 +8,6 @@ package foundation.e.drive.work; -import static androidx.work.BackoffPolicy.LINEAR; -import static java.util.concurrent.TimeUnit.MINUTES; -import static foundation.e.drive.account.setup.RootFolderSetupWorker.DATA_KEY_ENABLE; -import static foundation.e.drive.account.setup.RootFolderSetupWorker.DATA_KEY_ID; -import static foundation.e.drive.account.setup.RootFolderSetupWorker.DATA_KEY_LAST_ETAG; -import static foundation.e.drive.account.setup.RootFolderSetupWorker.DATA_KEY_LAST_MODIFIED; -import static foundation.e.drive.account.setup.RootFolderSetupWorker.DATA_KEY_LIBELLE; -import static foundation.e.drive.account.setup.RootFolderSetupWorker.DATA_KEY_LOCAL_PATH; -import static foundation.e.drive.account.setup.RootFolderSetupWorker.DATA_KEY_MEDIATYPE; -import static foundation.e.drive.account.setup.RootFolderSetupWorker.DATA_KEY_REMOTE_PATH; -import static foundation.e.drive.account.setup.RootFolderSetupWorker.DATA_KEY_SCAN_LOCAL; -import static foundation.e.drive.account.setup.RootFolderSetupWorker.DATA_KEY_SCAN_REMOTE; -import static foundation.e.drive.utils.AppConstants.WORK_GENERIC_TAG; -import static foundation.e.drive.utils.AppConstants.WORK_SETUP_TAG; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.work.Constraints; -import androidx.work.Data; -import androidx.work.NetworkType; -import androidx.work.OneTimeWorkRequest; -import androidx.work.PeriodicWorkRequest; - -import java.security.InvalidParameterException; - -import foundation.e.drive.account.AccountUserInfoWorker; -import foundation.e.drive.account.setup.FinishSetupWorker; -import foundation.e.drive.account.setup.RootFolderSetupWorker; -import foundation.e.drive.models.SyncedFolder; -import foundation.e.drive.periodicScan.FullScanWorker; -import foundation.e.drive.periodicScan.ListAppsWorker; - -import foundation.e.drive.synchronization.SyncWorker; - public class WorkRequestFactory { - public enum WorkType { - ONE_TIME_FULL_SCAN, - ONE_TIME_FORCED_FULL_SCAN, - ONE_TIME_APP_LIST, - ONE_TIME_USER_INFO, - ONE_TIME_ROOT_FOLDER_SETUP, - ONE_TIME_FINISH_SETUP, - ONE_TIME_SYNC - } - - - - /** - * Build an instance of OneTimeWorkRequest depending of the work type specified. - * @param type Should be ONE_TIME_USER_INFO, or FIRST_START, or CREATE_REMOTE_DIR - * or it will throw InvalidParameterException - * @param syncedFolder this parameter is required for CREATE_REMOTE_DIR work type. If null it will throw an NPE. - * @return OneTimeWorkRequest's instance. - */ - @NonNull - public static OneTimeWorkRequest getOneTimeWorkRequest(@NonNull WorkType type, @Nullable SyncedFolder syncedFolder) { - switch (type) { - case ONE_TIME_APP_LIST: - return createAppListGenerationWorkRequest(); - case ONE_TIME_FULL_SCAN: - return createFullScanWorkRequest(false); - case ONE_TIME_FORCED_FULL_SCAN: - return createFullScanWorkRequest(true); - case ONE_TIME_USER_INFO: - return createGetUserInfoWorkRequest(); - case ONE_TIME_FINISH_SETUP: - return createFinishSetupWorkRequest(); - case ONE_TIME_ROOT_FOLDER_SETUP: - if (syncedFolder == null) throw new NullPointerException("Synced folder is null"); - return createRootFolderSetupWorkRequest(syncedFolder); - case ONE_TIME_SYNC: - return createSyncWorkRequest(); - default: - throw new InvalidParameterException("Unsupported Work Type: " + type); - } - } - - private static OneTimeWorkRequest createSyncWorkRequest() { - final Constraints constraints = new Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .setRequiresBatteryNotLow(true) - .build(); - - final OneTimeWorkRequest.Builder builder = new OneTimeWorkRequest.Builder(SyncWorker.class); - - return builder.setBackoffCriteria(LINEAR, 2, MINUTES).addTag(WORK_GENERIC_TAG) - .setConstraints(constraints) - .build(); - } - - /** - * Create a workRequest to generate file which contains list of installed apps - * @return the workRequest - */ - @NonNull - private static OneTimeWorkRequest createAppListGenerationWorkRequest() { - final OneTimeWorkRequest.Builder builder = new OneTimeWorkRequest.Builder(ListAppsWorker.class); - - return builder.setBackoffCriteria(LINEAR, 2, MINUTES) - .addTag(WORK_GENERIC_TAG) - .build(); - } - - /** - * Create a OneTimeWorkRequest instance for - * a Full scan with constraints on network (should - * be unmetered) and battery (shouldn't be low) - * @return instance of OneTimeWorkRequest - */ - @NonNull - private static OneTimeWorkRequest createFullScanWorkRequest(boolean forced) { - final Constraints constraints = createUnmeteredNetworkAndHighBatteryConstraints(); - - final OneTimeWorkRequest.Builder builder = new OneTimeWorkRequest.Builder(FullScanWorker.class); - - final Data data = new Data.Builder() - .putBoolean(FullScanWorker.ACTION_FORCED_SYNC_KEY, forced) - .build(); - - return builder.setBackoffCriteria(LINEAR, 2, MINUTES) - .setConstraints(constraints) - .setInputData(data) - .addTag(WORK_GENERIC_TAG) - .build(); - } - - - /** - * Instanciate a OneTimeWorkRequest to retrieve user info - * @return instance of OneTimeWorkRequest - */ - @NonNull - private static OneTimeWorkRequest createGetUserInfoWorkRequest() { - final Constraints constraints = createUnmeteredNetworkAndHighBatteryConstraints(); - - final OneTimeWorkRequest.Builder builder = new OneTimeWorkRequest.Builder(AccountUserInfoWorker.class); - - return builder.setBackoffCriteria(LINEAR, 2, MINUTES) - .addTag(WORK_GENERIC_TAG) - .addTag(WORK_SETUP_TAG) - .setConstraints(constraints) - .build(); - } - - /** - * Create a OneTime workRequest to create a remote folder - * With constraints on network (unmetered) and battery (not low) - * @param syncedFolder SyncedFolder instance with data about folder to create - * @return Instance OneTimeWorkRequest - */ - @NonNull - private static OneTimeWorkRequest createRootFolderSetupWorkRequest(@NonNull SyncedFolder syncedFolder) { - final Constraints constraints = createUnmeteredNetworkAndHighBatteryConstraints(); - - return new OneTimeWorkRequest.Builder( - RootFolderSetupWorker.class) - .setBackoffCriteria(LINEAR, 2, MINUTES) - .setInputData(createDataFromSyncedFolder(syncedFolder)) - .addTag(WORK_GENERIC_TAG) - .addTag(WORK_SETUP_TAG) - .setConstraints(constraints) - .build(); - } - - /** - * Create a OneTime WorkRequest which finish setup process - * @return Instance of OneTimeWorkRequest - */ - @NonNull - private static OneTimeWorkRequest createFinishSetupWorkRequest() { - return new OneTimeWorkRequest.Builder(FinishSetupWorker.class) - .setBackoffCriteria(LINEAR, 2, MINUTES) - .addTag(WORK_GENERIC_TAG) - .addTag(WORK_SETUP_TAG) - .build(); - } - - /** - * Create Constraints for unmetered network - * and battery not low - * @return instance of Constraints - */ - @NonNull - private static Constraints createUnmeteredNetworkAndHighBatteryConstraints() { - return new Constraints.Builder() - .setRequiredNetworkType(NetworkType.UNMETERED) - .setRequiresBatteryNotLow(true) - .build(); - } - - /** - * Parse SyncedFolder instance in Data, used as data for WorkRequest - * @param folder SyncedFolder instance - * @return Data instance - */ - @NonNull - private static Data createDataFromSyncedFolder(@NonNull SyncedFolder folder) { - return new Data.Builder() - .putInt(DATA_KEY_ID, folder.getId()) - .putString(DATA_KEY_LIBELLE, folder.getLibelle()) - .putString(DATA_KEY_LOCAL_PATH, folder.getLocalFolder()) - .putString(DATA_KEY_REMOTE_PATH, folder.getRemoteFolder()) - .putString(DATA_KEY_LAST_ETAG, folder.getLastEtag()) - .putLong(DATA_KEY_LAST_MODIFIED, folder.getLastModified()) - .putBoolean(DATA_KEY_SCAN_LOCAL, folder.isScanLocal()) - .putBoolean(DATA_KEY_SCAN_REMOTE, folder.isScanRemote()) - .putBoolean(DATA_KEY_ENABLE, folder.isEnabled()) - .putBoolean(DATA_KEY_MEDIATYPE, folder.isMediaType()) - .build(); - } } \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt index 186f4f76..7a3de5bf 100644 --- a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt +++ b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt @@ -1,32 +1,60 @@ +/* + * 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 + * http://www.gnu.org/licenses/gpl.html + */ package foundation.e.drive.work +import androidx.work.BackoffPolicy.LINEAR import androidx.work.Constraints +import androidx.work.Data import androidx.work.NetworkType +import androidx.work.OneTimeWorkRequest +import androidx.work.OneTimeWorkRequestBuilder import androidx.work.PeriodicWorkRequest import androidx.work.PeriodicWorkRequestBuilder import foundation.e.drive.account.AccountUserInfoWorker +import foundation.e.drive.account.setup.FinishSetupWorker +import foundation.e.drive.account.setup.RootFolderSetupWorker +import foundation.e.drive.account.setup.RootFolderSetupWorker.DATA_KEY_ENABLE +import foundation.e.drive.account.setup.RootFolderSetupWorker.DATA_KEY_ID +import foundation.e.drive.account.setup.RootFolderSetupWorker.DATA_KEY_LAST_ETAG +import foundation.e.drive.account.setup.RootFolderSetupWorker.DATA_KEY_LAST_MODIFIED +import foundation.e.drive.account.setup.RootFolderSetupWorker.DATA_KEY_LIBELLE +import foundation.e.drive.account.setup.RootFolderSetupWorker.DATA_KEY_LOCAL_PATH +import foundation.e.drive.account.setup.RootFolderSetupWorker.DATA_KEY_MEDIATYPE +import foundation.e.drive.account.setup.RootFolderSetupWorker.DATA_KEY_REMOTE_PATH +import foundation.e.drive.account.setup.RootFolderSetupWorker.DATA_KEY_SCAN_LOCAL +import foundation.e.drive.account.setup.RootFolderSetupWorker.DATA_KEY_SCAN_REMOTE +import foundation.e.drive.models.SyncedFolder +import foundation.e.drive.periodicScan.FullScanWorker +import foundation.e.drive.periodicScan.ListAppsWorker import foundation.e.drive.periodicScan.PeriodicScanWorker +import foundation.e.drive.synchronization.SyncWorker import foundation.e.drive.utils.AppConstants.WORK_GENERIC_TAG +import foundation.e.drive.utils.AppConstants.WORK_SETUP_TAG import java.util.concurrent.TimeUnit.MINUTES enum class PeriodicWorkType { - PERIODIC_FETCH_USER_INFO, + FETCH_USER_INFO, PERIODIC_SCAN} enum class OneTimeWorkType { - ONE_TIME_FULL_SCAN, - ONE_TIME_FORCED_FULL_SCAN, - ONE_TIME_APP_LIST, - ONE_TIME_USER_INFO, - ONE_TIME_ROOT_FOLDER_SETUP, - ONE_TIME_FINISH_SETUP, + FULL_SCAN, + USER_TRIGGERED_FULL_SCAN, + LIST_APPS, + FETCH_USER_INFO, + ROOT_FOLDER_SETUP, + FINISH_SETUP, ONE_TIME_SYNC } object WorkRequestFactory { - private val PERIODIC_WORK_REPEAT_INTERVAL = 30L - private val PERIODIC_SCAN_FLEX_TIME = 5L + private const val PERIODIC_WORK_REPEAT_INTERVAL = 30L + private const val PERIODIC_SCAN_FLEX_TIME = 5L /** * Build an instance of PeriodicWorkRequest depending of the work type specified @@ -34,19 +62,165 @@ object WorkRequestFactory { * @return Periodic WorkRequest */ fun getPeriodicWorkRequest(type: PeriodicWorkType): PeriodicWorkRequest { - return when(type) { - PeriodicWorkType.PERIODIC_FETCH_USER_INFO -> createPeriodicGetUserInfoWorkRequest() + return when (type) { + PeriodicWorkType.FETCH_USER_INFO -> createPeriodicGetUserInfoWorkRequest() PeriodicWorkType.PERIODIC_SCAN -> createPeriodicScanWorkRequest() } } + /** + * Build an instance of OneTimeWorkRequest depending of the work type specified. + * @param type Should be ONE_TIME_USER_INFO, or FIRST_START, or CREATE_REMOTE_DIR + * or it will throw InvalidParameterException + * @param syncedFolder @Nullable this parameter is required for CREATE_REMOTE_DIR work type. Throw an NPE if null. + * @return OneTimeWorkRequest's instance. + */ + fun createOneTimeWorkRequest(type: OneTimeWorkType, syncedFolder: SyncedFolder? = null): OneTimeWorkRequest { + return when (type) { + OneTimeWorkType.FULL_SCAN -> createFullScanWorkRequest(false) + OneTimeWorkType.USER_TRIGGERED_FULL_SCAN -> createFullScanWorkRequest(true) + OneTimeWorkType.LIST_APPS -> createAppListGenerationWorkRequest() + OneTimeWorkType.FETCH_USER_INFO -> createGetUserInfoWorkRequest() + OneTimeWorkType.ROOT_FOLDER_SETUP -> { + if (syncedFolder == null) { + throw NullPointerException("Cannot create RootFolderSetupWorker without syncFolder") + } + createRootFolderSetupWorkRequest(syncedFolder) + } + OneTimeWorkType.FINISH_SETUP -> createFinishSetupWorkRequest() + OneTimeWorkType.ONE_TIME_SYNC -> createSyncWorkRequest() + } + } + + /** + * Create a OneTimeWorkRequest instance for + * a Full scan with constraints on network (should + * be unmetered) and battery (shouldn't be low) + * @return instance of OneTimeWorkRequest + */ + private fun createFullScanWorkRequest(forced: Boolean): OneTimeWorkRequest { + val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.UNMETERED) + .setRequiresBatteryNotLow(true) + .build() + + val data = Data.Builder() + .putBoolean(FullScanWorker.ACTION_FORCED_SYNC_KEY, forced) + .build() + + return OneTimeWorkRequestBuilder() + .setBackoffCriteria(LINEAR, 2, MINUTES) + .setConstraints(constraints) + .setInputData(data) + .addTag(WORK_GENERIC_TAG) + .build() + } + + /** + * Create a workRequest to generate file which contains list of installed apps + * @return the workRequest + */ + private fun createAppListGenerationWorkRequest(): OneTimeWorkRequest { + return OneTimeWorkRequestBuilder() + .setBackoffCriteria(LINEAR, 2, MINUTES) + .addTag(WORK_GENERIC_TAG) + .build() + + } + + /** + * Instanciate a OneTimeWorkRequest to retrieve user info + * @return instance of OneTimeWorkRequest + */ + private fun createGetUserInfoWorkRequest(): OneTimeWorkRequest { + val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.UNMETERED) + .setRequiresBatteryNotLow(true) + .build() + + return OneTimeWorkRequestBuilder() + .setConstraints(constraints) + .setBackoffCriteria(LINEAR, 2, MINUTES) + .addTag(WORK_GENERIC_TAG) + .addTag(WORK_SETUP_TAG) + .build() + } + + /** + * Create a OneTime workRequest to create a remote folder + * With constraints on network (unmetered) and battery (not low) + * @param syncedFolder SyncedFolder instance with data about folder to create + * @return Instance OneTimeWorkRequest + */ + private fun createRootFolderSetupWorkRequest(syncedFolder: SyncedFolder): OneTimeWorkRequest { + val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.UNMETERED) + .setRequiresBatteryNotLow(true) + .build() + + return OneTimeWorkRequestBuilder() + .setInputData(createDataFromSyncedFolder(syncedFolder)) + .setConstraints(constraints) + .setBackoffCriteria(LINEAR, 2, MINUTES) + .addTag(WORK_GENERIC_TAG) + .addTag(WORK_SETUP_TAG) + .build() + } + + /** + * Parse SyncedFolder instance in Data, used as data for WorkRequest + * @param folder SyncedFolder instance + * @return Data instance + */ + private fun createDataFromSyncedFolder(folder: SyncedFolder): Data { + return Data.Builder() + .putInt(DATA_KEY_ID, folder.getId()) + .putString(DATA_KEY_LIBELLE, folder.getLibelle()) + .putString(DATA_KEY_LOCAL_PATH, folder.getLocalFolder()) + .putString(DATA_KEY_REMOTE_PATH, folder.getRemoteFolder()) + .putString(DATA_KEY_LAST_ETAG, folder.getLastEtag()) + .putLong(DATA_KEY_LAST_MODIFIED, folder.getLastModified()) + .putBoolean(DATA_KEY_SCAN_LOCAL, folder.isScanLocal()) + .putBoolean(DATA_KEY_SCAN_REMOTE, folder.isScanRemote()) + .putBoolean(DATA_KEY_ENABLE, folder.isEnabled()) + .putBoolean(DATA_KEY_MEDIATYPE, folder.isMediaType()) + .build() + + } + + /** + * Create a OneTime WorkRequest which finish setup process + * @return Instance of OneTimeWorkRequest + */ + private fun createFinishSetupWorkRequest(): OneTimeWorkRequest { + return OneTimeWorkRequestBuilder() + .setBackoffCriteria(LINEAR, 2, MINUTES) + .addTag(WORK_GENERIC_TAG) + .addTag(WORK_SETUP_TAG) + .build() + + } + + private fun createSyncWorkRequest(): OneTimeWorkRequest { + val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .setRequiresBatteryNotLow(true) + .build() + + return OneTimeWorkRequestBuilder() + .setBackoffCriteria(LINEAR, 2, MINUTES) + .setConstraints(constraints) + .addTag(WORK_GENERIC_TAG) + .build() + } + /** * Create a PeriodicWorkRequest instance for * a Full scan with constraints on network (any) and battery (not low) * @return instance of PeriodicWorkRequest */ private fun createPeriodicScanWorkRequest(): PeriodicWorkRequest { - val constraint = Constraints.Builder() + val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .setRequiresBatteryNotLow(true) .build() @@ -54,7 +228,7 @@ object WorkRequestFactory { return PeriodicWorkRequestBuilder( PERIODIC_WORK_REPEAT_INTERVAL, MINUTES, PERIODIC_SCAN_FLEX_TIME,MINUTES) - .setConstraints(constraint) + .setConstraints(constraints) .addTag(WORK_GENERIC_TAG) .build() } -- GitLab From 192f2a25d347b1b7044c3866b16337cfe3129319 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Mon, 8 Apr 2024 18:33:11 +0200 Subject: [PATCH 15/24] chore: fix call to WorkRequestFactory for onetime work request in WorkLauncher --- .../foundation/e/drive/work/WorkLauncher.kt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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 f79e2d68..57d4e153 100644 --- a/app/src/main/java/foundation/e/drive/work/WorkLauncher.kt +++ b/app/src/main/java/foundation/e/drive/work/WorkLauncher.kt @@ -56,7 +56,7 @@ class WorkLauncher private constructor(context: Context) { fun enqueueOneTimeFullScan(isForced: Boolean) { if (isForced) { - val request = WorkRequestFactory.getOneTimeWorkRequest(WorkType.ONE_TIME_FORCED_FULL_SCAN, + val request = WorkRequestFactory.createOneTimeWorkRequest(OneTimeWorkType.USER_TRIGGERED_FULL_SCAN, null) workManager.enqueueUniqueWork(FullScanWorker.UNIQUE_WORK_NAME, ExistingWorkPolicy.REPLACE, @@ -64,7 +64,7 @@ class WorkLauncher private constructor(context: Context) { return } - val request = WorkRequestFactory.getOneTimeWorkRequest(WorkType.ONE_TIME_FULL_SCAN, + val request = WorkRequestFactory.createOneTimeWorkRequest(OneTimeWorkType.FULL_SCAN, null) workManager.enqueueUniqueWork(FullScanWorker.UNIQUE_WORK_NAME, ExistingWorkPolicy.KEEP, @@ -72,13 +72,13 @@ class WorkLauncher private constructor(context: Context) { } fun enqueueOneTimeAppListGenerator() { - val request = WorkRequestFactory.getOneTimeWorkRequest(WorkType.ONE_TIME_APP_LIST, + val request = WorkRequestFactory.createOneTimeWorkRequest(OneTimeWorkType.LIST_APPS, null) workManager.enqueue(request) } fun enqueueOneTimeSync() { - val request = WorkRequestFactory.getOneTimeWorkRequest(WorkType.ONE_TIME_SYNC, null) + val request = WorkRequestFactory.createOneTimeWorkRequest(OneTimeWorkType.ONE_TIME_SYNC, null) workManager.enqueueUniqueWork(SyncWorker.UNIQUE_WORK_NAME, ExistingWorkPolicy.KEEP, request) } @@ -89,13 +89,13 @@ class WorkLauncher private constructor(context: Context) { return false } - val getUserInfoRequest = WorkRequestFactory.getOneTimeWorkRequest( - WorkType.ONE_TIME_USER_INFO, + val getUserInfoRequest = WorkRequestFactory.createOneTimeWorkRequest( + OneTimeWorkType.FETCH_USER_INFO, null ) - val finishSetupRequest = WorkRequestFactory.getOneTimeWorkRequest( - WorkType.ONE_TIME_FINISH_SETUP, + val finishSetupRequest = WorkRequestFactory.createOneTimeWorkRequest( + OneTimeWorkType.FINISH_SETUP, null ) @@ -118,8 +118,8 @@ class WorkLauncher private constructor(context: Context) { RootSyncedFolderProvider.getSyncedFolderRoots(context) return rootSyncedFolderList.map { - WorkRequestFactory.getOneTimeWorkRequest( - WorkType.ONE_TIME_ROOT_FOLDER_SETUP, + WorkRequestFactory.createOneTimeWorkRequest( + OneTimeWorkType.ROOT_FOLDER_SETUP, it ) } -- GitLab From cab8bed19bc27438c3a9f0adda16881529c81662 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Mon, 8 Apr 2024 18:45:14 +0200 Subject: [PATCH 16/24] chore: delete Java version of WorkRequestFactory --- .../foundation/e/drive/work/WorkRequestFactory.java | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 app/src/main/java/foundation/e/drive/work/WorkRequestFactory.java diff --git a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.java b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.java deleted file mode 100644 index 74226df2..00000000 --- a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.java +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright © MURENA SAS 2022-2023. - * 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.work; - -public class WorkRequestFactory { - -} \ No newline at end of file -- GitLab From 8bfd8da6d5620d5c0f7e101836a50c6d606a3617 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Mon, 8 Apr 2024 18:45:48 +0200 Subject: [PATCH 17/24] chore: fix call to WorkRequestFactory in PeriodicScanWorker.java --- .../e/drive/periodicScan/PeriodicScanWorker.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/periodicScan/PeriodicScanWorker.java b/app/src/main/java/foundation/e/drive/periodicScan/PeriodicScanWorker.java index ffaf89c6..bcfcfbe9 100644 --- a/app/src/main/java/foundation/e/drive/periodicScan/PeriodicScanWorker.java +++ b/app/src/main/java/foundation/e/drive/periodicScan/PeriodicScanWorker.java @@ -8,9 +8,6 @@ package foundation.e.drive.periodicScan; -import static foundation.e.drive.work.WorkRequestFactory.WorkType.ONE_TIME_APP_LIST; -import static foundation.e.drive.work.WorkRequestFactory.WorkType.ONE_TIME_FULL_SCAN; - import android.content.Context; import androidx.annotation.NonNull; @@ -23,6 +20,7 @@ import androidx.work.WorkerParameters; import java.util.ArrayList; import java.util.List; +import foundation.e.drive.work.OneTimeWorkType; import foundation.e.drive.work.WorkRequestFactory; import timber.log.Timber; @@ -43,10 +41,11 @@ public class PeriodicScanWorker extends Worker { public Result doWork() { try { final WorkManager workManager = WorkManager.getInstance(getApplicationContext()); + final WorkRequestFactory workRequestFactory = WorkRequestFactory.INSTANCE; final List workRequestsLists = new ArrayList<>(); - workRequestsLists.add(WorkRequestFactory.getOneTimeWorkRequest(ONE_TIME_APP_LIST, null)); - workRequestsLists.add(WorkRequestFactory.getOneTimeWorkRequest(ONE_TIME_FULL_SCAN, null)); + workRequestsLists.add(workRequestFactory.createOneTimeWorkRequest(OneTimeWorkType.LIST_APPS, null)); + workRequestsLists.add(workRequestFactory.createOneTimeWorkRequest(OneTimeWorkType.FULL_SCAN, null)); workManager.beginUniqueWork(FullScanWorker.UNIQUE_WORK_NAME, ExistingWorkPolicy.KEEP, workRequestsLists) .enqueue(); -- GitLab From e575dd9ff918706a40eebc442fe19e526a80d142 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Mon, 8 Apr 2024 18:48:51 +0200 Subject: [PATCH 18/24] fix: change FullScanWorkRequest's network constraint from UNMETERED to CONNECTED --- app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt index 7a3de5bf..56cf2d66 100644 --- a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt +++ b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt @@ -100,7 +100,7 @@ object WorkRequestFactory { */ private fun createFullScanWorkRequest(forced: Boolean): OneTimeWorkRequest { val constraints = Constraints.Builder() - .setRequiredNetworkType(NetworkType.UNMETERED) + .setRequiredNetworkType(NetworkType.CONNECTED) .setRequiresBatteryNotLow(true) .build() -- GitLab From 358d3a5cd946998edb6dcc0c5af1912daf17f24b Mon Sep 17 00:00:00 2001 From: Vincent Bourgmayer Date: Tue, 9 Apr 2024 07:55:28 +0000 Subject: [PATCH 19/24] chore: add missing new line at end of file --- app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt index 56cf2d66..3a0bcaba 100644 --- a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt +++ b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt @@ -244,4 +244,4 @@ object WorkRequestFactory { .addTag(WORK_GENERIC_TAG) .build() } -} \ No newline at end of file +} -- GitLab From 50005766a93a09eaf09f3ff4ec535d156b8a7eec Mon Sep 17 00:00:00 2001 From: Jonathan Klee Date: Thu, 11 Apr 2024 07:58:51 +0000 Subject: [PATCH 20/24] chore: apply suggestion. Add extra space after enum in WorkRequestFactory --- app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt index 3a0bcaba..e7997bea 100644 --- a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt +++ b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt @@ -51,6 +51,7 @@ enum class OneTimeWorkType { FINISH_SETUP, ONE_TIME_SYNC } + object WorkRequestFactory { private const val PERIODIC_WORK_REPEAT_INTERVAL = 30L -- GitLab From ad6caf70f23f65117c00a71d847cd1207103f458 Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Thu, 11 Apr 2024 10:03:12 +0200 Subject: [PATCH 21/24] chore: Apply suggestion. rename method & const in WorkRequestFactory --- .../java/foundation/e/drive/work/WorkLauncher.kt | 4 ++-- .../foundation/e/drive/work/WorkRequestFactory.kt | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) 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 57d4e153..21027cb9 100644 --- a/app/src/main/java/foundation/e/drive/work/WorkLauncher.kt +++ b/app/src/main/java/foundation/e/drive/work/WorkLauncher.kt @@ -39,7 +39,7 @@ class WorkLauncher private constructor(context: Context) { } fun enqueuePeriodicUserInfoFetching() { - val request = WorkRequestFactory.getPeriodicWorkRequest(PeriodicWorkType.FETCH_USER_INFO) + val request = WorkRequestFactory.createPeriodicWorkRequest(PeriodicWorkType.FETCH_USER_INFO) workManager.enqueueUniquePeriodicWork( AccountUserInfoWorker.UNIQUE_WORK_NAME, ExistingPeriodicWorkPolicy.UPDATE, @@ -48,7 +48,7 @@ class WorkLauncher private constructor(context: Context) { } fun enqueuePeriodicFullScan() { - val request = WorkRequestFactory.getPeriodicWorkRequest(PeriodicWorkType.PERIODIC_SCAN) + val request = WorkRequestFactory.createPeriodicWorkRequest(PeriodicWorkType.PERIODIC_SCAN) workManager.enqueueUniquePeriodicWork(PeriodicScanWorker.UNIQUE_WORK_NAME, ExistingPeriodicWorkPolicy.KEEP, request) diff --git a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt index e7997bea..ce6960fb 100644 --- a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt +++ b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt @@ -54,15 +54,15 @@ enum class OneTimeWorkType { object WorkRequestFactory { - private const val PERIODIC_WORK_REPEAT_INTERVAL = 30L - private const val PERIODIC_SCAN_FLEX_TIME = 5L + private const val PERIODIC_WORK_REPEAT_INTERVAL_IN_MIN = 30L + private const val PERIODIC_SCAN_FLEX_TIME_IN_MIN = 5L /** * Build an instance of PeriodicWorkRequest depending of the work type specified * @param type WorkType. Should be PERIODIC_SCAN or PERIODIC_USER_INFO * @return Periodic WorkRequest */ - fun getPeriodicWorkRequest(type: PeriodicWorkType): PeriodicWorkRequest { + fun createPeriodicWorkRequest(type: PeriodicWorkType): PeriodicWorkRequest { return when (type) { PeriodicWorkType.FETCH_USER_INFO -> createPeriodicGetUserInfoWorkRequest() PeriodicWorkType.PERIODIC_SCAN -> createPeriodicScanWorkRequest() @@ -227,8 +227,8 @@ object WorkRequestFactory { .build() return PeriodicWorkRequestBuilder( - PERIODIC_WORK_REPEAT_INTERVAL, MINUTES, - PERIODIC_SCAN_FLEX_TIME,MINUTES) + PERIODIC_WORK_REPEAT_INTERVAL_IN_MIN, MINUTES, + PERIODIC_SCAN_FLEX_TIME_IN_MIN,MINUTES) .setConstraints(constraints) .addTag(WORK_GENERIC_TAG) .build() @@ -240,7 +240,7 @@ object WorkRequestFactory { .build() return PeriodicWorkRequestBuilder( - PERIODIC_WORK_REPEAT_INTERVAL, MINUTES) + PERIODIC_WORK_REPEAT_INTERVAL_IN_MIN, MINUTES) .setConstraints(constraint) .addTag(WORK_GENERIC_TAG) .build() -- GitLab From 3e2b1c1b3a630bc74aa69a5a0ad72335fee0459b Mon Sep 17 00:00:00 2001 From: vincent Bourgmayer Date: Mon, 15 Apr 2024 09:23:41 +0200 Subject: [PATCH 22/24] chore: fix: wrong doc in WorkRequestFactory --- .../main/java/foundation/e/drive/work/WorkRequestFactory.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt index ce6960fb..336c0b34 100644 --- a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt +++ b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt @@ -95,8 +95,7 @@ object WorkRequestFactory { /** * Create a OneTimeWorkRequest instance for - * a Full scan with constraints on network (should - * be unmetered) and battery (shouldn't be low) + * a Full scan with constraints on network (CONNECTED) and battery (shouldn't be low) * @return instance of OneTimeWorkRequest */ private fun createFullScanWorkRequest(forced: Boolean): OneTimeWorkRequest { @@ -217,7 +216,7 @@ object WorkRequestFactory { /** * Create a PeriodicWorkRequest instance for - * a Full scan with constraints on network (any) and battery (not low) + * a Full scan with constraints on network (CONNECTED) and battery (not low) * @return instance of PeriodicWorkRequest */ private fun createPeriodicScanWorkRequest(): PeriodicWorkRequest { -- GitLab From 25e00e771eeaf737e707e5d519188adf40f8899e Mon Sep 17 00:00:00 2001 From: Vincent Bourgmayer Date: Mon, 15 Apr 2024 07:51:31 +0000 Subject: [PATCH 23/24] feature: network constraint on fetching user info is now changed to 'connected' --- app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt index 336c0b34..f893338f 100644 --- a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt +++ b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt @@ -134,7 +134,7 @@ object WorkRequestFactory { */ private fun createGetUserInfoWorkRequest(): OneTimeWorkRequest { val constraints = Constraints.Builder() - .setRequiredNetworkType(NetworkType.UNMETERED) + .setRequiredNetworkType(NetworkType.CONNECTED) .setRequiresBatteryNotLow(true) .build() -- GitLab From 342c07d6239b3d464142b76ebfb5ec8b86abb148 Mon Sep 17 00:00:00 2001 From: Fahim Masud Choudhury Date: Mon, 15 Apr 2024 09:53:53 +0000 Subject: [PATCH 24/24] chore: apply suggestion on method doc --- .../e/drive/work/WorkRequestFactory.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt index f893338f..0c3b245b 100644 --- a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt +++ b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.kt @@ -58,7 +58,7 @@ object WorkRequestFactory { private const val PERIODIC_SCAN_FLEX_TIME_IN_MIN = 5L /** - * Build an instance of PeriodicWorkRequest depending of the work type specified + * Builds an instance of PeriodicWorkRequest depending of the work type specified * @param type WorkType. Should be PERIODIC_SCAN or PERIODIC_USER_INFO * @return Periodic WorkRequest */ @@ -70,7 +70,7 @@ object WorkRequestFactory { } /** - * Build an instance of OneTimeWorkRequest depending of the work type specified. + * Builds an instance of OneTimeWorkRequest depending of the work type specified. * @param type Should be ONE_TIME_USER_INFO, or FIRST_START, or CREATE_REMOTE_DIR * or it will throw InvalidParameterException * @param syncedFolder @Nullable this parameter is required for CREATE_REMOTE_DIR work type. Throw an NPE if null. @@ -94,7 +94,7 @@ object WorkRequestFactory { } /** - * Create a OneTimeWorkRequest instance for + * Creates a OneTimeWorkRequest instance for * a Full scan with constraints on network (CONNECTED) and battery (shouldn't be low) * @return instance of OneTimeWorkRequest */ @@ -117,7 +117,7 @@ object WorkRequestFactory { } /** - * Create a workRequest to generate file which contains list of installed apps + * Creates a workRequest to generate file which contains list of installed apps * @return the workRequest */ private fun createAppListGenerationWorkRequest(): OneTimeWorkRequest { @@ -129,7 +129,7 @@ object WorkRequestFactory { } /** - * Instanciate a OneTimeWorkRequest to retrieve user info + * Instantiates a OneTimeWorkRequest to retrieve user info * @return instance of OneTimeWorkRequest */ private fun createGetUserInfoWorkRequest(): OneTimeWorkRequest { @@ -147,7 +147,7 @@ object WorkRequestFactory { } /** - * Create a OneTime workRequest to create a remote folder + * Creates a OneTime workRequest to create a remote folder * With constraints on network (unmetered) and battery (not low) * @param syncedFolder SyncedFolder instance with data about folder to create * @return Instance OneTimeWorkRequest @@ -168,7 +168,7 @@ object WorkRequestFactory { } /** - * Parse SyncedFolder instance in Data, used as data for WorkRequest + * Parses SyncedFolder instance in Data, used as data for WorkRequest * @param folder SyncedFolder instance * @return Data instance */ @@ -189,7 +189,7 @@ object WorkRequestFactory { } /** - * Create a OneTime WorkRequest which finish setup process + * Creates a OneTime WorkRequest which finish setup process * @return Instance of OneTimeWorkRequest */ private fun createFinishSetupWorkRequest(): OneTimeWorkRequest { @@ -215,7 +215,7 @@ object WorkRequestFactory { } /** - * Create a PeriodicWorkRequest instance for + * Creates a PeriodicWorkRequest instance for * a Full scan with constraints on network (CONNECTED) and battery (not low) * @return instance of PeriodicWorkRequest */ -- GitLab