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

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

Progressively delay sync in case of failure, except for chunked upload

parent bd3ba52e
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