diff --git a/app/src/main/java/foundation/e/drive/models/SyncRequest.java b/app/src/main/java/foundation/e/drive/models/SyncRequest.java index e2412cdf2fa5d9fb550cd08364174c85a2ae205f..758060de3fbd52dd9c7d6c25c7adbda9017b40bf 100644 --- a/app/src/main/java/foundation/e/drive/models/SyncRequest.java +++ b/app/src/main/java/foundation/e/drive/models/SyncRequest.java @@ -32,8 +32,11 @@ public class SyncRequest { @Override public boolean equals(@Nullable Object obj) { if (obj instanceof SyncRequest) { - return (syncedFileState.getId() == ((SyncRequest) obj).syncedFileState.getId() ); + final SyncedFileState syncedFileState = ((SyncRequest) obj).getSyncedFileState(); + + return (syncedFileState.getLocalPath().equals(syncedFileState.getLocalPath()) + || syncedFileState.getRemotePath().equals(syncedFileState.getRemotePath())); } - return super.equals(obj); + return false; } } diff --git a/app/src/main/java/foundation/e/drive/models/SyncThreadHolder.java b/app/src/main/java/foundation/e/drive/models/SyncThreadHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..7b412a343a7a030eb3473a8d48b7d1da4e2acd26 --- /dev/null +++ b/app/src/main/java/foundation/e/drive/models/SyncThreadHolder.java @@ -0,0 +1,82 @@ +/* + * Copyright © Vincent Bourgmayer (/e/ foundation). + * 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.models; + +import android.content.Context; +import android.os.Handler; + +import com.owncloud.android.lib.common.OwnCloudClient; +import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; +import com.owncloud.android.lib.common.operations.RemoteOperation; + +import foundation.e.drive.operations.DownloadFileOperation; +import foundation.e.drive.operations.UploadFileOperation; + +/** + * @author vincent Bourgmayer + */ +public class SyncThreadHolder { + private final RemoteOperation remoteOperation; + private final SyncRequest syncRequest; + private boolean isRunning; + private final int threadIndex; + + public SyncThreadHolder(int threadIndex, SyncRequest request, Context context) { + super(); + this.threadIndex = threadIndex; + this.syncRequest = request; + remoteOperation = createRemoteOperation(syncRequest, context); + isRunning = false; + } + + public RemoteOperation getRemoteOperation() { + return remoteOperation; + } + + public SyncRequest getSyncRequest() { + return syncRequest; + } + + public boolean isRunning() { + return isRunning; + } + + public int getThreadIndex() { + return threadIndex; + } + + public void setRunning(boolean running) { + isRunning = running; + } + + private RemoteOperation createRemoteOperation(SyncRequest request, Context context) { + final RemoteOperation operation; + switch (request.getOperationType()){ + case UPLOAD: + final SyncedFileState sfs = request.getSyncedFileState(); + operation = new UploadFileOperation(sfs, context); + break; + case DOWNLOAD: + final DownloadRequest downloadRequest = (DownloadRequest) request; + operation = new DownloadFileOperation(downloadRequest.getRemoteFile(), downloadRequest.getSyncedFileState(), context); + break; + case REMOTE_DELETE: + default: + operation = null; + break; + } + return operation; + } + + + public SyncThreadHolder executeOperation(OwnCloudClient client, OnRemoteOperationListener listener, Handler handler) { + isRunning = true; + remoteOperation.execute(client, listener, handler); + return this; + } +} diff --git a/app/src/main/java/foundation/e/drive/services/SynchronizationService.java b/app/src/main/java/foundation/e/drive/services/SynchronizationService.java index 72cee50c44d164f321e499f76523e008e4695769..152313521f857de13336e6c82c4bfd807708615b 100644 --- a/app/src/main/java/foundation/e/drive/services/SynchronizationService.java +++ b/app/src/main/java/foundation/e/drive/services/SynchronizationService.java @@ -9,9 +9,6 @@ package foundation.e.drive.services; import android.accounts.Account; import android.accounts.AccountManager; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; @@ -24,7 +21,6 @@ import android.os.Message; import android.util.Log; import androidx.annotation.Nullable; -import androidx.core.app.NotificationCompat; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.operations.OnRemoteOperationListener; @@ -34,14 +30,11 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; -import java.util.Hashtable; import java.util.concurrent.ConcurrentLinkedDeque; -import foundation.e.drive.R; import foundation.e.drive.database.DbHelper; -import foundation.e.drive.models.DownloadRequest; import foundation.e.drive.models.SyncRequest; -import foundation.e.drive.models.SyncedFileState; +import foundation.e.drive.models.SyncThreadHolder; import foundation.e.drive.operations.DownloadFileOperation; import foundation.e.drive.operations.RemoveFileOperation; import foundation.e.drive.operations.UploadFileOperation; @@ -57,13 +50,13 @@ public class SynchronizationService extends Service implements OnRemoteOperation private final SynchronizationBinder binder = new SynchronizationBinder(); private ConcurrentLinkedDeque syncedRequestQueue; - private Hashtable startedOperations; //Operations which are running private Account account; private final int workerAmount = 4; - private boolean[] threadWorkingState; //State of the threads; true mean the thread is working - private Thread[] threadPool; + private SyncThreadHolder[] threadPool; private OwnCloudClient client; private OperationHandler handler; + private final Object queueLock = new Object(); + private final Object runningThreadLock = new Object(); @Override public int onStartCommand(Intent intent, int flags, int startId) { @@ -81,9 +74,7 @@ public class SynchronizationService extends Service implements OnRemoteOperation } syncedRequestQueue = new ConcurrentLinkedDeque<>(); - startedOperations = new Hashtable<>(); - threadPool = new Thread[workerAmount]; - threadWorkingState = new boolean[workerAmount]; + threadPool = new SyncThreadHolder[workerAmount]; client = DavClientProvider.getInstance().getClientInstance(account, getApplicationContext()); handler = new OperationHandler(this); @@ -101,12 +92,35 @@ public class SynchronizationService extends Service implements OnRemoteOperation Log.w(TAG, "System is low on memory. Service might get killed."); } - public boolean queueOperation(SyncRequest request){ - return syncedRequestQueue.add(request); + public void queueOperation(SyncRequest request){ + synchronized (runningThreadLock) { + for (SyncThreadHolder threadHolder : threadPool) { + if (threadHolder != null && threadHolder.getSyncRequest().equals(request)) { + return; + } + } + } + synchronized (queueLock) { + syncedRequestQueue.remove(request); + syncedRequestQueue.add(request); + queueLock.notify(); + } } - public boolean queueOperations(Collection requests){ - return syncedRequestQueue.addAll(requests); + public void queueOperations(Collection requests){ + synchronized (runningThreadLock) { + final ArrayList runningRequests = new ArrayList<>(); + for (SyncThreadHolder threadHolder : threadPool) { + if (threadHolder != null) { + runningRequests.add(threadHolder.getSyncRequest()); + } + } + requests.removeAll(runningRequests); + } + synchronized (queueLock) { + syncedRequestQueue.removeAll(requests); + syncedRequestQueue.addAll(requests); + } } public void startSynchronization(){ @@ -119,21 +133,22 @@ public class SynchronizationService extends Service implements OnRemoteOperation private void startWorker(int threadIndex){ if (syncedRequestQueue.isEmpty()) return; final boolean meteredNetworkAllowed = CommonUtils.isMeteredNetworkAllowed(account); - if (!threadWorkingState[threadIndex] && CommonUtils.haveNetworkConnection(getApplicationContext(), meteredNetworkAllowed)) { //check if the thread corresponding to threadIndex isn't already working - - final SyncRequest request = this.syncedRequestQueue.poll(); //return null if deque is empty - - final RemoteOperation operation = this.createRemoteOperation(request); + synchronized (runningThreadLock) { + if ((threadPool[threadIndex] != null && threadPool[threadIndex].isRunning() ) || !CommonUtils.haveNetworkConnection(getApplicationContext(), meteredNetworkAllowed)) { //check if the thread corresponding to threadIndex isn't already working + return; + } + } - if (operation != null) { - Log.v(TAG, " an operation has been poll from queue"); + final SyncRequest request; + synchronized (queueLock) { + request = syncedRequestQueue.poll(); //return null if deque is empty + } - if (CommonUtils.isThisSyncAllowed(account, request.getSyncedFileState().isMediaType())) { - CommonUtils.createNotificationChannel(this); - startedOperations.put(operation, threadIndex); - threadPool[threadIndex] = operation.execute(client, this, handler); - threadWorkingState[threadIndex] = true; - } + Log.v(TAG, " an operation has been poll from queue"); + final SyncThreadHolder threadHolder = new SyncThreadHolder(threadIndex, request, getApplicationContext()); + if (CommonUtils.isThisSyncAllowed(account, request.getSyncedFileState().isMediaType())) { + synchronized (runningThreadLock) { + threadPool[threadIndex] = threadHolder.executeOperation(client, this, handler); } } } @@ -141,10 +156,16 @@ public class SynchronizationService extends Service implements OnRemoteOperation @Override public void onRemoteOperationFinish(RemoteOperation callerOperation, RemoteOperationResult result) { Log.d(TAG, "onRemoteOperationFinish()"); - Integer threadIndex = this.startedOperations.remove(callerOperation); - if (threadIndex != null) { - this.threadWorkingState[threadIndex] = false; - this.startWorker(threadIndex); + + synchronized (runningThreadLock) { + for (SyncThreadHolder thread : threadPool) { + if (thread != null && callerOperation.equals(thread.getRemoteOperation())) { + final int threadIndex = thread.getThreadIndex(); + thread.setRunning(false); + startWorker(threadIndex); + break; + } + } } if (callerOperation instanceof RemoveFileOperation){ @@ -210,32 +231,11 @@ public class SynchronizationService extends Service implements OnRemoteOperation } } - private RemoteOperation createRemoteOperation(SyncRequest request){ - RemoteOperation operation; - switch (request.getOperationType()){ - case UPLOAD: - final SyncedFileState sfs = request.getSyncedFileState(); - operation = new UploadFileOperation(sfs, getApplicationContext()); - break; - case DOWNLOAD: - final DownloadRequest downloadRequest = (DownloadRequest) request; - operation = new DownloadFileOperation(downloadRequest.getRemoteFile(), downloadRequest.getSyncedFileState(), getApplicationContext()); - break; - case REMOTE_DELETE: - default: - operation = null; - break; - } - return operation; - } - - /** * Handler for the class */ static class OperationHandler extends Handler { private final String TAG = SynchronizationService.OperationHandler.class.getSimpleName(); - private final WeakReference serviceWeakRef; OperationHandler(SynchronizationService mOperationService){ @@ -247,8 +247,14 @@ public class SynchronizationService extends Service implements OnRemoteOperation Log.i(TAG, "handler.handleMessage()"); Bundle data = msg.getData(); - serviceWeakRef.get() - .threadWorkingState[data.getInt("thread index")] = data.getBoolean("mThreadWorkingState"); + synchronized (serviceWeakRef.get().runningThreadLock) { + + final SyncThreadHolder threadHolder = serviceWeakRef.get() + .threadPool[data.getInt("thread index")]; + if (threadHolder != null) { + threadHolder.setRunning(data.getBoolean("mThreadWorkingState")); + } + } } }