Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/stefan-niedermann/nextcloud-deck.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Niedermann <info@niedermann.it>2020-12-09 19:59:07 +0300
committerStefan Niedermann <info@niedermann.it>2020-12-09 19:59:07 +0300
commitdf900e53492c7b30cbb90b9180d6c3cdf59f38d9 (patch)
treeb88dc0386e6b448c95cf4fcb46fbeaf8edc8780f /app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync
parent034ae108ae4ab4c273ef4d74f1bfd39fbc4d8a84 (diff)
parentf29eed9db4c0906fa7887e446cf0325718ef6827 (diff)
Merge branch 'master' into fastlanefastlane
# Conflicts: # fastlane/metadata/android/en-US/images/phoneScreenshots/1.png # fastlane/metadata/android/en-US/images/phoneScreenshots/2.png # fastlane/metadata/android/en-US/images/phoneScreenshots/4.png
Diffstat (limited to 'app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync')
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java811
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/ServerAdapter.java57
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java294
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DateTypeConverter.java10
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DeckDatabase.java256
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/AttachmentDao.java4
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java9
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/CardDao.java16
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/JoinCardWithLabelDao.java3
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/JoinCardWithUserDao.java9
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/StackDao.java9
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserDao.java34
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserInBoardDao.java12
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserInGroupDao.java12
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/JoinCardWithOcsProjectDao.java13
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/OcsProjectDao.java13
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/OcsProjectResourceDao.java25
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/SingleCardWidgetModelDao.java (renamed from app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/SingleCardWidgetModelDao.java)3
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/StackWidgetModelDao.java19
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/WrappedLiveData.java6
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/Debouncer.java75
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/UserSearchLiveData.java93
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/DataPropagationHelper.java119
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/SyncHelper.java77
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AbstractSyncDataProvider.java33
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AccessControlDataProvider.java97
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/ActivityDataProvider.java17
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AttachmentDataProvider.java8
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/BoardDataProvider.java119
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/CardDataProvider.java76
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/DeckCommentsDataProvider.java9
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/LabelDataProvider.java26
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/OcsProjectDataProvider.java100
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/StackDataProvider.java43
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/UserDataProvider.java6
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/partial/BoardWithAclDownSyncDataProvider.java (renamed from app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/partial/BoardWitAclDownSyncDataProvider.java)2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/partial/BoardWithStacksAndLabelsUpSyncDataProvider.java37
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/util/AsyncUtil.java21
38 files changed, 1995 insertions, 578 deletions
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java
index 9ed9035cf..d93835423 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java
@@ -1,31 +1,43 @@
package it.niedermann.nextcloud.deck.persistence.sync;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.database.sqlite.SQLiteConstraintException;
import androidx.annotation.AnyThread;
+import androidx.annotation.ColorInt;
+import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.Size;
import androidx.annotation.WorkerThread;
import androidx.core.util.Pair;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import androidx.lifecycle.MutableLiveData;
+import com.nextcloud.android.sso.api.ParsedResponse;
import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
import java.io.File;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Date;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.NoSuchElementException;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.api.GsonConfig;
import it.niedermann.nextcloud.deck.api.IResponseCallback;
import it.niedermann.nextcloud.deck.api.LastSyncUtil;
+import it.niedermann.nextcloud.deck.exceptions.DeckException;
import it.niedermann.nextcloud.deck.exceptions.OfflineException;
import it.niedermann.nextcloud.deck.model.AccessControl;
import it.niedermann.nextcloud.deck.model.Account;
@@ -36,9 +48,11 @@ import it.niedermann.nextcloud.deck.model.JoinCardWithUser;
import it.niedermann.nextcloud.deck.model.Label;
import it.niedermann.nextcloud.deck.model.Stack;
import it.niedermann.nextcloud.deck.model.User;
+import it.niedermann.nextcloud.deck.model.appwidgets.StackWidgetModel;
import it.niedermann.nextcloud.deck.model.enums.DBStatus;
import it.niedermann.nextcloud.deck.model.full.FullBoard;
import it.niedermann.nextcloud.deck.model.full.FullCard;
+import it.niedermann.nextcloud.deck.model.full.FullCardWithProjects;
import it.niedermann.nextcloud.deck.model.full.FullSingleCardWidgetModel;
import it.niedermann.nextcloud.deck.model.full.FullStack;
import it.niedermann.nextcloud.deck.model.internal.FilterInformation;
@@ -46,12 +60,12 @@ import it.niedermann.nextcloud.deck.model.ocs.Capabilities;
import it.niedermann.nextcloud.deck.model.ocs.comment.DeckComment;
import it.niedermann.nextcloud.deck.model.ocs.comment.OcsComment;
import it.niedermann.nextcloud.deck.model.ocs.comment.full.FullDeckComment;
-import it.niedermann.nextcloud.deck.model.ocs.user.OcsUser;
-import it.niedermann.nextcloud.deck.model.ocs.user.OcsUserList;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProjectResource;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.ServerAdapter;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.DataBaseAdapter;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.extrawurst.UserSearchLiveData;
import it.niedermann.nextcloud.deck.persistence.sync.helpers.DataPropagationHelper;
import it.niedermann.nextcloud.deck.persistence.sync.helpers.SyncHelper;
import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.AbstractSyncDataProvider;
@@ -64,20 +78,23 @@ import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.CardPropa
import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.DeckCommentsDataProvider;
import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.LabelDataProvider;
import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.StackDataProvider;
-import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.partial.BoardWitAclDownSyncDataProvider;
-import it.niedermann.nextcloud.deck.util.DateUtil;
+import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.partial.BoardWithAclDownSyncDataProvider;
+import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.partial.BoardWithStacksAndLabelsUpSyncDataProvider;
+import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED;
import static java.net.HttpURLConnection.HTTP_UNAVAILABLE;
@SuppressWarnings("WeakerAccess")
public class SyncManager {
@NonNull
- private Context appContext;
+ private final Context appContext;
@NonNull
- private DataBaseAdapter dataBaseAdapter;
+ private final DataBaseAdapter dataBaseAdapter;
@NonNull
- private ServerAdapter serverAdapter;
+ private final ServerAdapter serverAdapter;
+
+ private static final Map<Long, List<IResponseCallback<Boolean>>> RUNNING_SYNCS = new ConcurrentHashMap<>();
@AnyThread
public SyncManager(@NonNull Context context) {
@@ -98,62 +115,35 @@ public class SyncManager {
}
@AnyThread
- public MutableLiveData<FullCard> synchronizeCardByRemoteId(long cardRemoteId, @NonNull Account account) {
- MutableLiveData<FullCard> liveData = new MutableLiveData<>();
- doAsync(() -> {
- Long accountId = account.getId();
- Card card = dataBaseAdapter.getCardByRemoteIdDirectly(accountId, cardRemoteId);
- FullStack stack = dataBaseAdapter.getFullStackByLocalIdDirectly(card.getStackId());
- // only sync this one card.
- stack.setCards(Collections.singletonList(card));
- Board board = dataBaseAdapter.getBoardByLocalIdDirectly(stack.getStack().getBoardId());
- new SyncHelper(serverAdapter, dataBaseAdapter, new Date()).setResponseCallback(new IResponseCallback<Boolean>(account) {
- @Override
- public void onResponse(Boolean response) {
- FullCard fullCard = dataBaseAdapter.getFullCardByLocalIdDirectly(accountId, card.getLocalId());
- liveData.postValue(fullCard);
- }
-
- @Override
- public void onError(Throwable throwable) {
- liveData.postValue(null);
- }
- }).doSyncFor(new CardDataProvider(null, board, stack));
- });
- return liveData;
- }
-
- // TODO if the card does not exist yet, try to synchronize it first, instead of directly returning null. If sync failed, return null.
- @AnyThread
public LiveData<Long> getLocalBoardIdByCardRemoteIdAndAccount(long cardRemoteId, @NonNull Account account) {
return dataBaseAdapter.getLocalBoardIdByCardRemoteIdAndAccountId(cardRemoteId, account.getId());
}
- @AnyThread
+ @WorkerThread
public boolean synchronizeEverything() {
List<Account> accounts = dataBaseAdapter.getAllAccountsDirectly();
if (accounts.size() > 0) {
- final BooleanResultHolder success = new BooleanResultHolder();
+ final AtomicBoolean success = new AtomicBoolean();
CountDownLatch latch = new CountDownLatch(accounts.size());
try {
for (Account account : accounts) {
new SyncManager(dataBaseAdapter.getContext(), account.getName()).synchronize(new IResponseCallback<Boolean>(account) {
@Override
public void onResponse(Boolean response) {
- success.result = success.result && Boolean.TRUE.equals(response);
+ success.set(success.get() && Boolean.TRUE.equals(response));
latch.countDown();
}
@Override
public void onError(Throwable throwable) {
- success.result = false;
+ success.set(false);
super.onError(throwable);
latch.countDown();
}
});
}
latch.await();
- return success.result;
+ return success.get();
} catch (InterruptedException e) {
DeckLog.logError(e);
return false;
@@ -164,76 +154,160 @@ public class SyncManager {
@AnyThread
public void synchronize(@NonNull IResponseCallback<Boolean> responseCallback) {
- if(responseCallback.getAccount() == null) {
+ synchronize(Collections.singletonList(responseCallback));
+ }
+
+ @AnyThread
+ public void synchronizeBoard(@NonNull IResponseCallback<Boolean> responseCallback, long localBoadId) {
+ doAsync(() -> {
+ FullBoard board = dataBaseAdapter.getFullBoardByLocalIdDirectly(responseCallback.getAccount().getId(), localBoadId);
+ try {
+ new SyncHelper(serverAdapter, dataBaseAdapter, null).setResponseCallback(responseCallback).doSyncFor(new StackDataProvider(null, board));
+ } catch (OfflineException e) {
+ responseCallback.onError(e);
+ }
+ });
+ }
+
+ @AnyThread
+ public void synchronizeCard(@NonNull IResponseCallback<Boolean> responseCallback, Card card) {
+ doAsync(() -> {
+ FullStack stack = dataBaseAdapter.getFullStackByLocalIdDirectly(card.getStackId());
+ Board board = dataBaseAdapter.getBoardByLocalIdDirectly(stack.getStack().getBoardId());
+ try {
+ new SyncHelper(serverAdapter, dataBaseAdapter, null).setResponseCallback(responseCallback).doSyncFor(new CardDataProvider(null, board, stack));
+ } catch (OfflineException e) {
+ responseCallback.onError(e);
+ }
+ });
+ }
+
+ private void synchronize(@NonNull @Size(min = 1) List<IResponseCallback<Boolean>> responseCallbacks) {
+ if (responseCallbacks == null || responseCallbacks.size() < 1) {
+ return;
+ }
+ IResponseCallback<Boolean> responseCallback = responseCallbacks.get(0);
+ Account callbackAccount = responseCallback.getAccount();
+ if (callbackAccount == null) {
throw new IllegalArgumentException(Account.class.getSimpleName() + " object in given " + IResponseCallback.class.getSimpleName() + " must not be null.");
}
- if(responseCallback.getAccount().getId() == null) {
+ Long callbackAccountId = callbackAccount.getId();
+ if (callbackAccountId == null) {
throw new IllegalArgumentException(Account.class.getSimpleName() + " object in given " + IResponseCallback.class.getSimpleName() + " must contain a valid id, but given id was null.");
}
- doAsync(() -> refreshCapabilities(new IResponseCallback<Capabilities>(responseCallback.getAccount()) {
- @Override
- public void onResponse(Capabilities response) {
- if (!response.isMaintenanceEnabled()) {
- if (response.getDeckVersion().isSupported(appContext)) {
- long accountId = responseCallback.getAccount().getId();
- Date lastSyncDate = LastSyncUtil.getLastSyncDate(responseCallback.getAccount().getId());
- Date now = DateUtil.nowInGMT();
-
- final SyncHelper syncHelper = new SyncHelper(serverAdapter, dataBaseAdapter, lastSyncDate);
+ List<IResponseCallback<Boolean>> queuedCallbacks = RUNNING_SYNCS.get(callbackAccountId);
+ if (queuedCallbacks != null) {
+ queuedCallbacks.addAll(responseCallbacks);
+ return;
+ } else {
+ RUNNING_SYNCS.put(callbackAccountId, new ArrayList<>(responseCallbacks));
+ }
+ doAsync(() -> {
+ List<IResponseCallback<Boolean>> existingQueue = RUNNING_SYNCS.get(callbackAccountId);
+ List<IResponseCallback<Boolean>> callbacksQueueForSync = existingQueue == null ? new ArrayList<>() : new ArrayList<>(existingQueue);
+ refreshCapabilities(new IResponseCallback<Capabilities>(responseCallback.getAccount()) {
+ @Override
+ public void onResponse(Capabilities response) {
+ if (response != null && !response.isMaintenanceEnabled()) {
+ if (response.getDeckVersion().isSupported(appContext)) {
+ long accountId = callbackAccountId;
+ Instant lastSyncDate = LastSyncUtil.getLastSyncDate(callbackAccountId);
- IResponseCallback<Boolean> callback = new IResponseCallback<Boolean>(responseCallback.getAccount()) {
- @Override
- public void onResponse(Boolean response) {
- syncHelper.setResponseCallback(new IResponseCallback<Boolean>(account) {
- @Override
- public void onResponse(Boolean response) {
- // TODO deactivate for dev
- LastSyncUtil.setLastSyncDate(accountId, now);
- responseCallback.onResponse(response);
- }
+ final SyncHelper syncHelper = new SyncHelper(serverAdapter, dataBaseAdapter, lastSyncDate);
- @Override
- public void onError(Throwable throwable) {
- super.onError(throwable);
- responseCallback.onError(throwable);
- }
- });
- doAsync(() -> {
- try {
- syncHelper.doUpSyncFor(new BoardDataProvider());
- } catch (Throwable e) {
- DeckLog.logError(e);
- responseCallback.onError(e);
- }
- });
+ IResponseCallback<Boolean> callback = new IResponseCallback<Boolean>(callbackAccount) {
+ @Override
+ public void onResponse(Boolean response) {
+ syncHelper.setResponseCallback(new IResponseCallback<Boolean>(account) {
+ @Override
+ public void onResponse(Boolean response) {
+ // TODO deactivate for dev
+ LastSyncUtil.setLastSyncDate(accountId, Instant.now());
+ respondCallbacksAfterSync(callbacksQueueForSync, response, null);
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ respondCallbacksAfterSync(callbacksQueueForSync, null, throwable);
+ }
+ });
+ doAsync(() -> {
+ try {
+ syncHelper.doUpSyncFor(new BoardDataProvider());
+ } catch (Throwable e) {
+ DeckLog.logError(e);
+ respondCallbacksAfterSync(callbacksQueueForSync, null, e);
+ }
+ });
- }
+ }
- @Override
- public void onError(Throwable throwable) {
- super.onError(throwable);
- responseCallback.onError(throwable);
- }
- };
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ respondCallbacksAfterSync(callbacksQueueForSync, null, throwable);
+ }
+ };
- syncHelper.setResponseCallback(callback);
+ syncHelper.setResponseCallback(callback);
- try {
- syncHelper.doSyncFor(new BoardDataProvider());
- } catch (Throwable e) {
- DeckLog.logError(e);
- responseCallback.onError(e);
+ try {
+ syncHelper.doSyncFor(new BoardDataProvider());
+ } catch (Throwable e) {
+ DeckLog.logError(e);
+ respondCallbacksAfterSync(callbacksQueueForSync, null, e);
+ }
+ } else {
+ respondCallbacksAfterSync(callbacksQueueForSync, Boolean.FALSE, null);
+ DeckLog.warn("No sync. Server version not supported: " + response.getDeckVersion().getOriginalVersion());
}
} else {
- responseCallback.onResponse(false);
- DeckLog.warn("No sync. Server version not supported: " + response.getDeckVersion().getOriginalVersion());
+ respondCallbacksAfterSync(callbacksQueueForSync, Boolean.FALSE, null);
+ if (response != null) {
+ DeckLog.warn("No sync. Status maintenance mode: " + response.isMaintenanceEnabled());
+ }
}
- } else {
- responseCallback.onResponse(false);
- DeckLog.warn("No sync. Status maintenance mode: " + response.isMaintenanceEnabled());
}
+ });
+ });
+ }
+
+ private void respondCallbacksAfterSync(List<IResponseCallback<Boolean>> callbacksQueueForSync, Boolean response, Throwable throwable) {
+ if (callbacksQueueForSync == null || callbacksQueueForSync.isEmpty()) {
+ return;
+ }
+ // notify done callbacks
+ DeckLog.info("SyncQueue: responding sync for " + callbacksQueueForSync.size() + " queued callbacks!");
+ List<IResponseCallback<Boolean>> callbacksQueue = new ArrayList<>(callbacksQueueForSync);
+ if (throwable == null) {
+ //success:
+ for (IResponseCallback<Boolean> callback : callbacksQueue) {
+ if (callback != null) callback.onResponse(response);
}
- }));
+ } else {
+ // failure:
+ for (IResponseCallback<Boolean> callback : callbacksQueue) {
+ if (callback != null) callback.onError(throwable);
+ }
+ }
+ // remove done callbacks from queue
+ IResponseCallback<Boolean> firstCallbackOfAccount = callbacksQueue.iterator().next();
+ List<IResponseCallback<Boolean>> queuedCallbacks = RUNNING_SYNCS.get(firstCallbackOfAccount.getAccount().getId());
+ if (queuedCallbacks == null) {
+ return;
+ }
+ for (IResponseCallback<Boolean> callback : callbacksQueue) {
+ queuedCallbacks.remove(callback);
+ }
+ // cleanup if done, or proceed if not
+ if (queuedCallbacks.isEmpty()) {
+ RUNNING_SYNCS.remove(firstCallbackOfAccount.getAccount().getId());
+ } else {
+ DeckLog.info("SyncQueue: starting sync for " + queuedCallbacks.size() + " queued callbacks!");
+ RUNNING_SYNCS.remove(firstCallbackOfAccount.getAccount().getId());
+ synchronize(queuedCallbacks);
+ }
}
//
@@ -274,14 +348,15 @@ public class SyncManager {
}
@AnyThread
- public WrappedLiveData<Account> createAccount(@NonNull Account accout) {
- return dataBaseAdapter.createAccount(accout);
+ public WrappedLiveData<Account> createAccount(@NonNull Account account) {
+ return dataBaseAdapter.createAccount(account);
}
public boolean hasInternetConnection() {
return serverAdapter.hasInternetConnection();
}
+ @AnyThread
public void deleteAccount(long id) {
doAsync(() -> {
dataBaseAdapter.deleteAccount(id);
@@ -289,10 +364,6 @@ public class SyncManager {
});
}
- public void updateAccount(Account account) {
- dataBaseAdapter.updateAccount(account);
- }
-
@AnyThread
public LiveData<Account> readAccount(long id) {
return dataBaseAdapter.readAccount(id);
@@ -320,7 +391,7 @@ public class SyncManager {
* - located at the given {@param host}
* - and have the permission to read the board with the given {@param boardRemoteId} (aka the {@link Board} is shared with this {@link User}).
*/
- @AnyThread
+ @MainThread
public LiveData<List<Account>> readAccountsForHostWithReadAccessToBoard(String host, long boardRemoteId) {
MediatorLiveData<List<Account>> liveData = new MediatorLiveData<>();
liveData.addSource(dataBaseAdapter.readAccountsForHostWithReadAccessToBoard(host, boardRemoteId), accounts -> {
@@ -333,7 +404,7 @@ public class SyncManager {
public void onResponse(Boolean response) {
liveData.postValue(dataBaseAdapter.readAccountsForHostWithReadAccessToBoardDirectly(host, boardRemoteId));
}
- }).doSyncFor(new BoardWitAclDownSyncDataProvider());
+ }).doSyncFor(new BoardWithAclDownSyncDataProvider());
}
});
});
@@ -345,15 +416,17 @@ public class SyncManager {
public void refreshCapabilities(@NonNull IResponseCallback<Capabilities> callback) {
doAsync(() -> {
try {
- serverAdapter.getCapabilities(new IResponseCallback<Capabilities>(callback.getAccount()) {
+ Account accountForEtag = dataBaseAdapter.getAccountByIdDirectly(callback.getAccount().getId());
+ serverAdapter.getCapabilities(accountForEtag.getEtag(), new IResponseCallback<ParsedResponse<Capabilities>>(callback.getAccount()) {
@Override
- public void onResponse(Capabilities response) {
+ public void onResponse(ParsedResponse<Capabilities> response) {
Account acc = dataBaseAdapter.getAccountByIdDirectly(account.getId());
- acc.applyCapabilities(response);
+ acc.applyCapabilities(response.getResponse(), response.getHeaders().get("ETag"));
dataBaseAdapter.updateAccount(acc);
- callback.onResponse(response);
+ callback.onResponse(response.getResponse());
}
+ @SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
if (throwable instanceof NextcloudHttpRequestFailedException) {
@@ -361,13 +434,28 @@ public class SyncManager {
if (requestFailedException.getStatusCode() == HTTP_UNAVAILABLE && requestFailedException.getCause() != null) {
String errorString = requestFailedException.getCause().getMessage();
Capabilities capabilities = GsonConfig.getGson().fromJson(errorString, Capabilities.class);
+ DeckLog.verbose("HTTP Status " + HTTP_UNAVAILABLE + ": This server seems to be in maintenance mode.");
if (capabilities.isMaintenanceEnabled()) {
- doAsync(() -> {
- onResponse(capabilities);
- });
+ doAsync(() -> onResponse(ParsedResponse.of(capabilities)));
} else {
onError(throwable);
}
+ } else if (requestFailedException.getStatusCode() == HTTP_NOT_MODIFIED) {
+ DeckLog.verbose("HTTP Status " + HTTP_NOT_MODIFIED + ": There haven't been any changes on the server side for this request.");
+ //could be after maintenance. so we have to at least revert the maintenance flag
+ doAsync(() -> {
+ Account acc = dataBaseAdapter.getAccountByIdDirectly(account.getId());
+ if (acc.isMaintenanceEnabled()) {
+ acc.setMaintenanceEnabled(false);
+ dataBaseAdapter.updateAccount(acc);
+ }
+ Capabilities capabilities = new Capabilities();
+ capabilities.setMaintenanceEnabled(false);
+ capabilities.setDeckVersion(acc.getServerDeckVersionAsObject());
+ capabilities.setTextColor(acc.getTextColor());
+ capabilities.setColor(acc.getColor());
+ callback.onResponse(capabilities);
+ });
}
} else {
callback.onError(throwable);
@@ -377,39 +465,30 @@ public class SyncManager {
} catch (OfflineException e) {
callback.onError(e);
}
-
- try {
- serverAdapter.getAllOcsUsers(new IResponseCallback<OcsUserList>(callback.getAccount()) {
- @Override
- public void onResponse(OcsUserList response) {
- Long accountId = callback.getAccount().getId();
- for (String ocsUserName : response) {
- User existingUser = dataBaseAdapter.getUserByUidDirectly(accountId, ocsUserName);
- if (existingUser == null) {
- // we don't know this user, lets get some details...
- serverAdapter.getOcsUserDetails(ocsUserName, new IResponseCallback<OcsUser>(callback.getAccount()) {
- @Override
- public void onResponse(OcsUser response) {
- User newUser = new User();
- newUser.setStatus(DBStatus.UP_TO_DATE.getId());
- newUser.setPrimaryKey(ocsUserName);
- newUser.setUid(ocsUserName);
- newUser.setDisplayname(response.getDisplayName());
- dataBaseAdapter.createUser(accountId, newUser);
- }
- });
- }
- }
- }
- });
- } catch (OfflineException ignored) {
- // Nothing to do here...
- }
});
}
/**
* @param accountId ID of the account
+ * @return all {@link Board}s no matter if {@link Board#archived} or not.
+ */
+ @SuppressWarnings("JavadocReference")
+ @AnyThread
+ public LiveData<List<Board>> getBoards(long accountId) {
+ return dataBaseAdapter.getBoards(accountId);
+ }
+
+ /**
+ * @param localProjectId LocalId of the OcsProject
+ * @return all {@link OcsProjectResource}s of the Project
+ */
+ @AnyThread
+ public LiveData<List<OcsProjectResource>> getResourcesForProject(long localProjectId) {
+ return dataBaseAdapter.getResourcesByLocalProjectId(localProjectId);
+ }
+
+ /**
+ * @param accountId ID of the account
* @param archived Decides whether only archived or not-archived boards for the specified account will be returned
* @return all archived or non-archived <code>Board</code>s depending on <code>archived</code> parameter
*/
@@ -445,8 +524,8 @@ public class SyncManager {
}
@AnyThread
- public LiveData<FullBoard> createBoard(long accountId, @NonNull Board board) {
- MutableLiveData<FullBoard> liveData = new MutableLiveData<>();
+ public WrappedLiveData<FullBoard> createBoard(long accountId, @NonNull Board board) {
+ WrappedLiveData<FullBoard> liveData = new WrappedLiveData<>();
doAsync(() -> {
Account account = dataBaseAdapter.getAccountByIdDirectly(accountId);
User owner = dataBaseAdapter.getUserByUidDirectly(accountId, account.getUserName());
@@ -465,6 +544,12 @@ public class SyncManager {
public void onResponse(FullBoard response) {
liveData.postValue(response);
}
+
+ @SuppressLint("MissingSuperCall")
+ @Override
+ public void onError(Throwable throwable, FullBoard entity) {
+ liveData.postError(throwable, entity);
+ }
});
}
});
@@ -474,58 +559,145 @@ public class SyncManager {
/**
* Creates a new {@link Board} and adds the same {@link Label} and {@link Stack} as in the origin {@link Board}.
* Owner of the target {@link Board} will be the {@link User} with the {@link Account} of {@param targetAccountId}.
- * Does <strong>not</strong> clone any {@link Card} or {@link AccessControl} from the origin {@link Board}.
+ *
+ * @param cloneCards determines whether or not the cards in this {@link Board} shall be cloned or not
+ * Does <strong>not</strong> clone any {@link Card} or {@link AccessControl} from the origin {@link Board}.
+ * <p>
+ * TODO implement https://github.com/stefan-niedermann/nextcloud-deck/issues/608
*/
@AnyThread
- public WrappedLiveData<FullBoard> cloneBoard(long originAccountId, long originBoardLocalId, long targetAccountId, String targetBoardTitle, String targetBoardColor) {
- WrappedLiveData<FullBoard> liveData = new WrappedLiveData<>();
+ public WrappedLiveData<FullBoard> cloneBoard(long originAccountId, long originBoardLocalId, long targetAccountId, @ColorInt int targetBoardColor, boolean cloneCards) {
+ final WrappedLiveData<FullBoard> liveData = new WrappedLiveData<>();
doAsync(() -> {
Account originAccount = dataBaseAdapter.getAccountByIdDirectly(originAccountId);
User newOwner = dataBaseAdapter.getUserByUidDirectly(originAccountId, originAccount.getUserName());
+ if (newOwner == null) {
+ liveData.postError(new DeckException(DeckException.Hint.UNKNOWN_ACCOUNT_USER_ID, "User with Account-UID \"" + originAccount.getUserName() + "\" not found."));
+ return;
+ }
FullBoard originalBoard = dataBaseAdapter.getFullBoardByLocalIdDirectly(originAccountId, originBoardLocalId);
+ String newBoardTitleBaseName = originalBoard.getBoard().getTitle().trim();
+ int newBoardTitleCopyIndex = 0;
+ //already a copy?
+ String regex = " \\(copy [0-9]+\\)$";
+ Pattern pattern = Pattern.compile(regex);
+ Matcher matcher = pattern.matcher(originalBoard.getBoard().getTitle());
+ if (matcher.find()) {
+ String found = matcher.group();
+ newBoardTitleBaseName = newBoardTitleBaseName.substring(0, newBoardTitleBaseName.length() - found.length());
+ Matcher indexMatcher = Pattern.compile("[0-9]+").matcher(found);
+ //noinspection ResultOfMethodCallIgnored
+ indexMatcher.find();
+ String oldIndexString = indexMatcher.group();
+ newBoardTitleCopyIndex = Integer.parseInt(oldIndexString);
+ }
+
+ String newBoardTitle;
+ do {
+ newBoardTitleCopyIndex++;
+ newBoardTitle = newBoardTitleBaseName + " (copy " + newBoardTitleCopyIndex + ")";
+
+ } while (dataBaseAdapter.getBoardForAccountByNameDirectly(targetAccountId, newBoardTitle) != null);
+
+
originalBoard.setAccountId(targetAccountId);
- originalBoard.getBoard().setTitle(targetBoardTitle);
- originalBoard.getBoard().setColor(targetBoardColor);
- originalBoard.getBoard().setOwnerId(newOwner.getId());
- originalBoard.setStatusEnum(DBStatus.LOCAL_EDITED);
- originalBoard.setOwner(newOwner);
originalBoard.setId(null);
originalBoard.setLocalId(null);
+ originalBoard.getBoard().setTitle(newBoardTitle);
+ originalBoard.getBoard().setColor(String.format("%06X", 0xFFFFFF & targetBoardColor));
+ originalBoard.getBoard().setOwnerId(newOwner.getLocalId());
+ originalBoard.setStatusEnum(DBStatus.LOCAL_EDITED);
+ originalBoard.setOwner(newOwner);
long newBoardId = dataBaseAdapter.createBoardDirectly(originAccountId, originalBoard.getBoard());
originalBoard.setLocalId(newBoardId);
- for (Stack stack : originalBoard.getStacks()) {
- stack.setLocalId(null);
- stack.setId(null);
- stack.setStatusEnum(DBStatus.LOCAL_EDITED);
- stack.setAccountId(targetAccountId);
- stack.setBoardId(newBoardId);
- dataBaseAdapter.createStack(targetAccountId, stack);
+ boolean isSameAccount = targetAccountId == originAccountId;
+
+ if (isSameAccount) {
+ List<AccessControl> aclList = originalBoard.getParticipants();
+ for (AccessControl acl : aclList) {
+ acl.setLocalId(null);
+ acl.setId(null);
+ acl.setBoardId(newBoardId);
+ dataBaseAdapter.createAccessControl(targetAccountId, acl);
+ }
}
+
+ Map<Long, Long> oldToNewLabelIdsDictionary = new HashMap<>();
+
for (Label label : originalBoard.getLabels()) {
+ Long oldLocalId = label.getLocalId();
label.setLocalId(null);
label.setId(null);
label.setAccountId(targetAccountId);
label.setStatusEnum(DBStatus.LOCAL_EDITED);
label.setBoardId(newBoardId);
- dataBaseAdapter.createLabel(targetAccountId, label);
+ long newLocalId = dataBaseAdapter.createLabelDirectly(targetAccountId, label);
+ oldToNewLabelIdsDictionary.put(oldLocalId, newLocalId);
}
- Account targetAccount = dataBaseAdapter.getAccountByIdDirectly(targetAccountId);
- new SyncHelper(serverAdapter, dataBaseAdapter, null)
- .setResponseCallback(new IResponseCallback<Boolean>(targetAccount) {
- @Override
- public void onResponse(Boolean response) {
- liveData.postValue(dataBaseAdapter.getFullBoardByLocalIdDirectly(targetAccountId, newBoardId));
- }
- @Override
- public void onError(Throwable throwable) {
- super.onError(throwable);
- liveData.postError(throwable);
+ List<Stack> oldStacks = originalBoard.getStacks();
+ for (Stack stack : oldStacks) {
+ Long oldStackId = stack.getLocalId();
+ stack.setLocalId(null);
+ stack.setId(null);
+ stack.setStatusEnum(DBStatus.LOCAL_EDITED);
+ stack.setAccountId(targetAccountId);
+ stack.setBoardId(newBoardId);
+ long createdStackId = dataBaseAdapter.createStack(targetAccountId, stack);
+ if (cloneCards) {
+ List<FullCard> oldCards = dataBaseAdapter.getFullCardsForStackDirectly(originAccountId, oldStackId, null);
+ for (FullCard oldCard : oldCards) {
+ Card newCard = oldCard.getCard();
+ newCard.setId(null);
+ newCard.setUserId(newOwner.getLocalId());
+ newCard.setLocalId(null);
+ newCard.setStackId(createdStackId);
+ newCard.setAccountId(targetAccountId);
+ newCard.setStatusEnum(DBStatus.LOCAL_EDITED);
+ long createdCardId = dataBaseAdapter.createCardDirectly(targetAccountId, newCard);
+ if (oldCard.getLabels() != null) {
+ for (Label oldLabel : oldCard.getLabels()) {
+ Long newLabelId = oldToNewLabelIdsDictionary.get(oldLabel.getLocalId());
+ if (newLabelId != null) {
+ dataBaseAdapter.createJoinCardWithLabel(newLabelId, createdCardId, DBStatus.LOCAL_EDITED);
+ } else
+ DeckLog.error("ID of created Label is null! Skipping assignment of \"" + oldLabel.getTitle() + "\"...");
+ }
+ }
+ if (isSameAccount && oldCard.getAssignedUsers() != null) {
+ for (User assignedUser : oldCard.getAssignedUsers()) {
+ dataBaseAdapter.createJoinCardWithUser(assignedUser.getLocalId(), createdCardId, DBStatus.LOCAL_EDITED);
+ }
}
- }).doSyncFor(new BoardDataProvider());
+ }
+ }
+ }
+ // dont trigger concurrent syncs!
+ List<IResponseCallback<Boolean>> queuedSync = RUNNING_SYNCS.get(targetAccountId);
+ if ((queuedSync == null || queuedSync.isEmpty()) && serverAdapter.hasInternetConnection()) {
+ Account targetAccount = dataBaseAdapter.getAccountByIdDirectly(targetAccountId);
+ ServerAdapter serverAdapterToUse = this.serverAdapter;
+ if (originAccountId != targetAccountId) {
+ serverAdapterToUse = new ServerAdapter(appContext, targetAccount.getName());
+ }
+ new SyncHelper(serverAdapterToUse, dataBaseAdapter, null)
+ .setResponseCallback(new IResponseCallback<Boolean>(targetAccount) {
+ @Override
+ public void onResponse(Boolean response) {
+ liveData.postValue(dataBaseAdapter.getFullBoardByLocalIdDirectly(targetAccountId, newBoardId));
+ }
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ liveData.postError(throwable);
+ }
+ }).doUpSyncFor(new BoardWithStacksAndLabelsUpSyncDataProvider(dataBaseAdapter.getFullBoardByLocalIdDirectly(targetAccountId, newBoardId)));
+ } else {
+ liveData.postValue(dataBaseAdapter.getFullBoardByLocalIdDirectly(targetAccountId, newBoardId));
+ }
});
return liveData;
}
@@ -624,6 +796,7 @@ public class SyncManager {
liveData.postValue(response);
}
+ @SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
liveData.postError(throwable);
@@ -633,8 +806,8 @@ public class SyncManager {
return liveData;
}
- public LiveData<List<FullStack>> getStacksForBoard(long accountId, long localBoardId) {
- return dataBaseAdapter.getFullStacksForBoard(accountId, localBoardId);
+ public LiveData<List<Stack>> getStacksForBoard(long accountId, long localBoardId) {
+ return dataBaseAdapter.getStacksForBoard(accountId, localBoardId);
}
public LiveData<FullStack> getStack(long accountId, long localStackId) {
@@ -651,8 +824,7 @@ public class SyncManager {
new AccessControlDataProvider(null, board, Collections.singletonList(entity)), entity, getCallbackToLiveDataConverter(account, liveData), ((entity1, response) -> {
response.setBoardId(entity.getBoardId());
response.setUserId(entity.getUser().getLocalId());
- }
- )
+ })
);
});
return liveData;
@@ -687,6 +859,7 @@ public class SyncManager {
liveData.postValue(response);
}
+ @SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
liveData.postError(throwable);
@@ -711,6 +884,7 @@ public class SyncManager {
liveData.postValue(response);
}
+ @SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
liveData.postError(throwable);
@@ -725,18 +899,30 @@ public class SyncManager {
}
@AnyThread
- public WrappedLiveData<FullStack> createStack(long accountId, @NonNull Stack stack) {
+ public WrappedLiveData<FullStack> createStack(long accountId, @NonNull String title, long boardLocalId) {
WrappedLiveData<FullStack> liveData = new WrappedLiveData<>();
doAsync(() -> {
+ Stack stack = new Stack(title, boardLocalId);
Account account = dataBaseAdapter.getAccountByIdDirectly(accountId);
FullBoard board = dataBaseAdapter.getFullBoardByLocalIdDirectly(accountId, stack.getBoardId());
FullStack fullStack = new FullStack();
- // TODO set stack order to (highest stack-order from board) + 1 and remove logic from caller
+ stack.setOrder(dataBaseAdapter.getHighestStackOrderInBoard(stack.getBoardId()) + 1);
stack.setAccountId(accountId);
stack.setBoardId(board.getLocalId());
fullStack.setStack(stack);
fullStack.setAccountId(accountId);
- new DataPropagationHelper(serverAdapter, dataBaseAdapter).createEntity(new StackDataProvider(null, board), fullStack, getCallbackToLiveDataConverter(account, liveData));
+ new DataPropagationHelper(serverAdapter, dataBaseAdapter).createEntity(new StackDataProvider(null, board), fullStack, new IResponseCallback<FullStack>(account) {
+ @Override
+ public void onResponse(FullStack response) {
+ liveData.postValue(response);
+ }
+
+ @SuppressLint("MissingSuperCall")
+ @Override
+ public void onError(Throwable throwable, FullStack entity) {
+ liveData.postError(throwable, entity);
+ }
+ });
});
return liveData;
}
@@ -754,36 +940,36 @@ public class SyncManager {
}
@AnyThread
- public WrappedLiveData<FullStack> updateStack(@NonNull FullStack stack) {
+ public WrappedLiveData<FullStack> updateStackTitle(long localStackId, @NonNull String newTitle) {
WrappedLiveData<FullStack> liveData = new WrappedLiveData<>();
doAsync(() -> {
+ FullStack stack = dataBaseAdapter.getFullStackByLocalIdDirectly(localStackId);
+ FullBoard fullBoard = dataBaseAdapter.getFullBoardByLocalIdDirectly(stack.getAccountId(), stack.getStack().getBoardId());
Account account = dataBaseAdapter.getAccountByIdDirectly(stack.getAccountId());
- FullBoard board = dataBaseAdapter.getFullBoardByLocalIdDirectly(stack.getAccountId(), stack.getStack().getBoardId());
- updateStack(account, board, stack, liveData);
+ stack.getStack().setTitle(newTitle);
+ updateStack(account, fullBoard, stack, liveData);
});
return liveData;
-
}
@AnyThread
private void updateStack(@NonNull Account account, @NonNull FullBoard board, @NonNull FullStack stack, @Nullable WrappedLiveData<FullStack> liveData) {
- doAsync(() -> {
- new DataPropagationHelper(serverAdapter, dataBaseAdapter).updateEntity(new StackDataProvider(null, board), stack, new IResponseCallback<FullStack>(account) {
- @Override
- public void onResponse(FullStack response) {
- if (liveData != null) {
- liveData.postValue(response);
- }
+ doAsync(() -> new DataPropagationHelper(serverAdapter, dataBaseAdapter).updateEntity(new StackDataProvider(null, board), stack, new IResponseCallback<FullStack>(account) {
+ @Override
+ public void onResponse(FullStack response) {
+ if (liveData != null) {
+ liveData.postValue(response);
}
+ }
- @Override
- public void onError(Throwable throwable) {
- if (liveData != null) {
- liveData.postError(throwable);
- }
+ @SuppressLint("MissingSuperCall")
+ @Override
+ public void onError(Throwable throwable) {
+ if (liveData != null) {
+ liveData.postError(throwable);
}
- });
- });
+ }
+ }));
}
/**
@@ -813,8 +999,8 @@ public class SyncManager {
});
}
- public LiveData<FullCard> getCardByLocalId(long accountId, long cardLocalId) {
- return dataBaseAdapter.getCardByLocalId(accountId, cardLocalId);
+ public LiveData<FullCardWithProjects> getFullCardWithProjectsByLocalId(long accountId, long cardLocalId) {
+ return dataBaseAdapter.getCardWithProjectsByLocalId(accountId, cardLocalId);
}
public LiveData<List<FullCard>> getFullCardsForStack(long accountId, long localStackId, @Nullable FilterInformation filter) {
@@ -863,8 +1049,8 @@ public class SyncManager {
// }
@AnyThread
- public LiveData<FullCard> createFullCard(long accountId, long localBoardId, long localStackId, @NonNull FullCard card) {
- MutableLiveData<FullCard> liveData = new MutableLiveData<>();
+ public WrappedLiveData<FullCard> createFullCard(long accountId, long localBoardId, long localStackId, @NonNull FullCard card) {
+ WrappedLiveData<FullCard> liveData = new WrappedLiveData<>();
doAsync(() -> {
Account account = dataBaseAdapter.getAccountByIdDirectly(accountId);
User owner = dataBaseAdapter.getUserByUidDirectly(accountId, account.getUserName());
@@ -875,7 +1061,7 @@ public class SyncManager {
card.getCard().setAccountId(accountId);
card.getCard().setStatusEnum(DBStatus.LOCAL_EDITED);
card.getCard().setOrder(dataBaseAdapter.getHighestCardOrderInStack(localStackId) + 1);
- long localCardId = dataBaseAdapter.createCard(accountId, card.getCard());
+ long localCardId = dataBaseAdapter.createCardDirectly(accountId, card.getCard());
card.getCard().setLocalId(localCardId);
List<User> assignedUsers = card.getAssignedUsers();
@@ -901,11 +1087,28 @@ public class SyncManager {
}
}
- liveData.postValue(card);
+
if (serverAdapter.hasInternetConnection()) {
new SyncHelper(serverAdapter, dataBaseAdapter, null)
- .setResponseCallback(IResponseCallback.getDefaultResponseCallback(account))
+ .setResponseCallback(new IResponseCallback<Boolean>(account) {
+ @Override
+ public void onResponse(Boolean response) {
+ liveData.postValue(card);
+ }
+
+ @SuppressLint("MissingSuperCall")
+ @Override
+ public void onError(Throwable throwable) {
+ if (throwable.getClass() == DeckException.class && ((DeckException)throwable).getHint().equals(DeckException.Hint.DEPENDENCY_NOT_SYNCED_YET)) {
+ liveData.postValue(card);
+ } else {
+ liveData.postError(throwable);
+ }
+ }
+ })
.doUpSyncFor(new CardDataProvider(null, board, stack));
+ } else {
+ liveData.postValue(card);
}
});
return liveData;
@@ -917,7 +1120,7 @@ public class SyncManager {
doAsync(() -> {
FullCard fullCard = dataBaseAdapter.getFullCardByLocalIdDirectly(card.getAccountId(), card.getLocalId());
if (fullCard == null) {
- throw new IllegalArgumentException("card to delete does not exist.");
+ throw new IllegalArgumentException("card with id " + card.getLocalId() + " to delete does not exist.");
}
Account account = dataBaseAdapter.getAccountByIdDirectly(card.getAccountId());
FullStack stack = dataBaseAdapter.getFullStackByLocalIdDirectly(card.getStackId());
@@ -935,12 +1138,12 @@ public class SyncManager {
FullStack stack = dataBaseAdapter.getFullStackByLocalIdDirectly(card.getCard().getStackId());
Board board = dataBaseAdapter.getBoardByLocalIdDirectly(stack.getStack().getBoardId());
card.getCard().setArchived(true);
- updateCardForArchive(account, stack, board, card, getCallbackToLiveDataConverter(account, liveData));
+ updateCardForArchive(stack, board, card, getCallbackToLiveDataConverter(account, liveData));
});
return liveData;
}
- private void updateCardForArchive(Account account, FullStack stack, Board board, FullCard card, @NonNull IResponseCallback<FullCard> callback) {
+ private void updateCardForArchive(FullStack stack, Board board, FullCard card, @NonNull IResponseCallback<FullCard> callback) {
new DataPropagationHelper(serverAdapter, dataBaseAdapter).updateEntity(new CardDataProvider(null, board, stack), card, callback);
}
@@ -952,29 +1155,34 @@ public class SyncManager {
FullStack stack = dataBaseAdapter.getFullStackByLocalIdDirectly(card.getCard().getStackId());
Board board = dataBaseAdapter.getBoardByLocalIdDirectly(stack.getStack().getBoardId());
card.getCard().setArchived(false);
- updateCardForArchive(account, stack, board, card, getCallbackToLiveDataConverter(account, liveData));
+ updateCardForArchive(stack, board, card, getCallbackToLiveDataConverter(account, liveData));
});
return liveData;
}
@AnyThread
- public WrappedLiveData<Void> archiveCardsInStack(long accountId, long stackLocalId) {
+ public WrappedLiveData<Void> archiveCardsInStack(long accountId, long stackLocalId, @NonNull FilterInformation filterInformation) {
WrappedLiveData<Void> liveData = new WrappedLiveData<>();
doAsync(() -> {
Account account = dataBaseAdapter.getAccountByIdDirectly(accountId);
FullStack stack = dataBaseAdapter.getFullStackByLocalIdDirectly(stackLocalId);
Board board = dataBaseAdapter.getBoardByLocalIdDirectly(stack.getStack().getBoardId());
- List<FullCard> cards = dataBaseAdapter.getFullCardsForStackDirectly(accountId, stackLocalId);
+ List<FullCard> cards = dataBaseAdapter.getFullCardsForStackDirectly(accountId, stackLocalId, filterInformation);
if (cards.size() > 0) {
CountDownLatch latch = new CountDownLatch(cards.size());
for (FullCard card : cards) {
+ if (card.getCard().isArchived()) {
+ latch.countDown();
+ continue;
+ }
card.getCard().setArchived(true);
- updateCardForArchive(account, stack, board, card, new IResponseCallback<FullCard>(account) {
+ updateCardForArchive(stack, board, card, new IResponseCallback<FullCard>(account) {
@Override
public void onResponse(FullCard response) {
latch.countDown();
}
+ @SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
latch.countDown();
@@ -996,21 +1204,35 @@ public class SyncManager {
}
@AnyThread
- public void archiveBoard(@NonNull Board board) {
+ public WrappedLiveData<FullBoard> archiveBoard(@NonNull Board board) {
+ WrappedLiveData<FullBoard> liveData = new WrappedLiveData<>();
doAsync(() -> {
- FullBoard b = dataBaseAdapter.getFullBoardByLocalIdDirectly(board.getAccountId(), board.getLocalId());
- b.getBoard().setArchived(true);
- updateBoard(b);
+ try {
+ FullBoard b = dataBaseAdapter.getFullBoardByLocalIdDirectly(board.getAccountId(), board.getLocalId());
+ b.getBoard().setArchived(true);
+ updateBoard(b);
+ liveData.postValue(b);
+ } catch (Throwable e) {
+ liveData.postError(e);
+ }
});
+ return liveData;
}
@AnyThread
- public void dearchiveBoard(@NonNull Board board) {
+ public WrappedLiveData<FullBoard> dearchiveBoard(@NonNull Board board) {
+ WrappedLiveData<FullBoard> liveData = new WrappedLiveData<>();
doAsync(() -> {
- FullBoard b = dataBaseAdapter.getFullBoardByLocalIdDirectly(board.getAccountId(), board.getLocalId());
- b.getBoard().setArchived(false);
- updateBoard(b);
+ try {
+ FullBoard b = dataBaseAdapter.getFullBoardByLocalIdDirectly(board.getAccountId(), board.getLocalId());
+ b.getBoard().setArchived(false);
+ updateBoard(b);
+ liveData.postValue(b);
+ } catch (Throwable e) {
+ liveData.postError(e);
+ }
});
+ return liveData;
}
@AnyThread
@@ -1055,6 +1277,7 @@ public class SyncManager {
liveData.postValue(dataBaseAdapter.getFullCardByLocalIdDirectly(card.getAccountId(), card.getLocalId()));
}
+ @SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
liveData.postError(throwable);
@@ -1092,39 +1315,44 @@ public class SyncManager {
public WrappedLiveData<Void> moveCard(long originAccountId, long originCardLocalId, long targetAccountId, long targetBoardLocalId, long targetStackLocalId) {
return LiveDataHelper.wrapInLiveData(() -> {
- FullCard originalCard = dataBaseAdapter.getFullCardByLocalIdDirectly(originAccountId, originCardLocalId);
+ final FullCard originalCard = dataBaseAdapter.getFullCardByLocalIdDirectly(originAccountId, originCardLocalId);
int newIndex = dataBaseAdapter.getHighestCardOrderInStack(targetStackLocalId) + 1;
- FullBoard originalBoard = dataBaseAdapter.getFullBoardByLocalCardIdDirectly(originCardLocalId);
+ final FullBoard originalBoard = dataBaseAdapter.getFullBoardByLocalCardIdDirectly(originCardLocalId);
// ### maybe shortcut possible? (just moved to another stack)
if (targetBoardLocalId == originalBoard.getLocalId()) {
reorder(originAccountId, originalCard, targetStackLocalId, newIndex);
return null;
}
// ### get rid of original card where it is now.
- Card originalInnerCard = originalCard.getCard();
- deleteCard(originalInnerCard);
+ final Card originalInnerCard = originalCard.getCard();
+ deleteCard(new Card(originalInnerCard));
// ### clone card itself
- Card targetCard = originalInnerCard;
- targetCard.setAccountId(targetAccountId);
- targetCard.setId(null);
- targetCard.setLocalId(null);
- targetCard.setStatusEnum(DBStatus.LOCAL_EDITED);
- targetCard.setStackId(targetStackLocalId);
- targetCard.setOrder(newIndex);
- //TODO: this needs to propagate to server as well, since anything else propagates as well (otherwise card isn't known on server)
- FullCard fullCardForServerPropagation = new FullCard();
- fullCardForServerPropagation.setCard(targetCard);
-
- Account targetAccount = dataBaseAdapter.getAccountByIdDirectly(targetAccountId);
- FullBoard targetBoard = dataBaseAdapter.getFullBoardByLocalIdDirectly(targetAccountId, targetBoardLocalId);
- FullStack targetFullStack = dataBaseAdapter.getFullStackByLocalIdDirectly(targetStackLocalId);
- User userOfTargetAccount = dataBaseAdapter.getUserByUidDirectly(targetAccountId, targetAccount.getUserName());
- CountDownLatch latch = new CountDownLatch(1);
- new DataPropagationHelper(serverAdapter, dataBaseAdapter).createEntity(new CardPropagationDataProvider(null, targetBoard.getBoard(), targetFullStack), fullCardForServerPropagation, new IResponseCallback<FullCard>(targetAccount) {
+ originalInnerCard.setAccountId(targetAccountId);
+ originalInnerCard.setId(null);
+ originalInnerCard.setLocalId(null);
+ originalInnerCard.setStatusEnum(DBStatus.LOCAL_EDITED);
+ originalInnerCard.setStackId(targetStackLocalId);
+ originalInnerCard.setOrder(newIndex);
+ originalInnerCard.setArchived(false);
+ originalInnerCard.setAttachmentCount(0);
+ originalInnerCard.setCommentsUnread(0);
+ final FullCard fullCardForServerPropagation = new FullCard();
+ fullCardForServerPropagation.setCard(originalInnerCard);
+
+ final Account targetAccount = dataBaseAdapter.getAccountByIdDirectly(targetAccountId);
+ final FullBoard targetBoard = dataBaseAdapter.getFullBoardByLocalIdDirectly(targetAccountId, targetBoardLocalId);
+ final FullStack targetFullStack = dataBaseAdapter.getFullStackByLocalIdDirectly(targetStackLocalId);
+ final User userOfTargetAccount = dataBaseAdapter.getUserByUidDirectly(targetAccountId, targetAccount.getUserName());
+ final CountDownLatch latch = new CountDownLatch(1);
+ ServerAdapter serverToUse = serverAdapter;
+ if (originAccountId != targetAccountId) {
+ serverToUse = new ServerAdapter(appContext, targetAccount.getName());
+ }
+ new DataPropagationHelper(serverToUse, dataBaseAdapter).createEntity(new CardPropagationDataProvider(null, targetBoard.getBoard(), targetFullStack), fullCardForServerPropagation, new IResponseCallback<FullCard>(targetAccount) {
@Override
public void onResponse(FullCard response) {
- targetCard.setId(response.getId());
- targetCard.setLocalId(response.getLocalId());
+ originalInnerCard.setId(response.getId());
+ originalInnerCard.setLocalId(response.getLocalId());
latch.countDown();
}
@@ -1134,8 +1362,10 @@ public class SyncManager {
throw new RuntimeException("unable to create card in moveCard target", throwable);
}
}, (FullCard entity, FullCard response) -> {
- response.getCard().setUserId(entity.getCard().getUserId());
+ response.getCard().setUserId(userOfTargetAccount.getLocalId());
response.getCard().setStackId(targetFullStack.getLocalId());
+ entity.getCard().setUserId(userOfTargetAccount.getLocalId());
+ entity.getCard().setStackId(targetFullStack.getLocalId());
});
try {
@@ -1145,7 +1375,7 @@ public class SyncManager {
throw new RuntimeException("error fulfilling countDownLatch", e);
}
- long newCardId = targetCard.getLocalId();
+ long newCardId = originalInnerCard.getLocalId();
// ### clone labels, assign them
// prepare
@@ -1154,7 +1384,7 @@ public class SyncManager {
List<AccessControl> aclOfTargetBoard = dataBaseAdapter.getAccessControlByLocalBoardIdDirectly(targetAccountId, targetBoard.getLocalId());
if (!hasManagePermission) {
for (AccessControl accessControl : aclOfTargetBoard) {
- if (accessControl.getUserId() == userOfTargetAccount.getLocalId() && accessControl.isPermissionManage()) {
+ if (accessControl.getUserId().equals(userOfTargetAccount.getLocalId()) && accessControl.isPermissionManage()) {
hasManagePermission = true;
break;
}
@@ -1178,10 +1408,10 @@ public class SyncManager {
originalLabel.setLocalId(null);
originalLabel.setStatusEnum(DBStatus.LOCAL_EDITED);
originalLabel.setAccountId(targetBoard.getAccountId());
- createAndAssignLabelToCard(originalBoard.getAccountId(), originalLabel, newCardId);
+ createAndAssignLabelToCard(targetBoard.getAccountId(), originalLabel, newCardId, serverToUse);
}
} else {
- assignLabelToCard(existingMatch, targetCard);
+ assignLabelToCard(existingMatch, originalInnerCard, serverToUse);
}
}
@@ -1194,7 +1424,7 @@ public class SyncManager {
boolean hasViewPermission = targetBoard.getBoard().getOwnerId() == assignedUser.getLocalId();
if (!hasViewPermission) {
for (AccessControl accessControl : aclOfTargetBoard) {
- if (accessControl.getUserId() == userOfTargetAccount.getLocalId()) {
+ if (accessControl.getUserId().equals(userOfTargetAccount.getLocalId())) {
// ACL exists, so viewing is granted
hasViewPermission = true;
break;
@@ -1202,7 +1432,7 @@ public class SyncManager {
}
}
if (hasViewPermission) {
- assignUserToCard(assignedUser, targetCard);
+ assignUserToCard(assignedUser, originalInnerCard);
}
}
}
@@ -1229,25 +1459,28 @@ public class SyncManager {
liveData.postValue(response);
}
+ @SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
liveData.postError(throwable);
}
- }, (entity, response) -> {
- response.setBoardId(board.getLocalId());
- });
+ }, (entity, response) -> response.setBoardId(board.getLocalId()));
});
return liveData;
}
- @AnyThread
public MutableLiveData<Label> createAndAssignLabelToCard(long accountId, @NonNull Label label, long localCardId) {
+ return createAndAssignLabelToCard(accountId, label, localCardId, serverAdapter);
+ }
+
+ @AnyThread
+ private MutableLiveData<Label> createAndAssignLabelToCard(long accountId, @NonNull Label label, long localCardId, ServerAdapter serverAdapterToUse) {
MutableLiveData<Label> liveData = new MutableLiveData<>();
doAsync(() -> {
Account account = dataBaseAdapter.getAccountByIdDirectly(accountId);
Board board = dataBaseAdapter.getBoardByLocalCardIdDirectly(localCardId);
label.setAccountId(accountId);
- new DataPropagationHelper(serverAdapter, dataBaseAdapter).createEntity(new LabelDataProvider(null, board, null), label, new IResponseCallback<Label>(account) {
+ new DataPropagationHelper(serverAdapterToUse, dataBaseAdapter).createEntity(new LabelDataProvider(null, board, null), label, new IResponseCallback<Label>(account) {
@Override
public void onResponse(Label response) {
assignLabelToCard(response, dataBaseAdapter.getCardByLocalIdDirectly(accountId, localCardId));
@@ -1317,6 +1550,11 @@ public class SyncManager {
@AnyThread
public void assignLabelToCard(@NonNull Label label, @NonNull Card card) {
+ assignLabelToCard(label, card, serverAdapter);
+ }
+
+ @AnyThread
+ public void assignLabelToCard(@NonNull Label label, @NonNull Card card, ServerAdapter serverAdapterToUse) {
doAsync(() -> {
final long localLabelId = label.getLocalId();
final long localCardId = card.getLocalId();
@@ -1327,8 +1565,8 @@ public class SyncManager {
Stack stack = dataBaseAdapter.getStackByLocalIdDirectly(card.getStackId());
Board board = dataBaseAdapter.getBoardByLocalIdDirectly(stack.getBoardId());
Account account = dataBaseAdapter.getAccountByIdDirectly(card.getAccountId());
- if (serverAdapter.hasInternetConnection()) {
- serverAdapter.assignLabelToCard(board.getId(), stack.getId(), card.getId(), label.getId(), new IResponseCallback<Void>(account) {
+ if (serverAdapterToUse.hasInternetConnection()) {
+ serverAdapterToUse.assignLabelToCard(board.getId(), stack.getId(), card.getId(), label.getId(), new IResponseCallback<Void>(account) {
@Override
public void onResponse(Void response) {
@@ -1395,11 +1633,6 @@ public class SyncManager {
return findProposalsForLabelsToAssign(accountId, boardId, -1L);
}
- // TODO Difference to getFullBoardByid() ??? I think those methods are equal, we should drop one of them.
- public LiveData<FullBoard> getFullBoard(Long accountId, Long localId) {
- return dataBaseAdapter.getFullBoardById(accountId, localId);
- }
-
public LiveData<User> getUserByLocalId(long accountId, long localId) {
return dataBaseAdapter.getUserByLocalId(accountId, localId);
}
@@ -1417,12 +1650,12 @@ public class SyncManager {
return dataBaseAdapter.searchUserByUidOrDisplayName(accountId, boardId, notYetAssignedToLocalCardId, searchTerm);
}
- public LiveData<List<User>> searchUserByUidOrDisplayNameForACL(final long accountId, final long notYetAssignedInACL, final String searchTerm) {
- return dataBaseAdapter.searchUserByUidOrDisplayNameForACL(accountId, notYetAssignedInACL, searchTerm);
+ public UserSearchLiveData searchUserByUidOrDisplayNameForACL() {
+ return new UserSearchLiveData(dataBaseAdapter, serverAdapter);
}
- public LiveData<Board> getBoard(long accountId, long remoteId) {
- return dataBaseAdapter.getBoard(accountId, remoteId);
+ public LiveData<Board> getBoardByRemoteId(long accountId, long remoteId) {
+ return dataBaseAdapter.getBoardByRemoteId(accountId, remoteId);
}
public LiveData<Stack> getStackByRemoteId(long accountId, long localBoardId, long remoteId) {
@@ -1445,10 +1678,6 @@ public class SyncManager {
return dataBaseAdapter.searchNotYetAssignedLabelsByTitle(accountId, boardId, notYetAssignedToLocalCardId, searchTerm);
}
- public String getServerUrl() {
- return serverAdapter.getServerUrl();
- }
-
/**
* @see <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/360">reenable reorder</a>
*/
@@ -1456,7 +1685,7 @@ public class SyncManager {
public void reorder(long accountId, @NonNull FullCard movedCard, long newStackId, int newIndex) {
doAsync(() -> {
// read cards of new stack
- List<FullCard> cardsOfNewStack = dataBaseAdapter.getFullCardsForStackDirectly(accountId, newStackId);
+ List<FullCard> cardsOfNewStack = dataBaseAdapter.getFullCardsForStackDirectly(accountId, newStackId, null);
int newOrder = newIndex;
if (cardsOfNewStack.size() > newIndex) {
newOrder = cardsOfNewStack.get(newIndex).getCard().getOrder();
@@ -1526,7 +1755,7 @@ public class SyncManager {
Stack stack = dataBaseAdapter.getStackByLocalIdDirectly(movedCard.getCard().getStackId());
FullBoard board = dataBaseAdapter.getFullBoardByLocalIdDirectly(accountId, stack.getBoardId());
Account account = dataBaseAdapter.getAccountByIdDirectly(movedCard.getCard().getAccountId());
- new SyncHelper(serverAdapter, dataBaseAdapter, new Date()).setResponseCallback(new IResponseCallback<Boolean>(account) {
+ new SyncHelper(serverAdapter, dataBaseAdapter, Instant.now()).setResponseCallback(new IResponseCallback<Boolean>(account) {
@Override
public void onResponse(Boolean response) {
// doNothing();
@@ -1597,7 +1826,7 @@ public class SyncManager {
}
private void reorderAscending(@NonNull Card movedCard, @NonNull List<Card> cardsToReorganize, int startingAtOrder) {
- Date now = new Date();
+ final Instant now = Instant.now();
for (Card card : cardsToReorganize) {
card.setOrder(startingAtOrder);
if (card.getStatus() == DBStatus.UP_TO_DATE.getId()) {
@@ -1625,7 +1854,7 @@ public class SyncManager {
WrappedLiveData<Attachment> liveData = new WrappedLiveData<>();
doAsync(() -> {
Attachment attachment = populateAttachmentEntityForFile(new Attachment(), localCardId, mimeType, file);
- Date now = new Date();
+ final Instant now = Instant.now();
attachment.setLastModifiedLocal(now);
attachment.setCreatedAt(now);
FullCard card = dataBaseAdapter.getFullCardByLocalIdDirectly(accountId, localCardId);
@@ -1645,7 +1874,7 @@ public class SyncManager {
WrappedLiveData<Attachment> liveData = new WrappedLiveData<>();
doAsync(() -> {
Attachment attachment = populateAttachmentEntityForFile(existing, existing.getCardId(), mimeType, file);
- attachment.setLastModifiedLocal(new Date());
+ attachment.setLastModifiedLocal(Instant.now());
if (serverAdapter.hasInternetConnection()) {
FullCard card = dataBaseAdapter.getFullCardByLocalIdDirectly(accountId, existing.getCardId());
Stack stack = dataBaseAdapter.getStackByLocalIdDirectly(card.getCard().getStackId());
@@ -1658,6 +1887,7 @@ public class SyncManager {
liveData.postValue(response);
}
+ @SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
liveData.postError(throwable);
@@ -1670,15 +1900,14 @@ public class SyncManager {
@AnyThread
private static Attachment populateAttachmentEntityForFile(@NonNull Attachment target, long localCardId, @NonNull String mimeType, @NonNull File file) {
- Attachment attachment = target;
- attachment.setCardId(localCardId);
- attachment.setMimetype(mimeType);
- attachment.setData(file.getName());
- attachment.setFilename(file.getName());
- attachment.setBasename(file.getName());
- attachment.setLocalPath(file.getAbsolutePath());
- attachment.setFilesize(file.length());
- return attachment;
+ target.setCardId(localCardId);
+ target.setMimetype(mimeType);
+ target.setData(file.getName());
+ target.setFilename(file.getName());
+ target.setBasename(file.getName());
+ target.setLocalPath(file.getAbsolutePath());
+ target.setFilesize(file.length());
+ return target;
}
@AnyThread
@@ -1726,7 +1955,27 @@ public class SyncManager {
doAsync(() -> dataBaseAdapter.deleteSingleCardWidget(widgetId));
}
- private static class BooleanResultHolder {
- public boolean result = true;
+ public void addStackWidget(int appWidgetId, long accountId, long stackId, boolean darkTheme) {
+ doAsync(() -> dataBaseAdapter.createStackWidget(appWidgetId, accountId, stackId, darkTheme));
+ }
+
+ @WorkerThread
+ public StackWidgetModel getStackWidgetModelDirectly(int appWidgetId) throws NoSuchElementException {
+ final StackWidgetModel model = dataBaseAdapter.getStackWidgetModelDirectly(appWidgetId);
+ if (model == null) {
+ throw new NoSuchElementException();
+ }
+ return model;
+ }
+
+ public void deleteStackWidgetModel(int appWidgetId) {
+ doAsync(() -> dataBaseAdapter.deleteStackWidget(appWidgetId));
+ }
+
+ /**
+ * FIXME https://github.com/stefan-niedermann/nextcloud-deck/issues/640
+ */
+ public static boolean ignoreExceptionOnVoidError(Throwable t) {
+ return t instanceof NullPointerException && "Attempt to invoke interface method 'void io.reactivex.disposables.Disposable.dispose()' on a null object reference".equals(t.getMessage());
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/ServerAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/ServerAdapter.java
index 4f41173e9..bff8ddfdb 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/ServerAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/ServerAdapter.java
@@ -13,18 +13,14 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
+import com.nextcloud.android.sso.api.ParsedResponse;
+
import java.io.File;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
import java.util.List;
-import java.util.Locale;
-import java.util.TimeZone;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.api.ApiProvider;
import it.niedermann.nextcloud.deck.api.IResponseCallback;
-import it.niedermann.nextcloud.deck.api.LastSyncUtil;
import it.niedermann.nextcloud.deck.api.RequestHelper;
import it.niedermann.nextcloud.deck.exceptions.OfflineException;
import it.niedermann.nextcloud.deck.model.AccessControl;
@@ -39,11 +35,12 @@ import it.niedermann.nextcloud.deck.model.full.FullStack;
import it.niedermann.nextcloud.deck.model.ocs.Capabilities;
import it.niedermann.nextcloud.deck.model.ocs.comment.DeckComment;
import it.niedermann.nextcloud.deck.model.ocs.comment.OcsComment;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProjectList;
+import it.niedermann.nextcloud.deck.model.ocs.user.GroupMemberUIDs;
import it.niedermann.nextcloud.deck.model.ocs.user.OcsUser;
import it.niedermann.nextcloud.deck.model.ocs.user.OcsUserList;
import it.niedermann.nextcloud.deck.model.propagation.CardUpdate;
import it.niedermann.nextcloud.deck.model.propagation.Reorder;
-import it.niedermann.nextcloud.deck.util.DateUtil;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
@@ -53,18 +50,11 @@ import static it.niedermann.nextcloud.deck.util.MimeTypeUtil.TEXT_PLAIN;
public class ServerAdapter {
- private String prefKeyWifiOnly;
-
- private static final DateFormat API_FORMAT =
- new SimpleDateFormat("E, dd MMM yyyy hh:mm:ss z", Locale.US);
-
- static {
- API_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));
- }
+ private final String prefKeyWifiOnly;
@NonNull
- private Context applicationContext;
- private ApiProvider provider;
+ private final Context applicationContext;
+ private final ApiProvider provider;
public ServerAdapter(@NonNull Context applicationContext) {
this(applicationContext, null);
@@ -135,32 +125,35 @@ public class ServerAdapter {
// return lastSyncHeader;
}
- // TODO not used
- private Date getLastSync(long accountId) {
- Date lastSync = DateUtil.nowInGMT();
- lastSync.setTime(LastSyncUtil.getLastSync(accountId));
+ public void getBoards(IResponseCallback<ParsedResponse<List<FullBoard>>> responseCallback) {
+ RequestHelper.request(provider, () ->
+ provider.getDeckAPI().getBoards(true, getLastSyncDateFormatted(responseCallback.getAccount().getId()), responseCallback.getAccount().getBoardsEtag()),
+ responseCallback);
+ }
- return lastSync;
+ public void getCapabilities(String eTag, IResponseCallback<ParsedResponse<Capabilities>> responseCallback) {
+ ensureInternetConnection();
+ RequestHelper.request(provider, () -> provider.getNextcloudAPI().getCapabilities(eTag), responseCallback);
}
- public void getBoards(IResponseCallback<List<FullBoard>> responseCallback) {
- RequestHelper.request(provider, () ->
- provider.getDeckAPI().getBoards(true, getLastSyncDateFormatted(responseCallback.getAccount().getId())),
- responseCallback);
+ public void getProjectsForCard(long remoteCardId, IResponseCallback<OcsProjectList> responseCallback) {
+ ensureInternetConnection();
+ RequestHelper.request(provider, () -> provider.getNextcloudAPI().getProjectsForCard(remoteCardId), responseCallback);
}
- public void getCapabilities(IResponseCallback<Capabilities> responseCallback) {
+ public void searchUser(String searchTerm, IResponseCallback<OcsUserList> responseCallback) {
ensureInternetConnection();
- RequestHelper.request(provider, () -> provider.getNextcloudAPI().getCapabilities(), responseCallback);
+ RequestHelper.request(provider, () -> provider.getNextcloudAPI().searchUser(searchTerm), responseCallback);
}
- public void getAllOcsUsers(IResponseCallback<OcsUserList> responseCallback) {
+
+ public void getSingleUserData(String userUid, IResponseCallback<OcsUser> responseCallback) {
ensureInternetConnection();
- RequestHelper.request(provider, () -> provider.getNextcloudAPI().getAllUsers(), responseCallback);
+ RequestHelper.request(provider, () -> provider.getNextcloudAPI().getSingleUserData(userUid), responseCallback);
}
- public void getOcsUserDetails(String ocsUserName, IResponseCallback<OcsUser> responseCallback) {
+ public void searchGroupMembers(String groupUID, IResponseCallback<GroupMemberUIDs> responseCallback) {
ensureInternetConnection();
- RequestHelper.request(provider, () -> provider.getNextcloudAPI().getUserDetails(ocsUserName), responseCallback);
+ RequestHelper.request(provider, () -> provider.getNextcloudAPI().searchGroupMembers(groupUID), responseCallback);
}
public void getActivitiesForCard(long cardId, IResponseCallback<List<it.niedermann.nextcloud.deck.model.ocs.Activity>> responseCallback) {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java
index c643289bd..982844f06 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java
@@ -2,16 +2,15 @@ package it.niedermann.nextcloud.deck.persistence.sync.adapters.db;
import android.content.Context;
+import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.lifecycle.LiveData;
import androidx.sqlite.db.SimpleSQLiteQuery;
-import org.jetbrains.annotations.NotNull;
-
+import java.time.Instant;
import java.util.ArrayList;
-import java.util.Date;
import java.util.List;
import it.niedermann.nextcloud.deck.DeckLog;
@@ -26,10 +25,12 @@ import it.niedermann.nextcloud.deck.model.JoinCardWithUser;
import it.niedermann.nextcloud.deck.model.Label;
import it.niedermann.nextcloud.deck.model.Stack;
import it.niedermann.nextcloud.deck.model.User;
+import it.niedermann.nextcloud.deck.model.appwidgets.StackWidgetModel;
import it.niedermann.nextcloud.deck.model.enums.DBStatus;
import it.niedermann.nextcloud.deck.model.enums.EDueType;
import it.niedermann.nextcloud.deck.model.full.FullBoard;
import it.niedermann.nextcloud.deck.model.full.FullCard;
+import it.niedermann.nextcloud.deck.model.full.FullCardWithProjects;
import it.niedermann.nextcloud.deck.model.full.FullSingleCardWidgetModel;
import it.niedermann.nextcloud.deck.model.full.FullStack;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractRemoteEntity;
@@ -39,10 +40,16 @@ import it.niedermann.nextcloud.deck.model.ocs.Activity;
import it.niedermann.nextcloud.deck.model.ocs.comment.DeckComment;
import it.niedermann.nextcloud.deck.model.ocs.comment.Mention;
import it.niedermann.nextcloud.deck.model.ocs.comment.full.FullDeckComment;
+import it.niedermann.nextcloud.deck.model.ocs.projects.JoinCardWithProject;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProject;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProjectResource;
+import it.niedermann.nextcloud.deck.model.relations.UserInBoard;
+import it.niedermann.nextcloud.deck.model.relations.UserInGroup;
import it.niedermann.nextcloud.deck.model.widget.singlecard.SingleCardWidgetModel;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData;
import it.niedermann.nextcloud.deck.ui.widget.singlecard.SingleCardWidget;
+import it.niedermann.nextcloud.deck.ui.widget.stack.StackWidget;
import static androidx.lifecycle.Transformations.distinctUntilChanged;
@@ -57,28 +64,28 @@ public class DataBaseAdapter {
this.db = DeckDatabase.getInstance(applicationContext);
}
- @NotNull
+ @NonNull
public Context getContext() {
return context;
}
private <T extends AbstractRemoteEntity> void markAsEditedIfNeeded(T entity, boolean setStatus) {
if (!setStatus) return;
- entity.setLastModifiedLocal(new Date()); // now.
+ entity.setLastModifiedLocal(Instant.now());
entity.setStatusEnum(DBStatus.LOCAL_EDITED);
}
private <T extends AbstractRemoteEntity> void markAsDeletedIfNeeded(T entity, boolean setStatus) {
if (!setStatus) return;
entity.setStatusEnum(DBStatus.LOCAL_DELETED);
- entity.setLastModifiedLocal(new Date()); // now.
+ entity.setLastModifiedLocal(Instant.now());
}
public LiveData<Boolean> hasAccounts() {
return LiveDataHelper.postCustomValue(db.getAccountDao().countAccounts(), data -> data != null && data > 0);
}
- public LiveData<Board> getBoard(long accountId, long remoteId) {
+ public LiveData<Board> getBoardByRemoteId(long accountId, long remoteId) {
return distinctUntilChanged(db.getBoardDao().getBoardByRemoteId(accountId, remoteId));
}
@@ -180,24 +187,66 @@ public class DataBaseAdapter {
if (filter == null) {
return LiveDataHelper.interceptLiveData(db.getCardDao().getFullCardsForStack(accountId, localStackId), this::filterRelationsForCard);
}
+ return LiveDataHelper.interceptLiveData(db.getCardDao().getFilteredFullCardsForStack(getQueryForFilter(filter, accountId, localStackId)), this::filterRelationsForCard);
+
+ }
+
+ private void fillSqlWithListValues(StringBuilder query, List<Object> args, @NonNull List<? extends IRemoteEntity> entities) {
+ for (int i = 0; i < entities.size(); i++) {
+ if (i > 0) {
+ query.append(", ");
+ }
+ query.append("?");
+ args.add(entities.get(i).getLocalId());
+ }
+ }
+
+ @WorkerThread
+ public List<FullCard> getFullCardsForStackDirectly(long accountId, long localStackId, FilterInformation filter) {
+ if (filter == null) {
+ return db.getCardDao().getFullCardsForStackDirectly(accountId, localStackId);
+ }
+ List<Object> args = new ArrayList<>();
+ args.add(accountId);
+ args.add(localStackId);
+
+ return db.getCardDao().getFilteredFullCardsForStackDirectly(getQueryForFilter(filter, accountId, localStackId));
+ }
+ @AnyThread
+ private SimpleSQLiteQuery getQueryForFilter(FilterInformation filter, long accountId, long localStackId) {
List<Object> args = new ArrayList<>();
- StringBuilder query = new StringBuilder("SELECT * FROM card c " +
- "WHERE accountId = ? AND stackId = ? ");
args.add(accountId);
args.add(localStackId);
+ StringBuilder query = new StringBuilder("SELECT * FROM card c " +
+ "WHERE accountId = ? AND stackId = ? ");
if (!filter.getLabels().isEmpty()) {
- query.append("and exists(select 1 from joincardwithlabel j where c.localId = cardId and labelId in (");
+ query.append("and (exists(select 1 from joincardwithlabel j where c.localId = cardId and labelId in (");
fillSqlWithListValues(query, args, filter.getLabels());
query.append(") and j.status<>3) ");
+ if (filter.isNoAssignedLabel()) {
+ query.append("or not exists(select 1 from joincardwithlabel j where c.localId = cardId and j.status<>3)) ");
+ } else {
+ query.append(") ");
+ }
+ } else if (filter.isNoAssignedLabel()) {
+ query.append("and not exists(select 1 from joincardwithlabel j where c.localId = cardId and j.status<>3) ");
}
if (!filter.getUsers().isEmpty()) {
- query.append("and exists(select 1 from joincardwithuser j where c.localId = cardId and userId in (");
+ query.append("and (exists(select 1 from joincardwithuser j where c.localId = cardId and userId in (");
fillSqlWithListValues(query, args, filter.getUsers());
query.append(") and j.status<>3) ");
+ if (filter.isNoAssignedUser()) {
+ query.append("or not exists(select 1 from joincardwithuser j where c.localId = cardId and j.status<>3)) ");
+ } else {
+ query.append(") ");
+ }
+ } else if (filter.isNoAssignedUser()) {
+ query.append("and not exists(select 1 from joincardwithuser j where c.localId = cardId and j.status<>3) ");
}
+
if (filter.getDueType() != EDueType.NO_FILTER) {
switch (filter.getDueType()) {
case NO_DUE:
@@ -219,24 +268,11 @@ public class DataBaseAdapter {
throw new IllegalArgumentException("Xou need to add your new EDueType value\"" + filter.getDueType() + "\" here!");
}
}
- query.append(" and status<>3 order by `order`, createdAt asc;");
- return LiveDataHelper.interceptLiveData(db.getCardDao().getFilteredFullCardsForStack(new SimpleSQLiteQuery(query.toString(), args.toArray())), this::filterRelationsForCard);
-
- }
-
- private void fillSqlWithListValues(StringBuilder query, List<Object> args, @NonNull List<? extends IRemoteEntity> entities) {
- for (int i = 0; i < entities.size(); i++) {
- if (i > 0) {
- query.append(", ");
- }
- query.append("?");
- args.add(entities.get(i).getLocalId());
+ if (filter.getArchiveStatus() != FilterInformation.EArchiveStatus.ALL) {
+ query.append(" and c.archived = " + (filter.getArchiveStatus() == FilterInformation.EArchiveStatus.ARCHIVED ? 1 : 0));
}
- }
-
- @WorkerThread
- public List<FullCard> getFullCardsForStackDirectly(long accountId, long localStackId) {
- return db.getCardDao().getFullCardsForStackDirectly(accountId, localStackId);
+ query.append(" and status<>3 order by `order`, createdAt asc;");
+ return new SimpleSQLiteQuery(query.toString(), args.toArray());
}
@WorkerThread
@@ -244,17 +280,20 @@ public class DataBaseAdapter {
return db.getUserDao().getUserByUidDirectly(accountId, uid);
}
+ @WorkerThread
public long createUser(long accountId, User user) {
user.setAccountId(accountId);
return db.getUserDao().insert(user);
}
+ @WorkerThread
public void updateUser(long accountId, User user, boolean setStatus) {
markAsEditedIfNeeded(user, setStatus);
user.setAccountId(accountId);
db.getUserDao().update(user);
}
+ @AnyThread
public LiveData<Label> getLabelByRemoteId(long accountId, long remoteId) {
return distinctUntilChanged(db.getLabelDao().getLabelByRemoteId(accountId, remoteId));
}
@@ -264,7 +303,8 @@ public class DataBaseAdapter {
return db.getLabelDao().getLabelByRemoteIdDirectly(accountId, remoteId);
}
- public long createLabel(long accountId, @NonNull Label label) {
+ @WorkerThread
+ public long createLabelDirectly(long accountId, @NonNull Label label) {
label.setAccountId(accountId);
return db.getLabelDao().insert(label);
}
@@ -320,6 +360,8 @@ public class DataBaseAdapter {
// readded!
existing.setStatusEnum(DBStatus.LOCAL_EDITED);
db.getJoinCardWithUserDao().update(existing);
+ } else if (existing != null) {
+ return;
} else {
JoinCardWithUser join = new JoinCardWithUser();
join.setCardId(localCardId);
@@ -344,6 +386,28 @@ public class DataBaseAdapter {
db.getJoinBoardWithLabelDao().deleteByBoardId(localBoardId);
}
+ public void deleteGroupMembershipsOfGroup(Long localGroupUserId) {
+ db.getUserInGroupDao().deleteByGroupId(localGroupUserId);
+ }
+
+ public void deleteBoardMembershipsOfBoard(Long localBoardId) {
+ db.getUserInBoardDao().deleteByBoardId(localBoardId);
+ }
+
+ public void addUserToGroup(Long localGroupUserId, Long localGroupMemberId) {
+ UserInGroup relation = new UserInGroup();
+ relation.setGroupId(localGroupUserId);
+ relation.setMemberId(localGroupMemberId);
+ db.getUserInGroupDao().insert(relation);
+ }
+
+ public void addUserToBoard(Long localUserId, Long localBoardId) {
+ UserInBoard relation = new UserInBoard();
+ relation.setBoardId(localBoardId);
+ relation.setUserId(localUserId);
+ db.getUserInBoardDao().insert(relation);
+ }
+
public void updateLabel(Label label, boolean setStatus) {
markAsEditedIfNeeded(label, setStatus);
db.getLabelDao().update(label);
@@ -391,6 +455,10 @@ public class DataBaseAdapter {
return distinctUntilChanged(db.getAccountDao().getAllAccounts());
}
+ public LiveData<List<Board>> getBoards(long accountId) {
+ return distinctUntilChanged(db.getBoardDao().getBoardsForAccount(accountId));
+ }
+
public LiveData<List<Board>> getBoards(long accountId, boolean archived) {
return distinctUntilChanged(
archived
@@ -431,8 +499,8 @@ public class DataBaseAdapter {
db.getBoardDao().update(board);
}
- public LiveData<List<FullStack>> getFullStacksForBoard(long accountId, long localBoardId) {
- return distinctUntilChanged(db.getStackDao().getFullStacksForBoard(accountId, localBoardId));
+ public LiveData<List<Stack>> getStacksForBoard(long accountId, long localBoardId) {
+ return distinctUntilChanged(db.getStackDao().getStacksForBoard(accountId, localBoardId));
}
@WorkerThread
@@ -440,27 +508,36 @@ public class DataBaseAdapter {
return db.getStackDao().getFullStacksForBoardDirectly(accountId, localBoardId);
}
+ @AnyThread
public LiveData<FullStack> getStack(long accountId, long localStackId) {
return distinctUntilChanged(db.getStackDao().getFullStack(accountId, localStackId));
}
+ @WorkerThread
public long createStack(long accountId, Stack stack) {
stack.setAccountId(accountId);
return db.getStackDao().insert(stack);
}
+ @WorkerThread
public void deleteStack(Stack stack, boolean setStatus) {
markAsDeletedIfNeeded(stack, setStatus);
db.getStackDao().update(stack);
}
+ @WorkerThread
public void deleteStackPhysically(Stack stack) {
db.getStackDao().delete(stack);
}
+ @WorkerThread
public void updateStack(Stack stack, boolean setStatus) {
markAsEditedIfNeeded(stack, setStatus);
db.getStackDao().update(stack);
+ if (db.getStackWidgetModelDao().containsStackLocalId(stack.getLocalId())) {
+ DeckLog.info("Notifying " + StackWidget.class.getSimpleName() + " about card changes for \"" + stack.getTitle() + "\"");
+ StackWidget.notifyDatasetChanged(context);
+ }
}
@WorkerThread
@@ -468,10 +545,16 @@ public class DataBaseAdapter {
return db.getCardDao().getCardByLocalIdDirectly(accountId, localCardId);
}
+ @AnyThread
public LiveData<FullCard> getCardByLocalId(long accountId, long localCardId) {
return LiveDataHelper.interceptLiveData(db.getCardDao().getFullCardByLocalId(accountId, localCardId), this::filterRelationsForCard);
}
+ @AnyThread
+ public LiveData<FullCardWithProjects> getCardWithProjectsByLocalId(long accountId, long localCardId) {
+ return LiveDataHelper.interceptLiveData(db.getCardDao().getFullCardWithProjectsByLocalId(accountId, localCardId), this::filterRelationsForCard);
+ }
+
@WorkerThread
public List<FullCard> getLocallyChangedCardsDirectly(long accountId) {
return db.getCardDao().getLocallyChangedCardsDirectly(accountId);
@@ -482,15 +565,27 @@ public class DataBaseAdapter {
return db.getCardDao().getLocallyChangedCardsByLocalStackIdDirectly(accountId, localStackId);
}
- public long createCard(long accountId, Card card) {
+ @WorkerThread
+ public long createCardDirectly(long accountId, Card card) {
card.setAccountId(accountId);
- return db.getCardDao().insert(card);
+ long newCardId = db.getCardDao().insert(card);
+
+ notifyStackWidgetsIfNeeded(card.getTitle(), card.getStackId());
+
+ return newCardId;
}
- public int getHighestCardOrderInStack(long localStackId){
+ @WorkerThread
+ public int getHighestCardOrderInStack(long localStackId) {
return db.getCardDao().getHighestOrderInStack(localStackId);
}
+ @WorkerThread
+ public int getHighestStackOrderInBoard(long localBoardId) {
+ return db.getStackDao().getHighestStackOrderInBoard(localBoardId);
+ }
+
+ @WorkerThread
public void deleteCard(Card card, boolean setStatus) {
markAsDeletedIfNeeded(card, setStatus);
if (setStatus) {
@@ -498,21 +593,35 @@ public class DataBaseAdapter {
} else {
deleteCardPhysically(card);
}
+
+ notifyStackWidgetsIfNeeded(card.getTitle(), card.getStackId());
}
+ @WorkerThread
public void deleteCardPhysically(Card card) {
db.getCardDao().delete(card);
}
+ @WorkerThread
public void updateCard(@NonNull Card card, boolean setStatus) {
markAsEditedIfNeeded(card, setStatus);
+ Long originalStackLocalId = db.getCardDao().getLocalStackIdByLocalCardId(card.getLocalId());
db.getCardDao().update(card);
if (db.getSingleCardWidgetModelDao().containsCardLocalId(card.getLocalId())) {
- DeckLog.info("Notifying widget about card changes for \"" + card.getTitle() + "\"");
+ DeckLog.info("Notifying " + SingleCardWidget.class.getSimpleName() + " about card changes for \"" + card.getTitle() + "\"");
SingleCardWidget.notifyDatasetChanged(context);
}
+ notifyStackWidgetsIfNeeded(card.getTitle(), card.getStackId(), originalStackLocalId);
+ }
+
+ private void notifyStackWidgetsIfNeeded(String cardTitle, long... affectedStackIds) {
+ if (db.getStackWidgetModelDao().containsStackLocalId(affectedStackIds)) {
+ DeckLog.info("Notifying " + StackWidget.class.getSimpleName() + " about card changes for \"" + cardTitle + "\"");
+ StackWidget.notifyDatasetChanged(context);
+ }
}
+ @WorkerThread
public long createAccessControl(long accountId, @NonNull AccessControl entity) {
entity.setAccountId(accountId);
return db.getAccessControlDao().insert(entity);
@@ -571,9 +680,9 @@ public class DataBaseAdapter {
return db.getUserDao().searchUserByUidOrDisplayName(accountId, boardId, notYetAssignedToLocalCardId, "%" + searchTerm.trim() + "%");
}
- public LiveData<List<User>> searchUserByUidOrDisplayNameForACL(final long accountId, final long notYetAssignedToACL, final String searchTerm) {
+ public List<User> searchUserByUidOrDisplayNameForACLDirectly(final long accountId, final long notYetAssignedToACL, final String searchTerm) {
validateSearchTerm(searchTerm);
- return db.getUserDao().searchUserByUidOrDisplayNameForACL(accountId, notYetAssignedToACL, "%" + searchTerm.trim() + "%");
+ return db.getUserDao().searchUserByUidOrDisplayNameForACLDirectly(accountId, notYetAssignedToACL, "%" + searchTerm.trim() + "%");
}
public LiveData<List<Label>> searchNotYetAssignedLabelsByTitle(final long accountId, final long boardId, final long notYetAssignedToLocalCardId, String searchTerm) {
@@ -618,9 +727,14 @@ public class DataBaseAdapter {
return db.getAttachmentDao().getLocallyChangedAttachmentsDirectly(accountId);
}
+ @WorkerThread
+ public List<Attachment> getLocallyChangedAttachmentsForStackDirectly(long localStackId) {
+ return db.getAttachmentDao().getLocallyChangedAttachmentsForStackDirectly(localStackId);
+ }
+
public long createAttachment(long accountId, @NonNull Attachment attachment) {
attachment.setAccountId(accountId);
- attachment.setCreatedAt(new Date());
+ attachment.setCreatedAt(Instant.now());
return db.getAttachmentDao().insert(attachment);
}
@@ -720,16 +834,24 @@ public class DataBaseAdapter {
return db.getJoinCardWithLabelDao().getAllDeletedJoinsWithRemoteIDs();
}
- public List<JoinCardWithLabel> getAllChangedJoins() {
+ public List<JoinCardWithLabel> getAllChangedLabelJoins() {
return db.getJoinCardWithLabelDao().getAllChangedJoins();
}
- public JoinCardWithLabel getRemoteIdsForJoin(Long localCardId, Long localLabelId) {
+ public List<JoinCardWithLabel> getAllChangedLabelJoinsForStack(Long localStackId) {
+ return db.getJoinCardWithLabelDao().getAllChangedJoinsForStack(localStackId);
+ }
+
+ public JoinCardWithLabel getAllChangedLabelJoinsWithRemoteIDs(Long localCardId, Long localLabelId) {
return db.getJoinCardWithLabelDao().getRemoteIdsForJoin(localCardId, localLabelId);
}
- public List<JoinCardWithUser> getAllDeletedUserJoinsWithRemoteIDs() {
- return db.getJoinCardWithUserDao().getDeletedJoinsWithRemoteIDs();
+ public List<JoinCardWithUser> getAllChangedUserJoinsWithRemoteIDs() {
+ return db.getJoinCardWithUserDao().getChangedJoinsWithRemoteIDs();
+ }
+
+ public List<JoinCardWithUser> getAllChangedUserJoinsWithRemoteIDsForStack(Long localStackId) {
+ return db.getJoinCardWithUserDao().getChangedJoinsWithRemoteIDsForStack(localStackId);
}
public void deleteJoinedLabelForCardPhysicallyByRemoteIDs(Long accountId, Long remoteCardId, Long remoteLabelId) {
@@ -837,11 +959,17 @@ public class DataBaseAdapter {
return db.getCommentDao().getCommentByLocalCardIdDirectly(localCardId);
}
+ @WorkerThread
public List<Card> getCardsWithLocallyChangedCommentsDirectly(Long accountId) {
return db.getCardDao().getCardsWithLocallyChangedCommentsDirectly(accountId);
}
@WorkerThread
+ public List<Card> getCardsWithLocallyChangedCommentsForStackDirectly(Long localStackId) {
+ return db.getCardDao().getCardsWithLocallyChangedCommentsForStackDirectly(localStackId);
+ }
+
+ @WorkerThread
public Long getLocalStackIdByRemoteStackIdDirectly(long accountId, Long stackId) {
return db.getStackDao().getLocalStackIdByRemoteStackIdDirectly(accountId, stackId);
}
@@ -910,11 +1038,89 @@ public class DataBaseAdapter {
db.getSingleCardWidgetModelDao().delete(model);
}
+ public long createStackWidget(int appWidgetId, long accountId, long stackId, boolean darkTheme) {
+ StackWidgetModel model = new StackWidgetModel();
+ model.setAppWidgetId(appWidgetId);
+ model.setAccountId(accountId);
+ model.setStackId(stackId);
+ model.setDarkTheme(darkTheme);
+
+ return db.getStackWidgetModelDao().insert(model);
+ }
+
+ public StackWidgetModel getStackWidgetModelDirectly(int appWidgetId) {
+ return db.getStackWidgetModelDao().getStackWidgetByAppWidgetIdDirectly(appWidgetId);
+ }
+
+ public void deleteStackWidget(int appWidgetId) {
+ StackWidgetModel model = new StackWidgetModel();
+ model.setAppWidgetId(appWidgetId);
+ db.getStackWidgetModelDao().delete(model);
+ }
+
public LiveData<List<Account>> readAccountsForHostWithReadAccessToBoard(String host, long boardRemoteId) {
- return db.getAccountDao().readAccountsForHostWithReadAccessToBoard("%"+host+"%", boardRemoteId);
+ return db.getAccountDao().readAccountsForHostWithReadAccessToBoard("%" + host + "%", boardRemoteId);
}
public List<Account> readAccountsForHostWithReadAccessToBoardDirectly(String host, long boardRemoteId) {
- return db.getAccountDao().readAccountsForHostWithReadAccessToBoardDirectly("%"+host+"%", boardRemoteId);
+ return db.getAccountDao().readAccountsForHostWithReadAccessToBoardDirectly("%" + host + "%", boardRemoteId);
+ }
+
+ public Board getBoardForAccountByNameDirectly(long account, String title) {
+ return db.getBoardDao().getBoardForAccountByNameDirectly(account, title);
+ }
+
+ public OcsProject getProjectByRemoteIdDirectly(long accountId, Long remoteId) {
+ return db.getOcsProjectDao().getProjectByRemoteIdDirectly(accountId, remoteId);
+ }
+
+ public Long createProjectDirectly(long accountId, OcsProject entity) {
+ entity.setAccountId(accountId);
+ return db.getOcsProjectDao().insert(entity);
+ }
+
+ public void deleteProjectResourcesForProjectIdDirectly(Long localProjectId) {
+ db.getOcsProjectResourceDao().deleteByProjectId(localProjectId);
+ }
+
+ public void updateProjectDirectly(long accountId, OcsProject entity) {
+ entity.setAccountId(accountId);
+ db.getOcsProjectDao().update(entity);
+ }
+
+ public void deleteProjectDirectly(OcsProject ocsProject) {
+ db.getOcsProjectResourceDao().deleteByProjectId(ocsProject.getLocalId());
+ db.getOcsProjectDao().delete(ocsProject);
+ }
+
+ public Long createProjectResourceDirectly(Long accountId, OcsProjectResource resource) {
+ resource.setAccountId(accountId);
+ return db.getOcsProjectResourceDao().insert(resource);
+ }
+
+ public int countProjectResourcesInProjectDirectly(Long projectLocalId) {
+ return db.getOcsProjectResourceDao().countProjectResourcesInProjectDirectly(projectLocalId);
+ }
+
+ public LiveData<Integer> countProjectResourcesInProject(Long projectLocalId) {
+ return db.getOcsProjectResourceDao().countProjectResourcesInProject(projectLocalId);
+ }
+
+ public LiveData<List<OcsProjectResource>> getResourcesByLocalProjectId(Long projectLocalId) {
+ return db.getOcsProjectResourceDao().getResourcesByLocalProjectId(projectLocalId);
+ }
+
+ public void assignCardToProjectIfMissng(Long accountId, Long localProjectId, Long remoteCardId) {
+ Card card = db.getCardDao().getCardByRemoteIdDirectly(accountId, remoteCardId);
+ if (card != null) {
+ JoinCardWithProject existing = db.getJoinCardWithOcsProjectDao().getAssignmentByCardIdAndProjectIdDirectly(card.getLocalId(), localProjectId);
+ if (existing == null) {
+ JoinCardWithProject assignment = new JoinCardWithProject();
+ assignment.setStatus(DBStatus.UP_TO_DATE.getId());
+ assignment.setCardId(card.getLocalId());
+ assignment.setProjectId(localProjectId);
+ db.getJoinCardWithOcsProjectDao().insert(assignment);
+ }
+ }
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DateTypeConverter.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DateTypeConverter.java
index 6b198a502..f6811e033 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DateTypeConverter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DateTypeConverter.java
@@ -2,17 +2,17 @@ package it.niedermann.nextcloud.deck.persistence.sync.adapters.db;
import androidx.room.TypeConverter;
-import java.util.Date;
+import java.time.Instant;
public class DateTypeConverter {
@TypeConverter
- public static Date toDate(Long value) {
- return value == null ? null : new Date(value);
+ public static Instant toInstant(Long value) {
+ return value == null ? null : Instant.ofEpochMilli(value);
}
@TypeConverter
- public static Long toLong(Date value) {
- return value == null ? null : value.getTime();
+ public static Long fromInstant(Instant value) {
+ return value == null ? null : value.toEpochMilli();
}
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DeckDatabase.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DeckDatabase.java
index 0e86557fa..a491946ed 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DeckDatabase.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DeckDatabase.java
@@ -1,8 +1,11 @@
package it.niedermann.nextcloud.deck.persistence.sync.adapters.db;
import android.content.Context;
+import android.content.SharedPreferences;
import android.database.Cursor;
+import android.graphics.Color;
+import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
import androidx.room.Database;
@@ -12,6 +15,7 @@ import androidx.room.TypeConverters;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
+import it.niedermann.android.util.ColorUtil;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.api.LastSyncUtil;
import it.niedermann.nextcloud.deck.model.AccessControl;
@@ -28,10 +32,16 @@ import it.niedermann.nextcloud.deck.model.Label;
import it.niedermann.nextcloud.deck.model.Permission;
import it.niedermann.nextcloud.deck.model.Stack;
import it.niedermann.nextcloud.deck.model.User;
+import it.niedermann.nextcloud.deck.model.appwidgets.StackWidgetModel;
import it.niedermann.nextcloud.deck.model.enums.DBStatus;
import it.niedermann.nextcloud.deck.model.ocs.Activity;
import it.niedermann.nextcloud.deck.model.ocs.comment.DeckComment;
import it.niedermann.nextcloud.deck.model.ocs.comment.Mention;
+import it.niedermann.nextcloud.deck.model.ocs.projects.JoinCardWithProject;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProject;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProjectResource;
+import it.niedermann.nextcloud.deck.model.relations.UserInBoard;
+import it.niedermann.nextcloud.deck.model.relations.UserInGroup;
import it.niedermann.nextcloud.deck.model.widget.singlecard.SingleCardWidgetModel;
import it.niedermann.nextcloud.deck.persistence.sync.SyncWorker;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.AccessControlDao;
@@ -49,9 +59,15 @@ import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.JoinCardWit
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.LabelDao;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.MentionDao;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.PermissionDao;
-import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.SingleCardWidgetModelDao;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.StackDao;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.UserDao;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.UserInBoardDao;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.UserInGroupDao;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.projects.JoinCardWithOcsProjectDao;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.projects.OcsProjectDao;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.projects.OcsProjectResourceDao;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets.SingleCardWidgetModelDao;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets.StackWidgetModelDao;
@Database(
entities = {
@@ -73,9 +89,15 @@ import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.UserDao;
DeckComment.class,
Mention.class,
SingleCardWidgetModel.class,
+ StackWidgetModel.class,
+ OcsProject.class,
+ OcsProjectResource.class,
+ JoinCardWithProject.class,
+ UserInGroup.class,
+ UserInBoard.class,
},
exportSchema = false,
- version = 15
+ version = 23
)
@TypeConverters({DateTypeConverter.class})
public abstract class DeckDatabase extends RoomDatabase {
@@ -175,8 +197,204 @@ public abstract class DeckDatabase extends RoomDatabase {
}
};
- public static final RoomDatabase.Callback ON_CREATE_CALLBACK = new RoomDatabase.Callback() {
+ private static final Migration MIGRATION_15_16 = new Migration(15, 16) {
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ database.execSQL("CREATE TABLE `StackWidgetModel` (`appWidgetId` INTEGER PRIMARY KEY, `accountId` INTEGER, `stackId` INTEGER, `darkTheme` INTEGER CHECK (`darkTheme` IN (0,1)) NOT NULL, " +
+ "FOREIGN KEY(`accountId`) REFERENCES `Account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE, " +
+ "FOREIGN KEY(`stackId`) REFERENCES `Stack`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE )");
+ database.execSQL("CREATE INDEX `index_StackWidgetModel_stackId` ON `StackWidgetModel` (`stackId`)");
+ database.execSQL("CREATE INDEX `index_StackWidgetModel_accountId` ON `StackWidgetModel` (`accountId`)");
+ }
+ };
+
+ private static final Migration MIGRATION_16_17 = new Migration(16, 17) {
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ database.execSQL("CREATE TABLE `OcsProject` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `name` TEXT NOT NULL, `status` INTEGER NOT NULL, `lastModified` INTEGER, `lastModifiedLocal` INTEGER)");
+ database.execSQL("CREATE UNIQUE INDEX `index_OcsProject_accountId_id` ON `OcsProject` (`accountId`, `id`)");
+ database.execSQL("CREATE INDEX `index_project_accID` ON `OcsProject` (`accountId`)");
+ database.execSQL("CREATE INDEX `index_OcsProject_id` ON `OcsProject` (`id`)");
+ database.execSQL("CREATE INDEX `index_OcsProject_lastModifiedLocal` ON `OcsProject` (`lastModifiedLocal`)");
+
+ database.execSQL("CREATE TABLE `OcsProjectResource` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `name` TEXT, `status` INTEGER NOT NULL, `lastModified` INTEGER, `lastModifiedLocal` INTEGER, `projectId` INTEGER NOT NULL, `type` TEXT , `link` TEXT , `path` TEXT, `iconUrl` TEXT , `previewAvailable` INTEGER, `mimetype` TEXT, FOREIGN KEY(`projectId`) REFERENCES `OcsProject`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE)");
+ database.execSQL("CREATE INDEX `index_projectResource_accID` ON `OcsProjectResource` (`accountId`)");
+ database.execSQL("CREATE INDEX `index_projectResource_projectId` ON `OcsProjectResource` (`projectId`)");
+ database.execSQL("CREATE UNIQUE INDEX `index_OcsProjectResource_accountId_id` ON `OcsProjectResource` (`accountId`, `id`, `projectId`)");
+ database.execSQL("CREATE INDEX `index_OcsProjectResource_id` ON `OcsProjectResource` (`id`)");
+ database.execSQL("CREATE INDEX `index_OcsProjectResource_lastModifiedLocal` ON `OcsProjectResource` (`lastModifiedLocal`)");
+
+ database.execSQL("CREATE TABLE `JoinCardWithProject` (`status` INTEGER NOT NULL, `projectId` INTEGER NOT NULL, `cardId` INTEGER NOT NULL, PRIMARY KEY (`projectId`, `cardId`), FOREIGN KEY(`cardId`) REFERENCES `Card`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY(`projectId`) REFERENCES `OcsProject`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE)");
+ database.execSQL("CREATE INDEX `index_JoinCardWithProject_projectId` ON `JoinCardWithProject` (`projectId`)");
+ database.execSQL("CREATE INDEX `index_JoinCardWithProject_cardId` ON `JoinCardWithProject` (`cardId`)");
+ }
+ };
+ private static final Migration MIGRATION_17_18 = new Migration(17, 18) {
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ // https://github.com/stefan-niedermann/nextcloud-deck/issues/435
+ database.execSQL("ALTER TABLE `Account` ADD `etag` TEXT");
+ }
+ };
+ private static final Migration MIGRATION_18_19 = new Migration(18, 19) {
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ // https://github.com/stefan-niedermann/nextcloud-deck/issues/619
+ database.execSQL("DROP INDEX `index_OcsProjectResource_accountId_id`");
+ database.execSQL("ALTER TABLE `OcsProjectResource` ADD `idString` TEXT");
+ database.execSQL("CREATE UNIQUE INDEX `index_OcsProjectResource_accountId_id` ON `OcsProjectResource` (`accountId`, `id`, `idString`, `projectId`)");
+ }
+ };
+ private static final Migration MIGRATION_19_20 = new Migration(19, 20) {
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ // https://github.com/stefan-niedermann/nextcloud-deck/issues/492
+ // https://github.com/stefan-niedermann/nextcloud-deck/issues/631
+ database.execSQL("CREATE TABLE `UserInGroup` (`groupId` INTEGER NOT NULL, `memberId` INTEGER NOT NULL, " +
+ "primary KEY(`groupId`, `memberId`), " +
+ "FOREIGN KEY(`groupId`) REFERENCES `User`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE, " +
+ "FOREIGN KEY(`memberId`) REFERENCES `User`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE)");
+ database.execSQL("CREATE UNIQUE INDEX `unique_idx_group_member` ON `UserInGroup` (`groupId`, `memberId`)");
+ database.execSQL("CREATE INDEX `index_UserInGroup_groupId` ON `UserInGroup` (`groupId`)");
+ database.execSQL("CREATE INDEX `index_UserInGroup_memberId` ON `UserInGroup` (`memberId`)");
+
+ database.execSQL("CREATE TABLE `UserInBoard` (`userId` INTEGER NOT NULL, `boardId` INTEGER NOT NULL, " +
+ "primary KEY(`userId`, `boardId`), " +
+ "FOREIGN KEY(`userId`) REFERENCES `User`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE, " +
+ "FOREIGN KEY(`boardId`) REFERENCES `Board`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE)");
+ database.execSQL("CREATE UNIQUE INDEX `unique_idx_user_board` ON `UserInBoard` (`userId`, `boardId`)");
+ database.execSQL("CREATE INDEX `index_UserInBoard_userId` ON `UserInBoard` (`userId`)");
+ database.execSQL("CREATE INDEX `index_UserInBoard_boardId` ON `UserInBoard` (`boardId`)");
+ }
+ };
+
+ private static final Migration MIGRATION_20_21 = new Migration(20, 21) {
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ // https://github.com/stefan-niedermann/nextcloud-deck/issues/556
+ String suffix = "_new";
+ {
+ String tableName = "Account";
+ database.execSQL("CREATE TABLE `" + tableName + suffix + "` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `userName` TEXT NOT NULL, `url` TEXT NOT NULL, " +
+ "`color` INTEGER NOT NULL DEFAULT 0, `textColor` INTEGER NOT NULL DEFAULT 0, `serverDeckVersion` TEXT NOT NULL DEFAULT '0.6.4', `maintenanceEnabled` INTEGER NOT NULL DEFAULT 0, `etag` TEXT)");
+ Cursor cursor = database.query("select * from `" + tableName + "`");
+ while (cursor.moveToNext()) {
+ String colorAsString1 = cursor.getString(4); // color
+ String colorAsString2 = cursor.getString(5); // textColor
+
+ @ColorInt Integer color1 = null;
+ @ColorInt Integer color2 = null;
+ try {
+ color1 = Color.parseColor(ColorUtil.INSTANCE.formatColorToParsableHexString(colorAsString1));
+ color2 = Color.parseColor(ColorUtil.INSTANCE.formatColorToParsableHexString(colorAsString2));
+ } catch (Exception e) {
+ color1 = Color.GRAY;
+ color2 = Color.GRAY;
+ }
+ database.execSQL("Insert into `" + tableName + suffix + "` VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", new Object[]{
+ cursor.getLong(0), cursor.getString(1), cursor.getString(2), cursor.getString(3),
+ color1, color2, cursor.getString(6), cursor.getInt(7), cursor.getString(8)});
+
+ }
+
+
+ database.execSQL("DROP TABLE `" + tableName + "`");
+ database.execSQL("ALTER TABLE `" + tableName + suffix + "` RENAME TO `" + tableName + "`");
+ database.execSQL("CREATE UNIQUE INDEX `index_Account_name` ON `" + tableName + "` (`name`)");
+ database.execSQL("UPDATE SQLITE_SEQUENCE SET seq = (select max(id) from " + tableName + ") WHERE name = ?", new Object[]{tableName});
+ }
+ {
+ String tableName = "Board";
+ database.execSQL("CREATE TABLE `" + tableName + suffix + "` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `status` INTEGER NOT NULL, " +
+ "`lastModified` INTEGER, `lastModifiedLocal` INTEGER, `title` TEXT, `ownerId` INTEGER NOT NULL, `color` INTEGER, " +
+ "`archived` INTEGER NOT NULL, `shared` INTEGER NOT NULL, `deletedAt` INTEGER, `permissionRead` INTEGER NOT NULL, " +
+ "`permissionEdit` INTEGER NOT NULL, `permissionManage` INTEGER NOT NULL, `permissionShare` INTEGER NOT NULL, " +
+ "FOREIGN KEY(`ownerId`) REFERENCES `User`(`localId`) ON UPDATE NO ACTION ON DELETE SET NULL )");
+ Cursor cursor = database.query("select * from `" + tableName + "`");
+ while (cursor.moveToNext()) {
+ String colorAsString1 = cursor.getString(8); // color
+
+ @ColorInt Integer color1 = null;
+ try {
+ color1 = Color.parseColor(ColorUtil.INSTANCE.formatColorToParsableHexString(colorAsString1));
+ } catch (Exception e) {
+ color1 = Color.GRAY;
+ }
+ database.execSQL("Insert into `" + tableName + suffix + "` VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", new Object[]{
+ cursor.getLong(0), cursor.getLong(1), cursor.getLong(2), cursor.getInt(3),
+ cursor.getLong(4), cursor.getLong(5), cursor.getString(6), cursor.getLong(7), color1,
+ cursor.getInt(9), cursor.getInt(10), cursor.getInt(11), cursor.getInt(12),
+ cursor.getInt(13), cursor.getInt(14), cursor.getInt(15)
+ });
+
+ }
+
+
+ database.execSQL("DROP TABLE `" + tableName + "`");
+ database.execSQL("ALTER TABLE `" + tableName + suffix + "` RENAME TO `" + tableName + "`");
+ database.execSQL("CREATE INDEX `index_Board_accountId` ON `" + tableName + "` (`accountId`)");
+ database.execSQL("CREATE UNIQUE INDEX `index_Board_accountId_id` ON `" + tableName + "` (`accountId`, `id`)");
+ database.execSQL("CREATE INDEX `index_Board_id` ON `" + tableName + "` (`id`)");
+ database.execSQL("CREATE INDEX `index_Board_ownerId` ON `" + tableName + "` (`ownerId`)");
+ database.execSQL("CREATE INDEX `index_Board_lastModifiedLocal` ON `" + tableName + "` (`lastModifiedLocal`)");
+ database.execSQL("UPDATE SQLITE_SEQUENCE SET seq = (select max(id) from " + tableName + ") WHERE name = ?", new Object[]{tableName});
+ }
+ {
+ String tableName = "Label";
+ database.execSQL("CREATE TABLE `" + tableName + suffix + "` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `status` INTEGER NOT NULL, " +
+ "`lastModified` INTEGER, `lastModifiedLocal` INTEGER, `title` TEXT, `color` INTEGER NOT NULL DEFAULT 0, `boardId` INTEGER NOT NULL, " +
+ "FOREIGN KEY(`boardId`) REFERENCES `Board`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE )");
+ Cursor cursor = database.query("select * from `" + tableName + "`");
+ while (cursor.moveToNext()) {
+ String colorAsString1 = cursor.getString(7); // color
+
+ @ColorInt Integer color1 = null;
+ try {
+ color1 = Color.parseColor(ColorUtil.INSTANCE.formatColorToParsableHexString(colorAsString1));
+ } catch (Exception e) {
+ color1 = Color.GRAY;
+ }
+ database.execSQL("Insert into `" + tableName + suffix + "` VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", new Object[]{
+ cursor.getLong(0), cursor.getLong(1), cursor.getLong(2), cursor.getInt(3),
+ cursor.getLong(4), cursor.getLong(5), cursor.getString(6), color1, cursor.getLong(8)});
+
+ }
+
+
+ database.execSQL("DROP TABLE `" + tableName + "`");
+ database.execSQL("ALTER TABLE `" + tableName + suffix + "` RENAME TO `" + tableName + "`");
+ database.execSQL("CREATE UNIQUE INDEX `index_Label_accountId_id` ON `" + tableName + "` (`accountId`, `id`)");
+ database.execSQL("CREATE INDEX `index_Label_boardId` ON `" + tableName + "` (`boardId`)");
+ database.execSQL("CREATE INDEX `index_Label_accountId` ON `" + tableName + "` (`accountId`)");
+ database.execSQL("CREATE UNIQUE INDEX `idx_label_title_unique` ON `" + tableName + "` (`boardId`, `title`)");
+ database.execSQL("CREATE INDEX `index_Label_id` ON `" + tableName + "` (`id`)");
+ database.execSQL("CREATE INDEX `index_Label_lastModifiedLocal` ON `" + tableName + "` (`lastModifiedLocal`)");
+ database.execSQL("UPDATE SQLITE_SEQUENCE SET seq = (select max(id) from " + tableName + ") WHERE name = ?", new Object[]{tableName});
+ }
+ }
+ };
+
+ private static final Migration MIGRATION_22_23 = new Migration(22, 23) {
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ // https://github.com/stefan-niedermann/nextcloud-deck/issues/359
+ database.execSQL("ALTER TABLE `Account` ADD `boardsEtag` TEXT");
+ database.execSQL("ALTER TABLE `Board` ADD `etag` TEXT");
+ database.execSQL("ALTER TABLE `Stack` ADD `etag` TEXT");
+ database.execSQL("ALTER TABLE `Card` ADD `etag` TEXT");
+ database.execSQL("ALTER TABLE `Label` ADD `etag` TEXT");
+ database.execSQL("ALTER TABLE `AccessControl` ADD `etag` TEXT");
+ database.execSQL("ALTER TABLE `Attachment` ADD `etag` TEXT");
+ database.execSQL("ALTER TABLE `User` ADD `etag` TEXT");
+ database.execSQL("ALTER TABLE `DeckComment` ADD `etag` TEXT");
+ database.execSQL("ALTER TABLE `Activity` ADD `etag` TEXT");
+ database.execSQL("ALTER TABLE `OcsProject` ADD `etag` TEXT");
+ database.execSQL("ALTER TABLE `OcsProjectResource` ADD `etag` TEXT");
+ }
+ };
+
+ public static final RoomDatabase.Callback ON_CREATE_CALLBACK = new RoomDatabase.Callback() {
@Override
public void onCreate(@NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
@@ -216,6 +434,26 @@ public abstract class DeckDatabase extends RoomDatabase {
.apply();
}
})
+ .addMigrations(MIGRATION_15_16)
+ .addMigrations(MIGRATION_16_17)
+ .addMigrations(MIGRATION_17_18)
+ .addMigrations(MIGRATION_18_19)
+ .addMigrations(MIGRATION_19_20)
+ .addMigrations(MIGRATION_20_21)
+ .addMigrations(new Migration(21, 22) {
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ // https://github.com/stefan-niedermann/nextcloud-deck/issues/715
+ final SharedPreferences.Editor lastSyncPref = context.getApplicationContext().getSharedPreferences("it.niedermann.nextcloud.deck.last_sync", Context.MODE_PRIVATE).edit();
+ Cursor cursor = database.query("select id from `Account`");
+ while (cursor.moveToNext()) {
+ lastSyncPref.remove("lS_" + cursor.getLong(0));
+ }
+ cursor.close();
+ lastSyncPref.apply();
+ }
+ })
+ .addMigrations(MIGRATION_22_23)
.fallbackToDestructiveMigration()
.addCallback(ON_CREATE_CALLBACK)
.build();
@@ -256,4 +494,16 @@ public abstract class DeckDatabase extends RoomDatabase {
public abstract MentionDao getMentionDao();
public abstract SingleCardWidgetModelDao getSingleCardWidgetModelDao();
+
+ public abstract StackWidgetModelDao getStackWidgetModelDao();
+
+ public abstract OcsProjectDao getOcsProjectDao();
+
+ public abstract OcsProjectResourceDao getOcsProjectResourceDao();
+
+ public abstract JoinCardWithOcsProjectDao getJoinCardWithOcsProjectDao();
+
+ public abstract UserInGroupDao getUserInGroupDao();
+
+ public abstract UserInBoardDao getUserInBoardDao();
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/AttachmentDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/AttachmentDao.java
index 2d0887903..8f7ee1cba 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/AttachmentDao.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/AttachmentDao.java
@@ -25,6 +25,10 @@ public interface AttachmentDao extends GenericDao<Attachment> {
@Query("SELECT * FROM attachment WHERE accountId = :accountId and (status<>1 or id is null or lastModified <> lastModifiedLocal)")
List<Attachment> getLocallyChangedAttachmentsDirectly(long accountId);
+ @Query("SELECT a.* FROM attachment a inner join card c on c.localId = a.cardId " +
+ "WHERE c.stackId = :localStackId and (a.status<>1 or a.id is null or a.lastModified <> a.lastModifiedLocal)")
+ List<Attachment> getLocallyChangedAttachmentsForStackDirectly(long localStackId);
+
@Query("SELECT * FROM attachment WHERE accountId = :accountId and cardId = :localCardId")
List<Attachment> getAttachmentsForLocalCardIdDirectly(long accountId, Long localCardId);
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java
index bd189e846..6cb322aa3 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java
@@ -13,6 +13,9 @@ import it.niedermann.nextcloud.deck.model.full.FullBoard;
@Dao
public interface BoardDao extends GenericDao<Board> {
+ @Query("SELECT * FROM board WHERE accountId = :accountId and (deletedAt = 0 or deletedAt is null) and status <> 3 order by title asc")
+ LiveData<List<Board>> getBoardsForAccount(final long accountId);
+
@Query("SELECT * FROM board WHERE accountId = :accountId and archived = 1 and (deletedAt = 0 or deletedAt is null) and status <> 3 order by title asc")
LiveData<List<Board>> getArchivedBoardsForAccount(final long accountId);
@@ -52,7 +55,8 @@ public interface BoardDao extends GenericDao<Board> {
@Query("SELECT b.* FROM board b JOIN stack s ON s.boardId = b.localId JOIN card c ON c.localId = :localCardId")
Board getBoardByLocalCardIdDirectly(long localCardId);
- @Query("SELECT b.* FROM board b JOIN stack s ON s.boardId = b.localId JOIN card c ON c.localId = :localCardId")
+ @Transaction
+ @Query("SELECT b.* FROM board b JOIN stack s ON s.boardId = b.localId JOIN card c ON c.localId = :localCardId and c.stackId = s.localId")
FullBoard getFullBoardByLocalCardIdDirectly(long localCardId);
@Transaction
@@ -72,4 +76,7 @@ public interface BoardDao extends GenericDao<Board> {
@Query("SELECT count(*) FROM board WHERE accountId = :accountId and archived = 1 and (deletedAt = 0 or deletedAt is null) and status <> 3")
LiveData<Integer> countArchivedBoards(long accountId);
+
+ @Query("SELECT * FROM board WHERE accountId = :accountId and title = :title")
+ Board getBoardForAccountByNameDirectly(long accountId, String title);
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/CardDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/CardDao.java
index 57163112a..82bdf1b8d 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/CardDao.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/CardDao.java
@@ -11,6 +11,7 @@ import java.util.List;
import it.niedermann.nextcloud.deck.model.Card;
import it.niedermann.nextcloud.deck.model.full.FullCard;
+import it.niedermann.nextcloud.deck.model.full.FullCardWithProjects;
@Dao
public interface CardDao extends GenericDao<Card> {
@@ -36,17 +37,24 @@ public interface CardDao extends GenericDao<Card> {
@Query("SELECT * FROM card WHERE accountId = :accountId AND archived = 0 AND stackId = :localStackId and status<>3 order by `order`, createdAt asc")
LiveData<List<FullCard>> getFullCardsForStack(final long accountId, final long localStackId);
- @Transaction // v not deleted!
+ @Transaction
@RawQuery(observedEntities = Card.class)
LiveData<List<FullCard>> getFilteredFullCardsForStack(SupportSQLiteQuery query);
@Transaction
+ @RawQuery(observedEntities = Card.class)
+ List<FullCard> getFilteredFullCardsForStackDirectly(SupportSQLiteQuery query);
+
+ @Transaction
@Query("SELECT * FROM card WHERE accountId = :accountId AND stackId = :localStackId order by `order`, createdAt asc")
List<FullCard> getFullCardsForStackDirectly(final long accountId, final long localStackId);
@Transaction
@Query("SELECT * FROM card WHERE accountId = :accountId and localId = :localCardId")
LiveData<FullCard> getFullCardByLocalId(final long accountId, final long localCardId);
+ @Transaction
+ @Query("SELECT * FROM card WHERE accountId = :accountId and localId = :localCardId")
+ LiveData<FullCardWithProjects> getFullCardWithProjectsByLocalId(final long accountId, final long localCardId);
@Transaction
@Query("SELECT * FROM card WHERE accountId = :accountId and id = :remoteId")
@@ -66,9 +74,15 @@ public interface CardDao extends GenericDao<Card> {
@Query("SELECT * FROM card c WHERE accountId = :accountId and exists ( select 1 from DeckComment dc where dc.objectId = c.localId and dc.status<>1)")
List<Card> getCardsWithLocallyChangedCommentsDirectly(Long accountId);
+ @Query("SELECT * FROM card c WHERE stackId = :localStackId and exists ( select 1 from DeckComment dc where dc.objectId = c.localId and dc.status<>1)")
+ List<Card> getCardsWithLocallyChangedCommentsForStackDirectly(Long localStackId);
+
@Query("SELECT count(*) FROM card c WHERE accountId = :accountId and stackId = :localStackId and status <> 3")
LiveData<Integer> countCardsInStack(long accountId, long localStackId);
@Query("SELECT coalesce(MAX(`order`), -1) FROM card c WHERE stackId = :localStackId and status <> 3")
Integer getHighestOrderInStack(Long localStackId);
+
+ @Query("SELECT c.stackId FROM card c WHERE localId = :localCardId")
+ Long getLocalStackIdByLocalCardId(Long localCardId);
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/JoinCardWithLabelDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/JoinCardWithLabelDao.java
index c96c64e7d..26e6c65a8 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/JoinCardWithLabelDao.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/JoinCardWithLabelDao.java
@@ -40,6 +40,9 @@ public interface JoinCardWithLabelDao extends GenericDao<JoinCardWithLabel> {
@Query("select * from joincardwithlabel WHERE status <> 1") // not UP_TO_DATE
List<JoinCardWithLabel> getAllChangedJoins();
+ @Query("select j.* from joincardwithlabel j inner join card c on j.cardId = c.localId WHERE c.stackId = :localStackId and j.status <> 1") // not UP_TO_DATE
+ List<JoinCardWithLabel> getAllChangedJoinsForStack(Long localStackId);
+
@Query("delete from joincardwithlabel " +
"where cardId = (select c.localId from card c where c.accountId = :accountId and c.id = :remoteCardId) " +
"and labelId = (select l.localId from label l where l.accountId = :accountId and l.id = :remoteLabelId)")
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/JoinCardWithUserDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/JoinCardWithUserDao.java
index 46554685f..416d52eed 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/JoinCardWithUserDao.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/JoinCardWithUserDao.java
@@ -25,7 +25,14 @@ public interface JoinCardWithUserDao extends GenericDao<JoinCardWithUser> {
"inner join card c on j.cardId = c.localId " +
"inner join user u on j.userId = u.localId " +
"WHERE j.status <> 1") // not UP_TO_DATE
- List<JoinCardWithUser> getDeletedJoinsWithRemoteIDs();
+ List<JoinCardWithUser> getChangedJoinsWithRemoteIDs();
+
+ @Query("select u.localId as userId, c.id as cardId, j.status from joincardwithuser j " +
+ "inner join card c on j.cardId = c.localId " +
+ "inner join user u on j.userId = u.localId " +
+ "WHERE c.stackId = :localStackId " +
+ "AND j.status <> 1") // not UP_TO_DATE
+ List<JoinCardWithUser> getChangedJoinsWithRemoteIDsForStack(Long localStackId);
@Query("delete from joincardwithuser " +
"where cardId = (select c.localId from card c where c.accountId = :accountId and c.id = :remoteCardId) " +
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/StackDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/StackDao.java
index 0fbccbe08..feb7e453b 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/StackDao.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/StackDao.java
@@ -13,7 +13,7 @@ import it.niedermann.nextcloud.deck.model.full.FullStack;
@Dao
public interface StackDao extends GenericDao<Stack> {
- @Query("SELECT * FROM stack WHERE accountId = :accountId AND boardId = :localBoardId order by `order` asc")
+ @Query("SELECT * FROM stack WHERE accountId = :accountId AND boardId = :localBoardId and status<>3 and (deletedAt is null or deletedAt = 0) order by `order` asc")
LiveData<List<Stack>> getStacksForBoard(final long accountId, final long localBoardId);
@Query("SELECT * FROM stack WHERE accountId = :accountId and boardId = :localBoardId and id = :remoteId")
@@ -31,10 +31,6 @@ public interface StackDao extends GenericDao<Stack> {
FullStack getFullStackByRemoteIdDirectly(final long accountId, final long localBoardId, final long remoteId);
@Transaction
- @Query("SELECT * FROM stack WHERE accountId = :accountId AND boardId = :localBoardId and status<>3 and (deletedAt is null or deletedAt = 0) order by `order` asc")
- LiveData<List<FullStack>> getFullStacksForBoard(final long accountId, final long localBoardId);
-
- @Transaction
@Query("SELECT * FROM stack WHERE accountId = :accountId and boardId = :localBoardId and id = :remoteId")
LiveData<FullStack> getFullStackByRemoteId(final long accountId, final long localBoardId, final long remoteId);
@@ -56,4 +52,7 @@ public interface StackDao extends GenericDao<Stack> {
@Query("SELECT localId FROM stack s WHERE accountId = :accountId and id = :stackId")
Long getLocalStackIdByRemoteStackIdDirectly(long accountId, Long stackId);
+
+ @Query("SELECT coalesce(MAX(`order`), -1) FROM stack s WHERE boardId = :localBoardId")
+ Integer getHighestStackOrderInBoard(long localBoardId);
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserDao.java
index 6f712d07f..6e42cdaae 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserDao.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserDao.java
@@ -26,12 +26,16 @@ public interface UserDao extends GenericDao<User> {
" where ju.userId = u.localId" +
" and ju.cardId = :notYetAssignedToLocalCardId AND status <> 3" + // not LOCAL_DELETED
" )" +
- " AND" +
- " (" +
- " EXISTS (" +
- " select 1 from accesscontrol" +
- " where userId = u.localId and boardId = :boardId" +
- " )" +
+ " AND ( " +
+ " EXISTS (" +
+ " select 1 from userinboard where boardId = :boardId AND userId = u.localId" +
+ " )" +
+ " OR" +
+ " EXISTS (" +
+ " select 1 from accesscontrol" + // v GROUP!
+ " where (userId = u.localId OR (type = 1 and exists(select 1 from UserInGroup uig where uig.memberId = u.localId and uig.groupId = userId))) " +
+ " and boardId = :boardId and status <> 3" +
+ " )" +
" OR" +
" EXISTS (" +
" select 1 from board where localId = :boardId AND ownerId = u.localId" +
@@ -48,7 +52,7 @@ public interface UserDao extends GenericDao<User> {
"and ( uid LIKE :searchTerm or displayname LIKE :searchTerm or primaryKey LIKE :searchTerm ) " +
"and u.localId <> (select b.ownerId from board b where localId = :boardId)" +
"ORDER BY u.displayname")
- LiveData<List<User>> searchUserByUidOrDisplayNameForACL(final long accountId, final long boardId, final String searchTerm);
+ List<User> searchUserByUidOrDisplayNameForACLDirectly(final long accountId, final long boardId, final String searchTerm);
@Query("SELECT * FROM user WHERE accountId = :accountId and uid = :uid")
User getUserByUidDirectly(final long accountId, final String uid);
@@ -66,12 +70,16 @@ public interface UserDao extends GenericDao<User> {
" where ju.userId = u.localId" +
" and ju.cardId = :notAssignedToLocalCardId AND status <> 3" + // not LOCAL_DELETED
" )" +
- " AND" +
- " (" +
- " EXISTS (" +
- " select 1 from accesscontrol" +
- " where userId = u.localId and boardId = :boardId" +
- " )" +
+ " AND ( " +
+ " EXISTS (" +
+ " select 1 from userinboard where boardId = :boardId AND userId = u.localId" +
+ " )" +
+ " OR" +
+ " EXISTS (" +
+ " select 1 from accesscontrol" + // v GROUP!
+ " where (userId = u.localId OR (type = 1 and exists(select 1 from UserInGroup uig where uig.memberId = u.localId and uig.groupId = userId))) " +
+ " and boardId = :boardId and status <> 3" +
+ " )" +
" OR" +
" EXISTS (" +
" select 1 from board where localId = :boardId AND ownerId = u.localId" +
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserInBoardDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserInBoardDao.java
new file mode 100644
index 000000000..7a0476c53
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserInBoardDao.java
@@ -0,0 +1,12 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao;
+
+import androidx.room.Dao;
+import androidx.room.Query;
+
+import it.niedermann.nextcloud.deck.model.relations.UserInBoard;
+
+@Dao
+public interface UserInBoardDao extends GenericDao<UserInBoard> {
+ @Query("DELETE FROM userinboard WHERE boardId = :localId")
+ void deleteByBoardId(long localId);
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserInGroupDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserInGroupDao.java
new file mode 100644
index 000000000..fb3f5b26e
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserInGroupDao.java
@@ -0,0 +1,12 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao;
+
+import androidx.room.Dao;
+import androidx.room.Query;
+
+import it.niedermann.nextcloud.deck.model.relations.UserInGroup;
+
+@Dao
+public interface UserInGroupDao extends GenericDao<UserInGroup> {
+ @Query("DELETE FROM useringroup WHERE groupId = :localId")
+ void deleteByGroupId(long localId);
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/JoinCardWithOcsProjectDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/JoinCardWithOcsProjectDao.java
new file mode 100644
index 000000000..d132796e4
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/JoinCardWithOcsProjectDao.java
@@ -0,0 +1,13 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.projects;
+
+import androidx.room.Dao;
+import androidx.room.Query;
+
+import it.niedermann.nextcloud.deck.model.ocs.projects.JoinCardWithProject;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.GenericDao;
+
+@Dao
+public interface JoinCardWithOcsProjectDao extends GenericDao<JoinCardWithProject> {
+ @Query("select * from JoinCardWithProject where projectId = :localProjectId and cardId = :localCardId")
+ JoinCardWithProject getAssignmentByCardIdAndProjectIdDirectly(Long localCardId, Long localProjectId);
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/OcsProjectDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/OcsProjectDao.java
new file mode 100644
index 000000000..fb0e3f836
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/OcsProjectDao.java
@@ -0,0 +1,13 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.projects;
+
+import androidx.room.Dao;
+import androidx.room.Query;
+
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProject;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.GenericDao;
+
+@Dao
+public interface OcsProjectDao extends GenericDao<OcsProject> {
+ @Query("select * from OcsProject where accountId = :accountId and id = :remoteId")
+ OcsProject getProjectByRemoteIdDirectly(long accountId, Long remoteId);
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/OcsProjectResourceDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/OcsProjectResourceDao.java
new file mode 100644
index 000000000..8a544667a
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/OcsProjectResourceDao.java
@@ -0,0 +1,25 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.projects;
+
+import androidx.lifecycle.LiveData;
+import androidx.room.Dao;
+import androidx.room.Query;
+
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProjectResource;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.GenericDao;
+
+@Dao
+public interface OcsProjectResourceDao extends GenericDao<OcsProjectResource> {
+ @Query("delete from OcsProjectResource where projectId = :localProjectId")
+ void deleteByProjectId(Long localProjectId);
+
+ @Query("select * from OcsProjectResource where projectId = :localProjectId")
+ LiveData<List<OcsProjectResource>> getResourcesByLocalProjectId(Long localProjectId);
+
+ @Query("select count(id) from OcsProjectResource where projectId = :localProjectId")
+ int countProjectResourcesInProjectDirectly(Long localProjectId);
+
+ @Query("select count(id) from OcsProjectResource where projectId = :localProjectId")
+ LiveData<Integer> countProjectResourcesInProject(Long localProjectId);
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/SingleCardWidgetModelDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/SingleCardWidgetModelDao.java
index 0c2a485c1..a26269c30 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/SingleCardWidgetModelDao.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/SingleCardWidgetModelDao.java
@@ -1,4 +1,4 @@
-package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao;
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets;
import androidx.room.Dao;
import androidx.room.Query;
@@ -6,6 +6,7 @@ import androidx.room.Transaction;
import it.niedermann.nextcloud.deck.model.full.FullSingleCardWidgetModel;
import it.niedermann.nextcloud.deck.model.widget.singlecard.SingleCardWidgetModel;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.GenericDao;
@Dao
public interface SingleCardWidgetModelDao extends GenericDao<SingleCardWidgetModel> {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/StackWidgetModelDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/StackWidgetModelDao.java
new file mode 100644
index 000000000..9b370e32b
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/StackWidgetModelDao.java
@@ -0,0 +1,19 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets;
+
+import androidx.room.Dao;
+import androidx.room.Query;
+import androidx.room.Transaction;
+
+import it.niedermann.nextcloud.deck.model.appwidgets.StackWidgetModel;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.GenericDao;
+
+@Dao
+public interface StackWidgetModelDao extends GenericDao<StackWidgetModel> {
+
+ @Query("SELECT * FROM stackwidgetmodel WHERE appwidgetid = :appWidgetId")
+ StackWidgetModel getStackWidgetByAppWidgetIdDirectly(final int appWidgetId);
+
+ @Transaction
+ @Query("SELECT EXISTS (SELECT 1 FROM stackwidgetmodel WHERE stackId in (:stackLocalIds))")
+ boolean containsStackLocalId(final long... stackLocalIds);
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/WrappedLiveData.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/WrappedLiveData.java
index 038fcbad8..003bceb26 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/WrappedLiveData.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/WrappedLiveData.java
@@ -1,5 +1,6 @@
package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.MutableLiveData;
@@ -28,10 +29,13 @@ public class WrappedLiveData<T> extends MutableLiveData<T> {
}
public void postError(@Nullable Throwable error) {
+ postError(error, null);
+ }
+ public void postError(@Nullable Throwable error, @NonNull T locallyCreatedEntity) {
if (error == null) {
DeckLog.warn("Given error is null");
}
setError(error);
- postValue(null);
+ postValue(locallyCreatedEntity);
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/Debouncer.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/Debouncer.java
new file mode 100644
index 000000000..fca4ca369
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/Debouncer.java
@@ -0,0 +1,75 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.extrawurst;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public class Debouncer <T> {
+ private final ScheduledExecutorService sched = Executors.newScheduledThreadPool(1);
+ private final ConcurrentHashMap<T, TimerTask> delayedMap = new ConcurrentHashMap<T, TimerTask>();
+ private final Callback<T> callback;
+ private final int interval;
+
+ public Debouncer(Callback<T> c, int interval) {
+ this.callback = c;
+ this.interval = interval;
+ }
+
+ public void call(T key) {
+ TimerTask task = new TimerTask(key);
+
+ TimerTask prev;
+ do {
+ prev = delayedMap.putIfAbsent(key, task);
+ if (prev == null)
+ sched.schedule(task, interval, TimeUnit.MILLISECONDS);
+ // Exit only if new task was added to map, or existing task was extended successfully
+ } while (prev != null && !prev.extend());
+ }
+
+ public void terminate() {
+ sched.shutdownNow();
+ }
+
+ public interface Callback<T> {
+ void call(T key);
+ }
+
+ // The task that wakes up when the wait time elapses
+ private class TimerTask implements Runnable {
+ private final T key;
+ private long dueTime;
+ private final Object lock = new Object();
+
+ public TimerTask(T key) {
+ this.key = key;
+ extend();
+ }
+
+ public boolean extend() {
+ synchronized (lock) {
+ if (dueTime < 0) // Task has been shutdown
+ return false;
+ dueTime = System.currentTimeMillis() + interval;
+ return true;
+ }
+ }
+
+ public void run() {
+ synchronized (lock) {
+ long remaining = dueTime - System.currentTimeMillis();
+ if (remaining > 0) { // Re-schedule task
+ sched.schedule(this, remaining, TimeUnit.MILLISECONDS);
+ } else { // Mark as terminated and invoke callback
+ dueTime = -1;
+ try {
+ callback.call(key);
+ } finally {
+ delayedMap.remove(key);
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/UserSearchLiveData.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/UserSearchLiveData.java
new file mode 100644
index 000000000..179d816eb
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/UserSearchLiveData.java
@@ -0,0 +1,93 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.extrawurst;
+
+import androidx.lifecycle.MediatorLiveData;
+
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.DeckLog;
+import it.niedermann.nextcloud.deck.api.IResponseCallback;
+import it.niedermann.nextcloud.deck.exceptions.OfflineException;
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.User;
+import it.niedermann.nextcloud.deck.model.enums.DBStatus;
+import it.niedermann.nextcloud.deck.model.ocs.user.OcsUser;
+import it.niedermann.nextcloud.deck.model.ocs.user.OcsUserList;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.ServerAdapter;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.DataBaseAdapter;
+
+public class UserSearchLiveData extends MediatorLiveData<List<User>> implements Debouncer.Callback<Long> {
+
+ private static final int DEBOUNCE_TIME = 300; // ms
+ private DataBaseAdapter db;
+ private ServerAdapter server;
+ long accountId;
+ String searchTerm;
+ long notYetAssignedInACL;
+ private Debouncer<Long> debouncer = new Debouncer<>(this, DEBOUNCE_TIME);
+
+ public UserSearchLiveData(DataBaseAdapter db, ServerAdapter server) {
+ this.db = db;
+ this.server = server;
+ }
+
+ public UserSearchLiveData search(long accountId, long notYetAssignedInACL, String searchTerm) {
+ this.accountId = accountId;
+ this.searchTerm = searchTerm;
+ this.notYetAssignedInACL = notYetAssignedInACL;
+ new Thread(() -> debouncer.call(notYetAssignedInACL)).start();
+ return this;
+ }
+
+
+ @Override
+ public void call(Long key) {
+ if (key!=notYetAssignedInACL){
+ return;
+ }
+
+ final String term = String.copyValueOf(searchTerm.toCharArray());
+
+ postCurrentFromDB(term);
+
+ if (server.hasInternetConnection()) {
+ try {
+ Account account = db.getAccountByIdDirectly(accountId);
+ server.searchUser(term, new IResponseCallback<OcsUserList>(account) {
+ @Override
+ public void onResponse(OcsUserList response) {
+ if (response == null || response.getUsers().isEmpty()){
+ return;
+ }
+ for (OcsUser user : response.getUsers()) {
+ User existingUser = db.getUserByUidDirectly(accountId, user.getId());
+ if (existingUser == null) {
+ User newUser = new User();
+ newUser.setStatus(DBStatus.UP_TO_DATE.getId());
+ newUser.setPrimaryKey(user.getId());
+ newUser.setUid(user.getId());
+ newUser.setDisplayname(user.getDisplayName());
+ db.createUser(accountId, newUser);
+ }
+ }
+ if (!term.equals(searchTerm)) {
+ return;
+ }
+ postCurrentFromDB(term);
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ }
+ });
+ } catch (OfflineException e) {
+ DeckLog.logError(e);
+ }
+ }
+ }
+
+ private void postCurrentFromDB(String term) {
+ List<User> foundInDB = db.searchUserByUidOrDisplayNameForACLDirectly(accountId, notYetAssignedInACL, term);
+ postValue(foundInDB);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/DataPropagationHelper.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/DataPropagationHelper.java
index 9590f5abd..782b6d951 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/DataPropagationHelper.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/DataPropagationHelper.java
@@ -34,27 +34,31 @@ public class DataPropagationHelper {
entity.setLocalId(newID);
boolean connected = serverAdapter.hasInternetConnection();
if (connected) {
- provider.createOnServer(serverAdapter, dataBaseAdapter, accountId, new IResponseCallback<T>(new Account(accountId)) {
- @Override
- public void onResponse(T response) {
- new Thread(() -> {
- response.setAccountId(accountId);
- response.setLocalId(newID);
- if (actionOnResponse!= null) {
- actionOnResponse.onResponse(entity, response);
- }
- response.setStatus(DBStatus.UP_TO_DATE.getId());
- provider.updateInDB(dataBaseAdapter, accountId, response, false);
- callback.onResponse(response);
- }).start();
- }
+ try {
+ provider.createOnServer(serverAdapter, dataBaseAdapter, accountId, new IResponseCallback<T>(new Account(accountId)) {
+ @Override
+ public void onResponse(T response) {
+ new Thread(() -> {
+ response.setAccountId(accountId);
+ response.setLocalId(newID);
+ if (actionOnResponse != null) {
+ actionOnResponse.onResponse(entity, response);
+ }
+ response.setStatus(DBStatus.UP_TO_DATE.getId());
+ provider.updateInDB(dataBaseAdapter, accountId, response, false);
+ callback.onResponse(response);
+ }).start();
+ }
- @Override
- public void onError(Throwable throwable) {
- super.onError(throwable);
- new Thread(() -> callback.onError(throwable)).start();
- }
- }, entity);
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ new Thread(() -> callback.onError(throwable, entity)).start();
+ }
+ }, entity);
+ } catch (Throwable t) {
+ callback.onError(t, entity);
+ }
} else {
callback.onResponse(entity);
}
@@ -71,24 +75,28 @@ public class DataPropagationHelper {
}
boolean connected = serverAdapter.hasInternetConnection();
if (entity.getId() != null && connected) {
- provider.updateOnServer(serverAdapter, dataBaseAdapter, accountId, new IResponseCallback<T>(new Account(accountId)) {
- @Override
- public void onResponse(T response) {
- new Thread(() -> {
- entity.setStatus(DBStatus.UP_TO_DATE.getId());
- provider.updateInDB(dataBaseAdapter, accountId, entity, false);
- callback.onResponse(entity);
- }).start();
- }
+ try {
+ provider.updateOnServer(serverAdapter, dataBaseAdapter, accountId, new IResponseCallback<T>(new Account(accountId)) {
+ @Override
+ public void onResponse(T response) {
+ new Thread(() -> {
+ entity.setStatus(DBStatus.UP_TO_DATE.getId());
+ provider.updateInDB(dataBaseAdapter, accountId, entity, false);
+ callback.onResponse(entity);
+ }).start();
+ }
- @Override
- public void onError(Throwable throwable) {
- super.onError(throwable);
- new Thread(() -> {
- callback.onError(throwable);
- }).start();
- }
- }, entity);
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ new Thread(() -> {
+ callback.onError(throwable, entity);
+ }).start();
+ }
+ }, entity);
+ } catch (Throwable t) {
+ callback.onError(t, entity);
+ }
} else {
callback.onResponse(entity);
}
@@ -98,23 +106,28 @@ public class DataPropagationHelper {
provider.deleteInDB(dataBaseAdapter, accountId, entity);
boolean connected = serverAdapter.hasInternetConnection();
if (entity.getId() != null && connected) {
- provider.deleteOnServer(serverAdapter, accountId, new IResponseCallback<Void>(new Account(accountId)) {
- @Override
- public void onResponse(Void response) {
- new Thread(() -> {
- provider.deletePhysicallyInDB(dataBaseAdapter, accountId, entity);
- callback.onResponse(null);
- }).start();
- }
+ try {
+ provider.deleteOnServer(serverAdapter, accountId, new IResponseCallback<Void>(new Account(accountId)) {
+ @Override
+ public void onResponse(Void response) {
+ new Thread(() -> {
+ provider.deletePhysicallyInDB(dataBaseAdapter, accountId, entity);
+ callback.onResponse(null);
+ }).start();
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ new Thread(() -> {
+ callback.onError(throwable);
+ }).start();
+ }
+ }, entity, dataBaseAdapter);
+ } catch (Throwable t) {
+ callback.onError(t);
+ }
- @Override
- public void onError(Throwable throwable) {
- super.onError(throwable);
- new Thread(() -> {
- callback.onError(throwable);
- }).start();
- }
- }, entity, dataBaseAdapter);
} else {
callback.onResponse(null);
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/SyncHelper.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/SyncHelper.java
index 18d8c3672..17c0de2cf 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/SyncHelper.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/SyncHelper.java
@@ -1,6 +1,12 @@
package it.niedermann.nextcloud.deck.persistence.sync.helpers;
-import java.util.Date;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
+
+import java.net.HttpURLConnection;
+import java.time.Instant;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@@ -15,28 +21,33 @@ import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.AbstractS
import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.IRelationshipProvider;
public class SyncHelper {
- private ServerAdapter serverAdapter;
- private DataBaseAdapter dataBaseAdapter;
+ private final ServerAdapter serverAdapter;
+ private final DataBaseAdapter dataBaseAdapter;
private Account account;
private long accountId;
private IResponseCallback<Boolean> responseCallback;
- private Date lastSync;
+ private final Instant lastSync;
- public SyncHelper(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, Date lastSync) {
+ public SyncHelper(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, Instant lastSync) {
this.serverAdapter = serverAdapter;
this.dataBaseAdapter = dataBaseAdapter;
this.lastSync = lastSync;
}
// Sync Server -> App
- public <T extends IRemoteEntity> void doSyncFor(final AbstractSyncDataProvider<T> provider){
+ public <T extends IRemoteEntity> void doSyncFor(@NonNull final AbstractSyncDataProvider<T> provider) {
provider.registerChildInParent(provider);
- provider.getAllFromServer(serverAdapter, accountId, new IResponseCallback<List<T>>(account) {
+ provider.getAllFromServer(serverAdapter, dataBaseAdapter, accountId, new IResponseCallback<List<T>>(account) {
@Override
public void onResponse(List<T> response) {
if (response != null) {
provider.goingDeeper();
for (T entityFromServer : response) {
+ if (entityFromServer == null) {
+ // see https://github.com/stefan-niedermann/nextcloud-deck/issues/574
+ DeckLog.error("Skipped null value from server for DataProvider: " + provider.getClass().getSimpleName());
+ continue;
+ }
entityFromServer.setAccountId(accountId);
T existingEntity = provider.getSingleFromDB(dataBaseAdapter, accountId, entityFromServer);
@@ -44,13 +55,14 @@ public class SyncHelper {
provider.createInDB(dataBaseAdapter, accountId, entityFromServer);
} else {
//TODO: how to handle deletes? what about archived?
- if (existingEntity.getStatus() != DBStatus.UP_TO_DATE.getId()){
- DeckLog.log("Conflicting changes on entity: "+existingEntity);
+ if (existingEntity.getStatus() != DBStatus.UP_TO_DATE.getId()) {
+ DeckLog.warn("Conflicting changes on entity: " + existingEntity);
// TODO: what to do?
} else {
-// if (existingEntity.getLastModified().getTime() == entityFromServer.getLastModified().getTime()) {
-// continue; // TODO: is this is ok for sure? -> isn`t! NPE
-// }
+ if (entityFromServer.getEtag() != null && entityFromServer.getEtag().equals(existingEntity.getEtag())) {
+ DeckLog.log("[" + provider.getClass().getSimpleName() + "] ETags do match! skipping " + existingEntity.getClass().getSimpleName() + " with localId: " + existingEntity.getLocalId());
+ continue;
+ }
provider.updateInDB(dataBaseAdapter, accountId, applyUpdatesFromRemote(provider, existingEntity, entityFromServer, accountId), false);
}
}
@@ -58,7 +70,7 @@ public class SyncHelper {
provider.goDeeper(SyncHelper.this, existingEntity, entityFromServer, responseCallback);
}
- provider.handleDeletes(serverAdapter, dataBaseAdapter, accountId, response);
+ provider.handleDeletes(serverAdapter, dataBaseAdapter, accountId, response);
provider.doneGoingDeeper(responseCallback, true);
} else {
@@ -68,25 +80,35 @@ public class SyncHelper {
@Override
public void onError(Throwable throwable) {
+ super.onError(throwable);
+ if (throwable.getClass() == NextcloudHttpRequestFailedException.class) {
+ NextcloudHttpRequestFailedException requestFailedException = (NextcloudHttpRequestFailedException) throwable;
+ if (HttpURLConnection.HTTP_NOT_MODIFIED == requestFailedException.getStatusCode()){
+ DeckLog.log("[" + provider.getClass().getSimpleName() + "] ETags do match! skipping this one.");
+ // well, etags say we're fine here. no need to go deeper.
+ provider.childDone(provider, responseCallback, false);
+ return;
+ }
+ }
provider.onError(throwable, responseCallback);
- DeckLog.logError(throwable);
responseCallback.onError(throwable);
}
}, lastSync);
}
// Sync App -> Server
- public <T extends IRemoteEntity> void doUpSyncFor(AbstractSyncDataProvider<T> provider){
+ public <T extends IRemoteEntity> void doUpSyncFor(@NonNull AbstractSyncDataProvider<T> provider) {
doUpSyncFor(provider, null);
}
- public <T extends IRemoteEntity> void doUpSyncFor(AbstractSyncDataProvider<T> provider, CountDownLatch countDownLatch){
- List<T> allFromDB = provider.getAllChangedFromDB(dataBaseAdapter, accountId, lastSync);
+
+ public <T extends IRemoteEntity> void doUpSyncFor(@NonNull AbstractSyncDataProvider<T> provider, @Nullable CountDownLatch countDownLatch) {
+ final List<T> allFromDB = provider.getAllChangedFromDB(dataBaseAdapter, accountId, lastSync);
if (allFromDB != null && !allFromDB.isEmpty()) {
for (T entity : allFromDB) {
- if (entity.getId()!=null) {
+ if (entity.getId() != null) {
if (entity.getStatusEnum() == DBStatus.LOCAL_DELETED) {
provider.deleteOnServer(serverAdapter, accountId, getDeleteCallback(provider, entity), entity, dataBaseAdapter);
- if (countDownLatch != null){
+ if (countDownLatch != null) {
countDownLatch.countDown();
}
} else {
@@ -98,13 +120,13 @@ public class SyncHelper {
}
} else {
provider.goDeeperForUpSync(this, serverAdapter, dataBaseAdapter, responseCallback);
- if (countDownLatch != null){
+ if (countDownLatch != null) {
countDownLatch.countDown();
}
}
}
- private <T extends IRemoteEntity> IResponseCallback<Void> getDeleteCallback(AbstractSyncDataProvider<T> provider, T entity) {
+ private <T extends IRemoteEntity> IResponseCallback<Void> getDeleteCallback(@NonNull AbstractSyncDataProvider<T> provider, T entity) {
return new IResponseCallback<Void>(account) {
@Override
public void onResponse(Void response) {
@@ -120,16 +142,17 @@ public class SyncHelper {
};
}
- private <T extends IRemoteEntity> IResponseCallback<T> getUpdateCallback(AbstractSyncDataProvider<T> provider, T entity, CountDownLatch countDownLatch) {
+ private <T extends IRemoteEntity> IResponseCallback<T> getUpdateCallback(@NonNull AbstractSyncDataProvider<T> provider, @NonNull T entity, @Nullable CountDownLatch countDownLatch) {
return new IResponseCallback<T>(account) {
@Override
public void onResponse(T response) {
response.setAccountId(this.account.getId());
T update = applyUpdatesFromRemote(provider, entity, response, accountId);
+ update.setId(response.getId());
update.setStatus(DBStatus.UP_TO_DATE.getId());
provider.updateInDB(dataBaseAdapter, accountId, update, false);
provider.goDeeperForUpSync(SyncHelper.this, serverAdapter, dataBaseAdapter, responseCallback);
- if (countDownLatch != null){
+ if (countDownLatch != null) {
countDownLatch.countDown();
}
}
@@ -138,20 +161,20 @@ public class SyncHelper {
public void onError(Throwable throwable) {
super.onError(throwable);
responseCallback.onError(throwable);
- if (countDownLatch != null){
+ if (countDownLatch != null) {
countDownLatch.countDown();
}
}
};
}
- public void fixRelations(IRelationshipProvider relationshipProvider) {
+ public void fixRelations(@NonNull IRelationshipProvider relationshipProvider) {
// this is OK, since the delete only affects records with status UP_TO_DATE
relationshipProvider.deleteAllExisting(dataBaseAdapter, accountId);
relationshipProvider.insertAllNecessary(dataBaseAdapter, accountId);
}
- private <T extends IRemoteEntity> T applyUpdatesFromRemote(AbstractSyncDataProvider<T> provider, T localEntity, T remoteEntity, Long accountId) {
+ private <T extends IRemoteEntity> T applyUpdatesFromRemote(@NonNull AbstractSyncDataProvider<T> provider, @NonNull T localEntity, @NonNull T remoteEntity, @NonNull Long accountId) {
if (!accountId.equals(localEntity.getAccountId())) {
throw new IllegalArgumentException("IDs of Accounts are not matching! WTF are you doin?!");
}
@@ -160,7 +183,7 @@ public class SyncHelper {
return remoteEntity;
}
- public SyncHelper setResponseCallback(IResponseCallback<Boolean> callback) {
+ public SyncHelper setResponseCallback(@NonNull IResponseCallback<Boolean> callback) {
this.responseCallback = callback;
this.account = responseCallback.getAccount();
accountId = account.getId();
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AbstractSyncDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AbstractSyncDataProvider.java
index 166d6c519..11fb38a66 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AbstractSyncDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AbstractSyncDataProvider.java
@@ -1,9 +1,10 @@
package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
+import java.time.Instant;
import java.util.ArrayList;
-import java.util.Date;
import java.util.List;
+import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.api.IResponseCallback;
import it.niedermann.nextcloud.deck.model.interfaces.IRemoteEntity;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.ServerAdapter;
@@ -26,28 +27,37 @@ public abstract class AbstractSyncDataProvider<T extends IRemoteEntity> {
}
}
- public void handleDeletes(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, List<T> entitiesFromServer){
+ public void handleDeletes(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, List<T> entitiesFromServer) {
// do nothing as a default.
}
/**
* Searches each entry of <code>listB</code> in list <code>listA</code> and returns the missing ones
+ *
* @param listA List
* @param listB List
* @return all entries of <code>listB</code> missing in <code>listA</code>
*/
- public static <T extends IRemoteEntity> List<T> findDelta(List<T> listA, List<T> listB){
+ public static <T extends IRemoteEntity> List<T> findDelta(List<T> listA, List<T> listB) {
List<T> delta = new ArrayList<>();
for (T b : listB) {
+ if (b == null) {
+ DeckLog.error("Entry in listB is null! skipping...");
+ continue;
+ }
boolean found = false;
for (T a : listA) {
- if ((a.getLocalId()!= null && b.getLocalId()!= null ? (a.getLocalId().equals(b.getLocalId()))
+ if (a == null) {
+ DeckLog.error("Entry in listA is null! skipping...");
+ continue;
+ }
+ if ((a.getLocalId() != null && b.getLocalId() != null ? (a.getLocalId().equals(b.getLocalId()))
: a.getId().equals(b.getId())) && b.getAccountId() == a.getAccountId()) {
found = true;
break;
}
}
- if (!found){
+ if (!found) {
delta.add(b);
}
}
@@ -58,7 +68,14 @@ public abstract class AbstractSyncDataProvider<T extends IRemoteEntity> {
children.add(child);
}
- public abstract void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<T>> responder, Date lastSync);
+ public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<T>> responder, Instant lastSync) {
+ return;
+ }
+
+ public void getAllFromServer(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, IResponseCallback<List<T>> responder, Instant lastSync) {
+ // Overridden, because we also need the DB-Adapter at some points here (see ACL data provider)
+ getAllFromServer(serverAdapter, accountId, responder, lastSync);
+ }
public abstract T getSingleFromDB(DataBaseAdapter dataBaseAdapter, long accountId, T entity);
@@ -72,7 +89,7 @@ public abstract class AbstractSyncDataProvider<T extends IRemoteEntity> {
public abstract void deleteInDB(DataBaseAdapter dataBaseAdapter, long accountId, T t);
- public void deletePhysicallyInDB(DataBaseAdapter dataBaseAdapter, long accountId, T t){
+ public void deletePhysicallyInDB(DataBaseAdapter dataBaseAdapter, long accountId, T t) {
deleteInDB(dataBaseAdapter, accountId, t);
}
@@ -106,7 +123,7 @@ public abstract class AbstractSyncDataProvider<T extends IRemoteEntity> {
stillGoingDeeper = true;
}
- public abstract List<T> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Date lastSync);
+ public abstract List<T> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync);
public void goDeeperForUpSync(SyncHelper syncHelper, ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, IResponseCallback<Boolean> callback) {
//do nothing
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AccessControlDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AccessControlDataProvider.java
index 3242ab7c1..6dde6b45c 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AccessControlDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AccessControlDataProvider.java
@@ -1,17 +1,24 @@
package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
-import java.util.Date;
+import java.time.Instant;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.api.IResponseCallback;
import it.niedermann.nextcloud.deck.model.AccessControl;
+import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.model.User;
import it.niedermann.nextcloud.deck.model.full.FullBoard;
+import it.niedermann.nextcloud.deck.model.ocs.user.GroupMemberUIDs;
+import it.niedermann.nextcloud.deck.model.ocs.user.OcsUser;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.ServerAdapter;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.DataBaseAdapter;
+import it.niedermann.nextcloud.deck.persistence.sync.helpers.util.AsyncUtil;
public class AccessControlDataProvider extends AbstractSyncDataProvider<AccessControl> {
+ private static final Long TYPE_GROUP = 1L;
private List<AccessControl> acl;
private FullBoard board;
@@ -22,10 +29,66 @@ public class AccessControlDataProvider extends AbstractSyncDataProvider<AccessCo
}
@Override
- public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<AccessControl>> responder, Date lastSync) {
+ public void getAllFromServer(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, IResponseCallback<List<AccessControl>> responder, Instant lastSync) {
+ AsyncUtil.awaitAsyncWork(acl.size(), latch -> {
+ for (AccessControl accessControl : acl) {
+ if (accessControl.getType() == TYPE_GROUP) {
+ serverAdapter.searchGroupMembers(accessControl.getUser().getUid(), new IResponseCallback<GroupMemberUIDs>(responder.getAccount()) {
+ @Override
+ public void onResponse(GroupMemberUIDs response) {
+ accessControl.setGroupMemberUIDs(response);
+ if (response.getUids().size() > 0) {
+ ensureGroupMembersInDB(getAccount(), dataBaseAdapter, serverAdapter, response);
+ }
+ latch.countDown();
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ latch.countDown();
+ }
+ });
+ } else latch.countDown();
+ }
+ });
+
responder.onResponse(acl);
}
+ private void ensureGroupMembersInDB(Account account, DataBaseAdapter dataBaseAdapter, ServerAdapter serverAdapter, GroupMemberUIDs response) {
+ CountDownLatch memberLatch = new CountDownLatch(response.getUids().size());
+ for (String uid : response.getUids()) {
+ User user = dataBaseAdapter.getUserByUidDirectly(account.getId(), uid);
+ if (user == null) {
+ // unknown user. fetch!
+ serverAdapter.getSingleUserData(uid, new IResponseCallback<OcsUser>(account) {
+ @Override
+ public void onResponse(OcsUser response) {
+ DeckLog.log(response.toString());
+ User user = new User();
+ user.setUid(response.getId());
+ user.setPrimaryKey(response.getId());
+ user.setDisplayname(response.getDisplayName());
+ dataBaseAdapter.createUser(account.getId(), user);
+ memberLatch.countDown();
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ memberLatch.countDown();
+ }
+ });
+ } else memberLatch.countDown();
+ }
+ try {
+ memberLatch.await();
+ } catch (InterruptedException e) {
+ DeckLog.logError(e);
+ }
+ }
+
@Override
public AccessControl getSingleFromDB(DataBaseAdapter dataBaseAdapter, long accountId, AccessControl entity) {
return dataBaseAdapter.getAccessControlByRemoteIdDirectly(accountId, entity.getEntity().getId());
@@ -34,7 +97,26 @@ public class AccessControlDataProvider extends AbstractSyncDataProvider<AccessCo
@Override
public long createInDB(DataBaseAdapter dataBaseAdapter, long accountId, AccessControl entity) {
prepareUser(dataBaseAdapter, accountId, entity);
- return dataBaseAdapter.createAccessControl(accountId, entity);
+ long newId = dataBaseAdapter.createAccessControl(accountId, entity);
+ entity.setLocalId(newId);
+ handleGroupMemberships(dataBaseAdapter, entity);
+ return newId;
+ }
+
+ private void handleGroupMemberships(DataBaseAdapter dataBaseAdapter, AccessControl entity) {
+ if (entity.getType() != TYPE_GROUP) {
+ return;
+ }
+ dataBaseAdapter.deleteGroupMembershipsOfGroup(entity.getUser().getLocalId());
+ if (entity.getGroupMemberUIDs() == null) {
+ return;
+ }
+ for (String groupMemberUID : entity.getGroupMemberUIDs().getUids()) {
+ User member = dataBaseAdapter.getUserByUidDirectly(entity.getAccountId(), groupMemberUID);
+ if (member != null) {
+ dataBaseAdapter.addUserToGroup(entity.getUserId(), member.getLocalId());
+ }
+ }
}
private void prepareUser(DataBaseAdapter dataBaseAdapter, long accountId, AccessControl entity) {
@@ -42,6 +124,7 @@ public class AccessControlDataProvider extends AbstractSyncDataProvider<AccessCo
if (user == null) {
long userId = dataBaseAdapter.createUser(accountId, entity.getUser());
entity.setUserId(userId);
+ entity.getUser().setLocalId(userId);
} else {
entity.setUserId(user.getLocalId());
entity.getUser().setLocalId(user.getLocalId());
@@ -52,7 +135,9 @@ public class AccessControlDataProvider extends AbstractSyncDataProvider<AccessCo
@Override
public void updateInDB(DataBaseAdapter dataBaseAdapter, long accountId, AccessControl entity, boolean setStatus) {
prepareUser(dataBaseAdapter, accountId, entity);
+ entity.setBoardId(board.getLocalId());
dataBaseAdapter.updateAccessControl(entity, setStatus);
+ handleGroupMemberships(dataBaseAdapter, entity);
}
@Override
@@ -64,6 +149,9 @@ public class AccessControlDataProvider extends AbstractSyncDataProvider<AccessCo
public void createOnServer(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, IResponseCallback<AccessControl> responder, AccessControl entity) {
AccessControl acl = new AccessControl(entity);
acl.setBoardId(board.getBoard().getId());
+ if (acl.getUser() == null && acl.getUserId() != null) {
+ acl.setUser(dataBaseAdapter.getUserByLocalIdDirectly(acl.getUserId()));
+ }
serverAdapter.createAccessControl(board.getBoard().getId(), acl, responder);
}
@@ -79,6 +167,7 @@ public class AccessControlDataProvider extends AbstractSyncDataProvider<AccessCo
@Override
public void deletePhysicallyInDB(DataBaseAdapter dataBaseAdapter, long accountId, AccessControl accessControl) {
+ dataBaseAdapter.deleteGroupMembershipsOfGroup(accessControl.getUser().getLocalId());
dataBaseAdapter.deleteAccessControl(accessControl, false);
}
@@ -88,7 +177,7 @@ public class AccessControlDataProvider extends AbstractSyncDataProvider<AccessCo
}
@Override
- public List<AccessControl> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Date lastSync) {
+ public List<AccessControl> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync) {
return dataBaseAdapter.getLocallyChangedAccessControl(accountId, board.getLocalId());
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/ActivityDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/ActivityDataProvider.java
index a3785ca78..5a7abf732 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/ActivityDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/ActivityDataProvider.java
@@ -1,7 +1,9 @@
package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
-import java.util.ArrayList;
-import java.util.Date;
+import androidx.annotation.NonNull;
+
+import java.time.Instant;
+import java.util.Collections;
import java.util.List;
import it.niedermann.nextcloud.deck.api.IResponseCallback;
@@ -12,15 +14,16 @@ import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.DataBaseAdapter
public class ActivityDataProvider extends AbstractSyncDataProvider<Activity> {
- protected Card card;
+ @NonNull
+ private final Card card;
- public ActivityDataProvider(AbstractSyncDataProvider<?> parent, Card card) {
+ public ActivityDataProvider(AbstractSyncDataProvider<?> parent, @NonNull Card card) {
super(parent);
this.card = card;
}
@Override
- public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<Activity>> responder, Date lastSync) {
+ public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<Activity>> responder, Instant lastSync) {
serverAdapter.getActivitiesForCard(card.getId(), responder);
}
@@ -65,7 +68,7 @@ public class ActivityDataProvider extends AbstractSyncDataProvider<Activity> {
}
@Override
- public List<Activity> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Date lastSync) {
- return new ArrayList<>();
+ public List<Activity> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync) {
+ return Collections.emptyList();
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AttachmentDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AttachmentDataProvider.java
index 37e00dada..781f7b1f4 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AttachmentDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AttachmentDataProvider.java
@@ -4,7 +4,7 @@ import android.net.Uri;
import java.io.File;
import java.io.IOException;
-import java.util.Date;
+import java.time.Instant;
import java.util.List;
import it.niedermann.nextcloud.deck.DeckLog;
@@ -32,7 +32,7 @@ public class AttachmentDataProvider extends AbstractSyncDataProvider<Attachment>
}
@Override
- public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<Attachment>> responder, Date lastSync) {
+ public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<Attachment>> responder, Instant lastSync) {
responder.onResponse(attachments);
}
@@ -104,7 +104,7 @@ public class AttachmentDataProvider extends AbstractSyncDataProvider<Attachment>
}
@Override
- public List<Attachment> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Date lastSync) {
+ public List<Attachment> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync) {
return dataBaseAdapter.getLocallyChangedAttachmentsByLocalCardIdDirectly(accountId, card.getLocalId());
}
@@ -120,7 +120,7 @@ public class AttachmentDataProvider extends AbstractSyncDataProvider<Attachment>
dataBaseAdapter.deleteAttachment(accountId, attachment, false);
}
for (Attachment attachment : entitiesFromServer) {
- if (attachment.getDeletedAt() != null && attachment.getDeletedAt().getTime() != 0) {
+ if (attachment.getDeletedAt() != null && attachment.getDeletedAt().toEpochMilli() != 0) {
Attachment toDelete = dataBaseAdapter.getAttachmentByRemoteIdDirectly(accountId, attachment.getId());
if (toDelete != null) {
dataBaseAdapter.deleteAttachment(accountId, toDelete, false);
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/BoardDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/BoardDataProvider.java
index 98163029f..f5e071adb 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/BoardDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/BoardDataProvider.java
@@ -1,14 +1,16 @@
package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
+import android.annotation.SuppressLint;
+
+import com.nextcloud.android.sso.api.ParsedResponse;
+
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.api.IResponseCallback;
import it.niedermann.nextcloud.deck.model.AccessControl;
import it.niedermann.nextcloud.deck.model.Board;
@@ -19,16 +21,33 @@ import it.niedermann.nextcloud.deck.model.full.FullStack;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.ServerAdapter;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.DataBaseAdapter;
import it.niedermann.nextcloud.deck.persistence.sync.helpers.SyncHelper;
+import it.niedermann.nextcloud.deck.persistence.sync.helpers.util.AsyncUtil;
public class BoardDataProvider extends AbstractSyncDataProvider<FullBoard> {
- public BoardDataProvider(){
+ public BoardDataProvider() {
super(null);
}
@Override
- public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<FullBoard>> responder, Date lastSync) {
- serverAdapter.getBoards(responder);
+ public void getAllFromServer(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, IResponseCallback<List<FullBoard>> responder, Instant lastSync) {
+ serverAdapter.getBoards(new IResponseCallback<ParsedResponse<List<FullBoard>>>(responder.getAccount()) {
+ @Override
+ public void onResponse(ParsedResponse<List<FullBoard>> response) {
+ String etag = response.getHeaders().get("ETag");
+ if (etag != null && !etag.equals(account.getBoardsEtag())) {
+ account.setBoardsEtag(etag);
+ dataBaseAdapter.updateAccount(account);
+ }
+ responder.onResponse(response.getResponse());
+ }
+
+ @SuppressLint("MissingSuperCall")
+ @Override
+ public void onError(Throwable throwable) {
+ responder.onError(throwable);
+ }
+ });
}
@Override
@@ -39,27 +58,65 @@ public class BoardDataProvider extends AbstractSyncDataProvider<FullBoard> {
@Override
public long createInDB(DataBaseAdapter dataBaseAdapter, long accountId, FullBoard entity) {
handleOwner(dataBaseAdapter, accountId, entity);
- return dataBaseAdapter.createBoardDirectly(accountId, entity.getBoard());
+ Long localId = dataBaseAdapter.createBoardDirectly(accountId, entity.getBoard());
+ entity.getBoard().setLocalId(localId);
+ handleUsers(dataBaseAdapter, accountId, entity);
+ return localId;
}
private void handleOwner(DataBaseAdapter dataBaseAdapter, long accountId, FullBoard entity) {
- if (entity.getOwner()!=null) {
- User remoteOwner = entity.getOwner();
- User owner = dataBaseAdapter.getUserByUidDirectly(accountId, remoteOwner.getUid());
- if (owner == null){
- dataBaseAdapter.createUser(accountId, remoteOwner);
- } else {
- dataBaseAdapter.updateUser(accountId, remoteOwner, false);
- }
- owner = dataBaseAdapter.getUserByUidDirectly(accountId, remoteOwner.getUid());
+ if (entity.getOwner() != null) {
+ User owner = createOrUpdateUser(dataBaseAdapter, accountId, entity.getOwner());
entity.getBoard().setOwnerId(owner.getLocalId());
}
}
+ private void handleUsers(DataBaseAdapter dataBaseAdapter, long accountId, FullBoard entity) {
+ dataBaseAdapter.deleteBoardMembershipsOfBoard(entity.getLocalId());
+ if (entity.getUsers() != null && !entity.getUsers().isEmpty()) {
+ for (User user : entity.getUsers()) {
+ if (user == null) {
+ continue;
+ }
+ User existing = createOrUpdateUser(dataBaseAdapter, accountId, user);
+ dataBaseAdapter.addUserToBoard(existing.getLocalId(), entity.getLocalId());
+ }
+ }
+ }
+
+ private User createOrUpdateUser(DataBaseAdapter dataBaseAdapter, long accountId, User remoteUser) {
+ User owner = dataBaseAdapter.getUserByUidDirectly(accountId, remoteUser.getUid());
+ if (owner == null) {
+ dataBaseAdapter.createUser(accountId, remoteUser);
+ } else {
+ dataBaseAdapter.updateUser(accountId, remoteUser, false);
+ }
+ return dataBaseAdapter.getUserByUidDirectly(accountId, remoteUser.getUid());
+ }
+
@Override
public void updateInDB(DataBaseAdapter dataBaseAdapter, long accountId, FullBoard entity, boolean setStatus) {
+ handleDefaultLabels(dataBaseAdapter, entity);
handleOwner(dataBaseAdapter, accountId, entity);
dataBaseAdapter.updateBoard(entity.getBoard(), setStatus);
+ handleUsers(dataBaseAdapter, accountId, entity);
+ }
+
+ private void handleDefaultLabels(DataBaseAdapter dataBaseAdapter, FullBoard entity) {
+ // ## merge labels (created at board creation):
+ // the server creates four default labels. if a board is copied, they will also be copied. At sync, after creating the board, the labels are already there.
+ // this merges the created default ones with the ones i already have.
+ if (entity != null && entity.getLabels() != null) {
+ for (Label label : entity.getLabels()) {
+ // does this label exist and unknown to server yet?
+ Label existing = dataBaseAdapter.getLabelByBoardIdAndTitleDirectly(entity.getLocalId(), label.getTitle());
+ if (existing != null && existing.getId() == null) {
+ // take our label and lets say it IS the same as on server (but use the local color, no matter what the server says)
+ existing.setId(label.getId());
+ dataBaseAdapter.updateLabel(existing, false);
+ }
+ }
+ }
}
@Override
@@ -71,19 +128,19 @@ public class BoardDataProvider extends AbstractSyncDataProvider<FullBoard> {
@Override
public void goDeeper(SyncHelper syncHelper, FullBoard existingEntity, FullBoard entityFromServer, IResponseCallback<Boolean> callback) {
List<Label> labels = entityFromServer.getLabels();
- if (labels != null && !labels.isEmpty()){
+ if (labels != null && !labels.isEmpty()) {
syncHelper.doSyncFor(new LabelDataProvider(this, existingEntity.getBoard(), labels));
}
List<AccessControl> acl = entityFromServer.getParticipants();
- if (acl != null && !acl.isEmpty()){
- for (AccessControl ac : acl){
+ if (acl != null && !acl.isEmpty()) {
+ for (AccessControl ac : acl) {
ac.setBoardId(existingEntity.getLocalId());
}
syncHelper.doSyncFor(new AccessControlDataProvider(this, existingEntity, acl));
}
- if (entityFromServer.getStacks() != null && !entityFromServer.getStacks().isEmpty()){
+ if (entityFromServer.getStacks() != null && !entityFromServer.getStacks().isEmpty()) {
syncHelper.doSyncFor(new StackDataProvider(this, existingEntity));
}
}
@@ -94,7 +151,7 @@ public class BoardDataProvider extends AbstractSyncDataProvider<FullBoard> {
}
@Override
- public List<FullBoard> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Date lastSync) {
+ public List<FullBoard> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync) {
return dataBaseAdapter.getLocallyChangedBoards(accountId);
}
@@ -102,21 +159,17 @@ public class BoardDataProvider extends AbstractSyncDataProvider<FullBoard> {
public void goDeeperForUpSync(SyncHelper syncHelper, ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, IResponseCallback<Boolean> callback) {
Long accountId = callback.getAccount().getId();
List<Label> locallyChangedLabels = dataBaseAdapter.getLocallyChangedLabels(accountId);
- CountDownLatch countDownLatch = new CountDownLatch(locallyChangedLabels.size());
- for (Label label : locallyChangedLabels) {
- Board board = dataBaseAdapter.getBoardByLocalIdDirectly(label.getBoardId());
- label.setBoardId(board.getId());
- syncHelper.doUpSyncFor(new LabelDataProvider(this, board, Collections.singletonList(label)), countDownLatch);
- }
- try {
- countDownLatch.await();
- } catch (InterruptedException e) {
- DeckLog.logError(e);
- }
+ AsyncUtil.awaitAsyncWork(locallyChangedLabels.size(), (countDownLatch) -> {
+ for (Label label : locallyChangedLabels) {
+ Board board = dataBaseAdapter.getBoardByLocalIdDirectly(label.getBoardId());
+ label.setBoardId(board.getId());
+ syncHelper.doUpSyncFor(new LabelDataProvider(this, board, Collections.singletonList(label)), countDownLatch);
+ }
+ });
List<Long> localBoardIDsWithChangedACL = dataBaseAdapter.getBoardIDsOfLocallyChangedAccessControl(accountId);
for (Long boardId : localBoardIDsWithChangedACL) {
- syncHelper.doUpSyncFor(new AccessControlDataProvider(this, dataBaseAdapter.getFullBoardByLocalIdDirectly(accountId, boardId) ,new ArrayList<>()));
+ syncHelper.doUpSyncFor(new AccessControlDataProvider(this, dataBaseAdapter.getFullBoardByLocalIdDirectly(accountId, boardId), new ArrayList<>()));
}
Set<Long> syncedBoards = new HashSet<>();
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/CardDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/CardDataProvider.java
index 03e02bd70..0ceac2cd8 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/CardDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/CardDataProvider.java
@@ -1,12 +1,15 @@
package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
+import android.annotation.SuppressLint;
+
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Date;
import java.util.List;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.api.IResponseCallback;
+import it.niedermann.nextcloud.deck.exceptions.DeckException;
import it.niedermann.nextcloud.deck.exceptions.OfflineException;
import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.model.Attachment;
@@ -37,7 +40,7 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
}
@Override
- public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<FullCard>> responder, Date lastSync) {
+ public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<FullCard>> responder, Instant lastSync) {
List<FullCard> result = new ArrayList<>();
if (stack.getCards() == null || stack.getCards().isEmpty()) {
@@ -54,6 +57,7 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
}
}
+ @SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
responder.onError(throwable);
@@ -70,7 +74,7 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
@Override
public long createInDB(DataBaseAdapter dataBaseAdapter, long accountId, FullCard entity) {
fixRelations(dataBaseAdapter, accountId, entity);
- return dataBaseAdapter.createCard(accountId, entity.getCard());
+ return dataBaseAdapter.createCardDirectly(accountId, entity.getCard());
}
protected CardUpdate toCardUpdate(FullCard card) {
@@ -144,10 +148,17 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
} else {
DeckLog.verbose("Comments - Version is too low, DONT SYNC");
}
+ syncHelper.doSyncFor(new OcsProjectDataProvider(this, existingEntity.getCard()));
}
@Override
public void createOnServer(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, IResponseCallback<FullCard> responder, FullCard entity) {
+ if (stack.getId() == null) {
+ responder.onError(new DeckException(DeckException.Hint.DEPENDENCY_NOT_SYNCED_YET, "Stack \"" +
+ stack.getStack().getTitle() + "\" for Card \"" + entity.getCard().getTitle() +
+ "\" is not synced yet. Perform a full sync (pull to refresh) as soon as you are online again."));
+ return;
+ }
entity.getCard().setStackId(stack.getId());
serverAdapter.createCard(board.getId(), stack.getId(), entity.getCard(), responder);
}
@@ -170,7 +181,7 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
}
@Override
- public List<FullCard> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Date lastSync) {
+ public List<FullCard> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync) {
if (board == null || stack == null) {
// no cards changed!
// (see call from StackDataProvider: goDeeperForUpSync called with null for board.)
@@ -184,8 +195,13 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
public void goDeeperForUpSync(SyncHelper syncHelper, ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, IResponseCallback<Boolean> callback) {
FullStack stack;
Board board;
+ List<JoinCardWithLabel> changedLabels;
+ if (this.stack == null) {
+ changedLabels = dataBaseAdapter.getAllChangedLabelJoins();
+ } else {
+ changedLabels = dataBaseAdapter.getAllChangedLabelJoinsForStack(this.stack.getLocalId());
+ }
- List<JoinCardWithLabel> changedLabels = dataBaseAdapter.getAllChangedJoins();
Account account = callback.getAccount();
for (JoinCardWithLabel changedLabelLocal : changedLabels) {
Card card = dataBaseAdapter.getCardByLocalIdDirectly(account.getId(), changedLabelLocal.getCardId());
@@ -201,7 +217,7 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
board = this.board;
}
- JoinCardWithLabel changedLabel = dataBaseAdapter.getRemoteIdsForJoin(changedLabelLocal.getCardId(), changedLabelLocal.getLabelId());
+ JoinCardWithLabel changedLabel = dataBaseAdapter.getAllChangedLabelJoinsWithRemoteIDs(changedLabelLocal.getCardId(), changedLabelLocal.getLabelId());
if (changedLabel.getStatusEnum() == DBStatus.LOCAL_DELETED) {
if (changedLabel.getLabelId() == null || changedLabel.getCardId() == null) {
dataBaseAdapter.deleteJoinedLabelForCardPhysicallyByRemoteIDs(account.getId(), changedLabel.getCardId(), changedLabel.getLabelId());
@@ -229,11 +245,22 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
}
}
- List<JoinCardWithUser> deletedUsers = dataBaseAdapter.getAllDeletedUserJoinsWithRemoteIDs();
- for (JoinCardWithUser deletedUser : deletedUsers) {
- Card card = dataBaseAdapter.getCardByRemoteIdDirectly(account.getId(), deletedUser.getCardId());
+
+ List<JoinCardWithUser> changedUsers;
+ if (this.stack == null) {
+ changedUsers = dataBaseAdapter.getAllChangedUserJoinsWithRemoteIDs();
+ } else {
+ changedUsers = dataBaseAdapter.getAllChangedUserJoinsWithRemoteIDsForStack(this.stack.getLocalId());
+ }
+ for (JoinCardWithUser changedUser : changedUsers) {
+ // not already known to server?
+ if (changedUser.getCardId() == null) {
+ //skip for now
+ continue;
+ }
+ Card card = dataBaseAdapter.getCardByRemoteIdDirectly(account.getId(), changedUser.getCardId());
if (this.stack == null) {
- stack = dataBaseAdapter.getFullStackByLocalIdDirectly(card.getLocalId());
+ stack = dataBaseAdapter.getFullStackByLocalIdDirectly(card.getStackId());
} else {
stack = this.stack;
}
@@ -243,16 +270,16 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
} else {
board = this.board;
}
- User user = dataBaseAdapter.getUserByLocalIdDirectly(deletedUser.getUserId());
- if (deletedUser.getStatusEnum() == DBStatus.LOCAL_DELETED) {
- serverAdapter.unassignUserFromCard(board.getId(), stack.getId(), deletedUser.getCardId(), user.getUid(), new IResponseCallback<Void>(account) {
+ User user = dataBaseAdapter.getUserByLocalIdDirectly(changedUser.getUserId());
+ if (changedUser.getStatusEnum() == DBStatus.LOCAL_DELETED) {
+ serverAdapter.unassignUserFromCard(board.getId(), stack.getId(), changedUser.getCardId(), user.getUid(), new IResponseCallback<Void>(account) {
@Override
public void onResponse(Void response) {
- dataBaseAdapter.deleteJoinedUserForCardPhysicallyByRemoteIDs(account.getId(), deletedUser.getCardId(), user.getUid());
+ dataBaseAdapter.deleteJoinedUserForCardPhysicallyByRemoteIDs(account.getId(), changedUser.getCardId(), user.getUid());
}
});
- } else if (deletedUser.getStatusEnum() == DBStatus.LOCAL_EDITED) {
- serverAdapter.assignUserToCard(board.getId(), stack.getId(), deletedUser.getCardId(), user.getUid(), new IResponseCallback<Void>(account) {
+ } else if (changedUser.getStatusEnum() == DBStatus.LOCAL_EDITED) {
+ serverAdapter.assignUserToCard(board.getId(), stack.getId(), changedUser.getCardId(), user.getUid(), new IResponseCallback<Void>(account) {
@Override
public void onResponse(Void response) {
dataBaseAdapter.setStatusForJoinCardWithUser(card.getLocalId(), user.getLocalId(), DBStatus.UP_TO_DATE.getId());
@@ -261,7 +288,12 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
}
}
- List<Attachment> attachments = dataBaseAdapter.getLocallyChangedAttachmentsDirectly(account.getId());
+ List<Attachment> attachments;
+ if (this.stack == null) {
+ attachments = dataBaseAdapter.getLocallyChangedAttachmentsDirectly(account.getId());
+ } else {
+ attachments = dataBaseAdapter.getLocallyChangedAttachmentsForStackDirectly(this.stack.getLocalId());
+ }
for (Attachment attachment : attachments) {
FullCard card = dataBaseAdapter.getFullCardByLocalIdDirectly(account.getId(), attachment.getCardId());
stack = dataBaseAdapter.getFullStackByLocalIdDirectly(card.getCard().getStackId());
@@ -269,7 +301,12 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
syncHelper.doUpSyncFor(new AttachmentDataProvider(this, board, stack.getStack(), card, Collections.singletonList(attachment)));
}
- List<Card> cardsWithChangedComments = dataBaseAdapter.getCardsWithLocallyChangedCommentsDirectly(account.getId());
+ List<Card> cardsWithChangedComments;
+ if (this.stack == null) {
+ cardsWithChangedComments = dataBaseAdapter.getCardsWithLocallyChangedCommentsDirectly(account.getId());
+ } else {
+ cardsWithChangedComments = dataBaseAdapter.getCardsWithLocallyChangedCommentsForStackDirectly(this.stack.getLocalId());
+ }
for (Card card : cardsWithChangedComments) {
syncHelper.doUpSyncFor(new DeckCommentsDataProvider(this, card));
}
@@ -279,7 +316,7 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
@Override
public void handleDeletes(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, List<FullCard> entitiesFromServer) {
- List<FullCard> localCards = dataBaseAdapter.getFullCardsForStackDirectly(accountId, stack.getLocalId());
+ List<FullCard> localCards = dataBaseAdapter.getFullCardsForStackDirectly(accountId, stack.getLocalId(), null);
List<FullCard> delta = findDelta(entitiesFromServer, localCards);
for (FullCard cardToDelete : delta) {
if (cardToDelete.getId() == null) {
@@ -294,6 +331,7 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
// do not delete, it's still there and was just moved!
}
+ @SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
if (!(throwable instanceof OfflineException)) {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/DeckCommentsDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/DeckCommentsDataProvider.java
index 5b9be120f..06ef3030d 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/DeckCommentsDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/DeckCommentsDataProvider.java
@@ -1,8 +1,8 @@
package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Date;
import java.util.List;
import it.niedermann.nextcloud.deck.DeckLog;
@@ -24,10 +24,13 @@ public class DeckCommentsDataProvider extends AbstractSyncDataProvider<OcsCommen
}
@Override
- public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<OcsComment>> responder, Date lastSync) {
+ public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<OcsComment>> responder, Instant lastSync) {
serverAdapter.getCommentsForRemoteCardId(card.getId(), new IResponseCallback<OcsComment>(responder.getAccount()) {
@Override
public void onResponse(OcsComment response) {
+ if (response == null) {
+ response = new OcsComment();
+ }
List<OcsComment> comments = response.split();
Collections.sort(comments, (o1, o2) -> o1.getSingle().getCreationDateTime().compareTo(o2.getSingle().getCreationDateTime()));
verifyCommentListIntegrity(comments);
@@ -131,7 +134,7 @@ public class DeckCommentsDataProvider extends AbstractSyncDataProvider<OcsCommen
}
@Override
- public List<OcsComment> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Date lastSync) {
+ public List<OcsComment> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync) {
return new OcsComment(dataBaseAdapter.getLocallyChangedCommentsByLocalCardIdDirectly(accountId, card.getLocalId())).split();
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/LabelDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/LabelDataProvider.java
index 403d71f87..caa7c68e6 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/LabelDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/LabelDataProvider.java
@@ -1,8 +1,9 @@
package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
-import java.util.Date;
+import java.time.Instant;
import java.util.List;
+import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.api.IResponseCallback;
import it.niedermann.nextcloud.deck.exceptions.HandledServerErrors;
import it.niedermann.nextcloud.deck.model.Board;
@@ -12,14 +13,14 @@ import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.DataBaseAdapter
public class LabelDataProvider extends AbstractSyncDataProvider<Label> {
- private List<Label> labels;
- private Board board;
+ private final List<Label> labels;
+ private final Board board;
public LabelDataProvider(AbstractSyncDataProvider<?> parent, Board board, List<Label> labels) {
super(parent);
this.board = board;
this.labels = labels;
- if (this.labels!= null && board != null){
+ if (this.labels != null && board != null) {
for (Label label : labels) {
label.setBoardId(board.getLocalId());
}
@@ -27,7 +28,7 @@ public class LabelDataProvider extends AbstractSyncDataProvider<Label> {
}
@Override
- public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<Label>> responder, Date lastSync) {
+ public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<Label>> responder, Instant lastSync) {
responder.onResponse(labels);
}
@@ -44,7 +45,7 @@ public class LabelDataProvider extends AbstractSyncDataProvider<Label> {
updateInDB(dataBaseAdapter, accountId, entity, false);
return entity.getLocalId();
} else {
- return dataBaseAdapter.createLabel(accountId, entity);
+ return dataBaseAdapter.createLabelDirectly(accountId, entity);
}
}
@@ -53,7 +54,7 @@ public class LabelDataProvider extends AbstractSyncDataProvider<Label> {
dataBaseAdapter.updateLabel(entity, setStatus);
}
- private IResponseCallback<Label> getLabelUniqueHandler(DataBaseAdapter dataBaseAdapter, Label entitiy, IResponseCallback<Label> responder){
+ private IResponseCallback<Label> getLabelUniqueHandler(DataBaseAdapter dataBaseAdapter, Label entitiy, IResponseCallback<Label> responder) {
return new IResponseCallback<Label>(responder.getAccount()) {
@Override
public void onResponse(Label response) {
@@ -62,10 +63,13 @@ public class LabelDataProvider extends AbstractSyncDataProvider<Label> {
@Override
public void onError(Throwable throwable) {
- if (HandledServerErrors.LABELS_TITLE_MUST_BE_UNIQUE == HandledServerErrors.fromThrowable(throwable)){
+ if (HandledServerErrors.LABELS_TITLE_MUST_BE_UNIQUE == HandledServerErrors.fromThrowable(throwable)) {
+ DeckLog.log(throwable.getCause().getMessage() + ": " + entitiy.toString());
dataBaseAdapter.deleteLabelPhysically(entitiy);
+ responder.onResponse(entitiy);
+ } else {
+ responder.onError(throwable);
}
- responder.onError(throwable);
}
};
}
@@ -92,7 +96,7 @@ public class LabelDataProvider extends AbstractSyncDataProvider<Label> {
}
@Override
- public List<Label> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Date lastSync) {
+ public List<Label> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync) {
return labels;
}
@@ -105,7 +109,7 @@ public class LabelDataProvider extends AbstractSyncDataProvider<Label> {
public void handleDeletes(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, List<Label> entitiesFromServer) {
List<Label> deletedLabels = findDelta(labels, dataBaseAdapter.getFullBoardByLocalIdDirectly(accountId, board.getLocalId()).getLabels());
for (Label deletedLabel : deletedLabels) {
- if (deletedLabel.getId()!=null){
+ if (deletedLabel.getId() != null) {
// preserve new, unsynced card.
dataBaseAdapter.deleteLabelPhysically(deletedLabel);
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/OcsProjectDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/OcsProjectDataProvider.java
new file mode 100644
index 000000000..b9bfb8b31
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/OcsProjectDataProvider.java
@@ -0,0 +1,100 @@
+package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
+
+import java.time.Instant;
+import java.util.Collections;
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.DeckLog;
+import it.niedermann.nextcloud.deck.api.IResponseCallback;
+import it.niedermann.nextcloud.deck.model.Card;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProject;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProjectList;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProjectResource;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.ServerAdapter;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.DataBaseAdapter;
+
+public class OcsProjectDataProvider extends AbstractSyncDataProvider<OcsProject> {
+ private Card card;
+
+ public OcsProjectDataProvider(AbstractSyncDataProvider<?> parent, Card card) {
+ super(parent);
+ this.card = card;
+ }
+
+ @Override
+ public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<OcsProject>> responder, Instant lastSync) {
+ serverAdapter.getProjectsForCard(card.getId(), new IResponseCallback<OcsProjectList>(responder.getAccount()) {
+ @Override
+ public void onResponse(OcsProjectList response) {
+ responder.onResponse(response.getProjects());
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ // dont break the sync!
+ DeckLog.logError(throwable);
+ responder.onResponse(Collections.emptyList());
+ }
+ });
+ }
+
+ @Override
+ public OcsProject getSingleFromDB(DataBaseAdapter dataBaseAdapter, long accountId, OcsProject entity) {
+ return dataBaseAdapter.getProjectByRemoteIdDirectly(accountId, entity.getId());
+ }
+
+ @Override
+ public long createInDB(DataBaseAdapter dataBaseAdapter, long accountId, OcsProject entity) {
+ Long newId = dataBaseAdapter.createProjectDirectly(accountId, entity);
+ entity.setLocalId(newId);
+ updateResources(dataBaseAdapter, accountId, entity);
+ return newId;
+ }
+
+ @Override
+ public void updateInDB(DataBaseAdapter dataBaseAdapter, long accountId, OcsProject entity, boolean setStatus) {
+ dataBaseAdapter.updateProjectDirectly(accountId, entity);
+ dataBaseAdapter.deleteProjectResourcesForProjectIdDirectly(entity.getLocalId());
+ updateResources(dataBaseAdapter, accountId, entity);
+ }
+
+ @Override
+ public void deleteInDB(DataBaseAdapter dataBaseAdapter, long accountId, OcsProject ocsProject) {
+ if (ocsProject != null && ocsProject.getLocalId() != null) {
+ dataBaseAdapter.deleteProjectDirectly(ocsProject);
+ }
+ }
+
+ private void updateResources(DataBaseAdapter dataBaseAdapter, Long accountId, OcsProject entity) {
+ if (entity.getResources() != null) {
+ for (OcsProjectResource resource : entity.getResources()) {
+ resource.setProjectId(entity.getLocalId());
+ resource.setLocalId(dataBaseAdapter.createProjectResourceDirectly(accountId, resource));
+ if ("deck-card".equals(resource.getType())) {
+ dataBaseAdapter.assignCardToProjectIfMissng(accountId, entity.getLocalId(), resource.getId());
+ }
+ }
+ }
+ }
+
+ @Override
+ public void createOnServer(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, IResponseCallback<OcsProject> responder, OcsProject entity) {
+ // Do Nothing
+ }
+
+ @Override
+ public void updateOnServer(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, IResponseCallback<OcsProject> callback, OcsProject entity) {
+ // Do Nothing
+ }
+
+ @Override
+ public void deleteOnServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<Void> callback, OcsProject entity, DataBaseAdapter dataBaseAdapter) {
+ // Do Nothing
+ }
+
+ @Override
+ public List<OcsProject> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync) {
+ return Collections.emptyList();
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/StackDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/StackDataProvider.java
index a3c123afd..93761d616 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/StackDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/StackDataProvider.java
@@ -1,12 +1,13 @@
package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
+import java.time.Instant;
import java.util.Collections;
-import java.util.Date;
-import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.ConcurrentSkipListSet;
import it.niedermann.nextcloud.deck.api.IResponseCallback;
+import it.niedermann.nextcloud.deck.exceptions.DeckException;
import it.niedermann.nextcloud.deck.model.Board;
import it.niedermann.nextcloud.deck.model.Card;
import it.niedermann.nextcloud.deck.model.full.FullBoard;
@@ -19,13 +20,15 @@ import it.niedermann.nextcloud.deck.persistence.sync.helpers.SyncHelper;
public class StackDataProvider extends AbstractSyncDataProvider<FullStack> {
private FullBoard board;
+ private Set<Long> syncedStacks = new ConcurrentSkipListSet<>();
+
public StackDataProvider(AbstractSyncDataProvider<?> parent, FullBoard board) {
super(parent);
this.board = board;
}
@Override
- public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<FullStack>> responder, Date lastSync) {
+ public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<FullStack>> responder, Instant lastSync) {
serverAdapter.getStacks(board.getId(), responder);
}
@@ -54,12 +57,12 @@ public class StackDataProvider extends AbstractSyncDataProvider<FullStack> {
@Override
public void goDeeper(SyncHelper syncHelper, FullStack existingEntity, FullStack entityFromServer, IResponseCallback<Boolean> callback) {
- boolean serverHasCards = entityFromServer.getCards() != null && !entityFromServer.getCards().isEmpty();
- boolean weHaveCards = existingEntity.getCards() != null && !existingEntity.getCards().isEmpty();
- if (serverHasCards || weHaveCards){
+ boolean serverHasCards = entityFromServer.getCards() != null && !entityFromServer.getCards().isEmpty();
+ boolean weHaveCards = existingEntity.getCards() != null && !existingEntity.getCards().isEmpty();
+ if (serverHasCards || weHaveCards) {
existingEntity.setCards(entityFromServer.getCards());
List<Card> cards = existingEntity.getCards();
- if (cards != null ){
+ if (cards != null) {
for (Card card : cards) {
card.setStackId(existingEntity.getLocalId());
}
@@ -72,6 +75,9 @@ public class StackDataProvider extends AbstractSyncDataProvider<FullStack> {
@Override
public void createOnServer(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, IResponseCallback<FullStack> responder, FullStack entity) {
+ if (board.getId() == null) {
+ throw new DeckException(DeckException.Hint.DEPENDENCY_NOT_SYNCED_YET, "Board for this stack is not synced yet. Perform a full sync (pull to referesh) as soon as you are online again.");
+ }
entity.getStack().setBoardId(board.getId());
serverAdapter.createStack(board.getBoard(), entity.getStack(), responder);
}
@@ -89,8 +95,8 @@ public class StackDataProvider extends AbstractSyncDataProvider<FullStack> {
}
@Override
- public List<FullStack> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Date lastSync) {
- if (board == null){
+ public List<FullStack> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync) {
+ if (board == null) {
// no stacks changed!
// (see call from BoardDataProvider: goDeeperForUpSync called with null for board.)
// so we can just skip this one and proceed with cards.
@@ -103,16 +109,19 @@ public class StackDataProvider extends AbstractSyncDataProvider<FullStack> {
@Override
public void goDeeperForUpSync(SyncHelper syncHelper, ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, IResponseCallback<Boolean> callback) {
List<FullCard> changedCards = dataBaseAdapter.getLocallyChangedCardsDirectly(callback.getAccount().getId());
- Set<Long> syncedStacks = new HashSet<>();
- if (changedCards != null && changedCards.size() > 0){
+ if (changedCards != null && !changedCards.isEmpty()) {
for (FullCard changedCard : changedCards) {
long stackId = changedCard.getCard().getStackId();
- boolean added = syncedStacks.add(stackId);
- if (added) {
+ boolean alreadySynced = syncedStacks.contains(stackId);
+ if (!alreadySynced) {
FullStack stack = dataBaseAdapter.getFullStackByLocalIdDirectly(stackId);
- Board board = dataBaseAdapter.getBoardByLocalIdDirectly(stack.getStack().getBoardId());
- changedCard.getCard().setStackId(stack.getId());
- syncHelper.doUpSyncFor(new CardDataProvider(this, board, stack));
+ // already synced and known to server?
+ if (stack.getStack().getId() != null) {
+ syncedStacks.add(stackId);
+ Board board = dataBaseAdapter.getBoardByLocalIdDirectly(stack.getStack().getBoardId());
+ changedCard.getCard().setStackId(stack.getId());
+ syncHelper.doUpSyncFor(new CardDataProvider(this, board, stack));
+ }
}
}
} else {
@@ -132,7 +141,7 @@ public class StackDataProvider extends AbstractSyncDataProvider<FullStack> {
List<FullStack> localStacks = dataBaseAdapter.getFullStacksForBoardDirectly(accountId, board.getLocalId());
List<FullStack> delta = findDelta(entitiesFromServer, localStacks);
for (FullStack stackToDelete : delta) {
- if (stackToDelete.getId() == null){
+ if (stackToDelete.getId() == null) {
// not pushed up yet so:
continue;
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/UserDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/UserDataProvider.java
index 60c906bda..279ce9e55 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/UserDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/UserDataProvider.java
@@ -1,6 +1,6 @@
package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
-import java.util.Date;
+import java.time.Instant;
import java.util.List;
import it.niedermann.nextcloud.deck.api.IResponseCallback;
@@ -27,7 +27,7 @@ public class UserDataProvider extends AbstractSyncDataProvider<User> {
}
@Override
- public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<User>> responder, Date lastSync) {
+ public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<User>> responder, Instant lastSync) {
responder.onResponse(users);
}
@@ -67,7 +67,7 @@ public class UserDataProvider extends AbstractSyncDataProvider<User> {
}
@Override
- public List<User> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Date lastSync) {
+ public List<User> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync) {
return null;
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/partial/BoardWitAclDownSyncDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/partial/BoardWithAclDownSyncDataProvider.java
index 326d257ab..8516f0fc0 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/partial/BoardWitAclDownSyncDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/partial/BoardWithAclDownSyncDataProvider.java
@@ -11,7 +11,7 @@ import it.niedermann.nextcloud.deck.persistence.sync.helpers.SyncHelper;
import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.AccessControlDataProvider;
import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.BoardDataProvider;
-public class BoardWitAclDownSyncDataProvider extends BoardDataProvider {
+public class BoardWithAclDownSyncDataProvider extends BoardDataProvider {
@Override
public void goDeeper(SyncHelper syncHelper, FullBoard existingEntity, FullBoard entityFromServer, IResponseCallback<Boolean> callback) {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/partial/BoardWithStacksAndLabelsUpSyncDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/partial/BoardWithStacksAndLabelsUpSyncDataProvider.java
new file mode 100644
index 000000000..5f2c79ad3
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/partial/BoardWithStacksAndLabelsUpSyncDataProvider.java
@@ -0,0 +1,37 @@
+package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.partial;
+
+import java.time.Instant;
+import java.util.Collections;
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.api.IResponseCallback;
+import it.niedermann.nextcloud.deck.model.full.FullBoard;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.ServerAdapter;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.DataBaseAdapter;
+import it.niedermann.nextcloud.deck.persistence.sync.helpers.SyncHelper;
+import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.BoardDataProvider;
+
+public class BoardWithStacksAndLabelsUpSyncDataProvider extends BoardDataProvider {
+
+ private FullBoard board;
+
+ public BoardWithStacksAndLabelsUpSyncDataProvider(FullBoard boardToSync) {
+ board = boardToSync;
+ }
+
+ @Override
+ public List<FullBoard> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync) {
+ return Collections.singletonList(board);
+ }
+
+ @Override
+ public void goDeeper(SyncHelper syncHelper, FullBoard existingEntity, FullBoard entityFromServer, IResponseCallback<Boolean> callback) {
+ // do nothing!
+
+ }
+
+ @Override
+ public void handleDeletes(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, List<FullBoard> entitiesFromServer) {
+ // do nothing!
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/util/AsyncUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/util/AsyncUtil.java
new file mode 100644
index 000000000..faabda163
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/util/AsyncUtil.java
@@ -0,0 +1,21 @@
+package it.niedermann.nextcloud.deck.persistence.sync.helpers.util;
+
+import java.util.concurrent.CountDownLatch;
+
+import it.niedermann.nextcloud.deck.DeckLog;
+
+public class AsyncUtil {
+ public interface LatchCallback {
+ void doWork(CountDownLatch latch);
+ }
+
+ public static void awaitAsyncWork(int count, LatchCallback worker){
+ CountDownLatch countDownLatch = new CountDownLatch(count);
+ worker.doWork(countDownLatch);
+ try {
+ countDownLatch.await();
+ } catch (InterruptedException e) {
+ DeckLog.logError(e);
+ }
+ }
+}