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) {