Loading app/src/main/java/foundation/e/drive/EdriveApplication.java +2 −12 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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(); Loading app/src/main/java/foundation/e/drive/database/FailedSyncPrefsManager.java 0 → 100644 +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; } } app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading app/src/main/java/foundation/e/drive/services/ResetService.java +2 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -93,7 +94,7 @@ public class ResetService extends Service { .apply(); } deleteSharedPreferences(AppConstants.FAILED_TRANSFER_PREF); deleteSharedPreferences(FailedSyncPrefsManager.PREF_NAME); } private void removeCachedFiles() { Loading app/src/main/java/foundation/e/drive/services/SynchronizationService.java +48 −15 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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) Loading Loading @@ -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); Loading @@ -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;){ Loading Loading @@ -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 Loading
app/src/main/java/foundation/e/drive/EdriveApplication.java +2 −12 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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(); Loading
app/src/main/java/foundation/e/drive/database/FailedSyncPrefsManager.java 0 → 100644 +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; } }
app/src/main/java/foundation/e/drive/operations/UploadFileOperation.java +1 −1 Original line number Diff line number Diff line Loading @@ -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 Loading
app/src/main/java/foundation/e/drive/services/ResetService.java +2 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -93,7 +94,7 @@ public class ResetService extends Service { .apply(); } deleteSharedPreferences(AppConstants.FAILED_TRANSFER_PREF); deleteSharedPreferences(FailedSyncPrefsManager.PREF_NAME); } private void removeCachedFiles() { Loading
app/src/main/java/foundation/e/drive/services/SynchronizationService.java +48 −15 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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) Loading Loading @@ -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); Loading @@ -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;){ Loading Loading @@ -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