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

Commit 85cd5349 authored by Vincent Bourgmayer's avatar Vincent Bourgmayer
Browse files

Merge branch '110-r-InitializerServiceRefactoring' into '1-epic-refactoring-p1'

handle Initialization with Work Request

See merge request !100
parents d97332a2 9addc331
Loading
Loading
Loading
Loading
Loading
+0 −64
Original line number Diff line number Diff line
/*
 * Copyright © Vincent Bourgmayer (/e/ foundation).
 * 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.operations;

import android.content.Context;
import android.util.Log;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.resources.files.CreateFolderRemoteOperation;
import java.io.File;
import foundation.e.drive.database.DbHelper;
import foundation.e.drive.models.SyncedFolder;

/**
 * @author Vincent Bourgmayer
 * Perform initial folder creation
 */
public class CreateInitialFolderRemoteOperation extends RemoteOperation {
    private static final String TAG = CreateInitialFolderRemoteOperation.class.getSimpleName();

    private SyncedFolder mSyncedFolder;
    private String mRemotePath;
    private boolean mCreateFullPath;//should recreate parent path if not exist or not
    private final Context mContext;

    public CreateInitialFolderRemoteOperation(SyncedFolder root, boolean createFullPath, Context context) {
        super();
        this.mSyncedFolder = root;
        this.mRemotePath = root.getRemoteFolder();
        this.mCreateFullPath = createFullPath;
        this.mContext = context;
    }

    @Override
    protected RemoteOperationResult run(OwnCloudClient client) {
        File folder = new File(mSyncedFolder.getLocalFolder() );
        if( !folder.exists() ){
            Log.e(TAG, "Local folder doesn't exist, so create it");
            folder.mkdirs();
        }

        //Perfom request
        CreateFolderRemoteOperation createFolderOperation = new CreateFolderRemoteOperation(mRemotePath, mCreateFullPath);
        RemoteOperationResult createOperationResult  = createFolderOperation.execute(client, true);

        if(createOperationResult.isSuccess() || createOperationResult.getCode() == RemoteOperationResult.ResultCode.FOLDER_ALREADY_EXISTS ){
            if(DbHelper.insertSyncedFolder(mSyncedFolder, mContext) >= 0 ) {
                Log.d(TAG, "Insertion in DB succeed");
                return new RemoteOperationResult(RemoteOperationResult.ResultCode.OK);
            }else {
                Log.d(TAG, "insertion of folder in DB failed");
                return new RemoteOperationResult(RemoteOperationResult.ResultCode.FOLDER_ALREADY_EXISTS);
            }
        }
        return createOperationResult;
    }
}
+7 −113
Original line number Diff line number Diff line
@@ -16,27 +16,20 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;

import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.operations.OnRemoteOperationListener;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import foundation.e.drive.EdriveApplication;
import foundation.e.drive.models.SyncedFolder;
import foundation.e.drive.operations.CreateInitialFolderRemoteOperation;
import foundation.e.drive.utils.AppConstants;
import foundation.e.drive.utils.CommonUtils;

import static com.owncloud.android.lib.resources.files.FileUtils.PATH_SEPARATOR;
import static foundation.e.drive.utils.AppConstants.INITIALFOLDERS_NUMBER;
import static foundation.e.drive.utils.AppConstants.MEDIA_SYNCABLE_CATEGORIES;
import static foundation.e.drive.utils.AppConstants.SETTINGS_SYNCABLE_CATEGORIES;

@@ -46,21 +39,16 @@ import androidx.work.WorkManager;
/**
 * @author Vincent Bourgmayer
 */
public class InitializerService extends Service implements OnRemoteOperationListener {
public class InitializerService extends Service {
    private final String TAG = InitializerService.class.getSimpleName();

    private int existingRemoteFolderCounter; //Temporarily used to know if all remotePath exist
    private List<SyncedFolder> syncedFolders;
    private OwnCloudClient cloudClient;
    private Handler handler;
    private Account account;
    private int restartFolderCreationCounter =0;

