Loading app/src/main/java/foundation/e/drive/EdriveApplication.java +2 −10 Original line number Diff line number Diff line Loading @@ -12,14 +12,12 @@ import android.accounts.Account; import android.accounts.AccountManager; import android.app.Application; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Environment; 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; import foundation.e.drive.utils.ReleaseTree; Loading Loading @@ -58,27 +56,21 @@ public class EdriveApplication extends Application { .apply(); } startRecursiveFileObserver(); FailedSyncPrefsManager.getInstance(getApplicationContext()).clearPreferences(); final Intent SynchronizationServiceIntent = new Intent(getApplicationContext(), SynchronizationService.class); startService(SynchronizationServiceIntent); } /** * Start Recursive FileObserver if not already watching */ public void startRecursiveFileObserver() { synchronized public void startRecursiveFileObserver() { if (!mFileObserver.isWatching()) { fileEventListener.bindToSynchronizationService(); mFileObserver.startWatching(); Timber.d("Started RecursiveFileObserver on root folder"); } } public void stopRecursiveFileObserver() { synchronized public void stopRecursiveFileObserver() { mFileObserver.stopWatching(); fileEventListener.unbindFromSynchronizationService(); Timber.d("Stopped RecursiveFileObserver on root folder"); } Loading app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java +5 −18 Original line number Diff line number Diff line Loading @@ -28,8 +28,8 @@ import foundation.e.drive.database.DbHelper; import foundation.e.drive.models.SyncRequest; import foundation.e.drive.models.SyncedFileState; import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.services.SynchronizationService; import foundation.e.drive.utils.SynchronizationServiceConnection; import foundation.e.drive.synchronization.SyncRequestCollector; import foundation.e.drive.synchronization.SyncProxy; import timber.log.Timber; /** Loading @@ -39,7 +39,6 @@ import timber.log.Timber; public class FileEventListener { private final Context appContext; private final SynchronizationServiceConnection serviceConnection = new SynchronizationServiceConnection(); public FileEventListener(@NonNull Context applicationContext) { Timber.tag(FileEventListener.class.getSimpleName()); Loading Loading @@ -108,14 +107,10 @@ public class FileEventListener { private void sendSyncRequestToSynchronizationService(@NonNull SyncRequest request) { Timber.d("Sending a SyncRequest for %s", request.getSyncedFileState().getName()); SynchronizationService service = serviceConnection.getSynchronizationService(); final SyncRequestCollector syncManager = SyncProxy.INSTANCE; syncManager.queueSyncRequest(request, appContext.getApplicationContext()); syncManager.startSynchronization(appContext); if (service != null) { service.queueSyncRequest(request); service.startSynchronization(); } else { Timber.d("Impossible to send SyncRequest. FileEventListener is not bound to SynchronizationService"); } } /** Loading Loading @@ -245,12 +240,4 @@ public class FileEventListener { this.sendSyncRequestToSynchronizationService(disableSyncingRequest); } } public void unbindFromSynchronizationService(){ serviceConnection.unBind(appContext); } public void bindToSynchronizationService(){ serviceConnection.bindService(appContext); } } app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java +4 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ package foundation.e.drive.receivers; import android.app.Application; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; Loading @@ -18,6 +19,7 @@ import androidx.annotation.NonNull; import foundation.e.drive.BuildConfig; import foundation.e.drive.database.DbHelper; import foundation.e.drive.synchronization.SyncProxy; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import timber.log.Timber; Loading Loading @@ -51,6 +53,8 @@ public class BootCompletedReceiver extends BroadcastReceiver { Timber.e(exception); } } SyncProxy.INSTANCE.startListeningFiles((Application) context.getApplicationContext()); } } Loading app/src/main/java/foundation/e/drive/services/ObserverService.java +18 −50 Original line number Diff line number Diff line Loading @@ -14,7 +14,6 @@ import static foundation.e.drive.utils.AppConstants.SETUP_COMPLETED; import android.accounts.Account; import android.accounts.AccountManager; import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; Loading Loading @@ -48,11 +47,12 @@ import foundation.e.drive.models.SyncedFileState; import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.operations.ListFileRemoteOperation; import foundation.e.drive.receivers.DebugCmdReceiver; import foundation.e.drive.synchronization.SyncRequestCollector; import foundation.e.drive.synchronization.SyncProxy; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.DavClientProvider; import foundation.e.drive.utils.ServiceExceptionHandler; import foundation.e.drive.utils.SynchronizationServiceConnection; import timber.log.Timber; /** Loading @@ -62,26 +62,14 @@ import timber.log.Timber; * This service look for remote or looale file to synchronize */ public class ObserverService extends Service implements OnRemoteOperationListener{ private final static int INTERSYNC_MINIMUM_DELAY = 900000; // min delay between two sync in ms. private final static int INTERSYNC_MINIMUM_DELAY = 900000; // min delay execution two sync in ms. private List<SyncedFolder> mSyncedFolders; //List of synced folder private boolean isWorking = false; private Account mAccount; private HashMap<Integer, SyncRequest> syncRequests; //integer is SyncedFileState id; Parcelable is the operation private SynchronizationServiceConnection synchronizationServiceConnection = new SynchronizationServiceConnection() { @Override public void onServiceConnected(@Nullable ComponentName componentName, @NonNull IBinder iBinder) { super.onServiceConnected(componentName, iBinder); final SharedPreferences prefs = getApplicationContext().getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); if (!checkStartCondition(prefs, forcedSync)) { stopSelf(); return; } private final SyncRequestCollector syncManager = SyncProxy.INSTANCE; begin(); } }; private Handler handler; private HandlerThread handlerThread; // protected to avoid SyntheticAccessor Loading @@ -91,7 +79,6 @@ public class ObserverService extends Service implements OnRemoteOperationListene @Override public void onDestroy(){ Timber.v("onDestroy()"); synchronizationServiceConnection.unBind(getApplicationContext()); if (handlerThread != null) handlerThread.quitSafely(); mSyncedFolders = null; super.onDestroy(); Loading @@ -101,7 +88,6 @@ public class ObserverService extends Service implements OnRemoteOperationListene public void onCreate() { super.onCreate(); Timber.tag(ObserverService.class.getSimpleName()); synchronizationServiceConnection.bindService(getApplicationContext()); } @Override Loading @@ -120,6 +106,13 @@ public class ObserverService extends Service implements OnRemoteOperationListene this.syncRequests = new HashMap<>(); if (!checkStartCondition(prefs, forcedSync)) { syncManager.startListeningFiles(getApplication()); stopSelf(); return START_NOT_STICKY; } begin(); return START_STICKY; } Loading Loading @@ -156,11 +149,6 @@ public class ObserverService extends Service implements OnRemoteOperationListene return false; } if (isWorking) { Timber.d("ObserverService is already working"); return false; } // Check minimum delay since last call & not forced sync /*@todo is it really usefull to check time beetween to start as it is started by WorkManager? it matters only if we want to consider forced sync */ Loading @@ -178,12 +166,10 @@ public class ObserverService extends Service implements OnRemoteOperationListene return false; } final SynchronizationService service = synchronizationServiceConnection.getSynchronizationService(); final boolean isSyncServicePaused = service != null && service.pauseSync(); Timber.d("isSyncServicePaused ? %s", isSyncServicePaused); return isSyncServicePaused; final boolean startAllowed = syncManager.onPeriodicScanStart(getApplication()); Timber.d("starting periodic scan is allowed ? %s", startAllowed); return startAllowed; } /* Common methods */ Loading @@ -193,7 +179,6 @@ public class ObserverService extends Service implements OnRemoteOperationListene */ // protected to avoid SyntheticAccessor protected void begin(){ this.isWorking = true; clearCachedFile(); deleteOldestCrashlogs(); startScan(true); Loading Loading @@ -330,39 +315,22 @@ public class ObserverService extends Service implements OnRemoteOperationListene startScan(false); SynchronizationService service = synchronizationServiceConnection.getSynchronizationService(); if (service != null) { service.unPauseSync(); } if (!syncRequests.isEmpty()) { Timber.d("syncRequests contains %s", syncRequests.size()); passSyncRequestsToSynchronizationService(); syncManager.queueSyncRequests(syncRequests.values(), getApplicationContext()); syncManager.startSynchronization(getApplicationContext()); } else { Timber.i("There is no file to sync."); getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE) .edit() .putLong(AppConstants.KEY_LAST_SYNC_TIME, System.currentTimeMillis()) .apply(); syncManager.startListeningFiles(getApplication()); } this.isWorking = false; this.stopSelf(); } /** * Send all gathered SyncRequest to SynchronizationService */ private void passSyncRequestsToSynchronizationService() { SynchronizationService service = synchronizationServiceConnection.getSynchronizationService(); if (service != null) { service.queueSyncRequests(syncRequests.values()); service.startSynchronization(); } else { Timber.e("ERROR: binding to SynchronizationService lost"); } } /** * Prepare the list of files and SyncedFileState for synchronisation */ Loading app/src/main/java/foundation/e/drive/services/SynchronizationService.java +27 −137 Original line number Diff line number Diff line Loading @@ -15,7 +15,6 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; Loading @@ -28,10 +27,7 @@ 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.R; import foundation.e.drive.database.DbHelper; Loading @@ -41,6 +37,8 @@ import foundation.e.drive.models.SyncWrapper; import foundation.e.drive.models.SyncedFileState; import foundation.e.drive.operations.DownloadFileOperation; import foundation.e.drive.operations.UploadFileOperation; import foundation.e.drive.synchronization.SyncManager; import foundation.e.drive.synchronization.SyncProxy; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.DavClientProvider; Loading @@ -51,10 +49,7 @@ import timber.log.Timber; * @author Vincent Bourgmayer */ public class SynchronizationService extends Service implements OnRemoteOperationListener, FileSyncDisabler.FileSyncDisablingListener { private final SynchronizationBinder binder = new SynchronizationBinder(); private final static long maxPauseTimeInMs = 300000; //5 minutes, might need to be adapted private ConcurrentLinkedDeque<SyncRequest> syncRequestQueue; private ConcurrentHashMap<Integer, SyncWrapper> startedSync; //Integer is thread index (1 to workerAmount) private final SyncManager syncManager = SyncProxy.INSTANCE; private Account account; private final int workerAmount = 2; Loading @@ -64,7 +59,6 @@ public class SynchronizationService extends Service implements OnRemoteOperation private OwnCloudClient ocClient; private Handler handler; private HandlerThread handlerThread; private long pauseStartTime = 0L; @Override public void onCreate() { Loading @@ -84,12 +78,11 @@ public class SynchronizationService extends Service implements OnRemoteOperation if (account == null) { Timber.d("No account available"); syncManager.startListeningFiles(getApplication()); stopSelf(); return START_NOT_STICKY; } syncRequestQueue = new ConcurrentLinkedDeque<>(); startedSync = new ConcurrentHashMap<>(); threadPool = new Thread[workerAmount]; ocClient = DavClientProvider.getInstance().getClientInstance(account, getApplicationContext()); Loading @@ -101,6 +94,8 @@ public class SynchronizationService extends Service implements OnRemoteOperation handlerThread.start(); handler = new Handler(handlerThread.getLooper()); startSynchronization(); return START_REDELIVER_INTENT; } Loading @@ -113,114 +108,6 @@ public class SynchronizationService extends Service implements OnRemoteOperation super.onDestroy(); } @Nullable @Override public IBinder onBind(@Nullable Intent intent) { return binder; } /** * Try to pause synchronization. * Can only be paused if no sync is already running and no request queue is started * @return true if service successfully paused */ public boolean pauseSync() { pauseStartTime = System.currentTimeMillis(); final boolean isQueueEmpty = syncRequestQueue.isEmpty(); final boolean isNoStartedSync = startedSync.values().stream().noneMatch(SyncWrapper::isRunning); Timber.v("is queue empty ? %s ; is no started sync ? %s", isQueueEmpty, isNoStartedSync); final boolean isPausable = isQueueEmpty && isNoStartedSync; if (!isPausable) pauseStartTime = 0L; return isPausable; } /** * unpause synchronization */ public void unPauseSync() { pauseStartTime = 0L; } private boolean isPaused() { if (pauseStartTime == 0L) return false; return System.currentTimeMillis() - pauseStartTime < maxPauseTimeInMs; } /** * Add a SyncRequest into waiting queue if it matches some conditions: * - an equivalent sync Request (same file & same operation type) isn't already running * - failure limit isn't reach for this file * * It also remove already existing request for the same file from the waiting queue * @param request request to add to waiting queue */ public void queueSyncRequest(@NonNull SyncRequest request) { for (SyncWrapper syncWrapper : startedSync.values()) { //noinspection EqualsBetweenInconvertibleTypes if (syncWrapper.isRunning() && syncWrapper.equals(request)) { return; } } if (skipBecauseOfPreviousFail(request)) return; syncRequestQueue.remove(request); syncRequestQueue.add(request); } /** * Add a collection of SyncRequest into waiting queue if they met some conditions: * - an equivalent sync Request (same file & same operation type) isn't already running * - failure limit isn't reach for this file * * It also remove already existing request for the same file from the waiting queue * @param requests collections of requests to add */ public void queueSyncRequests(@NonNull Collection<SyncRequest> requests) { for (SyncWrapper syncWrapper : startedSync.values()) { if (syncWrapper.isRunning()) { //noinspection EqualsBetweenInconvertibleTypes requests.removeIf(syncWrapper::equals); } } requests.removeIf(this::skipBecauseOfPreviousFail); 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(){ Timber.d("startAllThreads"); for(int i =-1; ++i < workerAmount;){ Loading @@ -236,18 +123,22 @@ public class SynchronizationService extends Service implements OnRemoteOperation if (!isNetworkAvailable()) { Timber.d("No network available: Clear syncRequestQueue"); syncRequestQueue.clear(); syncManager.clearRequestQueue(); return; } if (!canStart(threadIndex) || isPaused()) { Timber.d("Can't start thread #%s, Sync is paused or thread is already running", threadIndex); if (!canStart(threadIndex)) { Timber.d("Can't start thread #%s, thread is already running", threadIndex); return; } final SyncRequest request = this.syncRequestQueue.poll(); //return null if empty final SyncRequest request = this.syncManager.pollSyncRequest(); //return null if empty if (request == null) { Timber.d("Thread #%s: No more sync request to start.", threadIndex); syncManager.removeStartedRequest(threadIndex); if (!syncManager.isAnySyncRequestRunning()) { syncManager.startListeningFiles(getApplication()); } return; } Loading @@ -263,7 +154,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation final FileSyncDisabler fileSyncDisabler = new FileSyncDisabler(request.getSyncedFileState()); threadPool[threadIndex] = new Thread(fileSyncDisabler.getRunnable(handler, threadIndex, getApplicationContext(), this)); threadPool[threadIndex].start(); startedSync.put(threadIndex, syncWrapper); syncManager.addStartedRequest(threadIndex,syncWrapper); return; } Loading @@ -278,7 +169,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation //noinspection deprecation threadPool[threadIndex] = operation.execute(ocClient, this, handler); startedSync.put(threadIndex, syncWrapper); syncManager.addStartedRequest(threadIndex, syncWrapper); } /** Loading @@ -287,7 +178,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation * @return false if nogo */ private boolean canStart(int threadIndex) { final SyncWrapper syncWrapper = startedSync.get(threadIndex); final SyncWrapper syncWrapper = syncManager.getStartedRequestOnThread(threadIndex); return (syncWrapper == null || !syncWrapper.isRunning()); } Loading Loading @@ -318,7 +209,11 @@ public class SynchronizationService extends Service implements OnRemoteOperation break; } for (Map.Entry<Integer, SyncWrapper> keyValue : startedSync.entrySet()) { if (isNetworkDisconnected) { syncManager.clearRequestQueue(); } for (Map.Entry<Integer, SyncWrapper> keyValue : syncManager.getStartedRequests().entrySet()) { final SyncWrapper wrapper = keyValue.getValue(); final RemoteOperation wrapperOperation = wrapper.getRemoteOperation(); if (wrapperOperation != null && wrapperOperation.equals(callerOperation)) { Loading @@ -328,10 +223,6 @@ public class SynchronizationService extends Service implements OnRemoteOperation break; } } if (isNetworkDisconnected) { syncRequestQueue.clear(); } } private void logSyncResult(@NonNull RemoteOperationResult.ResultCode resultCode, @NonNull RemoteOperation callerOperation) { Loading Loading @@ -371,7 +262,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation @Override public void onSyncDisabled(int threadId, boolean succeed) { final SyncWrapper wrapper = startedSync.get(threadId); final SyncWrapper wrapper = syncManager.getStartedRequestOnThread(threadId); if (wrapper != null) { final SyncRequest request = wrapper.getRequest(); Timber.d("%s sync disabled ? %s", request.getSyncedFileState().getLocalPath(), succeed); Loading @@ -381,10 +272,9 @@ public class SynchronizationService extends Service implements OnRemoteOperation startWorker(threadId); } public class SynchronizationBinder extends Binder { @NonNull public SynchronizationService getService(){ return SynchronizationService.this; } @Nullable @Override public IBinder onBind(@Nullable Intent intent) { return null; } } No newline at end of file Loading
app/src/main/java/foundation/e/drive/EdriveApplication.java +2 −10 Original line number Diff line number Diff line Loading @@ -12,14 +12,12 @@ import android.accounts.Account; import android.accounts.AccountManager; import android.app.Application; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Environment; 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; import foundation.e.drive.utils.ReleaseTree; Loading Loading @@ -58,27 +56,21 @@ public class EdriveApplication extends Application { .apply(); } startRecursiveFileObserver(); FailedSyncPrefsManager.getInstance(getApplicationContext()).clearPreferences(); final Intent SynchronizationServiceIntent = new Intent(getApplicationContext(), SynchronizationService.class); startService(SynchronizationServiceIntent); } /** * Start Recursive FileObserver if not already watching */ public void startRecursiveFileObserver() { synchronized public void startRecursiveFileObserver() { if (!mFileObserver.isWatching()) { fileEventListener.bindToSynchronizationService(); mFileObserver.startWatching(); Timber.d("Started RecursiveFileObserver on root folder"); } } public void stopRecursiveFileObserver() { synchronized public void stopRecursiveFileObserver() { mFileObserver.stopWatching(); fileEventListener.unbindFromSynchronizationService(); Timber.d("Stopped RecursiveFileObserver on root folder"); } Loading
app/src/main/java/foundation/e/drive/FileObservers/FileEventListener.java +5 −18 Original line number Diff line number Diff line Loading @@ -28,8 +28,8 @@ import foundation.e.drive.database.DbHelper; import foundation.e.drive.models.SyncRequest; import foundation.e.drive.models.SyncedFileState; import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.services.SynchronizationService; import foundation.e.drive.utils.SynchronizationServiceConnection; import foundation.e.drive.synchronization.SyncRequestCollector; import foundation.e.drive.synchronization.SyncProxy; import timber.log.Timber; /** Loading @@ -39,7 +39,6 @@ import timber.log.Timber; public class FileEventListener { private final Context appContext; private final SynchronizationServiceConnection serviceConnection = new SynchronizationServiceConnection(); public FileEventListener(@NonNull Context applicationContext) { Timber.tag(FileEventListener.class.getSimpleName()); Loading Loading @@ -108,14 +107,10 @@ public class FileEventListener { private void sendSyncRequestToSynchronizationService(@NonNull SyncRequest request) { Timber.d("Sending a SyncRequest for %s", request.getSyncedFileState().getName()); SynchronizationService service = serviceConnection.getSynchronizationService(); final SyncRequestCollector syncManager = SyncProxy.INSTANCE; syncManager.queueSyncRequest(request, appContext.getApplicationContext()); syncManager.startSynchronization(appContext); if (service != null) { service.queueSyncRequest(request); service.startSynchronization(); } else { Timber.d("Impossible to send SyncRequest. FileEventListener is not bound to SynchronizationService"); } } /** Loading Loading @@ -245,12 +240,4 @@ public class FileEventListener { this.sendSyncRequestToSynchronizationService(disableSyncingRequest); } } public void unbindFromSynchronizationService(){ serviceConnection.unBind(appContext); } public void bindToSynchronizationService(){ serviceConnection.bindService(appContext); } }
app/src/main/java/foundation/e/drive/receivers/BootCompletedReceiver.java +4 −0 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ package foundation.e.drive.receivers; import android.app.Application; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; Loading @@ -18,6 +19,7 @@ import androidx.annotation.NonNull; import foundation.e.drive.BuildConfig; import foundation.e.drive.database.DbHelper; import foundation.e.drive.synchronization.SyncProxy; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import timber.log.Timber; Loading Loading @@ -51,6 +53,8 @@ public class BootCompletedReceiver extends BroadcastReceiver { Timber.e(exception); } } SyncProxy.INSTANCE.startListeningFiles((Application) context.getApplicationContext()); } } Loading
app/src/main/java/foundation/e/drive/services/ObserverService.java +18 −50 Original line number Diff line number Diff line Loading @@ -14,7 +14,6 @@ import static foundation.e.drive.utils.AppConstants.SETUP_COMPLETED; import android.accounts.Account; import android.accounts.AccountManager; import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; Loading Loading @@ -48,11 +47,12 @@ import foundation.e.drive.models.SyncedFileState; import foundation.e.drive.models.SyncedFolder; import foundation.e.drive.operations.ListFileRemoteOperation; import foundation.e.drive.receivers.DebugCmdReceiver; import foundation.e.drive.synchronization.SyncRequestCollector; import foundation.e.drive.synchronization.SyncProxy; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.DavClientProvider; import foundation.e.drive.utils.ServiceExceptionHandler; import foundation.e.drive.utils.SynchronizationServiceConnection; import timber.log.Timber; /** Loading @@ -62,26 +62,14 @@ import timber.log.Timber; * This service look for remote or looale file to synchronize */ public class ObserverService extends Service implements OnRemoteOperationListener{ private final static int INTERSYNC_MINIMUM_DELAY = 900000; // min delay between two sync in ms. private final static int INTERSYNC_MINIMUM_DELAY = 900000; // min delay execution two sync in ms. private List<SyncedFolder> mSyncedFolders; //List of synced folder private boolean isWorking = false; private Account mAccount; private HashMap<Integer, SyncRequest> syncRequests; //integer is SyncedFileState id; Parcelable is the operation private SynchronizationServiceConnection synchronizationServiceConnection = new SynchronizationServiceConnection() { @Override public void onServiceConnected(@Nullable ComponentName componentName, @NonNull IBinder iBinder) { super.onServiceConnected(componentName, iBinder); final SharedPreferences prefs = getApplicationContext().getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE); if (!checkStartCondition(prefs, forcedSync)) { stopSelf(); return; } private final SyncRequestCollector syncManager = SyncProxy.INSTANCE; begin(); } }; private Handler handler; private HandlerThread handlerThread; // protected to avoid SyntheticAccessor Loading @@ -91,7 +79,6 @@ public class ObserverService extends Service implements OnRemoteOperationListene @Override public void onDestroy(){ Timber.v("onDestroy()"); synchronizationServiceConnection.unBind(getApplicationContext()); if (handlerThread != null) handlerThread.quitSafely(); mSyncedFolders = null; super.onDestroy(); Loading @@ -101,7 +88,6 @@ public class ObserverService extends Service implements OnRemoteOperationListene public void onCreate() { super.onCreate(); Timber.tag(ObserverService.class.getSimpleName()); synchronizationServiceConnection.bindService(getApplicationContext()); } @Override Loading @@ -120,6 +106,13 @@ public class ObserverService extends Service implements OnRemoteOperationListene this.syncRequests = new HashMap<>(); if (!checkStartCondition(prefs, forcedSync)) { syncManager.startListeningFiles(getApplication()); stopSelf(); return START_NOT_STICKY; } begin(); return START_STICKY; } Loading Loading @@ -156,11 +149,6 @@ public class ObserverService extends Service implements OnRemoteOperationListene return false; } if (isWorking) { Timber.d("ObserverService is already working"); return false; } // Check minimum delay since last call & not forced sync /*@todo is it really usefull to check time beetween to start as it is started by WorkManager? it matters only if we want to consider forced sync */ Loading @@ -178,12 +166,10 @@ public class ObserverService extends Service implements OnRemoteOperationListene return false; } final SynchronizationService service = synchronizationServiceConnection.getSynchronizationService(); final boolean isSyncServicePaused = service != null && service.pauseSync(); Timber.d("isSyncServicePaused ? %s", isSyncServicePaused); return isSyncServicePaused; final boolean startAllowed = syncManager.onPeriodicScanStart(getApplication()); Timber.d("starting periodic scan is allowed ? %s", startAllowed); return startAllowed; } /* Common methods */ Loading @@ -193,7 +179,6 @@ public class ObserverService extends Service implements OnRemoteOperationListene */ // protected to avoid SyntheticAccessor protected void begin(){ this.isWorking = true; clearCachedFile(); deleteOldestCrashlogs(); startScan(true); Loading Loading @@ -330,39 +315,22 @@ public class ObserverService extends Service implements OnRemoteOperationListene startScan(false); SynchronizationService service = synchronizationServiceConnection.getSynchronizationService(); if (service != null) { service.unPauseSync(); } if (!syncRequests.isEmpty()) { Timber.d("syncRequests contains %s", syncRequests.size()); passSyncRequestsToSynchronizationService(); syncManager.queueSyncRequests(syncRequests.values(), getApplicationContext()); syncManager.startSynchronization(getApplicationContext()); } else { Timber.i("There is no file to sync."); getSharedPreferences(AppConstants.SHARED_PREFERENCE_NAME, Context.MODE_PRIVATE) .edit() .putLong(AppConstants.KEY_LAST_SYNC_TIME, System.currentTimeMillis()) .apply(); syncManager.startListeningFiles(getApplication()); } this.isWorking = false; this.stopSelf(); } /** * Send all gathered SyncRequest to SynchronizationService */ private void passSyncRequestsToSynchronizationService() { SynchronizationService service = synchronizationServiceConnection.getSynchronizationService(); if (service != null) { service.queueSyncRequests(syncRequests.values()); service.startSynchronization(); } else { Timber.e("ERROR: binding to SynchronizationService lost"); } } /** * Prepare the list of files and SyncedFileState for synchronisation */ Loading
app/src/main/java/foundation/e/drive/services/SynchronizationService.java +27 −137 Original line number Diff line number Diff line Loading @@ -15,7 +15,6 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; Loading @@ -28,10 +27,7 @@ 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.R; import foundation.e.drive.database.DbHelper; Loading @@ -41,6 +37,8 @@ import foundation.e.drive.models.SyncWrapper; import foundation.e.drive.models.SyncedFileState; import foundation.e.drive.operations.DownloadFileOperation; import foundation.e.drive.operations.UploadFileOperation; import foundation.e.drive.synchronization.SyncManager; import foundation.e.drive.synchronization.SyncProxy; import foundation.e.drive.utils.AppConstants; import foundation.e.drive.utils.CommonUtils; import foundation.e.drive.utils.DavClientProvider; Loading @@ -51,10 +49,7 @@ import timber.log.Timber; * @author Vincent Bourgmayer */ public class SynchronizationService extends Service implements OnRemoteOperationListener, FileSyncDisabler.FileSyncDisablingListener { private final SynchronizationBinder binder = new SynchronizationBinder(); private final static long maxPauseTimeInMs = 300000; //5 minutes, might need to be adapted private ConcurrentLinkedDeque<SyncRequest> syncRequestQueue; private ConcurrentHashMap<Integer, SyncWrapper> startedSync; //Integer is thread index (1 to workerAmount) private final SyncManager syncManager = SyncProxy.INSTANCE; private Account account; private final int workerAmount = 2; Loading @@ -64,7 +59,6 @@ public class SynchronizationService extends Service implements OnRemoteOperation private OwnCloudClient ocClient; private Handler handler; private HandlerThread handlerThread; private long pauseStartTime = 0L; @Override public void onCreate() { Loading @@ -84,12 +78,11 @@ public class SynchronizationService extends Service implements OnRemoteOperation if (account == null) { Timber.d("No account available"); syncManager.startListeningFiles(getApplication()); stopSelf(); return START_NOT_STICKY; } syncRequestQueue = new ConcurrentLinkedDeque<>(); startedSync = new ConcurrentHashMap<>(); threadPool = new Thread[workerAmount]; ocClient = DavClientProvider.getInstance().getClientInstance(account, getApplicationContext()); Loading @@ -101,6 +94,8 @@ public class SynchronizationService extends Service implements OnRemoteOperation handlerThread.start(); handler = new Handler(handlerThread.getLooper()); startSynchronization(); return START_REDELIVER_INTENT; } Loading @@ -113,114 +108,6 @@ public class SynchronizationService extends Service implements OnRemoteOperation super.onDestroy(); } @Nullable @Override public IBinder onBind(@Nullable Intent intent) { return binder; } /** * Try to pause synchronization. * Can only be paused if no sync is already running and no request queue is started * @return true if service successfully paused */ public boolean pauseSync() { pauseStartTime = System.currentTimeMillis(); final boolean isQueueEmpty = syncRequestQueue.isEmpty(); final boolean isNoStartedSync = startedSync.values().stream().noneMatch(SyncWrapper::isRunning); Timber.v("is queue empty ? %s ; is no started sync ? %s", isQueueEmpty, isNoStartedSync); final boolean isPausable = isQueueEmpty && isNoStartedSync; if (!isPausable) pauseStartTime = 0L; return isPausable; } /** * unpause synchronization */ public void unPauseSync() { pauseStartTime = 0L; } private boolean isPaused() { if (pauseStartTime == 0L) return false; return System.currentTimeMillis() - pauseStartTime < maxPauseTimeInMs; } /** * Add a SyncRequest into waiting queue if it matches some conditions: * - an equivalent sync Request (same file & same operation type) isn't already running * - failure limit isn't reach for this file * * It also remove already existing request for the same file from the waiting queue * @param request request to add to waiting queue */ public void queueSyncRequest(@NonNull SyncRequest request) { for (SyncWrapper syncWrapper : startedSync.values()) { //noinspection EqualsBetweenInconvertibleTypes if (syncWrapper.isRunning() && syncWrapper.equals(request)) { return; } } if (skipBecauseOfPreviousFail(request)) return; syncRequestQueue.remove(request); syncRequestQueue.add(request); } /** * Add a collection of SyncRequest into waiting queue if they met some conditions: * - an equivalent sync Request (same file & same operation type) isn't already running * - failure limit isn't reach for this file * * It also remove already existing request for the same file from the waiting queue * @param requests collections of requests to add */ public void queueSyncRequests(@NonNull Collection<SyncRequest> requests) { for (SyncWrapper syncWrapper : startedSync.values()) { if (syncWrapper.isRunning()) { //noinspection EqualsBetweenInconvertibleTypes requests.removeIf(syncWrapper::equals); } } requests.removeIf(this::skipBecauseOfPreviousFail); 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(){ Timber.d("startAllThreads"); for(int i =-1; ++i < workerAmount;){ Loading @@ -236,18 +123,22 @@ public class SynchronizationService extends Service implements OnRemoteOperation if (!isNetworkAvailable()) { Timber.d("No network available: Clear syncRequestQueue"); syncRequestQueue.clear(); syncManager.clearRequestQueue(); return; } if (!canStart(threadIndex) || isPaused()) { Timber.d("Can't start thread #%s, Sync is paused or thread is already running", threadIndex); if (!canStart(threadIndex)) { Timber.d("Can't start thread #%s, thread is already running", threadIndex); return; } final SyncRequest request = this.syncRequestQueue.poll(); //return null if empty final SyncRequest request = this.syncManager.pollSyncRequest(); //return null if empty if (request == null) { Timber.d("Thread #%s: No more sync request to start.", threadIndex); syncManager.removeStartedRequest(threadIndex); if (!syncManager.isAnySyncRequestRunning()) { syncManager.startListeningFiles(getApplication()); } return; } Loading @@ -263,7 +154,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation final FileSyncDisabler fileSyncDisabler = new FileSyncDisabler(request.getSyncedFileState()); threadPool[threadIndex] = new Thread(fileSyncDisabler.getRunnable(handler, threadIndex, getApplicationContext(), this)); threadPool[threadIndex].start(); startedSync.put(threadIndex, syncWrapper); syncManager.addStartedRequest(threadIndex,syncWrapper); return; } Loading @@ -278,7 +169,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation //noinspection deprecation threadPool[threadIndex] = operation.execute(ocClient, this, handler); startedSync.put(threadIndex, syncWrapper); syncManager.addStartedRequest(threadIndex, syncWrapper); } /** Loading @@ -287,7 +178,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation * @return false if nogo */ private boolean canStart(int threadIndex) { final SyncWrapper syncWrapper = startedSync.get(threadIndex); final SyncWrapper syncWrapper = syncManager.getStartedRequestOnThread(threadIndex); return (syncWrapper == null || !syncWrapper.isRunning()); } Loading Loading @@ -318,7 +209,11 @@ public class SynchronizationService extends Service implements OnRemoteOperation break; } for (Map.Entry<Integer, SyncWrapper> keyValue : startedSync.entrySet()) { if (isNetworkDisconnected) { syncManager.clearRequestQueue(); } for (Map.Entry<Integer, SyncWrapper> keyValue : syncManager.getStartedRequests().entrySet()) { final SyncWrapper wrapper = keyValue.getValue(); final RemoteOperation wrapperOperation = wrapper.getRemoteOperation(); if (wrapperOperation != null && wrapperOperation.equals(callerOperation)) { Loading @@ -328,10 +223,6 @@ public class SynchronizationService extends Service implements OnRemoteOperation break; } } if (isNetworkDisconnected) { syncRequestQueue.clear(); } } private void logSyncResult(@NonNull RemoteOperationResult.ResultCode resultCode, @NonNull RemoteOperation callerOperation) { Loading Loading @@ -371,7 +262,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation @Override public void onSyncDisabled(int threadId, boolean succeed) { final SyncWrapper wrapper = startedSync.get(threadId); final SyncWrapper wrapper = syncManager.getStartedRequestOnThread(threadId); if (wrapper != null) { final SyncRequest request = wrapper.getRequest(); Timber.d("%s sync disabled ? %s", request.getSyncedFileState().getLocalPath(), succeed); Loading @@ -381,10 +272,9 @@ public class SynchronizationService extends Service implements OnRemoteOperation startWorker(threadId); } public class SynchronizationBinder extends Binder { @NonNull public SynchronizationService getService(){ return SynchronizationService.this; } @Nullable @Override public IBinder onBind(@Nullable Intent intent) { return null; } } No newline at end of file