Loading app/build.gradle +2 −2 Original line number Diff line number Diff line Loading @@ -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 Loading app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java +45 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -239,7 +248,7 @@ public class NotesRepository { e.printStackTrace(); apiProvider.invalidateAPICache(); } serviceWasDown(false); db.getAccountDao().deleteAccount(account); notifyWidgets(); } Loading Loading @@ -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() { Loading app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesServerSyncTask.java +23 −7 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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); Loading Loading @@ -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()); } Loading @@ -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()); } Loading Loading @@ -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); Loading app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/ServiceDownException.java 0 → 100644 +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); } } Loading
app/build.gradle +2 −2 Original line number Diff line number Diff line Loading @@ -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 Loading
app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java +45 −2 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -239,7 +248,7 @@ public class NotesRepository { e.printStackTrace(); apiProvider.invalidateAPICache(); } serviceWasDown(false); db.getAccountDao().deleteAccount(account); notifyWidgets(); } Loading Loading @@ -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() { Loading
app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesServerSyncTask.java +23 −7 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) { Loading @@ -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); Loading Loading @@ -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()); } Loading @@ -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()); } Loading Loading @@ -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); Loading
app/src/main/java/it/niedermann/owncloud/notes/persistence/sync/ServiceDownException.java 0 → 100644 +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); } }