Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1e3b5389 authored by Vincent Bourgmayer's avatar Vincent Bourgmayer
Browse files

Merge branch '6745-eDrive-Trashbin' into 'main'

Implement trashbin

See merge request !210
parents a0da4066 b0c4f715
Loading
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
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'
+1 −0
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/> <!-- for Android 30+ -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+86 −0
Original line number Diff line number Diff line
/*
 * 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
+0 −4
Original line number Diff line number Diff line
@@ -82,16 +82,12 @@ public class RemoteContentScanner extends AbstractContentScanner<RemoteFile> {

    @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
+55 −123
Original line number Diff line number Diff line
/*
 * 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<SyncedFolder> syncedFolders;
    private OwnCloudClient cloudClient;
    private Account account;

    @Override
@@ -60,11 +50,8 @@ 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, "");

@@ -78,111 +65,56 @@ public class InitializerService extends Service {
                    .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());
        if (checkStartConditions(prefs, accountName, accountType)) {
            start();
                } else {
                    Timber.i("Got account is invalid");
                    stopSelf();
                }
            }
        }
        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<String> 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<String> 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()");
        
    private String getExternalFolder(String directory) {
        return CommonUtils.getLocalPath(Environment.getExternalStoragePublicDirectory(directory))+ PATH_SEPARATOR;
    }
        CommonUtils.registerPeriodicUserInfoChecking(WorkManager.getInstance(this));

    @Override
    public void onDestroy() {
        super.onDestroy();
        this.account = null;
        this.cloudClient = null;
        if (this.syncedFolders != null) this.syncedFolders.clear();
        this.syncedFolders = null;
        final List<SyncedFolder> syncedFolders = RootSyncedFolderProvider.INSTANCE.getSyncedFolderRoots(getApplicationContext());

        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
Loading