diff --git a/app/build.gradle b/app/build.gradle index 094951fb7e8c7200df92e4f121aa68c1a93f1d13..c8c8651ff7bf3cb8c43e906800856bd2964b1d87 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,6 @@ plugins { id 'com.android.application' + id 'org.jetbrains.kotlin.android' } @@ -35,7 +36,7 @@ def getSentryDsn = { -> } android { - compileSdk 31 + compileSdk 33 defaultConfig { applicationId "foundation.e.drive" minSdk 26 @@ -99,6 +100,7 @@ dependencies { implementation 'com.google.android.material:material:1.6.0' implementation 'com.github.bumptech.glide:glide:4.14.2' implementation 'com.github.bumptech.glide:annotations:4.14.2' + implementation 'androidx.core:core-ktx:+' annotationProcessor 'com.github.bumptech.glide:compiler:4.14.2' implementation "androidx.work:work-runtime:2.7.1" implementation 'androidx.test:core:1.4.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1c47ced4b8f66e63166711e90003ef99f349d1ca..4cf129c0f2c9a489d7a0c274f5271028dad4ea37 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -5,6 +5,7 @@ + diff --git a/app/src/main/java/foundation/e/drive/RecycleBin.kt b/app/src/main/java/foundation/e/drive/RecycleBin.kt new file mode 100644 index 0000000000000000000000000000000000000000..447106bbf12db6f6f755bf326bafa90a055adb16 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/RecycleBin.kt @@ -0,0 +1,86 @@ +/* + * 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 + */ + +@file:JvmName("RecycleBin") + +package foundation.e.drive + +import foundation.e.drive.utils.AppConstants +import timber.log.Timber +import java.io.File +import java.io.IOException +import java.nio.file.Files +import kotlin.io.path.Path +import kotlin.time.Duration +import kotlin.time.DurationUnit +import kotlin.time.toDuration + +/** + * This class contains method for trashing file & cleaning the trash + */ +object RecycleBin { + private val DELAY_FOR_DELETION = 30.toDuration(DurationUnit.DAYS) + private val BIN_PATH = AppConstants.RECYCLE_BIN_PATH //TMP only, Need to find a way to get context + + /** + * Remove files which are in recycle bin + * for more than DELAY_FOR_DELETION + * @return false as soon as some files that should be removed is not removed + */ + fun clearOldestFiles(): Boolean { + val binDir = File(BIN_PATH) + + if (!binDir.exists()) return true + + try { + val filesToRemove = binDir.listFiles { file -> + computeTimeInBin(file.lastModified()) > DELAY_FOR_DELETION + } + + filesToRemove?.forEach { file -> file?.delete() } + } catch (exception: IOException) { + //Note that some files might have already been removed + Timber.e(exception, "Caught exception when clearing oldest file in bin") + return false + } + return true + } + + /** + * Compute time from which file is in Bin + * and return it as a Duration in days + */ + private fun computeTimeInBin(fileLastModified: Long): Duration { + return (System.currentTimeMillis() - fileLastModified).toDuration(DurationUnit.DAYS) + } + + /** + * put a file into the bin + */ + fun trashFile(file: File): Boolean { + File(BIN_PATH).mkdirs() //Assert that recycle bin exist + + if (file.exists()) { + val targetPath = File(BIN_PATH, file.name).absolutePath + try { + val moveResult = Files.move(file.toPath(), Path(targetPath)) + if (moveResult.toFile().exists()) { + return true + } + } catch (exception: IOException) { + Timber.e(exception) + } catch (exception: SecurityException) { + Timber.e(exception) + } catch (exception: NullPointerException) { + Timber.e(exception) + } + } + Timber.d("Can't move %s to trashbin", file.absolutePath) + return false + } +} \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/contentScanner/RemoteContentScanner.java b/app/src/main/java/foundation/e/drive/contentScanner/RemoteContentScanner.java index 0e8d37ed2e4a208bee9ea625725a45abfe403c0e..9ff7c18c5c58bacadae5b383a9e5c02a487c4b74 100644 --- a/app/src/main/java/foundation/e/drive/contentScanner/RemoteContentScanner.java +++ b/app/src/main/java/foundation/e/drive/contentScanner/RemoteContentScanner.java @@ -82,16 +82,12 @@ public class RemoteContentScanner extends AbstractContentScanner { @Override protected void onMissingFile(SyncedFileState fileState) { - //TODO: disabled file deletion feature for now to handle accidental file deletion. - //Uncomment the following block when we resolve the issue: https://gitlab.e.foundation/e/backlog/-/issues/6711 -/* if (!fileState.hasBeenSynchronizedOnce()) { return; } Timber.d("Add local deletion request for file: %s", fileState.getLocalPath()); this.syncRequests.put(fileState.getId(), new SyncRequest(fileState, LOCAL_DELETE)); -*/ } @Override diff --git a/app/src/main/java/foundation/e/drive/services/InitializerService.java b/app/src/main/java/foundation/e/drive/services/InitializerService.java index 7c0e0738130e610a2fb3f10741ac39ee416cb239..c6438174e77c8ab39a944da563b732032db9a8ce 100644 --- a/app/src/main/java/foundation/e/drive/services/InitializerService.java +++ b/app/src/main/java/foundation/e/drive/services/InitializerService.java @@ -1,6 +1,6 @@ /* * Copyright © CLEUS SAS 2018-2019. - * Copyright © ECORP SAS 2022. + * Copyright © ECORP 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 @@ -15,26 +15,18 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.os.Build; -import android.os.Environment; import android.os.IBinder; -import com.owncloud.android.lib.common.OwnCloudClient; - -import java.util.ArrayList; -import java.util.Arrays; +import java.io.File; import java.util.List; 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 timber.log.Timber; -import static com.owncloud.android.lib.resources.files.FileUtils.PATH_SEPARATOR; -import static foundation.e.drive.utils.AppConstants.MEDIA_SYNCABLE_CATEGORIES; -import static foundation.e.drive.utils.AppConstants.SETTINGS_SYNCABLE_CATEGORIES; - +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.work.WorkManager; @@ -44,8 +36,6 @@ import androidx.work.WorkManager; * @author Abhishek Aggarwal */ public class InitializerService extends Service { - private List syncedFolders; - private OwnCloudClient cloudClient; private Account account; @Override @@ -60,129 +50,71 @@ public class InitializerService extends Service { CommonUtils.setServiceUnCaughtExceptionHandler(this); //Get account - SharedPreferences prefs = this.getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); + final SharedPreferences prefs = this.getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); - if (prefs.getBoolean(AppConstants.INITIALIZATION_HAS_BEEN_DONE, false)) { - Timber.i("Initializer has already been done"); - } else { - String accountName = prefs.getString(AccountManager.KEY_ACCOUNT_NAME, ""); - String accountType = prefs.getString(AccountManager.KEY_ACCOUNT_TYPE, ""); - - if (accountName.isEmpty() && accountType.isEmpty() && intent.getExtras() != null) { - - accountName = intent.getExtras().getString(AccountManager.KEY_ACCOUNT_NAME, ""); - accountType = intent.getExtras().getString(AccountManager.KEY_ACCOUNT_TYPE, ""); - - prefs.edit().putString(AccountManager.KEY_ACCOUNT_NAME, accountName) - .putString(AccountManager.KEY_ACCOUNT_TYPE, accountType) - .apply(); - } - - if (accountName.isEmpty()) { - Timber.d("Account's name not found"); - stopSelf(); - } else { - this.account = CommonUtils.getAccount(accountName, accountType, AccountManager.get(this)); - if (this.account != null) { - this.cloudClient = DavClientProvider.getInstance().getClientInstance(account, getApplicationContext()); - start(); - } else { - Timber.i("Got account is invalid"); - stopSelf(); - } - } + String accountName = prefs.getString(AccountManager.KEY_ACCOUNT_NAME, ""); + String accountType = prefs.getString(AccountManager.KEY_ACCOUNT_TYPE, ""); + + if (accountName.isEmpty() && accountType.isEmpty() && intent.getExtras() != null) { + + accountName = intent.getExtras().getString(AccountManager.KEY_ACCOUNT_NAME, ""); + accountType = intent.getExtras().getString(AccountManager.KEY_ACCOUNT_TYPE, ""); + + prefs.edit().putString(AccountManager.KEY_ACCOUNT_NAME, accountName) + .putString(AccountManager.KEY_ACCOUNT_TYPE, accountType) + .apply(); + } + + if (checkStartConditions(prefs, accountName, accountType)) { + start(); } return super.onStartCommand(intent, flags, startId); } - public void start() { - Timber.d("start()"); - if (cloudClient == null) { - stopSelf(); - return; + /** + * Check if condition are present to start + * - Initialization not already done + * - AccountName is not empty + * - Account available + * @return true if condition are met + */ + private boolean checkStartConditions(@NonNull final SharedPreferences prefs, + @NonNull final String accountName, @NonNull final String accountType) { + if (prefs.getBoolean(AppConstants.INITIALIZATION_HAS_BEEN_DONE, false)) { + Timber.w("Initialization has already been done"); + return false; } - - CommonUtils.registerPeriodicUserInfoChecking(WorkManager.getInstance(this)); - final List syncCategories = new ArrayList<>(); - syncCategories.addAll(Arrays.asList(MEDIA_SYNCABLE_CATEGORIES)); - syncCategories.addAll(Arrays.asList(SETTINGS_SYNCABLE_CATEGORIES)); + if (accountName.isEmpty()) { + Timber.w("No account Name available"); + return false; + } - getInitialSyncedFolders(syncCategories); - CommonUtils.registerInitializationWorkers(syncedFolders, WorkManager.getInstance(getApplicationContext()) ); + account = CommonUtils.getAccount(accountName, accountType, AccountManager.get(this)); + if (account == null) { + Timber.w("got Invalid %s account for username: %s ", accountType, accountName); + return false; + } + return true; } /** - * Return a list of SyncedFolder - * @param categories categories indicating which syncedFolder to create + * Set up base component for eDrive: + * - Register basic worker + * - build root folders to sync + * - Create Recycle bin for eDrive */ - private void getInitialSyncedFolders(List categories) { - Timber.d("getInitialSyncedFolders"); - - this.syncedFolders = new ArrayList<>(); - - for(int i=-1, size = categories.size(); ++i < size;) { - final String DEVICE_SPECIFIC_PATH = PATH_SEPARATOR+"Devices"+PATH_SEPARATOR+ Build.BRAND+"_"+ Build.MODEL+"_" - + Build.SERIAL; - switch (categories.get(i)) { - case "Images": - syncedFolders.add(new SyncedFolder(categories.get(i), getExternalFolder(Environment.DIRECTORY_DCIM), - "/Photos/", true)); - syncedFolders.add(new SyncedFolder(categories.get(i), getExternalFolder(Environment.DIRECTORY_PICTURES), - "/Pictures/", true)); - break; - case "Movies": - syncedFolders.add(new SyncedFolder(categories.get(i), getExternalFolder(Environment.DIRECTORY_MOVIES), - "/Movies/", true)); - break; - case "Music": - syncedFolders.add(new SyncedFolder(categories.get(i), getExternalFolder(Environment.DIRECTORY_MUSIC), - "/Music/", true)); - break; - case "Ringtones": - syncedFolders.add(new SyncedFolder(categories.get(i), getExternalFolder(Environment.DIRECTORY_RINGTONES), - "/Ringtones/", true)); - break; - case "Documents": - syncedFolders.add(new SyncedFolder(categories.get(i), getExternalFolder(Environment.DIRECTORY_DOCUMENTS), - "/Documents/", true)); - break; - case "Podcasts": - syncedFolders.add(new SyncedFolder(categories.get(i), getExternalFolder(Environment.DIRECTORY_PODCASTS), - "/Podcasts/", true)); - break; - case "Rom settings": - - String remoteFolderPath = DEVICE_SPECIFIC_PATH+"/rom_settings/"; - syncedFolders.add(new SyncedFolder(categories.get(i), "/data/system/users/0/", remoteFolderPath, true, false, true, false)); - try{ - syncedFolders.add(new SyncedFolder( - categories.get(i), - getFilesDir().getCanonicalPath()+PATH_SEPARATOR, - remoteFolderPath+"app_list/", - true, - false, - CommonUtils.isSettingsSyncEnabled(account), - false)); - } catch (Exception exception) { - Timber.e(exception); - } - break; - } - } - } + private void start() { + Timber.d("start()"); + + CommonUtils.registerPeriodicUserInfoChecking(WorkManager.getInstance(this)); - private String getExternalFolder(String directory) { - return CommonUtils.getLocalPath(Environment.getExternalStoragePublicDirectory(directory))+ PATH_SEPARATOR; - } + final List syncedFolders = RootSyncedFolderProvider.INSTANCE.getSyncedFolderRoots(getApplicationContext()); - @Override - public void onDestroy() { - super.onDestroy(); - this.account = null; - this.cloudClient = null; - if (this.syncedFolders != null) this.syncedFolders.clear(); - this.syncedFolders = null; + final boolean recycleBinCreated = new File(AppConstants.RECYCLE_BIN_PATH).mkdirs(); + if (!recycleBinCreated) Timber.w("Cannot create recycle bin. It may be already existing"); + + CommonUtils.registerInitializationWorkers(syncedFolders, WorkManager.getInstance(getApplicationContext()) ); } @Nullable diff --git a/app/src/main/java/foundation/e/drive/services/ObserverService.java b/app/src/main/java/foundation/e/drive/services/ObserverService.java index 5664eb1cbc6d680339fe9f284debee2192e85103..a9b761050bc3d625475002494ac2af23d0ad0cd2 100644 --- a/app/src/main/java/foundation/e/drive/services/ObserverService.java +++ b/app/src/main/java/foundation/e/drive/services/ObserverService.java @@ -222,7 +222,6 @@ public class ObserverService extends Service implements OnRemoteOperationListene /** * Clear cached file unused: - * remove each cached file which isn't in OperationManagerService.lockedSyncedFileState(); */ private void clearCachedFile(){ Timber.i("clearCachedFile()"); diff --git a/app/src/main/java/foundation/e/drive/services/SynchronizationService.java b/app/src/main/java/foundation/e/drive/services/SynchronizationService.java index 749461675a35d4217446029b29b7f22d5ec0efd9..78c87b07e86f748415d8f713305332b3c89f9e8e 100644 --- a/app/src/main/java/foundation/e/drive/services/SynchronizationService.java +++ b/app/src/main/java/foundation/e/drive/services/SynchronizationService.java @@ -236,16 +236,13 @@ public class SynchronizationService extends Service implements OnRemoteOperation final SyncWrapper syncWrapper = new SyncWrapper(request, account, getApplicationContext()); if (request.getOperationType().equals(SyncRequest.Type.LOCAL_DELETE)) { - //TODO: disabled file deletion feature for now to handle accidental file deletion. - //Uncomment the following block when we resolve the issue: https://gitlab.e.foundation/e/backlog/-/issues/6711 -/* + Timber.v(" starts " + request.getSyncedFileState().getName() + " local deletion on thread " + threadIndex); final LocalFileDeleter fileDeleter = new LocalFileDeleter(request.getSyncedFileState()); threadPool[threadIndex] = new Thread(fileDeleter.getRunnable( handler, threadIndex, getApplicationContext(), this)); threadPool[threadIndex].start(); startedSync.put(threadIndex, syncWrapper); -*/ return; } diff --git a/app/src/main/java/foundation/e/drive/utils/AppConstants.java b/app/src/main/java/foundation/e/drive/utils/AppConstants.java index bad256e6ab2ec8b0e8698d509008d26dd305c1d0..0d907fc37a1f13b464f65b80a487e732363054b7 100644 --- a/app/src/main/java/foundation/e/drive/utils/AppConstants.java +++ b/app/src/main/java/foundation/e/drive/utils/AppConstants.java @@ -1,6 +1,6 @@ /* * Copyright © CLEUS SAS 2018-2019. - * Copyright © ECORP SAS 2022. + * Copyright © ECORP 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 @@ -9,7 +9,10 @@ package foundation.e.drive.utils; +import static com.owncloud.android.lib.resources.files.FileUtils.PATH_SEPARATOR; + import android.os.Build; +import android.os.Environment; import java.text.SimpleDateFormat; import java.util.Locale; @@ -47,6 +50,8 @@ public abstract class AppConstants { public static final String WORK_INITIALIZATION_TAG = "eDrive-init"; public static final String USER_AGENT = "eos(" + getBuildTime() + ")-eDrive(" + BuildConfig.VERSION_NAME + ")"; + public static final String RECYCLE_BIN_PATH = Environment.getExternalStorageDirectory() + PATH_SEPARATOR + "Sync trash"; + /** * Get a readable OS's build date String * diff --git a/app/src/main/java/foundation/e/drive/utils/RootSyncedFolderProvider.kt b/app/src/main/java/foundation/e/drive/utils/RootSyncedFolderProvider.kt new file mode 100644 index 0000000000000000000000000000000000000000..2c3d46b90f3c5039e862da228194585dd91ab558 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/utils/RootSyncedFolderProvider.kt @@ -0,0 +1,118 @@ +/* + * 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.utils + +import android.content.Context +import android.os.Build +import android.os.Build.BRAND +import android.os.Build.MODEL +import android.os.Environment +import android.os.Environment.DIRECTORY_PODCASTS +import android.os.Environment.DIRECTORY_MUSIC +import android.os.Environment.DIRECTORY_DCIM +import android.os.Environment.DIRECTORY_DOCUMENTS +import android.os.Environment.DIRECTORY_PICTURES +import android.os.Environment.DIRECTORY_RINGTONES +import android.os.Environment.DIRECTORY_MOVIES +import com.owncloud.android.lib.resources.files.FileUtils.PATH_SEPARATOR +import foundation.e.drive.models.SyncedFolder + +object RootSyncedFolderProvider { + private const val CATEGORY_IMAGES = "Images" + private const val CATEGORY_MOVIES = "Movies" + private const val CATEGORY_MUSIC = "Music" + private const val CATEGORY_RINGTONES = "Ringtones" + private const val CATEGORY_DOCUMENTS = "Documents" + private const val CATEGORY_PODCASTS = "Podcasts" + private const val CATEGORY_ROM_SETTINGS = "Rom settings" + + private const val LOCAL_ROM_SETTINGS_PATH = "/data/system/users/0/" + private val REMOTE_ROM_SETTINGS_PATH = PATH_SEPARATOR + "Devices" + PATH_SEPARATOR + BRAND + "_" + MODEL + "_" + Build.getSerial() + "/rom_settings/" + private val REMOTE_APP_LIST_PATH = REMOTE_ROM_SETTINGS_PATH + "app_list/" + + fun getSyncedFolderRoots(context : Context): ArrayList { + val syncedFolders = ArrayList() + val categories = getSyncableCategories() + + categories.forEach { + when (it) { + CATEGORY_IMAGES -> { + syncedFolders.add(createPhotosSyncedFolder()) + syncedFolders.add(createPicturesSyncedFolder()) + } + CATEGORY_MOVIES -> syncedFolders.add(createMovieSyncedFolder()) + CATEGORY_DOCUMENTS -> syncedFolders.add(createDocumentsSyncedFolder()) + CATEGORY_MUSIC -> syncedFolders.add(createMusicsSyncedFolder()) + CATEGORY_PODCASTS -> syncedFolders.add(createPodcastsSyncedFolder()) + CATEGORY_RINGTONES -> syncedFolders.add(createRingtonesSyncedFolder()) + CATEGORY_ROM_SETTINGS -> { + syncedFolders.add(createRomSettingsSyncedFolder()) + syncedFolders.add(createAppListSyncedFolder(context)) + } + } + } + return syncedFolders + } + + private fun getSyncableCategories(): List { + return listOf(CATEGORY_IMAGES, + CATEGORY_MOVIES, + CATEGORY_MUSIC, + CATEGORY_RINGTONES, + CATEGORY_DOCUMENTS, + CATEGORY_PODCASTS, + CATEGORY_ROM_SETTINGS) + } + + private fun createPhotosSyncedFolder(): SyncedFolder { + return createMediaSyncedFolder(CATEGORY_IMAGES, DIRECTORY_DCIM, "/Photos/") + } + + private fun createPicturesSyncedFolder(): SyncedFolder { + return createMediaSyncedFolder(CATEGORY_IMAGES, DIRECTORY_PICTURES, "/Pictures/") + } + + private fun createMovieSyncedFolder(): SyncedFolder { + return createMediaSyncedFolder(CATEGORY_MOVIES, DIRECTORY_MOVIES, "/Movies/") + } + + private fun createDocumentsSyncedFolder(): SyncedFolder { + return createMediaSyncedFolder(CATEGORY_DOCUMENTS, DIRECTORY_DOCUMENTS, "/Documents/") + } + + private fun createMusicsSyncedFolder(): SyncedFolder { + return createMediaSyncedFolder(CATEGORY_MUSIC, DIRECTORY_MUSIC, "/Music/") + } + + private fun createRingtonesSyncedFolder(): SyncedFolder { + return createMediaSyncedFolder(CATEGORY_RINGTONES, DIRECTORY_RINGTONES, "/Ringtones/") + } + + private fun createPodcastsSyncedFolder(): SyncedFolder { + return createMediaSyncedFolder(CATEGORY_PODCASTS, DIRECTORY_PODCASTS, "/Podcasts/") + } + + private fun createRomSettingsSyncedFolder(): SyncedFolder { + return createSettingsSyncedFolder(CATEGORY_ROM_SETTINGS, LOCAL_ROM_SETTINGS_PATH, REMOTE_ROM_SETTINGS_PATH) + } + + private fun createAppListSyncedFolder(context: Context): SyncedFolder { + val localPath = context.filesDir.absolutePath + PATH_SEPARATOR + return createSettingsSyncedFolder(CATEGORY_ROM_SETTINGS, localPath, REMOTE_APP_LIST_PATH) + } + + private fun createMediaSyncedFolder(category: String, publicDirectoryType: String, remotePath: String): SyncedFolder { + val dirPath = CommonUtils.getLocalPath(Environment.getExternalStoragePublicDirectory(publicDirectoryType)) + val localPath = dirPath + PATH_SEPARATOR + return SyncedFolder(category, localPath, remotePath, true) + } + + private fun createSettingsSyncedFolder(category: String, localPath: String, remotePath: String): SyncedFolder { + return SyncedFolder(category, localPath, remotePath, true, false, true, false) + } +} \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/work/FirstStartWorker.java b/app/src/main/java/foundation/e/drive/work/FirstStartWorker.java index 7cdbf4f15c6eb94fde151eec34e1b7d64a148d60..5890e103e6c450b5ff71fe13f3a01363f2dce8cc 100644 --- a/app/src/main/java/foundation/e/drive/work/FirstStartWorker.java +++ b/app/src/main/java/foundation/e/drive/work/FirstStartWorker.java @@ -10,6 +10,7 @@ package foundation.e.drive.work; import static foundation.e.drive.utils.AppConstants.INITIALFOLDERS_NUMBER; import static foundation.e.drive.work.WorkRequestFactory.WorkType.ONE_TIME_APP_LIST; +import static foundation.e.drive.work.WorkRequestFactory.WorkType.PERIODIC_CLEANING; import static foundation.e.drive.work.WorkRequestFactory.WorkType.PERIODIC_SCAN; import android.content.Context; @@ -74,5 +75,9 @@ public class FirstStartWorker extends Worker { workManager.enqueueUniquePeriodicWork(PeriodicWorker.UNIQUE_WORK_NAME, ExistingPeriodicWorkPolicy.KEEP, WorkRequestFactory.getPeriodicWorkRequest(PERIODIC_SCAN)); + + workManager.enqueueUniquePeriodicWork(RecycleBinCleaningWorker.UNIQUE_NAME, + ExistingPeriodicWorkPolicy.KEEP, + WorkRequestFactory.getPeriodicWorkRequest(PERIODIC_CLEANING)); } } \ No newline at end of file diff --git a/app/src/main/java/foundation/e/drive/work/LocalFileDeleter.java b/app/src/main/java/foundation/e/drive/work/LocalFileDeleter.java index df8e0ad818ee42be0701ec4262e553d7ee8678d9..bfbc1c350dcd21d92c34400d97e5665a7ea45956 100644 --- a/app/src/main/java/foundation/e/drive/work/LocalFileDeleter.java +++ b/app/src/main/java/foundation/e/drive/work/LocalFileDeleter.java @@ -13,6 +13,7 @@ import android.provider.MediaStore; import java.io.File; +import foundation.e.drive.RecycleBin; import foundation.e.drive.database.DbHelper; import foundation.e.drive.models.SyncedFileState; import foundation.e.drive.utils.CommonUtils; @@ -37,34 +38,21 @@ public class LocalFileDeleter { public Runnable getRunnable(final Handler handler, final int threadId, final Context context, final LocalFileDeletionListener listener) { return () -> { - final boolean succeed = deleteFile(fileState, context); - notifyCompletion(threadId, listener, succeed, handler); - }; - } + final File file = new File(fileState.getLocalPath()); + final boolean succeed = RecycleBin.INSTANCE.trashFile(file); + + if (succeed) { + context.getContentResolver().delete(MediaStore.Files.getContentUri("external"), + MediaStore.Files.FileColumns.DATA + "=?", + new String[]{CommonUtils.getLocalPath(file)}); - private boolean deleteFile(SyncedFileState fileState, Context context) { - final File file = new File(fileState.getLocalPath()); - if (file.exists()) { - try { - if (!file.delete()) { - Timber.d("local file ( %s ) removal failed", file.getName()); - return false; + if (DbHelper.manageSyncedFileStateDB(fileState, "DELETE", context) <= 0) { + Timber.d("Failed to remove %s from DB", file.getName()); } - } catch (SecurityException exception) { - Timber.e(exception); - return false; } - context.getContentResolver().delete(MediaStore.Files.getContentUri("external"), - MediaStore.Files.FileColumns.DATA + "=?", - new String[]{CommonUtils.getLocalPath(file)}); - } - - - if (DbHelper.manageSyncedFileStateDB(fileState, "DELETE", context) <= 0) { - Timber.d("Failed to remove %s from DB", file.getName()); - } - return true; + notifyCompletion(threadId, listener, succeed, handler); + }; } private void notifyCompletion(final int threadId, final LocalFileDeletionListener listener, final boolean success, final Handler handler) { diff --git a/app/src/main/java/foundation/e/drive/work/RecycleBinCleaningWorker.kt b/app/src/main/java/foundation/e/drive/work/RecycleBinCleaningWorker.kt new file mode 100644 index 0000000000000000000000000000000000000000..45328ed3589a82b870153b36ebee9c798d234632 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/work/RecycleBinCleaningWorker.kt @@ -0,0 +1,34 @@ +/* + * 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 android.content.Context +import androidx.work.Worker +import androidx.work.WorkerParameters +import foundation.e.drive.RecycleBin + +/** + * This class is the worker that call cleaning of eDrive's technical files + * and cleaning + * @author Vincent Bourgmayer + */ + +class RecycleBinCleaningWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) { + companion object { + const val UNIQUE_NAME = "binCleaner" + } + + override fun doWork(): Result { + val result = RecycleBin.clearOldestFiles() + + if (result) + return Result.success() + else + return Result.failure() + } +} \ No newline at end of file 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 52084c10f3d664bd87838ee2c64c474f0d4a8532..3b6a0892151eb1c74ff88426269270f2271c4fd1 100644 --- a/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.java +++ b/app/src/main/java/foundation/e/drive/work/WorkRequestFactory.java @@ -26,7 +26,6 @@ import androidx.work.Constraints; import androidx.work.Data; import androidx.work.NetworkType; import androidx.work.OneTimeWorkRequest; -import androidx.work.OutOfQuotaPolicy; import androidx.work.PeriodicWorkRequest; import java.security.InvalidParameterException; @@ -39,6 +38,7 @@ public class WorkRequestFactory { public enum WorkType { PERIODIC_USER_INFO, PERIODIC_SCAN, + PERIODIC_CLEANING, ONE_TIME_FULL_SCAN, ONE_TIME_APP_LIST, ONE_TIME_USER_INFO, @@ -58,11 +58,23 @@ public class WorkRequestFactory { return createPeriodicScanWorkRequest(); case PERIODIC_USER_INFO: return createPeriodicGetUserInfoWorkRequest(); + case PERIODIC_CLEANING: + return createPeriodicCleaningWorkRequest(); default: throw new InvalidParameterException("Unsupported Work Type: " + type); } } + /** + * Create a periodic work request that clean the trash bin + * @return + */ + private static PeriodicWorkRequest createPeriodicCleaningWorkRequest() { + return new PeriodicWorkRequest.Builder(RecycleBinCleaningWorker.class, 1, TimeUnit.DAYS, 2, TimeUnit.HOURS) + .addTag(AppConstants.WORK_GENERIC_TAG) + .build(); + } + /** * Create a PeridocWorkRequest instance for * a Full scan with constraints on network (should @@ -125,7 +137,6 @@ public class WorkRequestFactory { /** * Create a workRequest to generate file which contains list of installed apps - * @param expedited if request is expedited * @return the workRequest */ private static OneTimeWorkRequest createOneTimeAppListGenerationWorkRequest() { diff --git a/build.gradle b/build.gradle index 3858e8721ea3bb6fc4d91cb1cab840b43a985749..2d560f5fea0b4f87ee1048719049ceb63400a562 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ plugins { id 'com.android.application' version '7.1.3' apply false id 'com.android.library' version '7.1.3' apply false + id 'org.jetbrains.kotlin.android' version '1.8.20-RC' apply false } task clean(type: Delete) {