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

Commit f6691ac9 authored by Guillaume Jacquart's avatar Guillaume Jacquart
Browse files

Merge branch '3040-limit_calls_to_unavailable_server' into 'main'

3040 limit calls to unavailable server

See merge request !65
parents f4560967 1a764294
Loading
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -7,8 +7,8 @@ def buildDate = { ->

def appMajor = 3
def appMinor = 7
def appPatch = 3
def appVersionCode = 3007003
def appPatch = 4
def appVersionCode = 3007004

android {
    compileSdkVersion 33
+45 −2
Original line number Diff line number Diff line
@@ -43,6 +43,8 @@ import com.nextcloud.android.sso.model.SingleSignOnAccount;

import org.jetbrains.annotations.NotNull;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
@@ -87,9 +89,16 @@ public class NotesRepository {

    private static final String PREF_KEY_MIGRATION_DONE = "old_note_migration_done";
    private static final String PREF_KEY_REMOTE_CONFLICT_RESOLVED = "remote_conflict_resolved_1";
    private static final String PREF_KEY_LAST_SYNC_ATTEMPT = "last_synchronisation_attempt";
    private static final String PREF_KEY_WAIT_BEFORE_NEXT_SYNC = "wait_before_next_synchronisation";

    private static final String TAG = NotesRepository.class.getSimpleName();

    // Delay between first ServerDownException occurs, and next synchronisation attempt: 5seconds.
    private static final long MIN_DELAY_AFTER_FAILED_SYNCHRONIZATION = 5000L;
    // MAX backoff delay after a ServerDownException: 24 hours
    private static final long MAX_DELAY_BETWEEN_SYNCHRONIZATIONS = 24 * 60 * 60 * 1000;

    private static NotesRepository instance;

    private final ApiProvider apiProvider;
@@ -239,7 +248,7 @@ public class NotesRepository {
            e.printStackTrace();
            apiProvider.invalidateAPICache();
        }

        serviceWasDown(false);
        db.getAccountDao().deleteAccount(account);
        notifyWidgets();
    }
@@ -777,12 +786,46 @@ public class NotesRepository {
     * This method respects the user preference "Sync on Wi-Fi only".
     * <p>
     * NoteServerSyncHelper observes changes in the network connection.
     *
     * It also handle exponential back of in case of server unavailability. Return false if the
     * synchronisation should be delayed.
     * The current state can be retrieved with this method.
     *
     * @return true if sync is possible, otherwise false.
     */
    public boolean isSyncPossible() {
        return isSyncPossible;
        if (!isSyncPossible) {
            return false;
        }

        final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
        Instant lastSync = Instant.ofEpochMilli(sharedPreferences.getLong(PREF_KEY_LAST_SYNC_ATTEMPT, 0L));
        long waitBeforeNextAttemp = sharedPreferences.getLong(PREF_KEY_WAIT_BEFORE_NEXT_SYNC, 0L);
        boolean cooldownTimeElapsed = lastSync.plus(waitBeforeNextAttemp, ChronoUnit.MILLIS).isBefore(Instant.now());
        if (!cooldownTimeElapsed) {
            Log.i(TAG, "Sync NOT possible, previous ServiceWasDown error was less than " + waitBeforeNextAttemp + " ms ago.");
        }
        return cooldownTimeElapsed;
    }

    public void serviceWasDown(boolean down) {
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
        long now = Instant.now().toEpochMilli();
        long waitBeforeNextAttempt = sharedPreferences.getLong(PREF_KEY_WAIT_BEFORE_NEXT_SYNC, 0L);

        if (down) {
            waitBeforeNextAttempt = Math.min(
                    Math.max(waitBeforeNextAttempt * 2, MIN_DELAY_AFTER_FAILED_SYNCHRONIZATION),
                    MAX_DELAY_BETWEEN_SYNCHRONIZATIONS
            );
        } else {
            waitBeforeNextAttempt = 0L;
        }

        sharedPreferences.edit()
                .putLong(PREF_KEY_LAST_SYNC_ATTEMPT, now)
                .putLong(PREF_KEY_WAIT_BEFORE_NEXT_SYNC, waitBeforeNextAttempt)
                .apply();
    }

    public boolean isNetworkConnected() {
+23 −7
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import it.niedermann.owncloud.notes.BuildConfig;
import it.niedermann.owncloud.notes.persistence.entity.Account;
import it.niedermann.owncloud.notes.persistence.entity.Note;
import it.niedermann.owncloud.notes.persistence.sync.NotesAPI;
import it.niedermann.owncloud.notes.persistence.sync.ServiceDownException;
import it.niedermann.owncloud.notes.shared.model.DBStatus;
import it.niedermann.owncloud.notes.shared.model.ISyncCallback;
import it.niedermann.owncloud.notes.shared.model.SyncResultStatus;
@@ -93,6 +94,7 @@ abstract class NotesServerSyncTask extends Thread {
        Log.i(TAG, "STARTING SYNCHRONIZATION");

        final var status = new SyncResultStatus();
        try {
            pushLocalChanges(status);

            if (status.retrySync) {
@@ -104,6 +106,14 @@ abstract class NotesServerSyncTask extends Thread {
                status.pullSuccessful = pullRemoteChanges();
            }

            repo.serviceWasDown(false);
        } catch (ServiceDownException e) {
            exceptions.add(e);
            status.pushSuccessful = false;
            status.pullSuccessful = false;
            repo.serviceWasDown(true);
        }

        Log.i(TAG, "SYNCHRONIZATION FINISHED");

        onPostExecute(status);
@@ -152,6 +162,8 @@ abstract class NotesServerSyncTask extends Thread {
                                        Log.e(TAG, "   ...Tried to recreate \"" + note.getTitle() + "\" (#" + note.getId() + ") but the server response was null.");
                                        throw new Exception("Server returned null after recreating \"" + note.getTitle() + "\" (#" + note.getId() + ")");
                                    }
                                } else if (editResponse.code() == HTTP_NOT_FOUND) {
                                    throw new ServiceDownException("404 on POST a new note");
                                } else {
                                    handleException(createResponse.message());
                                }
@@ -168,6 +180,8 @@ abstract class NotesServerSyncTask extends Thread {
                                    throw new Exception("Server returned null after creating \"" + note.getTitle() + "\" (#" + note.getId() + ")");
                                }
                                repo.updateRemoteId(note.getId(), remoteNote.getRemoteId());
                            } else if (createResponse.code() == HTTP_NOT_FOUND) {
                                throw new ServiceDownException("404 on POST a new note");
                            } else {
                                handleException(createResponse.message());
                            }
@@ -209,6 +223,8 @@ abstract class NotesServerSyncTask extends Thread {
                if (!retrySync) {
                    exceptions.add(e);
                }
            } catch (ServiceDownException e) {
                throw e;
            } catch (Exception e) {
                if (e instanceof TokenMismatchException) {
                    apiProvider.invalidateAPICache(ssoAccount);
+7 −0
Original line number Diff line number Diff line
package it.niedermann.owncloud.notes.persistence.sync;

public class ServiceDownException extends RuntimeException {
    public ServiceDownException(String message) {
        super(message);
    }
}