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

Commit e68e06f8 authored by Vincent Bourgmayer's avatar Vincent Bourgmayer
Browse files

Merge branch '307-o-progressiveyDelaySyncAfterFailure' into 'v1-oreo'

Progressively delay sync in case of failure

See merge request !166
parents bd3ba52e af7827f2
Loading
Loading
Loading
Loading
Loading
+2 −12
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ import android.util.Log;

import foundation.e.drive.FileObservers.FileEventListener;
import foundation.e.drive.FileObservers.RecursiveFileObserver;
import foundation.e.drive.database.FailedSyncPrefsManager;
import foundation.e.drive.services.SynchronizationService;
import foundation.e.drive.utils.AppConstants;
import foundation.e.drive.utils.CommonUtils;
@@ -50,7 +51,7 @@ public class EdriveApplication extends Application {
            Log.d(TAG, "Account already registered");
            startRecursiveFileObserver();

            resetTransferFailureCounter();
            FailedSyncPrefsManager.getInstance(getApplicationContext()).clearPreferences();

            final Intent SynchronizationServiceIntent = new Intent(getApplicationContext(), SynchronizationService.class);
            startService(SynchronizationServiceIntent);
@@ -87,17 +88,6 @@ public class EdriveApplication extends Application {
        Log.d(TAG, "RecursiveFileObserver on root folder stops watching ");
    }


    /**
     * Clear sharedPreference that store failed transfer counter.
     * Doing this here, allow to restart it once a day.
     */
    private void resetTransferFailureCounter() {
        final SharedPreferences prefs = getSharedPreferences(AppConstants.FAILED_TRANSFER_PREF, Context.MODE_PRIVATE);
        prefs.edit().clear().commit();
    }


    @Override
    public void onLowMemory() {
        super.onLowMemory();
+72 −0
Original line number Diff line number Diff line
/*
 * Copyright © ECORP SAS 2022.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v3.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/gpl.html
 */

package foundation.e.drive.database;

import android.content.Context;
import android.content.SharedPreferences;

/**
 * @author vincent Bourgmayer
 */
public class FailedSyncPrefsManager {
    public final static String PREF_NAME = "failed_transfer";
    private final static String FAILURE_COUNTER_KEY = "C";
    private final static String FAILURE_TIME_KEY = "T";

    private static FailedSyncPrefsManager instance = null;

    private SharedPreferences preferences;
    private FailedSyncPrefsManager() {};

    public static FailedSyncPrefsManager getInstance(Context context) {
        if (instance == null) {
            instance = new FailedSyncPrefsManager();
            instance.preferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
        }

        return instance;
    }


    public int getFailureCounterForFile(int fileStateId) {
        return preferences.getInt(getFailureCounterKey(fileStateId), 0);
    }

    public long getLastFailureTimeForFile(int fileStateId) {
        return preferences.getLong(getFailureTimeKey(fileStateId), 0);
    }

    public void saveFileSyncFailure(int fileStateId) {
        final int failure_counter = getFailureCounterForFile(fileStateId)+1;
        final long timestamp = System.currentTimeMillis() / 1000;

        preferences.edit()
            .putInt(getFailureCounterKey(fileStateId), failure_counter)
            .putLong(getFailureTimeKey(fileStateId), timestamp)
            .commit();
    }

    public void clearPreferences() {
        preferences.edit().clear().commit();
    }

    public void removeDataForFile(int fileStateId) {
        preferences.edit()
                .remove(getFailureCounterKey(fileStateId))
                .remove(getFailureTimeKey(fileStateId))
                .commit();
    }
    private String getFailureCounterKey(int fileStateId) {
        return fileStateId + FAILURE_COUNTER_KEY;
    }

    private String getFailureTimeKey(int fileStateId) {
        return fileStateId + FAILURE_TIME_KEY;
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -42,7 +42,7 @@ import foundation.e.drive.utils.DavClientProvider;
 */
public class UploadFileOperation extends RemoteOperation {
    private final static String TAG = UploadFileOperation.class.getSimpleName();
    private final static int FILE_SIZE_FLOOR_FOR_CHUNKED = 3072000; //3MB
    public final static int FILE_SIZE_FLOOR_FOR_CHUNKED = 3072000; //3MB
    private final Context context;
    private final SyncedFileState syncedState;
    private final Account account; // TODO Remove as soon as  nextcloud library move all Operation to NextcloudClient instead of OwncloudClient
+2 −1
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import java.io.File;

import foundation.e.drive.EdriveApplication;
import foundation.e.drive.database.DbHelper;
import foundation.e.drive.database.FailedSyncPrefsManager;
import foundation.e.drive.utils.AppConstants;

import static foundation.e.drive.utils.AppConstants.INITIALFOLDERS_NUMBER;
@@ -93,7 +94,7 @@ public class ResetService extends Service {
                    .apply();
        }

        deleteSharedPreferences(AppConstants.FAILED_TRANSFER_PREF);
        deleteSharedPreferences(FailedSyncPrefsManager.PREF_NAME);
    }

    private void removeCachedFiles() {
+48 −15
Original line number Diff line number Diff line
@@ -7,7 +7,7 @@
 */
package foundation.e.drive.services;

import static foundation.e.drive.utils.AppConstants.FAILED_TRANSFER_PREF;
import static foundation.e.drive.operations.UploadFileOperation.FILE_SIZE_FLOOR_FOR_CHUNKED;

import android.accounts.Account;
import android.accounts.AccountManager;
@@ -28,12 +28,14 @@ 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.io.File;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;

import foundation.e.drive.database.DbHelper;
import foundation.e.drive.database.FailedSyncPrefsManager;
import foundation.e.drive.models.SyncRequest;
import foundation.e.drive.models.SyncWrapper;
import foundation.e.drive.operations.DownloadFileOperation;
@@ -49,7 +51,6 @@ import foundation.e.drive.utils.DavClientProvider;
public class SynchronizationService extends Service implements OnRemoteOperationListener {
    private final static String TAG = SynchronizationService.class.getSimpleName();
    private final SynchronizationBinder binder = new SynchronizationBinder();
    private final static int FAILURE_LIMIT = 3;
    private ConcurrentLinkedDeque<SyncRequest> syncRequestQueue;
    private ConcurrentHashMap<Integer, SyncWrapper> startedSync; //Integer is thread index (1 to workerAmount)

@@ -112,10 +113,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation
                return;
            }
        }
        final SharedPreferences prefs = getSharedPreferences(FAILED_TRANSFER_PREF, Context.MODE_PRIVATE);
        if (prefs.getInt(request.getSyncedFileState().getLocalPath(), 0) >= FAILURE_LIMIT) {
            return;
        }
        if (skipBecauseOfPreviousFail(request)) return;

        syncRequestQueue.remove(request);
        syncRequestQueue.add(request);
@@ -135,13 +133,44 @@ public class SynchronizationService extends Service implements OnRemoteOperation
                requests.removeIf(syncRequest -> syncWrapper.equals(syncRequest));
            }
        }
        final SharedPreferences prefs = getSharedPreferences(FAILED_TRANSFER_PREF, Context.MODE_PRIVATE);
        requests.removeIf(syncRequest -> prefs.getInt(syncRequest.getSyncedFileState().getLocalPath(), 0) >= FAILURE_LIMIT);

        requests.removeIf(syncRequest -> skipBecauseOfPreviousFail(syncRequest));

        syncRequestQueue.removeAll(requests);
        syncRequestQueue.addAll(requests);
    }

    /**
     * Progressively delay synchronization of a file in case of failure
     * @param request SyncRequest for the given file
     * @return false if file can be sync or true if it must wait
     */
    private boolean skipBecauseOfPreviousFail(SyncRequest request) {
        final FailedSyncPrefsManager failedSyncPref = FailedSyncPrefsManager.getInstance(getApplicationContext());
        final int fileStateId = request.getSyncedFileState().getId();
        final int failureCounter = failedSyncPref.getFailureCounterForFile(fileStateId);

        long delay;
        switch (failureCounter) {
            case 0:
                return false;
            case 1:
                delay = 3600; //1h
                break;
            case 2:
                delay = 7200; //2h
                break;
            case 3:
                delay = 18000; // 5h
                break;
            default:
                return true;
        }

        final long timeStamp = System.currentTimeMillis() / 1000;
        return timeStamp <  failedSyncPref.getLastFailureTimeForFile(fileStateId) + delay;
    }

    public void startSynchronization(){
        Log.d(TAG, "startAllThreads");
        for(int i =-1; ++i < workerAmount;){
@@ -253,16 +282,20 @@ public class SynchronizationService extends Service implements OnRemoteOperation
    }

    private void updateFailureCounter(SyncRequest request, boolean success) {
        final SharedPreferences prefs = getSharedPreferences(FAILED_TRANSFER_PREF, Context.MODE_PRIVATE);
        final String failure_key = request.getSyncedFileState().getLocalPath();
        final int previous_failure_count = prefs.getInt(failure_key, 0);
        final SharedPreferences.Editor pref_editor = prefs.edit();
        final FailedSyncPrefsManager failedPref = FailedSyncPrefsManager.getInstance(getApplicationContext());
        final int fileStateId = request.getSyncedFileState().getId();

        if (success) {
            pref_editor.remove(failure_key);
            failedPref.removeDataForFile(fileStateId);
        } else {
            pref_editor.putInt(failure_key, previous_failure_count + 1);
            if (request.getOperationType().equals(SyncRequest.Type.UPLOAD)) {
                final File file = new File(request.getSyncedFileState().getLocalPath());
                if (file.length() >= FILE_SIZE_FLOOR_FOR_CHUNKED) {
                    return; //do not delay future synchronization when it's for a chunked upload
                }
            }
            failedPref.saveFileSyncFailure(fileStateId);
        }
        pref_editor.commit();
    }

    public class SynchronizationBinder extends Binder{
Loading