    @Override
    public void onCreate() {
        Log.i(TAG, "onCreate()");
        super.onCreate();
        this.existingRemoteFolderCounter = 0;
    }

    @Override
@@ -112,21 +100,13 @@ public class InitializerService extends Service implements OnRemoteOperationList
            return;
        }

        List<String> syncCategories = new ArrayList<>();
        final List<String> syncCategories = new ArrayList<>();

        if (CommonUtils.isMediaSyncEnabled(account)) {
        syncCategories.addAll(Arrays.asList(MEDIA_SYNCABLE_CATEGORIES));
        }

        if (CommonUtils.isSettingsSyncEnabled(account)) {
        syncCategories.addAll(Arrays.asList(SETTINGS_SYNCABLE_CATEGORIES));
        }

        getInitialSyncedFolders(syncCategories);

        this.existingRemoteFolderCounter = 0;

        CreateNextRemoteFolder();
        CommonUtils.registerInitializationWorkers(syncedFolders, WorkManager.getInstance(getApplicationContext()) );
    }

    /**
@@ -142,8 +122,6 @@ public class InitializerService extends Service implements OnRemoteOperationList
            final String DEVICE_SPECIFIC_PATH = PATH_SEPARATOR+"Devices"+PATH_SEPARATOR+ Build.BRAND+"_"+ Build.MODEL+"_"
                    + Build.SERIAL;
            switch (categories.get(i)) {
                case "Medias":
                    break;
                case "Images":
                    syncedFolders.add(new SyncedFolder(categories.get(i), getExternalFolder(Environment.DIRECTORY_DCIM),
                            "/Photos/", true));
@@ -171,6 +149,7 @@ public class InitializerService extends Service implements OnRemoteOperationList
                            "/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{
@@ -180,7 +159,7 @@ public class InitializerService extends Service implements OnRemoteOperationList
                        remoteFolderPath+"app_list/",
                                true,
                                false,
                                true,
                                CommonUtils.isSettingsSyncEnabled(account),
                                false));
                    } catch (Exception e) { Log.e(TAG, e.toString()); }
                break;
@@ -192,94 +171,9 @@ public class InitializerService extends Service implements OnRemoteOperationList
        return CommonUtils.getLocalPath(Environment.getExternalStoragePublicDirectory(directory))+ PATH_SEPARATOR;
    }

    private void CreateNextRemoteFolder() {
        Log.i(TAG, "createNextRemoteFolder()");
        this.restartFolderCreationCounter = 0;

        if (this.syncedFolders == null || this.syncedFolders.isEmpty()) {
            this.stopSelf();
        }

        //It means that there are still folders to create
        if (this.existingRemoteFolderCounter < this.syncedFolders.size()) {
            if (this.handler == null) this.handler = new Handler();

            CreateInitialFolderRemoteOperation createFolderOperation =
                    new CreateInitialFolderRemoteOperation(
                        this.syncedFolders.get(this.existingRemoteFolderCounter),
                            true,
                            this);

            createFolderOperation.execute(this.cloudClient, this, this.handler);

        } else if (this.existingRemoteFolderCounter ==  this.syncedFolders.size()) {
            doLastStep();
        } else {
            Log.e(TAG, "this.existingRemoteFolderCounter : "+this.existingRemoteFolderCounter+" > this.mSyncedFolders.size() : "+this.syncedFolders.size());
            this.stopSelf();
        }
    }

    @Override
    public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
        Log.i(TAG, "onRemoteOperationFinish()");
        if (operation instanceof CreateInitialFolderRemoteOperation) {

            if (result.isSuccess() || result.getCode() == RemoteOperationResult.ResultCode.FOLDER_ALREADY_EXISTS) {
                this.existingRemoteFolderCounter+=1;
                CreateNextRemoteFolder();

            } else if (result.getHttpCode() == 423 || result.getHttpCode() == 409) {//file locked or conflict in result
                Log.e(TAG, result.getLogMessage());

                if (this.restartFolderCreationCounter < 3) {
                    Log.w(TAG, " restart operation");
                    operation.execute(this.cloudClient, this, this.handler);
                    this.restartFolderCreationCounter+=1;
                } else {
                    Log.e(TAG, "Remote folder's creation failed due to conflict with server");
                    stopSelf();
                }
            } else {
                Log.e(TAG, result.getLogMessage()+" "+result.getHttpCode());
                stopSelf();
            }
        }
    }


    /**
     * Function to check if all remote folder have been created
     **/
    private void doLastStep() {
        Log.i(TAG, "doLastStep()");
        final Context appContext = getApplicationContext();
        appContext.getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME,
                Context.MODE_PRIVATE)
                .edit()
                .putBoolean(AppConstants.INITIALIZATION_HAS_BEEN_DONE, true)
                .putInt(INITIALFOLDERS_NUMBER, syncedFolders.size())
                .apply();

        CommonUtils.registerPeriodicFullScanWorker(WorkManager.getInstance(appContext));

        //all folder have been created
        ((EdriveApplication) this.getApplication()).startRecursiveFileObserver();

        Intent SynchronizationServiceIntent = new Intent(getApplicationContext(), foundation.e.drive.services.SynchronizationService.class);
        startService(SynchronizationServiceIntent);

        //Immediatly start ObserverService to not have to wait 30 minutes.
        Intent observersServiceIntent = new Intent(getApplicationContext(), foundation.e.drive.services.ObserverService.class);
        startService(observersServiceIntent);

        stopSelf();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        this.handler = null;
        this.account = null;
        this.cloudClient = null;
        if (this.syncedFolders != null) this.syncedFolders.clear();
+5 −5
Original line number Diff line number Diff line
@@ -34,11 +34,11 @@ public abstract class AppConstants {
    public static final String[] MEDIA_SYNCABLE_CATEGORIES = new String[]{"Images", "Movies", "Music", "Ringtones", "Documents", "Podcasts"};
    public static final String[] SETTINGS_SYNCABLE_CATEGORIES = new String[] {"Rom settings"};

    public final static String notificationChannelID ="3310";
    public final static String notificationChannelName="eDrive channel";
    public final static String WORK_GENERIC_TAG="eDrive";

    public final static String USER_AGENT = "eos("+getBuildTime()+")-eDrive("+ BuildConfig.VERSION_NAME +")";
    public static final String notificationChannelID ="3310";
    public static final String notificationChannelName="eDrive channel";
    public static final String WORK_GENERIC_TAG="eDrive";
    public static final String WORK_INITIALIZATION_TAG="eDrive-init";
    public static final String USER_AGENT = "eos("+getBuildTime()+")-eDrive("+ BuildConfig.VERSION_NAME +")";

    /**
     * Get a readable OS's build date String
+87 −2
Original line number Diff line number Diff line
@@ -17,9 +17,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.media.MediaScannerConnection;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.Uri;
import android.util.Log;
import android.webkit.MimeTypeMap;
@@ -32,18 +30,36 @@ import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.resources.files.FileUtils;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;


import foundation.e.drive.models.SyncedFolder;
import foundation.e.drive.work.CreateRemoteFolderWorker;
import foundation.e.drive.work.FirstStartWorker;
import foundation.e.drive.work.FullScanWorker;

import static foundation.e.drive.utils.AppConstants.MEDIASYNC_PROVIDER_AUTHORITY;
import static foundation.e.drive.utils.AppConstants.SETTINGSYNC_PROVIDER_AUTHORITY;
import static foundation.e.drive.work.CreateRemoteFolderWorker.DATA_KEY_ENABLE;
import static foundation.e.drive.work.CreateRemoteFolderWorker.DATA_KEY_ID;
import static foundation.e.drive.work.CreateRemoteFolderWorker.DATA_KEY_LAST_ETAG;
import static foundation.e.drive.work.CreateRemoteFolderWorker.DATA_KEY_LAST_MODIFIED;
import static foundation.e.drive.work.CreateRemoteFolderWorker.DATA_KEY_LIBELLE;
import static foundation.e.drive.work.CreateRemoteFolderWorker.DATA_KEY_LOCAL_PATH;
import static foundation.e.drive.work.CreateRemoteFolderWorker.DATA_KEY_MEDIATYPE;
import static foundation.e.drive.work.CreateRemoteFolderWorker.DATA_KEY_REMOTE_PATH;
import static foundation.e.drive.work.CreateRemoteFolderWorker.DATA_KEY_SCAN_LOCAL;
import static foundation.e.drive.work.CreateRemoteFolderWorker.DATA_KEY_SCAN_REMOTE;

import androidx.annotation.NonNull;
import androidx.work.BackoffPolicy;
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;

@@ -304,4 +320,73 @@ public abstract class CommonUtils {

        workManager.enqueueUniquePeriodicWork(FullScanWorker.UNIQUE_WORK_NAME, ExistingPeriodicWorkPolicy.KEEP, periodicFullScanRequest);
    }


    /**
     * This method create a chain of WorkRequests to perform Initialization tasks:
     *  Firstly, it creates WorkRequest to create remote root folders on ecloud
     *  Then, once all folders are present on cloud, run the FirstStartWorker.
     *
     *  in details:
     *  - Create 9 remote folders on ecloud
     *  - Run a first fullscan (ObserverService)
     *  - start SynchronizationService
     *  - Active FileObserver
     *  - Schedule periodic fullscan.
     *
     * @param syncedFolders List of SyncedFolder for which we want to create a remote folder on ecloud
     * @param workManager WorkManager instance to register WorkRequest
     */
    public static void registerInitializationWorkers(List<SyncedFolder> syncedFolders, WorkManager workManager){
        if (syncedFolders == null || syncedFolders.isEmpty()) {
            Log.e(TAG, "Can't create remote folders. List is empty");
            return;
        }

        final Constraints constraints = new Constraints.Builder()
                .setRequiredNetworkType(NetworkType.UNMETERED)
                .setRequiresBatteryNotLow(true)
                .build();

        final List<OneTimeWorkRequest> workRequests = new ArrayList<>();

        for (SyncedFolder folder : syncedFolders) {
            final OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(
                CreateRemoteFolderWorker.class)
                .setBackoffCriteria(BackoffPolicy.LINEAR, 2, TimeUnit.MINUTES)
                .setInputData(createDataFromSyncedFolder(folder))
                .addTag(AppConstants.WORK_GENERIC_TAG)
                .addTag(AppConstants.WORK_INITIALIZATION_TAG)
                .setConstraints(constraints)
                .build();

            workRequests.add(oneTimeWorkRequest);
        }

        final OneTimeWorkRequest firstStartRequest = new OneTimeWorkRequest.Builder(FirstStartWorker.class)
            .setBackoffCriteria(BackoffPolicy.LINEAR, 2, TimeUnit.MINUTES)
            .addTag(AppConstants.WORK_GENERIC_TAG)
            .addTag(AppConstants.WORK_INITIALIZATION_TAG)
            .build();

        workManager.beginWith(workRequests)
            .then(firstStartRequest)
            .enqueue();
    }


    private static Data createDataFromSyncedFolder(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
+127 −0
Original line number Diff line number Diff line
/*
 * Copyright © Vincent Bourgmayer (/e/ foundation).
 * 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.accounts.Account;
import android.accounts.AccountManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.work.Data;
import androidx.work.Worker;
import androidx.work.WorkerParameters;

import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.resources.files.CreateFolderRemoteOperation;

import java.io.File;

import foundation.e.drive.database.DbHelper;
import foundation.e.drive.models.SyncedFolder;
import foundation.e.drive.utils.AppConstants;
import foundation.e.drive.utils.CommonUtils;

/**
 * Create folder on ecloud for a given local folder
 * @author Vincent Bourgmayer
 */
public class CreateRemoteFolderWorker extends Worker {
    private static final String TAG = CreateRemoteFolderWorker.class.getSimpleName();
    public static final String DATA_KEY_ID="id";
    public static final String DATA_KEY_LIBELLE="libelle";
    public static final String DATA_KEY_LOCAL_PATH="localPath";
    public static final String DATA_KEY_REMOTE_PATH="remotePath";
    public static final String DATA_KEY_LAST_ETAG="etag";
    public static final String DATA_KEY_LAST_MODIFIED="lModified";
    public static final String DATA_KEY_SCAN_LOCAL="scanLocal";
    public static final String DATA_KEY_SCAN_REMOTE="scanRemote";
    public static final String DATA_KEY_ENABLE="enable";
    public static final String DATA_KEY_MEDIATYPE="mediaType";

    public CreateRemoteFolderWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    @NonNull
    @Override
    public Result doWork() {
        final Context context = getApplicationContext();
        if (!CommonUtils.haveNetworkConnection(context)) {
            Log.e(TAG, "Can't create remote folder because there is no unmetered connexion");
            return Result.retry();
        }

        final Account account = getAccount();
        if (account == null) {
            Log.e(TAG, "Can't get valid account: stop everything");
            return Result.failure();
        }

        final SyncedFolder syncedFolder = getSyncedFolderFromData();
        Log.d(TAG, "doWork() for :"+syncedFolder.getLocalFolder());
        final File folder = new File(syncedFolder.getLocalFolder() );
        if (!folder.exists()) {
            folder.mkdirs();
            syncedFolder.setLastModified(folder.lastModified());
        }

        final OwnCloudClient client = CommonUtils.getOwnCloudClient(account, context);
        if (client == null) {
            Log.e(TAG, "Can't get OwnCloudClient.");
            return Result.retry();
        }

        final CreateFolderRemoteOperation mkcolRequest =
            new CreateFolderRemoteOperation(syncedFolder.getRemoteFolder(), true);

        try {
            final RemoteOperationResult result = mkcolRequest.execute(client, true);
            if (result.isSuccess() || result.getCode() == RemoteOperationResult.ResultCode.FOLDER_ALREADY_EXISTS) {
                if(DbHelper.insertSyncedFolder(syncedFolder, context) >= 0 ) {
                    Log.d(TAG, "Insertion in DB succeed");
                }
                return Result.success();
            }
        } catch (Exception e) {
            Log.e(TAG, "Exception: "+e.getClass().getSimpleName());
        }
        return Result.retry();
    }

    private SyncedFolder getSyncedFolderFromData() {
        Data data = getInputData();
        SyncedFolder result = new SyncedFolder(
                data.getString(DATA_KEY_LIBELLE),
                data.getString(DATA_KEY_LOCAL_PATH),
                data.getString(DATA_KEY_REMOTE_PATH),
                data.getBoolean(DATA_KEY_SCAN_LOCAL, true),
                data.getBoolean(DATA_KEY_SCAN_REMOTE, true),
                data.getBoolean(DATA_KEY_ENABLE, true),
                data.getBoolean(DATA_KEY_MEDIATYPE, true));

        result.setLastModified(data.getLong(DATA_KEY_LAST_MODIFIED, 0L));
        result.setLastEtag(data.getString(DATA_KEY_LAST_ETAG));
        result.setId(data.getInt(DATA_KEY_ID, -1));

        return result;
    }


    private Account getAccount() {
        SharedPreferences prefs = getApplicationContext().getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE);
        final String accountName = prefs.getString(AccountManager.KEY_ACCOUNT_NAME, "");
        final String accountType = prefs.getString(AccountManager.KEY_ACCOUNT_TYPE, "");

        if (accountName.isEmpty() && accountType.isEmpty()) return null;
        return CommonUtils.getAccount(accountName, accountType, AccountManager.get(getApplicationContext()));
    }
}
Loading