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

github.com/stefan-niedermann/nextcloud-notes.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Niedermann <info@niedermann.it>2021-04-23 20:29:30 +0300
committerStefan Niedermann <info@niedermann.it>2021-04-26 11:47:11 +0300
commitd40f5dfa76601f154dc8b8a2ed9993ba09d796ad (patch)
tree437b8bfdf38c07f8eca782ffb53d08017a6e73f0
parentf95d91c272b986fc9489a3bf3e3cf3feeb171eb4 (diff)
#1170 Migrate NotesServerSyncHelper to NotesRepository
-rw-r--r--app/build.gradle1
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/accountswitcher/AccountSwitcherDialog.java10
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java34
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/edit/NotePreviewFragment.java12
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/edit/category/CategoryViewModel.java8
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/importaccount/ImportAccountViewModel.java8
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/main/MainViewModel.java99
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/manageaccounts/ManageAccountsActivity.java2
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/manageaccounts/ManageAccountsViewModel.java15
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/CapabilitiesWorker.java10
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesDatabase.java463
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java951
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesServerSyncHelper.java346
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesServerSyncTask.java30
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/SyncWorker.java8
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java1
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_13_14.java12
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_15_16.java12
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListViewModel.java12
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidget.java10
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidgetConfigurationActivity.java12
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidgetFactory.java18
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidget.java14
-rw-r--r--app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidgetFactory.java12
-rw-r--r--app/src/test/java/it/niedermann/owncloud/notes/persistence/NotesRepositoryTest.java151
25 files changed, 1282 insertions, 969 deletions
diff --git a/app/build.gradle b/app/build.gradle
index d5944241..a1f7d7da 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -102,6 +102,7 @@ dependencies {
// Testing
testImplementation 'junit:junit:4.13.2'
+ testImplementation 'org.mockito:mockito-core:3.9.0'
testImplementation 'org.robolectric:robolectric:4.5.1'
testImplementation 'androidx.test:core:1.3.0'
testImplementation 'androidx.test.ext:junit:1.1.2'
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/accountswitcher/AccountSwitcherDialog.java b/app/src/main/java/it/niedermann/owncloud/notes/accountswitcher/AccountSwitcherDialog.java
index 79467d86..f5643d86 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/accountswitcher/AccountSwitcherDialog.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/accountswitcher/AccountSwitcherDialog.java
@@ -21,7 +21,7 @@ import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.branding.BrandedDialogFragment;
import it.niedermann.owncloud.notes.databinding.DialogAccountSwitcherBinding;
import it.niedermann.owncloud.notes.manageaccounts.ManageAccountsActivity;
-import it.niedermann.owncloud.notes.persistence.NotesDatabase;
+import it.niedermann.owncloud.notes.persistence.NotesRepository;
import it.niedermann.owncloud.notes.persistence.entity.Account;
import static it.niedermann.owncloud.notes.branding.BrandingUtil.applyBrandToLayerDrawable;
@@ -33,7 +33,7 @@ public class AccountSwitcherDialog extends BrandedDialogFragment {
private static final String KEY_CURRENT_ACCOUNT_ID = "current_account_id";
- private NotesDatabase db;
+ private NotesRepository repo;
private DialogAccountSwitcherBinding binding;
private AccountSwitcherListener accountSwitcherListener;
private long currentAccountId;
@@ -55,7 +55,7 @@ public class AccountSwitcherDialog extends BrandedDialogFragment {
this.currentAccountId = args.getLong(KEY_CURRENT_ACCOUNT_ID);
}
- db = NotesDatabase.getInstance(requireActivity());
+ repo = NotesRepository.getInstance(requireContext());
}
@NonNull
@@ -63,7 +63,7 @@ public class AccountSwitcherDialog extends BrandedDialogFragment {
public Dialog onCreateDialog(Bundle savedInstanceState) {
binding = DialogAccountSwitcherBinding.inflate(requireActivity().getLayoutInflater());
- final LiveData<Account> account$ = db.getAccountDao().getAccountById$(currentAccountId);
+ final LiveData<Account> account$ = repo.getAccountById$(currentAccountId);
account$.observe(requireActivity(), (currentLocalAccount) -> {
account$.removeObservers(requireActivity());
@@ -81,7 +81,7 @@ public class AccountSwitcherDialog extends BrandedDialogFragment {
dismiss();
}));
binding.accountsList.setAdapter(adapter);
- final LiveData<List<Account>> localAccounts$ = db.getAccountDao().getAccounts$();
+ final LiveData<List<Account>> localAccounts$ = repo.getAccounts$();
localAccounts$.observe(requireActivity(), (localAccounts) -> {
localAccounts$.removeObservers(requireActivity());
for (Account localAccount : localAccounts) {
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java
index 383d9e1b..1b4b8289 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java
@@ -41,7 +41,7 @@ import it.niedermann.owncloud.notes.edit.category.CategoryDialogFragment;
import it.niedermann.owncloud.notes.edit.category.CategoryDialogFragment.CategoryDialogListener;
import it.niedermann.owncloud.notes.edit.title.EditTitleDialogFragment;
import it.niedermann.owncloud.notes.edit.title.EditTitleDialogFragment.EditTitleListener;
-import it.niedermann.owncloud.notes.persistence.NotesDatabase;
+import it.niedermann.owncloud.notes.persistence.NotesRepository;
import it.niedermann.owncloud.notes.persistence.entity.Account;
import it.niedermann.owncloud.notes.persistence.entity.Note;
import it.niedermann.owncloud.notes.shared.model.ApiVersion;
@@ -76,7 +76,7 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
@Nullable
private Note originalNote;
private int originalScrollY;
- protected NotesDatabase db;
+ protected NotesRepository repo;
private NoteFragmentListener listener;
private boolean titleModified = false;
@@ -90,7 +90,7 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
} catch (ClassCastException e) {
throw new ClassCastException(context.getClass() + " must implement " + NoteFragmentListener.class);
}
- db = NotesDatabase.getInstance(context);
+ repo = NotesRepository.getInstance(context);
}
@Override
@@ -99,7 +99,7 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
new Thread(() -> {
try {
SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(requireContext().getApplicationContext());
- this.localAccount = db.getAccountDao().getAccountByName(ssoAccount.name);
+ this.localAccount = repo.getAccountByName(ssoAccount.name);
if (savedInstanceState == null) {
long id = requireArguments().getLong(PARAM_NOTE_ID);
@@ -107,11 +107,11 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
long accountId = requireArguments().getLong(PARAM_ACCOUNT_ID);
if (accountId > 0) {
/* Switch account if account id has been provided */
- this.localAccount = db.getAccountDao().getAccountById(accountId);
+ this.localAccount = repo.getAccountById(accountId);
SingleAccountHelper.setCurrentAccount(requireContext().getApplicationContext(), localAccount.getAccountName());
}
isNew = false;
- note = originalNote = db.getNoteDao().getNoteById(id);
+ note = originalNote = repo.getNoteById(id);
requireActivity().runOnUiThread(() -> onNoteLoaded(note));
requireActivity().invalidateOptionsMenu();
} else {
@@ -126,7 +126,7 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
requireActivity().invalidateOptionsMenu();
}
} else {
- note = db.addNote(localAccount.getId(), cloudNote);
+ note = repo.addNote(localAccount.getId(), cloudNote);
originalNote = null;
requireActivity().runOnUiThread(() -> onNoteLoaded(note));
requireActivity().invalidateOptionsMenu();
@@ -213,19 +213,19 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
if (itemId == R.id.menu_cancel) {
new Thread(() -> {
if (originalNote == null) {
- db.deleteNoteAndSync(localAccount, note.getId());
+ repo.deleteNoteAndSync(localAccount, note.getId());
} else {
- db.updateNoteAndSync(localAccount, originalNote, null, null, null);
+ repo.updateNoteAndSync(localAccount, originalNote, null, null, null);
}
}).start();
listener.close();
return true;
} else if (itemId == R.id.menu_delete) {
- db.deleteNoteAndSync(localAccount, note.getId());
+ repo.deleteNoteAndSync(localAccount, note.getId());
listener.close();
return true;
} else if (itemId == R.id.menu_favorite) {
- db.toggleFavoriteAndSync(localAccount, note.getId());
+ repo.toggleFavoriteAndSync(localAccount, note.getId());
listener.onNoteUpdated(note);
prepareFavoriteOption(item);
return true;
@@ -288,7 +288,7 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
public void onCloseNote() {
if (!titleModified && originalNote == null && getContent().isEmpty()) {
- db.deleteNoteAndSync(localAccount, note.getId());
+ repo.deleteNoteAndSync(localAccount, note.getId());
}
}
@@ -304,13 +304,13 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
if (note.getContent().equals(newContent)) {
if (note.getScrollY() != originalScrollY) {
Log.v(TAG, "... only saving new scroll state, since content did not change");
- db.getNoteDao().updateScrollY(note.getId(), note.getScrollY());
+ repo.updateScrollY(note.getId(), note.getScrollY());
} else {
Log.v(TAG, "... not saving, since nothing has changed");
}
} else {
// FIXME requires database queries on main thread!
- note = db.updateNoteAndSync(localAccount, note, newContent, null, callback);
+ note = repo.updateNoteAndSync(localAccount, note, newContent, null, callback);
listener.onNoteUpdated(note);
requireActivity().invalidateOptionsMenu();
}
@@ -354,7 +354,7 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
@Override
public void onCategoryChosen(String category) {
- db.setCategory(localAccount, note.getId(), category);
+ repo.setCategory(localAccount, note.getId(), category);
note.setCategory(category);
listener.onNoteUpdated(note);
}
@@ -364,13 +364,13 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego
titleModified = true;
note.setTitle(newTitle);
new Thread(() -> {
- note = db.updateNoteAndSync(localAccount, note, note.getContent(), newTitle, null);
+ note = repo.updateNoteAndSync(localAccount, note, note.getContent(), newTitle, null);
requireActivity().runOnUiThread(() -> listener.onNoteUpdated(note));
}).start();
}
public void moveNote(Account account) {
- db.moveNoteToAnotherAccount(account, note);
+ repo.moveNoteToAnotherAccount(account, note);
listener.close();
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/edit/NotePreviewFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/edit/NotePreviewFragment.java
index fca142e9..f1626149 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/edit/NotePreviewFragment.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/edit/NotePreviewFragment.java
@@ -125,7 +125,7 @@ public class NotePreviewFragment extends SearchableBaseNoteFragment implements O
protected void registerInternalNoteLinkHandler() {
binding.singleNoteContent.registerOnLinkClickCallback((link) -> {
try {
- final long noteLocalId = db.getNoteDao().getLocalIdByRemoteId(this.note.getAccountId(), Long.parseLong(link));
+ final long noteLocalId = repo.getLocalIdByRemoteId(this.note.getAccountId(), Long.parseLong(link));
Log.i(TAG, "Found note for remoteId \"" + link + "\" in account \"" + this.note.getAccountId() + "\" with localId + \"" + noteLocalId + "\". Attempt to open " + EditNoteActivity.class.getSimpleName() + " for this note.");
startActivity(new Intent(requireActivity().getApplicationContext(), EditNoteActivity.class).putExtra(EditNoteActivity.PARAM_NOTE_ID, noteLocalId));
return true;
@@ -153,20 +153,20 @@ public class NotePreviewFragment extends SearchableBaseNoteFragment implements O
@Override
public void onRefresh() {
- if (noteLoaded && db.getNoteServerSyncHelper().isSyncPossible() && SSOUtil.isConfigured(getContext())) {
+ if (noteLoaded && repo.isSyncPossible() && SSOUtil.isConfigured(getContext())) {
binding.swiperefreshlayout.setRefreshing(true);
new Thread(() -> {
try {
- final Account account = db.getAccountDao().getAccountByName(SingleAccountHelper.getCurrentSingleSignOnAccount(requireContext()).name);
- db.getNoteServerSyncHelper().addCallbackPull(account, () -> new Thread(() -> {
- note = db.getNoteDao().getNoteById(note.getId());
+ final Account account = repo.getAccountByName(SingleAccountHelper.getCurrentSingleSignOnAccount(requireContext()).name);
+ repo.addCallbackPull(account, () -> new Thread(() -> {
+ note = repo.getNoteById(note.getId());
changedText = note.getContent();
requireActivity().runOnUiThread(() -> {
binding.singleNoteContent.setMarkdownString(note.getContent());
binding.swiperefreshlayout.setRefreshing(false);
});
}).start());
- db.getNoteServerSyncHelper().scheduleSync(account, false);
+ repo.scheduleSync(account, false);
} catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
e.printStackTrace();
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/edit/category/CategoryViewModel.java b/app/src/main/java/it/niedermann/owncloud/notes/edit/category/CategoryViewModel.java
index 96900a3b..ea5efd37 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/edit/category/CategoryViewModel.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/edit/category/CategoryViewModel.java
@@ -11,7 +11,7 @@ import androidx.lifecycle.MutableLiveData;
import java.util.List;
import it.niedermann.owncloud.notes.main.navigation.NavigationItem;
-import it.niedermann.owncloud.notes.persistence.NotesDatabase;
+import it.niedermann.owncloud.notes.persistence.NotesRepository;
import static androidx.lifecycle.Transformations.map;
import static androidx.lifecycle.Transformations.switchMap;
@@ -19,14 +19,14 @@ import static it.niedermann.owncloud.notes.shared.util.DisplayUtils.convertToCat
public class CategoryViewModel extends AndroidViewModel {
- private final NotesDatabase db;
+ private final NotesRepository repo;
@NonNull
private final MutableLiveData<String> searchTerm = new MutableLiveData<>("");
public CategoryViewModel(@NonNull Application application) {
super(application);
- db = NotesDatabase.getInstance(application);
+ repo = NotesRepository.getInstance(application);
}
public void postSearchTerm(@NonNull String searchTerm) {
@@ -36,7 +36,7 @@ public class CategoryViewModel extends AndroidViewModel {
@NonNull
public LiveData<List<NavigationItem.CategoryNavigationItem>> getCategories(long accountId) {
return switchMap(this.searchTerm, searchTerm ->
- map(db.getNoteDao().searchCategories$(accountId, TextUtils.isEmpty(searchTerm) ? "%" : "%" + searchTerm + "%"),
+ map(repo.searchCategories$(accountId, TextUtils.isEmpty(searchTerm) ? "%" : "%" + searchTerm + "%"),
categories -> convertToCategoryNavigationItem(getApplication(), categories)));
}
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/importaccount/ImportAccountViewModel.java b/app/src/main/java/it/niedermann/owncloud/notes/importaccount/ImportAccountViewModel.java
index 04b30d8d..abcf68e3 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/importaccount/ImportAccountViewModel.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/importaccount/ImportAccountViewModel.java
@@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
-import it.niedermann.owncloud.notes.persistence.NotesDatabase;
+import it.niedermann.owncloud.notes.persistence.NotesRepository;
import it.niedermann.owncloud.notes.persistence.entity.Account;
import it.niedermann.owncloud.notes.shared.model.Capabilities;
@@ -15,14 +15,14 @@ public class ImportAccountViewModel extends AndroidViewModel {
private static final String TAG = ImportAccountViewModel.class.getSimpleName();
@NonNull
- private final NotesDatabase db;
+ private final NotesRepository repo;
public ImportAccountViewModel(@NonNull Application application) {
super(application);
- this.db = NotesDatabase.getInstance(application.getApplicationContext());
+ this.repo = NotesRepository.getInstance(application);
}
public LiveData<Account> addAccount(@NonNull String url, @NonNull String username, @NonNull String accountName, @NonNull Capabilities capabilities) {
- return db.addAccount(url, username, accountName, capabilities);
+ return repo.addAccount(url, username, accountName, capabilities);
}
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/main/MainViewModel.java b/app/src/main/java/it/niedermann/owncloud/notes/main/MainViewModel.java
index 77104856..2e43d78d 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/main/MainViewModel.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/main/MainViewModel.java
@@ -34,8 +34,7 @@ import it.niedermann.owncloud.notes.exception.IntendedOfflineException;
import it.niedermann.owncloud.notes.main.navigation.NavigationAdapter;
import it.niedermann.owncloud.notes.main.navigation.NavigationItem;
import it.niedermann.owncloud.notes.persistence.CapabilitiesClient;
-import it.niedermann.owncloud.notes.persistence.NotesDatabase;
-import it.niedermann.owncloud.notes.persistence.NotesServerSyncHelper;
+import it.niedermann.owncloud.notes.persistence.NotesRepository;
import it.niedermann.owncloud.notes.persistence.entity.Account;
import it.niedermann.owncloud.notes.persistence.entity.CategoryWithNotesCount;
import it.niedermann.owncloud.notes.persistence.entity.Note;
@@ -73,7 +72,7 @@ public class MainViewModel extends AndroidViewModel {
private static final String KEY_EXPANDED_CATEGORY = "expandedCategory";
@NonNull
- private final NotesDatabase db;
+ private final NotesRepository repo;
@NonNull
private final MutableLiveData<Account> currentAccount = new MutableLiveData<>();
@@ -86,7 +85,7 @@ public class MainViewModel extends AndroidViewModel {
public MainViewModel(@NonNull Application application, @NonNull SavedStateHandle savedStateHandle) {
super(application);
- this.db = NotesDatabase.getInstance(application);
+ this.repo = NotesRepository.getInstance(application);
this.state = savedStateHandle;
}
@@ -175,7 +174,7 @@ public class MainViewModel extends AndroidViewModel {
@NonNull
@MainThread
public LiveData<Pair<NavigationCategory, CategorySortingMethod>> getCategorySortingMethodOfSelectedCategory() {
- return switchMap(getSelectedCategory(), selectedCategory -> map(db.getCategoryOrder(selectedCategory), sortingMethod -> new Pair<>(selectedCategory, sortingMethod)));
+ return switchMap(getSelectedCategory(), selectedCategory -> map(repo.getCategoryOrder(selectedCategory), sortingMethod -> new Pair<>(selectedCategory, sortingMethod)));
}
public LiveData<Void> modifyCategoryOrder(@NonNull NavigationCategory selectedCategory, @NonNull CategorySortingMethod sortingMethod) {
@@ -184,7 +183,7 @@ public class MainViewModel extends AndroidViewModel {
return new MutableLiveData<>(null);
} else {
Log.v(TAG, "[modifyCategoryOrder] - currentAccount: " + currentAccount.getAccountName());
- db.modifyCategoryOrder(currentAccount.getId(), selectedCategory, sortingMethod);
+ repo.modifyCategoryOrder(currentAccount.getId(), selectedCategory, sortingMethod);
return new MutableLiveData<>(null);
}
});
@@ -225,22 +224,22 @@ public class MainViewModel extends AndroidViewModel {
case RECENT: {
Log.v(TAG, "[getNotesListLiveData] - category: " + RECENT);
fromDatabase = sortingMethod.second == SORT_MODIFIED_DESC
- ? db.getNoteDao().searchRecentByModified$(accountId, searchQueryOrWildcard)
- : db.getNoteDao().searchRecentLexicographically$(accountId, searchQueryOrWildcard);
+ ? repo.searchRecentByModified$(accountId, searchQueryOrWildcard)
+ : repo.searchRecentLexicographically$(accountId, searchQueryOrWildcard);
break;
}
case FAVORITES: {
Log.v(TAG, "[getNotesListLiveData] - category: " + FAVORITES);
fromDatabase = sortingMethod.second == SORT_MODIFIED_DESC
- ? db.getNoteDao().searchFavoritesByModified$(accountId, searchQueryOrWildcard)
- : db.getNoteDao().searchFavoritesLexicographically$(accountId, searchQueryOrWildcard);
+ ? repo.searchFavoritesByModified$(accountId, searchQueryOrWildcard)
+ : repo.searchFavoritesLexicographically$(accountId, searchQueryOrWildcard);
break;
}
case UNCATEGORIZED: {
Log.v(TAG, "[getNotesListLiveData] - category: " + UNCATEGORIZED);
fromDatabase = sortingMethod.second == SORT_MODIFIED_DESC
- ? db.getNoteDao().searchUncategorizedByModified$(accountId, searchQueryOrWildcard)
- : db.getNoteDao().searchUncategorizedLexicographically$(accountId, searchQueryOrWildcard);
+ ? repo.searchUncategorizedByModified$(accountId, searchQueryOrWildcard)
+ : repo.searchUncategorizedLexicographically$(accountId, searchQueryOrWildcard);
break;
}
case DEFAULT_CATEGORY:
@@ -251,8 +250,8 @@ public class MainViewModel extends AndroidViewModel {
}
Log.v(TAG, "[getNotesListLiveData] - category: " + category);
fromDatabase = sortingMethod.second == SORT_MODIFIED_DESC
- ? db.getNoteDao().searchCategoryByModified$(accountId, searchQueryOrWildcard, category)
- : db.getNoteDao().searchCategoryLexicographically$(accountId, searchQueryOrWildcard, category);
+ ? repo.searchCategoryByModified$(accountId, searchQueryOrWildcard, category)
+ : repo.searchCategoryLexicographically$(accountId, searchQueryOrWildcard, category);
break;
}
}
@@ -294,11 +293,11 @@ public class MainViewModel extends AndroidViewModel {
Log.v(TAG, "[getNavigationCategories] - currentAccount: " + currentAccount.getAccountName());
return switchMap(getExpandedCategory(), expandedCategory -> {
Log.v(TAG, "[getNavigationCategories] - expandedCategory: " + expandedCategory);
- return switchMap(db.getNoteDao().count$(currentAccount.getId()), (count) -> {
+ return switchMap(repo.count$(currentAccount.getId()), (count) -> {
Log.v(TAG, "[getNavigationCategories] - count: " + count);
- return switchMap(db.getNoteDao().countFavorites$(currentAccount.getId()), (favoritesCount) -> {
+ return switchMap(repo.countFavorites$(currentAccount.getId()), (favoritesCount) -> {
Log.v(TAG, "[getNavigationCategories] - favoritesCount: " + favoritesCount);
- return distinctUntilChanged(map(db.getNoteDao().getCategories$(currentAccount.getId()), fromDatabase ->
+ return distinctUntilChanged(map(repo.getCategories$(currentAccount.getId()), fromDatabase ->
fromCategoriesWithNotesCount(getApplication(), expandedCategory, fromDatabase, count, favoritesCount)
));
});
@@ -390,22 +389,21 @@ public class MainViewModel extends AndroidViewModel {
*/
public void synchronizeCapabilities(@NonNull Account localAccount, @NonNull IResponseCallback<Void> callback) {
new Thread(() -> {
- final NotesServerSyncHelper syncHelper = db.getNoteServerSyncHelper();
- if (!syncHelper.isSyncPossible()) {
- syncHelper.updateNetworkStatus();
+ if (!repo.isSyncPossible()) {
+ repo.updateNetworkStatus();
}
- if (syncHelper.isSyncPossible()) {
+ if (repo.isSyncPossible()) {
try {
final Capabilities capabilities = CapabilitiesClient.getCapabilities(getApplication(), AccountImporter.getSingleSignOnAccount(getApplication(), localAccount.getAccountName()), localAccount.getCapabilitiesETag());
- db.getAccountDao().updateCapabilitiesETag(localAccount.getId(), capabilities.getETag());
- db.getAccountDao().updateBrand(localAccount.getId(), capabilities.getColor(), capabilities.getTextColor());
+ repo.updateCapabilitiesETag(localAccount.getId(), capabilities.getETag());
+ repo.updateBrand(localAccount.getId(), capabilities.getColor(), capabilities.getTextColor());
localAccount.setColor(capabilities.getColor());
localAccount.setTextColor(capabilities.getTextColor());
BrandingUtil.saveBrandColors(getApplication(), localAccount.getColor(), localAccount.getTextColor());
- db.updateApiVersion(localAccount.getId(), capabilities.getApiVersion());
+ repo.updateApiVersion(localAccount.getId(), capabilities.getApiVersion());
callback.onSuccess(null);
} catch (NextcloudFilesAppAccountNotFoundException e) {
- db.getAccountDao().deleteAccount(localAccount);
+ repo.deleteAccount(localAccount);
callback.onError(e);
} catch (Exception e) {
if (e instanceof NextcloudHttpRequestFailedException && ((NextcloudHttpRequestFailedException) e).getStatusCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
@@ -416,7 +414,7 @@ public class MainViewModel extends AndroidViewModel {
}
}
} else {
- if (syncHelper.isNetworkConnected() && syncHelper.isSyncOnlyOnWifi()) {
+ if (repo.isNetworkConnected() && repo.isSyncOnlyOnWifi()) {
callback.onError(new IntendedOfflineException("Network is connected, but sync is not possible."));
} else {
callback.onError(new NetworkErrorException("Sync is not possible, because network is not connected."));
@@ -431,15 +429,14 @@ public class MainViewModel extends AndroidViewModel {
public void synchronizeNotes(@NonNull Account currentAccount, @NonNull IResponseCallback<Void> callback) {
new Thread(() -> {
Log.v(TAG, "[synchronize] - currentAccount: " + currentAccount.getAccountName());
- final NotesServerSyncHelper syncHelper = db.getNoteServerSyncHelper();
- if (!syncHelper.isSyncPossible()) {
- syncHelper.updateNetworkStatus();
+ if (!repo.isSyncPossible()) {
+ repo.updateNetworkStatus();
}
- if (syncHelper.isSyncPossible()) {
- syncHelper.scheduleSync(currentAccount, false);
+ if (repo.isSyncPossible()) {
+ repo.scheduleSync(currentAccount, false);
callback.onSuccess(null);
} else { // Sync is not possible
- if (syncHelper.isNetworkConnected() && syncHelper.isSyncOnlyOnWifi()) {
+ if (repo.isNetworkConnected() && repo.isSyncOnlyOnWifi()) {
callback.onError(new IntendedOfflineException("Network is connected, but sync is not possible."));
} else {
callback.onError(new NetworkErrorException("Sync is not possible, because network is not connected."));
@@ -449,25 +446,25 @@ public class MainViewModel extends AndroidViewModel {
}
public LiveData<Boolean> getSyncStatus() {
- return db.getNoteServerSyncHelper().getSyncStatus();
+ return repo.getSyncStatus();
}
public LiveData<ArrayList<Throwable>> getSyncErrors() {
- return db.getNoteServerSyncHelper().getSyncErrors();
+ return repo.getSyncErrors();
}
public LiveData<Boolean> hasMultipleAccountsConfigured() {
- return map(db.getAccountDao().countAccounts$(), (counter) -> counter != null && counter > 1);
+ return map(repo.countAccounts$(), (counter) -> counter != null && counter > 1);
}
@WorkerThread
public Account getLocalAccountByAccountName(String accountName) {
- return db.getAccountDao().getAccountByName(accountName);
+ return repo.getAccountByName(accountName);
}
@WorkerThread
public List<Account> getAccounts() {
- return db.getAccountDao().getAccounts();
+ return repo.getAccounts();
}
public LiveData<Void> setCategory(Iterable<Long> noteIds, @NonNull String category) {
@@ -477,7 +474,7 @@ public class MainViewModel extends AndroidViewModel {
} else {
Log.v(TAG, "[setCategory] - currentAccount: " + currentAccount.getAccountName());
for (Long noteId : noteIds) {
- db.setCategory(currentAccount, noteId, category);
+ repo.setCategory(currentAccount, noteId, category);
}
return new MutableLiveData<>(null);
}
@@ -485,9 +482,9 @@ public class MainViewModel extends AndroidViewModel {
}
public LiveData<Note> moveNoteToAnotherAccount(Account account, Long noteId) {
- return switchMap(db.getNoteDao().getNoteById$(noteId), (note) -> {
+ return switchMap(repo.getNoteById$(noteId), (note) -> {
Log.v(TAG, "[moveNoteToAnotherAccount] - note: " + note);
- return db.moveNoteToAnotherAccount(account, note);
+ return repo.moveNoteToAnotherAccount(account, note);
});
}
@@ -497,7 +494,7 @@ public class MainViewModel extends AndroidViewModel {
return new MutableLiveData<>(null);
} else {
Log.v(TAG, "[toggleFavoriteAndSync] - currentAccount: " + currentAccount.getAccountName());
- db.toggleFavoriteAndSync(currentAccount, noteId);
+ repo.toggleFavoriteAndSync(currentAccount, noteId);
return new MutableLiveData<>(null);
}
});
@@ -509,7 +506,7 @@ public class MainViewModel extends AndroidViewModel {
return new MutableLiveData<>(null);
} else {
Log.v(TAG, "[deleteNoteAndSync] - currentAccount: " + currentAccount.getAccountName());
- db.deleteNoteAndSync(currentAccount, id);
+ repo.deleteNoteAndSync(currentAccount, id);
return new MutableLiveData<>(null);
}
});
@@ -522,7 +519,7 @@ public class MainViewModel extends AndroidViewModel {
} else {
Log.v(TAG, "[deleteNotesAndSync] - currentAccount: " + currentAccount.getAccountName());
for (Long id : ids) {
- db.deleteNoteAndSync(currentAccount, id);
+ repo.deleteNoteAndSync(currentAccount, id);
}
return new MutableLiveData<>(null);
}
@@ -530,7 +527,7 @@ public class MainViewModel extends AndroidViewModel {
}
public LiveData<Account> addAccount(@NonNull String url, @NonNull String username, @NonNull String accountName, @NonNull Capabilities capabilities) {
- return db.addAccount(url, username, accountName, capabilities);
+ return repo.addAccount(url, username, accountName, capabilities);
}
public LiveData<Note> getFullNote$(long id) {
@@ -539,7 +536,7 @@ public class MainViewModel extends AndroidViewModel {
@WorkerThread
public Note getFullNote(long id) {
- return db.getNoteDao().getNoteById(id);
+ return repo.getNoteById(id);
}
public LiveData<List<Note>> getFullNotesWithCategory(@NonNull Collection<Long> ids) {
@@ -552,7 +549,7 @@ public class MainViewModel extends AndroidViewModel {
new Thread(() -> notes.postValue(
ids
.stream()
- .map(id -> db.getNoteDao().getNoteById(id))
+ .map(repo::getNoteById)
.collect(Collectors.toList())
)).start();
return notes;
@@ -566,7 +563,7 @@ public class MainViewModel extends AndroidViewModel {
return new MutableLiveData<>();
} else {
Log.v(TAG, "[addNoteAndSync] - currentAccount: " + currentAccount.getAccountName());
- return db.addNoteAndSync(currentAccount, note);
+ return repo.addNoteAndSync(currentAccount, note);
}
});
}
@@ -575,25 +572,25 @@ public class MainViewModel extends AndroidViewModel {
return switchMap(getCurrentAccount(), currentAccount -> {
if (currentAccount != null) {
Log.v(TAG, "[updateNoteAndSync] - currentAccount: " + currentAccount.getAccountName());
- db.updateNoteAndSync(currentAccount, oldNote, newContent, newTitle, null);
+ repo.updateNoteAndSync(currentAccount, oldNote, newContent, newTitle, null);
}
return new MutableLiveData<>(null);
});
}
public void createOrUpdateSingleNoteWidgetData(SingleNoteWidgetData data) {
- db.getWidgetSingleNoteDao().createOrUpdateSingleNoteWidgetData(data);
+ repo.createOrUpdateSingleNoteWidgetData(data);
}
public LiveData<Integer> getAccountsCount() {
- return db.getAccountDao().countAccounts$();
+ return repo.countAccounts$();
}
@WorkerThread
public String collectNoteContents(@NonNull List<Long> noteIds) {
final StringBuilder noteContents = new StringBuilder();
for (Long noteId : noteIds) {
- final Note fullNote = db.getNoteDao().getNoteById(noteId);
+ final Note fullNote = repo.getNoteById(noteId);
final String tempFullNote = fullNote.getContent();
if (!TextUtils.isEmpty(tempFullNote)) {
if (noteContents.length() > 0) {
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/manageaccounts/ManageAccountsActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/manageaccounts/ManageAccountsActivity.java
index 4c307b07..dee5661f 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/manageaccounts/ManageAccountsActivity.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/manageaccounts/ManageAccountsActivity.java
@@ -63,7 +63,7 @@ public class ManageAccountsActivity extends LockedActivity {
@Override
public void onSuccess(Long unsynchronizedChangesCount) {
runOnUiThread(() -> {
- if (unsynchronizedChangesCount != null && unsynchronizedChangesCount > 0) {
+ if (unsynchronizedChangesCount > 0) {
new BrandedDeleteAlertDialogBuilder(ManageAccountsActivity.this)
.setTitle(getString(R.string.remove_account, accountToDelete.getUserName()))
.setMessage(getResources().getQuantityString(R.plurals.remove_account_message, (int) unsynchronizedChangesCount.longValue(), accountToDelete.getAccountName(), unsynchronizedChangesCount))
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/manageaccounts/ManageAccountsViewModel.java b/app/src/main/java/it/niedermann/owncloud/notes/manageaccounts/ManageAccountsViewModel.java
index 3e7e430a..2ee45cf8 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/manageaccounts/ManageAccountsViewModel.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/manageaccounts/ManageAccountsViewModel.java
@@ -15,6 +15,7 @@ import com.nextcloud.android.sso.helper.SingleAccountHelper;
import java.util.List;
import it.niedermann.owncloud.notes.persistence.NotesDatabase;
+import it.niedermann.owncloud.notes.persistence.NotesRepository;
import it.niedermann.owncloud.notes.persistence.entity.Account;
import it.niedermann.owncloud.notes.shared.model.IResponseCallback;
@@ -25,28 +26,28 @@ public class ManageAccountsViewModel extends AndroidViewModel {
private static final String TAG = ManageAccountsViewModel.class.getSimpleName();
@NonNull
- private final NotesDatabase db;
+ private final NotesRepository repo;
public ManageAccountsViewModel(@NonNull Application application) {
super(application);
- this.db = NotesDatabase.getInstance(application);
+ this.repo = NotesRepository.getInstance(application);
}
public void getCurrentAccount(@NonNull Context context, @NonNull IResponseCallback<Account> callback) {
try {
- callback.onSuccess(db.getAccountDao().getAccountByName((SingleAccountHelper.getCurrentSingleSignOnAccount(context).name)));
+ callback.onSuccess(repo.getAccountByName((SingleAccountHelper.getCurrentSingleSignOnAccount(context).name)));
} catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
callback.onError(e);
}
}
public LiveData<List<Account>> getAccounts$() {
- return distinctUntilChanged(db.getAccountDao().getAccounts$());
+ return distinctUntilChanged(repo.getAccounts$());
}
public void deleteAccount(@NonNull Account account, @NonNull Context context) {
new Thread(() -> {
- final List<Account> accounts = db.getAccountDao().getAccounts();
+ final List<Account> accounts = repo.getAccounts();
for (int i = 0; i < accounts.size(); i++) {
if (accounts.get(i).getId() == account.getId()) {
if (i > 0) {
@@ -56,7 +57,7 @@ public class ManageAccountsViewModel extends AndroidViewModel {
} else {
selectAccount(null, context);
}
- db.deleteAccount(accounts.get(i));
+ repo.deleteAccount(accounts.get(i));
break;
}
}
@@ -68,6 +69,6 @@ public class ManageAccountsViewModel extends AndroidViewModel {
}
public void countUnsynchronizedNotes(long accountId, @NonNull IResponseCallback<Long> callback) {
- new Thread(() -> callback.onSuccess(db.getNoteDao().countUnsynchronizedNotes(accountId))).start();
+ new Thread(() -> callback.onSuccess(repo.countUnsynchronizedNotes(accountId))).start();
}
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/CapabilitiesWorker.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/CapabilitiesWorker.java
index a9590225..8c38fbde 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/CapabilitiesWorker.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/CapabilitiesWorker.java
@@ -42,15 +42,15 @@ public class CapabilitiesWorker extends Worker {
@NonNull
@Override
public Result doWork() {
- final NotesDatabase db = NotesDatabase.getInstance(getApplicationContext());
- for (Account account : db.getAccountDao().getAccounts()) {
+ final NotesRepository repo = NotesRepository.getInstance(getApplicationContext());
+ for (Account account : repo.getAccounts()) {
try {
final SingleSignOnAccount ssoAccount = AccountImporter.getSingleSignOnAccount(getApplicationContext(), account.getAccountName());
Log.i(TAG, "Refreshing capabilities for " + ssoAccount.name);
final Capabilities capabilities = CapabilitiesClient.getCapabilities(getApplicationContext(), ssoAccount, account.getCapabilitiesETag());
- db.getAccountDao().updateCapabilitiesETag(account.getId(), capabilities.getETag());
- db.getAccountDao().updateBrand(account.getId(), capabilities.getColor(), capabilities.getTextColor());
- db.updateApiVersion(account.getId(), capabilities.getApiVersion());
+ repo.updateCapabilitiesETag(account.getId(), capabilities.getETag());
+ repo.updateBrand(account.getId(), capabilities.getColor(), capabilities.getTextColor());
+ repo.updateApiVersion(account.getId(), capabilities.getApiVersion());
Log.i(TAG, capabilities.toString());
} catch (Exception e) {
if (e instanceof NextcloudHttpRequestFailedException) {
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesDatabase.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesDatabase.java
index c6f80280..4f4aaa78 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesDatabase.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesDatabase.java
@@ -1,43 +1,15 @@
package it.niedermann.owncloud.notes.persistence;
import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.ShortcutInfo;
-import android.content.pm.ShortcutManager;
-import android.graphics.drawable.Icon;
-import android.text.TextUtils;
import android.util.Log;
-import androidx.annotation.AnyThread;
-import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.WorkerThread;
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.MutableLiveData;
-import androidx.preference.PreferenceManager;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.TypeConverters;
import androidx.sqlite.db.SupportSQLiteDatabase;
-import com.nextcloud.android.sso.AccountImporter;
-import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-
-import it.niedermann.android.sharedpreferences.SharedPreferenceIntLiveData;
-import it.niedermann.owncloud.notes.R;
-import it.niedermann.owncloud.notes.edit.EditNoteActivity;
import it.niedermann.owncloud.notes.persistence.dao.AccountDao;
import it.niedermann.owncloud.notes.persistence.dao.CategoryOptionsDao;
import it.niedermann.owncloud.notes.persistence.dao.NoteDao;
@@ -61,24 +33,6 @@ import it.niedermann.owncloud.notes.persistence.migration.Migration_18_19;
import it.niedermann.owncloud.notes.persistence.migration.Migration_19_20;
import it.niedermann.owncloud.notes.persistence.migration.Migration_20_21;
import it.niedermann.owncloud.notes.persistence.migration.Migration_9_10;
-import it.niedermann.owncloud.notes.shared.model.ApiVersion;
-import it.niedermann.owncloud.notes.shared.model.Capabilities;
-import it.niedermann.owncloud.notes.shared.model.CategorySortingMethod;
-import it.niedermann.owncloud.notes.shared.model.DBStatus;
-import it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType;
-import it.niedermann.owncloud.notes.shared.model.ISyncCallback;
-import it.niedermann.owncloud.notes.shared.model.NavigationCategory;
-import it.niedermann.owncloud.notes.shared.util.NoteUtil;
-
-import static android.os.Build.VERSION.SDK_INT;
-import static android.os.Build.VERSION_CODES.O;
-import static androidx.lifecycle.Transformations.map;
-import static androidx.lifecycle.Transformations.switchMap;
-import static it.niedermann.owncloud.notes.edit.EditNoteActivity.ACTION_SHORTCUT;
-import static it.niedermann.owncloud.notes.shared.util.NoteUtil.generateNoteExcerpt;
-import static it.niedermann.owncloud.notes.widget.notelist.NoteListWidget.updateNoteListWidgets;
-import static it.niedermann.owncloud.notes.widget.singlenote.SingleNoteWidget.updateSingleNoteWidgets;
-import static java.util.stream.Collectors.toMap;
@Database(
entities = {
@@ -94,13 +48,16 @@ public abstract class NotesDatabase extends RoomDatabase {
private static final String TAG = NotesDatabase.class.getSimpleName();
private static final String NOTES_DB_NAME = "OWNCLOUD_NOTES";
- private static NotesDatabase instance;
- private static Context context;
- private static NotesServerSyncHelper serverSyncHelper;
- private static String defaultNonEmptyTitle;
+ private static volatile NotesDatabase instance;
+
+ public static NotesDatabase getInstance(@NonNull Context context) {
+ if (instance == null) {
+ instance = create(context.getApplicationContext());
+ }
+ return instance;
+ }
private static NotesDatabase create(final Context context) {
- defaultNonEmptyTitle = NoteUtil.generateNonEmptyNoteTitle("", context);
return Room.databaseBuilder(
context,
NotesDatabase.class,
@@ -110,9 +67,9 @@ public abstract class NotesDatabase extends RoomDatabase {
new Migration_10_11(context),
new Migration_11_12(context),
new Migration_12_13(context),
- new Migration_13_14(context, () -> instance.notifyWidgets()),
+ new Migration_13_14(context),
new Migration_14_15(),
- new Migration_15_16(context, () -> instance.notifyWidgets()),
+ new Migration_15_16(context),
new Migration_16_17(),
new Migration_17_18(),
new Migration_18_19(context),
@@ -144,404 +101,4 @@ public abstract class NotesDatabase extends RoomDatabase {
public abstract WidgetSingleNoteDao getWidgetSingleNoteDao();
public abstract WidgetNotesListDao getWidgetNotesListDao();
-
- public static NotesDatabase getInstance(@NonNull Context context) {
- if (instance == null) {
- instance = create(context.getApplicationContext());
- NotesDatabase.context = context.getApplicationContext();
- NotesDatabase.serverSyncHelper = NotesServerSyncHelper.getInstance(instance);
- }
- return instance;
- }
-
- public NotesServerSyncHelper getNoteServerSyncHelper() {
- return NotesDatabase.serverSyncHelper;
- }
-
- /**
- * Creates a new Note in the Database and adds a Synchronization Flag.
- *
- * @param note Note
- */
- @NonNull
- @MainThread
- public LiveData<Note> addNoteAndSync(Account account, Note note) {
- final Note entity = new Note(0, null, note.getModified(), note.getTitle(), note.getContent(), note.getCategory(), note.getFavorite(), note.getETag(), DBStatus.LOCAL_EDITED, account.getId(), generateNoteExcerpt(note.getContent(), note.getTitle()), 0);
- final MutableLiveData<Note> ret = new MutableLiveData<>();
- new Thread(() -> ret.postValue(addNote(account.getId(), entity))).start();
- return map(ret, newNote -> {
- notifyWidgets();
- serverSyncHelper.scheduleSync(account, true);
- return newNote;
- });
- }
-
- /**
- * Inserts a note directly into the Database.
- * Excerpt will be generated, {@link DBStatus#LOCAL_EDITED} will be applied in case the note has
- * already has a local ID, otherwise {@link DBStatus#VOID} will be applied.
- * No Synchronisation will be triggered! Use {@link #addNoteAndSync(Account, Note)}!
- *
- * @param note {@link Note} to be added.
- */
- @NonNull
- @WorkerThread
- public Note addNote(long accountId, @NonNull Note note) {
- note.setStatus(note.getId() > 0 ? DBStatus.LOCAL_EDITED : DBStatus.VOID);
- note.setAccountId(accountId);
- note.setExcerpt(generateNoteExcerpt(note.getContent(), note.getTitle()));
- return getNoteDao().getNoteById(getNoteDao().addNote(note));
- }
-
- @MainThread
- public LiveData<Note> moveNoteToAnotherAccount(Account account, @NonNull Note note) {
- return switchMap(getNoteDao().getContent$(note.getId()), (content) -> {
- final Note fullNote = new Note(null, note.getModified(), note.getTitle(), content, note.getCategory(), note.getFavorite(), null);
- deleteNoteAndSync(account, note.getId());
- return addNoteAndSync(account, fullNote);
- });
- }
-
- /**
- * @return a {@link Map} of remote IDs as keys and local IDs as values of all {@link Note}s of
- * the given {@param accountId} which are not {@link DBStatus#LOCAL_DELETED}
- */
- @NonNull
- @WorkerThread
- public Map<Long, Long> getIdMap(long accountId) {
- validateAccountId(accountId);
- return getNoteDao()
- .getRemoteIdAndId(accountId)
- .stream()
- .filter(note -> note.getRemoteId() != null)
- .collect(toMap(Note::getRemoteId, Note::getId));
- }
-
- @AnyThread
- public void toggleFavoriteAndSync(Account account, long noteId) {
- new Thread(() -> {
- getNoteDao().toggleFavorite(noteId);
- serverSyncHelper.scheduleSync(account, true);
- }).start();
- }
-
- /**
- * Set the category for a given note.
- * This method will search in the database to find out the category id in the db.
- * If there is no such category existing, this method will create it and search again.
- *
- * @param account The single sign on account
- * @param noteId The note which will be updated
- * @param category The category title which should be used to find the category id.
- */
- @AnyThread
- public void setCategory(@NonNull Account account, long noteId, @NonNull String category) {
- new Thread(() -> {
- getNoteDao().updateStatus(noteId, DBStatus.LOCAL_EDITED);
- getNoteDao().updateCategory(noteId, category);
- serverSyncHelper.scheduleSync(account, true);
- }).start();
- }
-
- /**
- * Updates a single Note with a new content.
- * The title is derived from the new content automatically, and modified date as well as DBStatus are updated, too -- if the content differs to the state in the database.
- *
- * @param oldNote Note to be changed
- * @param newContent New content. If this is <code>null</code>, then <code>oldNote</code> is saved again (useful for undoing changes).
- * @param newTitle New title. If this is <code>null</code>, then either the old title is reused (in case the note has been synced before) or a title is generated (in case it is a new note)
- * @param callback When the synchronization is finished, this callback will be invoked (optional).
- * @return changed {@link Note} if differs from database, otherwise the old {@link Note}.
- */
- @WorkerThread
- public Note updateNoteAndSync(Account localAccount, @NonNull Note oldNote, @Nullable String newContent, @Nullable String newTitle, @Nullable ISyncCallback callback) {
- final Note newNote;
- if (newContent == null) {
- newNote = new Note(oldNote.getId(), oldNote.getRemoteId(), oldNote.getModified(), oldNote.getTitle(), oldNote.getContent(), oldNote.getCategory(), oldNote.getFavorite(), oldNote.getETag(), DBStatus.LOCAL_EDITED, localAccount.getId(), oldNote.getExcerpt(), oldNote.getScrollY());
- } else {
- final String title;
- if (newTitle != null) {
- title = newTitle;
- } else {
- if ((oldNote.getRemoteId() == null || localAccount.getPreferredApiVersion() == null || localAccount.getPreferredApiVersion().compareTo(new ApiVersion("1.0", 0, 0)) < 0) &&
- (defaultNonEmptyTitle.equals(oldNote.getTitle()))) {
- title = NoteUtil.generateNonEmptyNoteTitle(newContent, context);
- } else {
- title = oldNote.getTitle();
- }
- }
- newNote = new Note(oldNote.getId(), oldNote.getRemoteId(), Calendar.getInstance(), title, newContent, oldNote.getCategory(), oldNote.getFavorite(), oldNote.getETag(), DBStatus.LOCAL_EDITED, localAccount.getId(), generateNoteExcerpt(newContent, title), oldNote.getScrollY());
- }
- int rows = getNoteDao().updateNote(newNote);
- // if data was changed, set new status and schedule sync (with callback); otherwise invoke callback directly.
- if (rows > 0) {
- notifyWidgets();
- if (callback != null) {
- serverSyncHelper.addCallbackPush(localAccount, callback);
- }
- serverSyncHelper.scheduleSync(localAccount, true);
- return newNote;
- } else {
- if (callback != null) {
- callback.onFinish();
- }
- return oldNote;
- }
- }
-
- /**
- * Marks a Note in the Database as Deleted. In the next Synchronization it will be deleted
- * from the Server.
- *
- * @param id long - ID of the Note that should be deleted
- */
- @AnyThread
- public void deleteNoteAndSync(Account account, long id) {
- new Thread(() -> {
- getNoteDao().updateStatus(id, DBStatus.LOCAL_DELETED);
- notifyWidgets();
- serverSyncHelper.scheduleSync(account, true);
-
- if (SDK_INT >= O) {
- ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
- if (shortcutManager != null) {
- shortcutManager.getPinnedShortcuts().forEach((shortcut) -> {
- String shortcutId = id + "";
- if (shortcut.getId().equals(shortcutId)) {
- Log.v(TAG, "Removing shortcut for " + shortcutId);
- shortcutManager.disableShortcuts(Collections.singletonList(shortcutId), context.getResources().getString(R.string.note_has_been_deleted));
- }
- });
- } else {
- Log.e(TAG, ShortcutManager.class.getSimpleName() + "is null.");
- }
- }
- }).start();
- }
-
- /**
- * Notify about changed notes.
- */
- @AnyThread
- protected void notifyWidgets() {
- new Thread(() -> {
- updateSingleNoteWidgets(context);
- updateNoteListWidgets(context);
- }).start();
- }
-
- @AnyThread
- void updateDynamicShortcuts(long accountId) {
- new Thread(() -> {
- if (SDK_INT >= android.os.Build.VERSION_CODES.N_MR1) {
- ShortcutManager shortcutManager = context.getApplicationContext().getSystemService(ShortcutManager.class);
- if (shortcutManager != null) {
- if (!shortcutManager.isRateLimitingActive()) {
- List<ShortcutInfo> newShortcuts = new ArrayList<>();
-
- for (Note note : getNoteDao().getRecentNotes(accountId)) {
- if (!TextUtils.isEmpty(note.getTitle())) {
- Intent intent = new Intent(context.getApplicationContext(), EditNoteActivity.class);
- intent.putExtra(EditNoteActivity.PARAM_NOTE_ID, note.getId());
- intent.setAction(ACTION_SHORTCUT);
-
- newShortcuts.add(new ShortcutInfo.Builder(context.getApplicationContext(), note.getId() + "")
- .setShortLabel(note.getTitle() + "")
- .setIcon(Icon.createWithResource(context.getApplicationContext(), note.getFavorite() ? R.drawable.ic_star_yellow_24dp : R.drawable.ic_star_grey_ccc_24dp))
- .setIntent(intent)
- .build());
- } else {
- // Prevent crash https://github.com/stefan-niedermann/nextcloud-notes/issues/613
- Log.e(TAG, "shortLabel cannot be empty " + note);
- }
- }
- Log.d(TAG, "Update dynamic shortcuts");
- shortcutManager.removeAllDynamicShortcuts();
- shortcutManager.addDynamicShortcuts(newShortcuts);
- }
- }
- }
- }).start();
- }
-
- @AnyThread
- public LiveData<Account> addAccount(@NonNull String url, @NonNull String username, @NonNull String accountName, @NonNull Capabilities capabilities) {
- return getAccountDao().getAccountById$(getAccountDao().insert(new Account(url, username, accountName, capabilities)));
- }
-
- /**
- * @param apiVersion has to be a JSON array as a string <code>["0.2", "1.0", ...]</code>
- * @return whether or not the given {@link ApiVersion} has been written to the database
- * @throws IllegalArgumentException if the apiVersion does not match the expected format
- */
- public boolean updateApiVersion(long accountId, @Nullable String apiVersion) throws IllegalArgumentException {
- validateAccountId(accountId);
- if (apiVersion != null) {
- try {
- JSONArray apiVersions = new JSONArray(apiVersion);
- for (int i = 0; i < apiVersions.length(); i++) {
- ApiVersion.of(apiVersions.getString(i));
- }
- if (apiVersions.length() > 0) {
- final int updatedRows = getAccountDao().updateApiVersion(accountId, apiVersion);
- if (updatedRows == 1) {
- Log.i(TAG, "Updated apiVersion to \"" + apiVersion + "\" for accountId = " + accountId);
- } else {
- Log.e(TAG, "Updated " + updatedRows + " but expected only 1 for accountId = " + accountId + " and apiVersion = \"" + apiVersion + "\"");
- }
- return true;
- } else {
- Log.i(TAG, "Given API version is a valid JSON array but does not contain any valid API versions. Do not update database.");
- }
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException("API version does contain a non-valid version: " + apiVersion);
- } catch (JSONException e) {
- throw new IllegalArgumentException("API version must contain be a JSON array: " + apiVersion);
- }
- } else {
- Log.v(TAG, "Given API version is null. Do not update database");
- }
- return false;
- }
-
- /**
- * @param localAccount the {@link Account} that should be deleted
- * @throws IllegalArgumentException if no account has been deleted by the given accountId
- */
- @AnyThread
- public void deleteAccount(@NonNull Account localAccount) throws IllegalArgumentException {
- validateAccountId(localAccount.getId());
- new Thread(() -> {
- int deletedAccounts = getAccountDao().deleteAccount(localAccount);
- if (deletedAccounts < 1) {
- Log.e(TAG, "AccountId '" + localAccount.getId() + "' did not delete any account");
- throw new IllegalArgumentException("The given accountId does not delete any row");
- } else if (deletedAccounts > 1) {
- Log.e(TAG, "AccountId '" + localAccount.getId() + "' deleted unexpectedly '" + deletedAccounts + "' accounts");
- }
-
- try {
- SSOClient.invalidateAPICache(AccountImporter.getSingleSignOnAccount(context, localAccount.getAccountName()));
- } catch (NextcloudFilesAppAccountNotFoundException e) {
- e.printStackTrace();
- SSOClient.invalidateAPICache();
- }
-
- // TODO this should already be handled by foreign key cascade, no?
- final int deletedNotes = getNoteDao().deleteByAccountId(localAccount.getId());
- Log.v(TAG, "Deleted " + deletedNotes + " notes from account " + localAccount.getId());
- }).start();
- }
-
- private static void validateAccountId(long accountId) {
- if (accountId < 1) {
- throw new IllegalArgumentException("accountId must be greater than 0");
- }
- }
-
- /**
- * Modifies the sorting method for one category, the category can be normal category or
- * one of "All notes", "Favorite", and "Uncategorized".
- * If category is one of these three, sorting method will be modified in android.content.SharedPreference.
- * The user can determine use which sorting method to show the notes for a category.
- * When the user changes the sorting method, this method should be called.
- *
- * @param accountId The user accountID
- * @param selectedCategory The category to be modified
- * @param sortingMethod The sorting method in {@link CategorySortingMethod} enum format
- */
- @AnyThread
- public void modifyCategoryOrder(long accountId, @NonNull NavigationCategory selectedCategory, @NonNull CategorySortingMethod sortingMethod) {
- validateAccountId(accountId);
-
- new Thread(() -> {
- final Context ctx = context.getApplicationContext();
- final SharedPreferences.Editor sp = PreferenceManager.getDefaultSharedPreferences(ctx).edit();
- int orderIndex = sortingMethod.getId();
-
- switch (selectedCategory.getType()) {
- case FAVORITES: {
- sp.putInt(ctx.getString(R.string.action_sorting_method) + ' ' + ctx.getString(R.string.label_favorites), orderIndex);
- break;
- }
- case UNCATEGORIZED: {
- sp.putInt(ctx.getString(R.string.action_sorting_method) + ' ' + ctx.getString(R.string.action_uncategorized), orderIndex);
- break;
- }
- case RECENT: {
- sp.putInt(ctx.getString(R.string.action_sorting_method) + ' ' + ctx.getString(R.string.label_all_notes), orderIndex);
- break;
- }
- case DEFAULT_CATEGORY:
- default: {
- final String category = selectedCategory.getCategory();
- if (category != null) {
- if (getCategoryOptionsDao().modifyCategoryOrder(accountId, category, sortingMethod) == 0) {
- // Nothing updated means we didn't have this yet
- final CategoryOptions categoryOptions = new CategoryOptions();
- categoryOptions.setAccountId(accountId);
- categoryOptions.setCategory(category);
- categoryOptions.setSortingMethod(sortingMethod);
- getCategoryOptionsDao().addCategoryOptions(categoryOptions);
- }
- } else {
- throw new IllegalStateException("Tried to modify category order for " + ENavigationCategoryType.DEFAULT_CATEGORY + "but category is null.");
- }
- break;
- }
- }
- sp.apply();
- }).start();
- }
-
- /**
- * Gets the sorting method of a {@link NavigationCategory}, the category can be normal
- * {@link CategoryOptions} or one of {@link ENavigationCategoryType}.
- * If the category no normal {@link CategoryOptions}, sorting method will be got from
- * {@link SharedPreferences}.
- * <p>
- * The sorting method of the category can be used to decide to use which sorting method to show
- * the notes for each categories.
- *
- * @param selectedCategory The category
- * @return The sorting method in CategorySortingMethod enum format
- */
- @NonNull
- @MainThread
- public LiveData<CategorySortingMethod> getCategoryOrder(@NonNull NavigationCategory selectedCategory) {
- final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
- String prefKey;
-
- switch (selectedCategory.getType()) {
- // TODO make this account specific
- case RECENT: {
- prefKey = context.getString(R.string.action_sorting_method) + ' ' + context.getString(R.string.label_all_notes);
- break;
- }
- case FAVORITES: {
- prefKey = context.getString(R.string.action_sorting_method) + ' ' + context.getString(R.string.label_favorites);
- break;
- }
- case UNCATEGORIZED: {
- prefKey = context.getString(R.string.action_sorting_method) + ' ' + context.getString(R.string.action_uncategorized);
- break;
- }
- case DEFAULT_CATEGORY:
- default: {
- final String category = selectedCategory.getCategory();
- if (category != null) {
- return getCategoryOptionsDao().getCategoryOrder(selectedCategory.getAccountId(), category);
- } else {
- Log.e(TAG, "Cannot read " + CategorySortingMethod.class.getSimpleName() + " for " + ENavigationCategoryType.DEFAULT_CATEGORY + ".");
- return new MutableLiveData<>(CategorySortingMethod.SORT_MODIFIED_DESC);
- }
- }
- }
-
- return map(new SharedPreferenceIntLiveData(sp, prefKey, CategorySortingMethod.SORT_MODIFIED_DESC.getId()), CategorySortingMethod::findById);
- }
-
- public Context getContext() {
- return NotesDatabase.context;
- }
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java
new file mode 100644
index 00000000..55b786dc
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesRepository.java
@@ -0,0 +1,951 @@
+package it.niedermann.owncloud.notes.persistence;
+
+import android.accounts.NetworkErrorException;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Icon;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.AnyThread;
+import androidx.annotation.ColorInt;
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.preference.PreferenceManager;
+
+import com.nextcloud.android.sso.AccountImporter;
+import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
+import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
+import com.nextcloud.android.sso.helper.SingleAccountHelper;
+import com.nextcloud.android.sso.model.SingleSignOnAccount;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import it.niedermann.android.sharedpreferences.SharedPreferenceIntLiveData;
+import it.niedermann.owncloud.notes.R;
+import it.niedermann.owncloud.notes.edit.EditNoteActivity;
+import it.niedermann.owncloud.notes.persistence.entity.Account;
+import it.niedermann.owncloud.notes.persistence.entity.CategoryOptions;
+import it.niedermann.owncloud.notes.persistence.entity.CategoryWithNotesCount;
+import it.niedermann.owncloud.notes.persistence.entity.Note;
+import it.niedermann.owncloud.notes.persistence.entity.NotesListWidgetData;
+import it.niedermann.owncloud.notes.persistence.entity.SingleNoteWidgetData;
+import it.niedermann.owncloud.notes.shared.model.ApiVersion;
+import it.niedermann.owncloud.notes.shared.model.Capabilities;
+import it.niedermann.owncloud.notes.shared.model.CategorySortingMethod;
+import it.niedermann.owncloud.notes.shared.model.DBStatus;
+import it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType;
+import it.niedermann.owncloud.notes.shared.model.ISyncCallback;
+import it.niedermann.owncloud.notes.shared.model.NavigationCategory;
+import it.niedermann.owncloud.notes.shared.model.SyncResultStatus;
+import it.niedermann.owncloud.notes.shared.util.NoteUtil;
+import it.niedermann.owncloud.notes.shared.util.SSOUtil;
+
+import static android.os.Build.VERSION.SDK_INT;
+import static android.os.Build.VERSION_CODES.O;
+import static androidx.lifecycle.Transformations.distinctUntilChanged;
+import static androidx.lifecycle.Transformations.map;
+import static androidx.lifecycle.Transformations.switchMap;
+import static it.niedermann.owncloud.notes.edit.EditNoteActivity.ACTION_SHORTCUT;
+import static it.niedermann.owncloud.notes.shared.util.NoteUtil.generateNoteExcerpt;
+import static it.niedermann.owncloud.notes.widget.notelist.NoteListWidget.updateNoteListWidgets;
+import static it.niedermann.owncloud.notes.widget.singlenote.SingleNoteWidget.updateSingleNoteWidgets;
+import static java.util.stream.Collectors.toMap;
+
+@SuppressWarnings("UnusedReturnValue")
+public class NotesRepository {
+
+ private static final String TAG = NotesRepository.class.getSimpleName();
+
+ private static NotesRepository instance;
+
+ private final ExecutorService executor = Executors.newSingleThreadExecutor();
+ private final Context context;
+ private final NotesDatabase db;
+ private final String defaultNonEmptyTitle;
+
+ /**
+ * Track network connection changes using a {@link BroadcastReceiver}
+ */
+ private boolean isSyncPossible = false;
+ private boolean networkConnected = false;
+ private String syncOnlyOnWifiKey;
+ private boolean syncOnlyOnWifi;
+ private final MutableLiveData<Boolean> syncStatus = new MutableLiveData<>(false);
+ private final MutableLiveData<ArrayList<Throwable>> syncErrors = new MutableLiveData<>();
+
+ /**
+ * @see <a href="https://stackoverflow.com/a/3104265">Do not make this a local variable.</a>
+ */
+ @SuppressWarnings("FieldCanBeLocal")
+ private final SharedPreferences.OnSharedPreferenceChangeListener onSharedPreferenceChangeListener = (SharedPreferences prefs, String key) -> {
+ if (syncOnlyOnWifiKey.equals(key)) {
+ syncOnlyOnWifi = prefs.getBoolean(syncOnlyOnWifiKey, false);
+ updateNetworkStatus();
+ }
+ };
+
+ private final BroadcastReceiver networkReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ updateNetworkStatus();
+ if (isSyncPossible() && SSOUtil.isConfigured(context)) {
+ new Thread(() -> {
+ try {
+ scheduleSync(getAccountByName(SingleAccountHelper.getCurrentSingleSignOnAccount(context).name), false);
+ } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
+ Log.v(TAG, "Can not select current SingleSignOn account after network changed, do not sync.");
+ }
+ }).start();
+ }
+ }
+ };
+
+ // current state of the synchronization
+ private final Map<Long, Boolean> syncActive = new HashMap<>();
+ private final Map<Long, Boolean> syncScheduled = new HashMap<>();
+
+ // list of callbacks for both parts of synchronization
+ private final Map<Long, List<ISyncCallback>> callbacksPush = new HashMap<>();
+ private final Map<Long, List<ISyncCallback>> callbacksPull = new HashMap<>();
+
+
+ public static synchronized NotesRepository getInstance(@NonNull Context context) {
+ if (instance == null) {
+ instance = new NotesRepository(context);
+ }
+ return instance;
+ }
+
+ private NotesRepository(final Context context) {
+ this.context = context.getApplicationContext();
+ this.db = NotesDatabase.getInstance(this.context);
+ this.defaultNonEmptyTitle = NoteUtil.generateNonEmptyNoteTitle("", this.context);
+ this.syncOnlyOnWifiKey = context.getApplicationContext().getResources().getString(R.string.pref_key_wifi_only);
+
+ // Registers BroadcastReceiver to track network connection changes.
+ context.getApplicationContext().registerReceiver(networkReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.context.getApplicationContext());
+ prefs.registerOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener);
+ syncOnlyOnWifi = prefs.getBoolean(syncOnlyOnWifiKey, false);
+
+ updateNetworkStatus();
+ }
+
+
+ // Accounts
+
+ public List<Account> getAccounts() {
+ return db.getAccountDao().getAccounts();
+ }
+
+ public int deleteAccount(Account localAccount) {
+ return db.getAccountDao().deleteAccount(localAccount);
+ }
+
+ public Account getAccountByName(String accountName) {
+ return db.getAccountDao().getAccountByName(accountName);
+ }
+
+ public Account getAccountById(long accountId) {
+ return db.getAccountDao().getAccountById(accountId);
+ }
+
+ public LiveData<List<Account>> getAccounts$() {
+ return db.getAccountDao().getAccounts$();
+ }
+
+ public LiveData<Account> getAccountById$(long accountId) {
+ return db.getAccountDao().getAccountById$(accountId);
+ }
+
+ public LiveData<Integer> countAccounts$() {
+ return db.getAccountDao().countAccounts$();
+ }
+
+ public void updateBrand(long id, @ColorInt Integer color, @ColorInt Integer textColor) {
+ db.getAccountDao().updateBrand(id, color, textColor);
+ }
+
+ public void updateETag(long id, String eTag) {
+ db.getAccountDao().updateETag(id, eTag);
+ }
+
+ public void updateCapabilitiesETag(long id, String capabilitiesETag) {
+ db.getAccountDao().updateCapabilitiesETag(id, capabilitiesETag);
+ }
+
+ public void updateModified(long id, long modified) {
+ db.getAccountDao().updateModified(id, modified);
+ }
+
+
+ // Notes
+
+ public LiveData<Note> getNoteById$(long id) {
+ return db.getNoteDao().getNoteById$(id);
+ }
+
+ public Note getNoteById(long id) {
+ return db.getNoteDao().getNoteById(id);
+ }
+
+ public LiveData<Integer> count$(long accountId) {
+ return db.getNoteDao().count$(accountId);
+ }
+
+ public LiveData<Integer> countFavorites$(long accountId) {
+ return db.getNoteDao().countFavorites$(accountId);
+ }
+
+ public void updateScrollY(long id, int scrollY) {
+ db.getNoteDao().updateScrollY(id, scrollY);
+ }
+
+ public LiveData<List<CategoryWithNotesCount>> searchCategories$(Long accountId, String searchTerm) {
+ return db.getNoteDao().searchCategories$(accountId, searchTerm);
+ }
+
+ public LiveData<List<Note>> searchRecentByModified$(long accountId, String query) {
+ return db.getNoteDao().searchRecentByModified$(accountId, query);
+ }
+
+ public List<Note> searchRecentByModified(long accountId, String query) {
+ return db.getNoteDao().searchRecentByModified(accountId, query);
+ }
+
+ public LiveData<List<Note>> searchRecentLexicographically$(long accountId, String query) {
+ return db.getNoteDao().searchRecentLexicographically$(accountId, query);
+ }
+
+ public LiveData<List<Note>> searchFavoritesByModified$(long accountId, String query) {
+ return db.getNoteDao().searchFavoritesByModified$(accountId, query);
+ }
+
+ public List<Note> searchFavoritesByModified(long accountId, String query) {
+ return db.getNoteDao().searchFavoritesByModified(accountId, query);
+ }
+
+ public LiveData<List<Note>> searchFavoritesLexicographically$(long accountId, String query) {
+ return db.getNoteDao().searchFavoritesLexicographically$(accountId, query);
+ }
+
+ public LiveData<List<Note>> searchUncategorizedByModified$(long accountId, String query) {
+ return db.getNoteDao().searchUncategorizedByModified$(accountId, query);
+ }
+
+ public List<Note> searchUncategorizedByModified(long accountId, String query) {
+ return db.getNoteDao().searchUncategorizedByModified(accountId, query);
+ }
+
+ public LiveData<List<Note>> searchUncategorizedLexicographically$(long accountId, String query) {
+ return db.getNoteDao().searchUncategorizedLexicographically$(accountId, query);
+ }
+
+ public LiveData<List<Note>> searchCategoryByModified$(long accountId, String query, String category) {
+ return db.getNoteDao().searchCategoryByModified$(accountId, query, category);
+ }
+
+ public List<Note> searchCategoryByModified(long accountId, String query, String category) {
+ return db.getNoteDao().searchCategoryByModified(accountId, query, category);
+ }
+
+ public LiveData<List<Note>> searchCategoryLexicographically$(long accountId, String query, String category) {
+ return db.getNoteDao().searchCategoryLexicographically$(accountId, query, category);
+ }
+
+ public LiveData<List<CategoryWithNotesCount>> getCategories$(Long accountId) {
+ return db.getNoteDao().getCategories$(accountId);
+ }
+
+ public void updateRemoteId(long id, Long remoteId) {
+ db.getNoteDao().updateRemoteId(id, remoteId);
+ }
+
+ public Long getLocalIdByRemoteId(long accountId, long remoteId) {
+ return db.getNoteDao().getLocalIdByRemoteId(accountId, remoteId);
+ }
+
+ public List<Note> getLocalModifiedNotes(long accountId) {
+ return db.getNoteDao().getLocalModifiedNotes(accountId);
+ }
+
+ public void deleteByNoteId(long id, DBStatus forceDBStatus) {
+ db.getNoteDao().deleteByNoteId(id, forceDBStatus);
+ }
+
+ public int updateIfNotModifiedLocallyDuringSync(long noteId, Long targetModified, String targetTitle, boolean targetFavorite, String targetETag, String targetContent, String targetExcerpt, String contentBeforeSyncStart, String categoryBeforeSyncStart, boolean favoriteBeforeSyncStart) {
+ return db.getNoteDao().updateIfNotModifiedLocallyDuringSync(noteId, targetModified, targetTitle, targetFavorite, targetETag, targetContent, targetExcerpt, contentBeforeSyncStart, categoryBeforeSyncStart, favoriteBeforeSyncStart);
+ }
+
+ public int updateIfNotModifiedLocallyAndAnyRemoteColumnHasChanged(long id, Long modified, String title, boolean favorite, String category, String eTag, String content, String excerpt) {
+ return db.getNoteDao().updateIfNotModifiedLocallyAndAnyRemoteColumnHasChanged(id, modified, title, favorite, category, eTag, content, excerpt);
+ }
+
+ public long countUnsynchronizedNotes(long accountId) {
+ final Long unsynchronizedNotesCount = db.getNoteDao().countUnsynchronizedNotes(accountId);
+ return unsynchronizedNotesCount == null ? 0 : unsynchronizedNotesCount;
+ }
+
+
+ // SingleNoteWidget
+
+ public void createOrUpdateSingleNoteWidgetData(SingleNoteWidgetData data) {
+ db.getWidgetSingleNoteDao().createOrUpdateSingleNoteWidgetData(data);
+ }
+
+ public void removeSingleNoteWidget(int id) {
+ db.getWidgetSingleNoteDao().removeSingleNoteWidget(id);
+ }
+
+ public SingleNoteWidgetData getSingleNoteWidgetData(int id) {
+ return db.getWidgetSingleNoteDao().getSingleNoteWidgetData(id);
+ }
+
+
+ // ListWidget
+
+ public void createOrUpdateNoteListWidgetData(NotesListWidgetData data) {
+ db.getWidgetNotesListDao().createOrUpdateNoteListWidgetData(data);
+ }
+
+ public void removeNoteListWidget(int appWidgetId) {
+ db.getWidgetNotesListDao().removeNoteListWidget(appWidgetId);
+ }
+
+ public NotesListWidgetData getNoteListWidgetData(int appWidgetId) {
+ return db.getWidgetNotesListDao().getNoteListWidgetData(appWidgetId);
+ }
+
+ /**
+ * Creates a new Note in the Database and adds a Synchronization Flag.
+ *
+ * @param note Note
+ */
+ @NonNull
+ @MainThread
+ public LiveData<Note> addNoteAndSync(Account account, Note note) {
+ final Note entity = new Note(0, null, note.getModified(), note.getTitle(), note.getContent(), note.getCategory(), note.getFavorite(), note.getETag(), DBStatus.LOCAL_EDITED, account.getId(), generateNoteExcerpt(note.getContent(), note.getTitle()), 0);
+ final MutableLiveData<Note> ret = new MutableLiveData<>();
+ new Thread(() -> ret.postValue(addNote(account.getId(), entity))).start();
+ return map(ret, newNote -> {
+ notifyWidgets();
+ scheduleSync(account, true);
+ return newNote;
+ });
+ }
+
+ /**
+ * Inserts a note directly into the Database.
+ * Excerpt will be generated, {@link DBStatus#LOCAL_EDITED} will be applied in case the note has
+ * already has a local ID, otherwise {@link DBStatus#VOID} will be applied.
+ * No Synchronisation will be triggered! Use {@link #addNoteAndSync(Account, Note)}!
+ *
+ * @param note {@link Note} to be added.
+ */
+ @NonNull
+ @WorkerThread
+ public Note addNote(long accountId, @NonNull Note note) {
+ note.setStatus(note.getId() > 0 ? DBStatus.LOCAL_EDITED : DBStatus.VOID);
+ note.setAccountId(accountId);
+ note.setExcerpt(generateNoteExcerpt(note.getContent(), note.getTitle()));
+ return db.getNoteDao().getNoteById(db.getNoteDao().addNote(note));
+ }
+
+ @MainThread
+ public LiveData<Note> moveNoteToAnotherAccount(Account account, @NonNull Note note) {
+ return switchMap(db.getNoteDao().getContent$(note.getId()), (content) -> {
+ final Note fullNote = new Note(null, note.getModified(), note.getTitle(), content, note.getCategory(), note.getFavorite(), null);
+ deleteNoteAndSync(account, note.getId());
+ return addNoteAndSync(account, fullNote);
+ });
+ }
+
+ /**
+ * @return a {@link Map} of remote IDs as keys and local IDs as values of all {@link Note}s of
+ * the given {@param accountId} which are not {@link DBStatus#LOCAL_DELETED}
+ */
+ @NonNull
+ @WorkerThread
+ public Map<Long, Long> getIdMap(long accountId) {
+ validateAccountId(accountId);
+ return db.getNoteDao()
+ .getRemoteIdAndId(accountId)
+ .stream()
+ .filter(note -> note.getRemoteId() != null)
+ .collect(toMap(Note::getRemoteId, Note::getId));
+ }
+
+ @AnyThread
+ public void toggleFavoriteAndSync(Account account, long noteId) {
+ new Thread(() -> {
+ db.getNoteDao().toggleFavorite(noteId);
+ scheduleSync(account, true);
+ }).start();
+ }
+
+ /**
+ * Set the category for a given note.
+ * This method will search in the database to find out the category id in the db.
+ * If there is no such category existing, this method will create it and search again.
+ *
+ * @param account The single sign on account
+ * @param noteId The note which will be updated
+ * @param category The category title which should be used to find the category id.
+ */
+ @AnyThread
+ public void setCategory(@NonNull Account account, long noteId, @NonNull String category) {
+ new Thread(() -> {
+ db.getNoteDao().updateStatus(noteId, DBStatus.LOCAL_EDITED);
+ db.getNoteDao().updateCategory(noteId, category);
+ scheduleSync(account, true);
+ }).start();
+ }
+
+ /**
+ * Updates a single Note with a new content.
+ * The title is derived from the new content automatically, and modified date as well as DBStatus are updated, too -- if the content differs to the state in the database.
+ *
+ * @param oldNote Note to be changed
+ * @param newContent New content. If this is <code>null</code>, then <code>oldNote</code> is saved again (useful for undoing changes).
+ * @param newTitle New title. If this is <code>null</code>, then either the old title is reused (in case the note has been synced before) or a title is generated (in case it is a new note)
+ * @param callback When the synchronization is finished, this callback will be invoked (optional).
+ * @return changed {@link Note} if differs from database, otherwise the old {@link Note}.
+ */
+ @WorkerThread
+ public Note updateNoteAndSync(Account localAccount, @NonNull Note oldNote, @Nullable String newContent, @Nullable String newTitle, @Nullable ISyncCallback callback) {
+ final Note newNote;
+ if (newContent == null) {
+ newNote = new Note(oldNote.getId(), oldNote.getRemoteId(), oldNote.getModified(), oldNote.getTitle(), oldNote.getContent(), oldNote.getCategory(), oldNote.getFavorite(), oldNote.getETag(), DBStatus.LOCAL_EDITED, localAccount.getId(), oldNote.getExcerpt(), oldNote.getScrollY());
+ } else {
+ final String title;
+ if (newTitle != null) {
+ title = newTitle;
+ } else {
+ if ((oldNote.getRemoteId() == null || localAccount.getPreferredApiVersion() == null || localAccount.getPreferredApiVersion().compareTo(new ApiVersion("1.0", 0, 0)) < 0) &&
+ (defaultNonEmptyTitle.equals(oldNote.getTitle()))) {
+ title = NoteUtil.generateNonEmptyNoteTitle(newContent, context);
+ } else {
+ title = oldNote.getTitle();
+ }
+ }
+ newNote = new Note(oldNote.getId(), oldNote.getRemoteId(), Calendar.getInstance(), title, newContent, oldNote.getCategory(), oldNote.getFavorite(), oldNote.getETag(), DBStatus.LOCAL_EDITED, localAccount.getId(), generateNoteExcerpt(newContent, title), oldNote.getScrollY());
+ }
+ int rows = db.getNoteDao().updateNote(newNote);
+ // if data was changed, set new status and schedule sync (with callback); otherwise invoke callback directly.
+ if (rows > 0) {
+ notifyWidgets();
+ if (callback != null) {
+ addCallbackPush(localAccount, callback);
+ }
+ scheduleSync(localAccount, true);
+ return newNote;
+ } else {
+ if (callback != null) {
+ callback.onFinish();
+ }
+ return oldNote;
+ }
+ }
+
+ /**
+ * Marks a Note in the Database as Deleted. In the next Synchronization it will be deleted
+ * from the Server.
+ *
+ * @param id long - ID of the Note that should be deleted
+ */
+ @AnyThread
+ public void deleteNoteAndSync(Account account, long id) {
+ new Thread(() -> {
+ db.getNoteDao().updateStatus(id, DBStatus.LOCAL_DELETED);
+ notifyWidgets();
+ scheduleSync(account, true);
+
+ if (SDK_INT >= O) {
+ ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class);
+ if (shortcutManager != null) {
+ shortcutManager.getPinnedShortcuts().forEach((shortcut) -> {
+ String shortcutId = id + "";
+ if (shortcut.getId().equals(shortcutId)) {
+ Log.v(TAG, "Removing shortcut for " + shortcutId);
+ shortcutManager.disableShortcuts(Collections.singletonList(shortcutId), context.getResources().getString(R.string.note_has_been_deleted));
+ }
+ });
+ } else {
+ Log.e(TAG, ShortcutManager.class.getSimpleName() + "is null.");
+ }
+ }
+ }).start();
+ }
+
+ /**
+ * Notify about changed notes.
+ */
+ @AnyThread
+ protected void notifyWidgets() {
+ new Thread(() -> {
+ updateSingleNoteWidgets(context);
+ updateNoteListWidgets(context);
+ }).start();
+ }
+
+ @AnyThread
+ void updateDynamicShortcuts(long accountId) {
+ new Thread(() -> {
+ if (SDK_INT >= android.os.Build.VERSION_CODES.N_MR1) {
+ ShortcutManager shortcutManager = context.getApplicationContext().getSystemService(ShortcutManager.class);
+ if (shortcutManager != null) {
+ if (!shortcutManager.isRateLimitingActive()) {
+ List<ShortcutInfo> newShortcuts = new ArrayList<>();
+
+ for (Note note : db.getNoteDao().getRecentNotes(accountId)) {
+ if (!TextUtils.isEmpty(note.getTitle())) {
+ Intent intent = new Intent(context.getApplicationContext(), EditNoteActivity.class);
+ intent.putExtra(EditNoteActivity.PARAM_NOTE_ID, note.getId());
+ intent.setAction(ACTION_SHORTCUT);
+
+ newShortcuts.add(new ShortcutInfo.Builder(context.getApplicationContext(), note.getId() + "")
+ .setShortLabel(note.getTitle() + "")
+ .setIcon(Icon.createWithResource(context.getApplicationContext(), note.getFavorite() ? R.drawable.ic_star_yellow_24dp : R.drawable.ic_star_grey_ccc_24dp))
+ .setIntent(intent)
+ .build());
+ } else {
+ // Prevent crash https://github.com/stefan-niedermann/nextcloud-notes/issues/613
+ Log.e(TAG, "shortLabel cannot be empty " + note);
+ }
+ }
+ Log.d(TAG, "Update dynamic shortcuts");
+ shortcutManager.removeAllDynamicShortcuts();
+ shortcutManager.addDynamicShortcuts(newShortcuts);
+ }
+ }
+ }
+ }).start();
+ }
+
+ @AnyThread
+ public LiveData<Account> addAccount(@NonNull String url, @NonNull String username, @NonNull String accountName, @NonNull Capabilities capabilities) {
+ return db.getAccountDao().getAccountById$(db.getAccountDao().insert(new Account(url, username, accountName, capabilities)));
+ }
+
+ /**
+ * @param apiVersion has to be a JSON array as a string <code>["0.2", "1.0", ...]</code>
+ * @return whether or not the given {@link ApiVersion} has been written to the database
+ * @throws IllegalArgumentException if the apiVersion does not match the expected format
+ */
+ public boolean updateApiVersion(long accountId, @Nullable String apiVersion) throws IllegalArgumentException {
+ validateAccountId(accountId);
+ if (apiVersion != null) {
+ try {
+ JSONArray apiVersions = new JSONArray(apiVersion);
+ for (int i = 0; i < apiVersions.length(); i++) {
+ ApiVersion.of(apiVersions.getString(i));
+ }
+ if (apiVersions.length() > 0) {
+ final int updatedRows = db.getAccountDao().updateApiVersion(accountId, apiVersion);
+ if (updatedRows == 1) {
+ Log.i(TAG, "Updated apiVersion to \"" + apiVersion + "\" for accountId = " + accountId);
+ } else {
+ Log.e(TAG, "Updated " + updatedRows + " but expected only 1 for accountId = " + accountId + " and apiVersion = \"" + apiVersion + "\"");
+ }
+ return true;
+ } else {
+ Log.i(TAG, "Given API version is a valid JSON array but does not contain any valid API versions. Do not update database.");
+ }
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("API version does contain a non-valid version: " + apiVersion);
+ } catch (JSONException e) {
+ throw new IllegalArgumentException("API version must contain be a JSON array: " + apiVersion);
+ }
+ } else {
+ Log.v(TAG, "Given API version is null. Do not update database");
+ }
+ return false;
+ }
+
+ /**
+ * @param localAccount the {@link Account} that should be deleted
+ * @throws IllegalArgumentException if no account has been deleted by the given accountId
+ */
+ @AnyThread
+ public LiveData<Void> deleteAccount$(@NonNull Account localAccount) throws IllegalArgumentException {
+ validateAccountId(localAccount.getId());
+ MutableLiveData<Void> ret = new MutableLiveData<>();
+ new Thread(() -> {
+ int deletedAccounts = db.getAccountDao().deleteAccount(localAccount);
+ if (deletedAccounts < 1) {
+ Log.e(TAG, "AccountId '" + localAccount.getId() + "' did not delete any account");
+ throw new IllegalArgumentException("The given accountId does not delete any row");
+ } else if (deletedAccounts > 1) {
+ Log.e(TAG, "AccountId '" + localAccount.getId() + "' deleted unexpectedly '" + deletedAccounts + "' accounts");
+ }
+
+ try {
+ SSOClient.invalidateAPICache(AccountImporter.getSingleSignOnAccount(context, localAccount.getAccountName()));
+ } catch (NextcloudFilesAppAccountNotFoundException e) {
+ e.printStackTrace();
+ SSOClient.invalidateAPICache();
+ }
+
+ // TODO this should already be handled by foreign key cascade, no?
+ final int deletedNotes = db.getNoteDao().deleteByAccountId(localAccount.getId());
+ Log.v(TAG, "Deleted " + deletedNotes + " notes from account " + localAccount.getId());
+ ret.postValue(null);
+ }).start();
+ return ret;
+ }
+
+ private static void validateAccountId(long accountId) {
+ if (accountId < 1) {
+ throw new IllegalArgumentException("accountId must be greater than 0");
+ }
+ }
+
+ /**
+ * Modifies the sorting method for one category, the category can be normal category or
+ * one of "All notes", "Favorite", and "Uncategorized".
+ * If category is one of these three, sorting method will be modified in android.content.SharedPreference.
+ * The user can determine use which sorting method to show the notes for a category.
+ * When the user changes the sorting method, this method should be called.
+ *
+ * @param accountId The user accountID
+ * @param selectedCategory The category to be modified
+ * @param sortingMethod The sorting method in {@link CategorySortingMethod} enum format
+ */
+ @AnyThread
+ public void modifyCategoryOrder(long accountId, @NonNull NavigationCategory selectedCategory, @NonNull CategorySortingMethod sortingMethod) {
+ validateAccountId(accountId);
+
+ new Thread(() -> {
+ final Context ctx = context.getApplicationContext();
+ final SharedPreferences.Editor sp = PreferenceManager.getDefaultSharedPreferences(ctx).edit();
+ int orderIndex = sortingMethod.getId();
+
+ switch (selectedCategory.getType()) {
+ case FAVORITES: {
+ sp.putInt(ctx.getString(R.string.action_sorting_method) + ' ' + ctx.getString(R.string.label_favorites), orderIndex);
+ break;
+ }
+ case UNCATEGORIZED: {
+ sp.putInt(ctx.getString(R.string.action_sorting_method) + ' ' + ctx.getString(R.string.action_uncategorized), orderIndex);
+ break;
+ }
+ case RECENT: {
+ sp.putInt(ctx.getString(R.string.action_sorting_method) + ' ' + ctx.getString(R.string.label_all_notes), orderIndex);
+ break;
+ }
+ case DEFAULT_CATEGORY:
+ default: {
+ final String category = selectedCategory.getCategory();
+ if (category != null) {
+ if (db.getCategoryOptionsDao().modifyCategoryOrder(accountId, category, sortingMethod) == 0) {
+ // Nothing updated means we didn't have this yet
+ final CategoryOptions categoryOptions = new CategoryOptions();
+ categoryOptions.setAccountId(accountId);
+ categoryOptions.setCategory(category);
+ categoryOptions.setSortingMethod(sortingMethod);
+ db.getCategoryOptionsDao().addCategoryOptions(categoryOptions);
+ }
+ } else {
+ throw new IllegalStateException("Tried to modify category order for " + ENavigationCategoryType.DEFAULT_CATEGORY + "but category is null.");
+ }
+ break;
+ }
+ }
+ sp.apply();
+ }).start();
+ }
+
+ /**
+ * Gets the sorting method of a {@link NavigationCategory}, the category can be normal
+ * {@link CategoryOptions} or one of {@link ENavigationCategoryType}.
+ * If the category no normal {@link CategoryOptions}, sorting method will be got from
+ * {@link SharedPreferences}.
+ * <p>
+ * The sorting method of the category can be used to decide to use which sorting method to show
+ * the notes for each categories.
+ *
+ * @param selectedCategory The category
+ * @return The sorting method in CategorySortingMethod enum format
+ */
+ @NonNull
+ @MainThread
+ public LiveData<CategorySortingMethod> getCategoryOrder(@NonNull NavigationCategory selectedCategory) {
+ final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+ String prefKey;
+
+ switch (selectedCategory.getType()) {
+ // TODO make this account specific
+ case RECENT: {
+ prefKey = context.getString(R.string.action_sorting_method) + ' ' + context.getString(R.string.label_all_notes);
+ break;
+ }
+ case FAVORITES: {
+ prefKey = context.getString(R.string.action_sorting_method) + ' ' + context.getString(R.string.label_favorites);
+ break;
+ }
+ case UNCATEGORIZED: {
+ prefKey = context.getString(R.string.action_sorting_method) + ' ' + context.getString(R.string.action_uncategorized);
+ break;
+ }
+ case DEFAULT_CATEGORY:
+ default: {
+ final String category = selectedCategory.getCategory();
+ if (category != null) {
+ return db.getCategoryOptionsDao().getCategoryOrder(selectedCategory.getAccountId(), category);
+ } else {
+ Log.e(TAG, "Cannot read " + CategorySortingMethod.class.getSimpleName() + " for " + ENavigationCategoryType.DEFAULT_CATEGORY + ".");
+ return new MutableLiveData<>(CategorySortingMethod.SORT_MODIFIED_DESC);
+ }
+ }
+ }
+
+ return map(new SharedPreferenceIntLiveData(sp, prefKey, CategorySortingMethod.SORT_MODIFIED_DESC.getId()), CategorySortingMethod::findById);
+ }
+
+ public Context getContext() {
+ return context;
+ }
+
+
+ @Override
+ protected void finalize() throws Throwable {
+ context.getApplicationContext().unregisterReceiver(networkReceiver);
+ super.finalize();
+ }
+
+ /**
+ * Synchronization is only possible, if there is an active network connection.
+ * <p>
+ * This method respects the user preference "Sync on Wi-Fi only".
+ * <p>
+ * NoteServerSyncHelper observes changes in the network connection.
+ * The current state can be retrieved with this method.
+ *
+ * @return true if sync is possible, otherwise false.
+ */
+ public boolean isSyncPossible() {
+ return isSyncPossible;
+ }
+
+ public boolean isNetworkConnected() {
+ return networkConnected;
+ }
+
+ public boolean isSyncOnlyOnWifi() {
+ return syncOnlyOnWifi;
+ }
+
+ /**
+ * Adds a callback method to the NoteServerSyncHelper for the synchronization part push local changes to the server.
+ * All callbacks will be executed once the synchronization operations are done.
+ * After execution the callback will be deleted, so it has to be added again if it shall be
+ * executed the next time all synchronize operations are finished.
+ *
+ * @param callback Implementation of ISyncCallback, contains one method that shall be executed.
+ */
+ public void addCallbackPush(Account account, ISyncCallback callback) {
+ if (account == null) {
+ Log.i(TAG, "ssoAccount is null. Is this a local account?");
+ callback.onScheduled();
+ callback.onFinish();
+ } else {
+ if (!callbacksPush.containsKey(account.getId())) {
+ callbacksPush.put(account.getId(), new ArrayList<>());
+ }
+ Objects.requireNonNull(callbacksPush.get(account.getId())).add(callback);
+ }
+ }
+
+ /**
+ * Adds a callback method to the NoteServerSyncHelper for the synchronization part pull remote changes from the server.
+ * All callbacks will be executed once the synchronization operations are done.
+ * After execution the callback will be deleted, so it has to be added again if it shall be
+ * executed the next time all synchronize operations are finished.
+ *
+ * @param callback Implementation of ISyncCallback, contains one method that shall be executed.
+ */
+ public void addCallbackPull(Account account, ISyncCallback callback) {
+ if (account == null) {
+ Log.i(TAG, "ssoAccount is null. Is this a local account?");
+ callback.onScheduled();
+ callback.onFinish();
+ } else {
+ if (!callbacksPull.containsKey(account.getId())) {
+ callbacksPull.put(account.getId(), new ArrayList<>());
+ }
+ Objects.requireNonNull(callbacksPull.get(account.getId())).add(callback);
+ }
+ }
+
+ /**
+ * Schedules a synchronization and start it directly, if the network is connected and no
+ * synchronization is currently running.
+ *
+ * @param onlyLocalChanges Whether to only push local changes to the server or to also load the whole list of notes from the server.
+ */
+ public void scheduleSync(Account account, boolean onlyLocalChanges) {
+ if (account == null) {
+ Log.i(TAG, SingleSignOnAccount.class.getSimpleName() + " is null. Is this a local account?");
+ } else {
+ if (syncActive.get(account.getId()) == null) {
+ syncActive.put(account.getId(), false);
+ }
+ Log.d(TAG, "Sync requested (" + (onlyLocalChanges ? "onlyLocalChanges" : "full") + "; " + (Boolean.TRUE.equals(syncActive.get(account.getId())) ? "sync active" : "sync NOT active") + ") ...");
+ if (isSyncPossible() && (!Boolean.TRUE.equals(syncActive.get(account.getId())) || onlyLocalChanges)) {
+ try {
+ SingleSignOnAccount ssoAccount = AccountImporter.getSingleSignOnAccount(context, account.getAccountName());
+ Log.d(TAG, "... starting now");
+ final NotesClient notesClient = NotesClient.newInstance(account.getPreferredApiVersion(), context);
+ final NotesServerSyncTask syncTask = new NotesServerSyncTask(notesClient, this, account, ssoAccount, onlyLocalChanges) {
+ @Override
+ void onPreExecute() {
+ syncStatus.postValue(true);
+ if (!syncScheduled.containsKey(localAccount.getId()) || syncScheduled.get(localAccount.getId()) == null) {
+ syncScheduled.put(localAccount.getId(), false);
+ }
+ if (!onlyLocalChanges && Boolean.TRUE.equals(syncScheduled.get(localAccount.getId()))) {
+ syncScheduled.put(localAccount.getId(), false);
+ }
+ syncActive.put(localAccount.getId(), true);
+ }
+
+ @Override
+ void onPostExecute(SyncResultStatus status) {
+ for (Throwable e : exceptions) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ if (!status.pullSuccessful || !status.pushSuccessful) {
+ syncErrors.postValue(exceptions);
+ }
+ syncActive.put(localAccount.getId(), false);
+ // notify callbacks
+ if (callbacks.containsKey(localAccount.getId()) && callbacks.get(localAccount.getId()) != null) {
+ for (ISyncCallback callback : Objects.requireNonNull(callbacks.get(localAccount.getId()))) {
+ callback.onFinish();
+ }
+ }
+ notifyWidgets();
+ updateDynamicShortcuts(localAccount.getId());
+ // start next sync if scheduled meanwhile
+ if (syncScheduled.containsKey(localAccount.getId()) && syncScheduled.get(localAccount.getId()) != null && Boolean.TRUE.equals(syncScheduled.get(localAccount.getId()))) {
+ scheduleSync(localAccount, false);
+ }
+ syncStatus.postValue(false);
+ }
+ };
+ syncTask.addCallbacks(account, callbacksPush.get(account.getId()));
+ callbacksPush.put(account.getId(), new ArrayList<>());
+ if (!onlyLocalChanges) {
+ syncTask.addCallbacks(account, callbacksPull.get(account.getId()));
+ callbacksPull.put(account.getId(), new ArrayList<>());
+ }
+ executor.submit(syncTask);
+ } catch (NextcloudFilesAppAccountNotFoundException e) {
+ Log.e(TAG, "... Could not find " + SingleSignOnAccount.class.getSimpleName() + " for account name " + account.getAccountName());
+ e.printStackTrace();
+ }
+ } else if (!onlyLocalChanges) {
+ Log.d(TAG, "... scheduled");
+ syncScheduled.put(account.getId(), true);
+ if (callbacksPush.containsKey(account.getId()) && callbacksPush.get(account.getId()) != null) {
+ final List<ISyncCallback> callbacks = callbacksPush.get(account.getId());
+ if (callbacks != null) {
+ for (ISyncCallback callback : callbacks) {
+ callback.onScheduled();
+ }
+ } else {
+ Log.w(TAG, "List of push-callbacks was set for account \"" + account.getAccountName() + "\" but it was null");
+ }
+ }
+ } else {
+ Log.d(TAG, "... do nothing");
+ if (callbacksPush.containsKey(account.getId()) && callbacksPush.get(account.getId()) != null) {
+ final List<ISyncCallback> callbacks = callbacksPush.get(account.getId());
+ if (callbacks != null) {
+ for (ISyncCallback callback : callbacks) {
+ callback.onScheduled();
+ }
+ } else {
+ Log.w(TAG, "List of push-callbacks was set for account \"" + account.getAccountName() + "\" but it was null");
+ }
+ }
+ }
+ }
+ }
+
+ public void updateNetworkStatus() {
+ try {
+ final ConnectivityManager connMgr = (ConnectivityManager) context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
+
+ if (connMgr == null) {
+ throw new NetworkErrorException("ConnectivityManager is null");
+ }
+
+ final NetworkInfo activeInfo = connMgr.getActiveNetworkInfo();
+
+ if (activeInfo == null) {
+ throw new NetworkErrorException("NetworkInfo is null");
+ }
+
+ if (activeInfo.isConnected()) {
+ networkConnected = true;
+
+ final NetworkInfo networkInfo = connMgr.getNetworkInfo((ConnectivityManager.TYPE_WIFI));
+
+ if (networkInfo == null) {
+ throw new NetworkErrorException("connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI) is null");
+ }
+
+ isSyncPossible = !syncOnlyOnWifi || networkInfo.isConnected();
+
+ if (isSyncPossible) {
+ Log.d(TAG, "Network connection established.");
+ } else {
+ Log.d(TAG, "Network connected, but not used because only synced on wifi.");
+ }
+ } else {
+ networkConnected = false;
+ isSyncPossible = false;
+ Log.d(TAG, "No network connection.");
+ }
+ } catch (NetworkErrorException e) {
+ e.printStackTrace();
+ networkConnected = false;
+ isSyncPossible = false;
+ }
+ }
+
+ @NonNull
+ public LiveData<Boolean> getSyncStatus() {
+ return distinctUntilChanged(this.syncStatus);
+ }
+
+ @NonNull
+ public LiveData<ArrayList<Throwable>> getSyncErrors() {
+ return this.syncErrors;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesServerSyncHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesServerSyncHelper.java
deleted file mode 100644
index e9fab1b0..00000000
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesServerSyncHelper.java
+++ /dev/null
@@ -1,346 +0,0 @@
-package it.niedermann.owncloud.notes.persistence;
-
-import android.accounts.NetworkErrorException;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.MutableLiveData;
-import androidx.preference.PreferenceManager;
-
-import com.nextcloud.android.sso.AccountImporter;
-import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
-import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
-import com.nextcloud.android.sso.helper.SingleAccountHelper;
-import com.nextcloud.android.sso.model.SingleSignOnAccount;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-import it.niedermann.owncloud.notes.R;
-import it.niedermann.owncloud.notes.persistence.entity.Account;
-import it.niedermann.owncloud.notes.shared.model.ISyncCallback;
-import it.niedermann.owncloud.notes.shared.model.SyncResultStatus;
-import it.niedermann.owncloud.notes.shared.util.SSOUtil;
-
-import static androidx.lifecycle.Transformations.distinctUntilChanged;
-
-/**
- * Helps to synchronize the Database to the Server.
- */
-public class NotesServerSyncHelper {
-
- private static final String TAG = NotesServerSyncHelper.class.getSimpleName();
-
- private final ExecutorService executor = Executors.newSingleThreadExecutor();
-
- private static NotesServerSyncHelper instance;
-
- private final NotesDatabase db;
- private final Context context;
-
- /**
- * Track network connection changes using a {@link BroadcastReceiver}
- */
- private boolean isSyncPossible = false;
- private boolean networkConnected = false;
- private String syncOnlyOnWifiKey;
- private boolean syncOnlyOnWifi;
- private final MutableLiveData<Boolean> syncStatus = new MutableLiveData<>(false);
- private final MutableLiveData<ArrayList<Throwable>> syncErrors = new MutableLiveData<>();
-
- /**
- * @see <a href="https://stackoverflow.com/a/3104265">Do not make this a local variable.</a>
- */
- @SuppressWarnings("FieldCanBeLocal")
- private final SharedPreferences.OnSharedPreferenceChangeListener onSharedPreferenceChangeListener = (SharedPreferences prefs, String key) -> {
- if (syncOnlyOnWifiKey.equals(key)) {
- syncOnlyOnWifi = prefs.getBoolean(syncOnlyOnWifiKey, false);
- updateNetworkStatus();
- }
- };
-
- private final BroadcastReceiver networkReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- updateNetworkStatus();
- if (isSyncPossible() && SSOUtil.isConfigured(context)) {
- new Thread(() -> {
- try {
- scheduleSync(db.getAccountDao().getAccountByName(SingleAccountHelper.getCurrentSingleSignOnAccount(context).name), false);
- } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
- Log.v(TAG, "Can not select current SingleSignOn account after network changed, do not sync.");
- }
- }).start();
- }
- }
- };
-
- // current state of the synchronization
- private final Map<Long, Boolean> syncActive = new HashMap<>();
- private final Map<Long, Boolean> syncScheduled = new HashMap<>();
-
- // list of callbacks for both parts of synchronization
- private final Map<Long, List<ISyncCallback>> callbacksPush = new HashMap<>();
- private final Map<Long, List<ISyncCallback>> callbacksPull = new HashMap<>();
-
- private NotesServerSyncHelper(NotesDatabase db) {
- this.db = db;
- this.context = db.getContext();
- this.syncOnlyOnWifiKey = context.getApplicationContext().getResources().getString(R.string.pref_key_wifi_only);
-
- // Registers BroadcastReceiver to track network connection changes.
- context.getApplicationContext().registerReceiver(networkReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
-
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.context.getApplicationContext());
- prefs.registerOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener);
- syncOnlyOnWifi = prefs.getBoolean(syncOnlyOnWifiKey, false);
-
- updateNetworkStatus();
- }
-
- /**
- * Get (or create) instance from NoteServerSyncHelper.
- * This has to be a singleton in order to realize correct registering and unregistering of
- * the BroadcastReceiver, which listens on changes of network connectivity.
- *
- * @param db {@link NotesDatabase}
- * @return NoteServerSyncHelper
- */
- public static synchronized NotesServerSyncHelper getInstance(NotesDatabase db) {
- if (instance == null) {
- instance = new NotesServerSyncHelper(db);
- }
- return instance;
- }
-
- @Override
- protected void finalize() throws Throwable {
- context.getApplicationContext().unregisterReceiver(networkReceiver);
- super.finalize();
- }
-
- /**
- * Synchronization is only possible, if there is an active network connection.
- * <p>
- * This method respects the user preference "Sync on Wi-Fi only".
- * <p>
- * NoteServerSyncHelper observes changes in the network connection.
- * The current state can be retrieved with this method.
- *
- * @return true if sync is possible, otherwise false.
- */
- public boolean isSyncPossible() {
- return isSyncPossible;
- }
-
- public boolean isNetworkConnected() {
- return networkConnected;
- }
-
- public boolean isSyncOnlyOnWifi() {
- return syncOnlyOnWifi;
- }
-
- /**
- * Adds a callback method to the NoteServerSyncHelper for the synchronization part push local changes to the server.
- * All callbacks will be executed once the synchronization operations are done.
- * After execution the callback will be deleted, so it has to be added again if it shall be
- * executed the next time all synchronize operations are finished.
- *
- * @param callback Implementation of ISyncCallback, contains one method that shall be executed.
- */
- public void addCallbackPush(Account account, ISyncCallback callback) {
- if (account == null) {
- Log.i(TAG, "ssoAccount is null. Is this a local account?");
- callback.onScheduled();
- callback.onFinish();
- } else {
- if (!callbacksPush.containsKey(account.getId())) {
- callbacksPush.put(account.getId(), new ArrayList<>());
- }
- Objects.requireNonNull(callbacksPush.get(account.getId())).add(callback);
- }
- }
-
- /**
- * Adds a callback method to the NoteServerSyncHelper for the synchronization part pull remote changes from the server.
- * All callbacks will be executed once the synchronization operations are done.
- * After execution the callback will be deleted, so it has to be added again if it shall be
- * executed the next time all synchronize operations are finished.
- *
- * @param callback Implementation of ISyncCallback, contains one method that shall be executed.
- */
- public void addCallbackPull(Account account, ISyncCallback callback) {
- if (account == null) {
- Log.i(TAG, "ssoAccount is null. Is this a local account?");
- callback.onScheduled();
- callback.onFinish();
- } else {
- if (!callbacksPull.containsKey(account.getId())) {
- callbacksPull.put(account.getId(), new ArrayList<>());
- }
- Objects.requireNonNull(callbacksPull.get(account.getId())).add(callback);
- }
- }
-
- /**
- * Schedules a synchronization and start it directly, if the network is connected and no
- * synchronization is currently running.
- *
- * @param onlyLocalChanges Whether to only push local changes to the server or to also load the whole list of notes from the server.
- */
- public void scheduleSync(Account account, boolean onlyLocalChanges) {
- if (account == null) {
- Log.i(TAG, SingleSignOnAccount.class.getSimpleName() + " is null. Is this a local account?");
- } else {
- if (syncActive.get(account.getId()) == null) {
- syncActive.put(account.getId(), false);
- }
- Log.d(TAG, "Sync requested (" + (onlyLocalChanges ? "onlyLocalChanges" : "full") + "; " + (Boolean.TRUE.equals(syncActive.get(account.getId())) ? "sync active" : "sync NOT active") + ") ...");
- if (isSyncPossible() && (!Boolean.TRUE.equals(syncActive.get(account.getId())) || onlyLocalChanges)) {
- try {
- SingleSignOnAccount ssoAccount = AccountImporter.getSingleSignOnAccount(context, account.getAccountName());
- Log.d(TAG, "... starting now");
- final NotesClient notesClient = NotesClient.newInstance(account.getPreferredApiVersion(), context);
- final NotesServerSyncTask syncTask = new NotesServerSyncTask(notesClient, db, account, ssoAccount, onlyLocalChanges) {
- @Override
- void onPreExecute() {
- syncStatus.postValue(true);
- if (!syncScheduled.containsKey(localAccount.getId()) || syncScheduled.get(localAccount.getId()) == null) {
- syncScheduled.put(localAccount.getId(), false);
- }
- if (!onlyLocalChanges && Boolean.TRUE.equals(syncScheduled.get(localAccount.getId()))) {
- syncScheduled.put(localAccount.getId(), false);
- }
- syncActive.put(localAccount.getId(), true);
- }
-
- @Override
- void onPostExecute(SyncResultStatus status) {
- for (Throwable e : exceptions) {
- Log.e(TAG, e.getMessage(), e);
- }
- if (!status.pullSuccessful || !status.pushSuccessful) {
- syncErrors.postValue(exceptions);
- }
- syncActive.put(localAccount.getId(), false);
- // notify callbacks
- if (callbacks.containsKey(localAccount.getId()) && callbacks.get(localAccount.getId()) != null) {
- for (ISyncCallback callback : Objects.requireNonNull(callbacks.get(localAccount.getId()))) {
- callback.onFinish();
- }
- }
- db.notifyWidgets();
- db.updateDynamicShortcuts(localAccount.getId());
- // start next sync if scheduled meanwhile
- if (syncScheduled.containsKey(localAccount.getId()) && syncScheduled.get(localAccount.getId()) != null && Boolean.TRUE.equals(syncScheduled.get(localAccount.getId()))) {
- scheduleSync(localAccount, false);
- }
- syncStatus.postValue(false);
- }
- };
- syncTask.addCallbacks(account, callbacksPush.get(account.getId()));
- callbacksPush.put(account.getId(), new ArrayList<>());
- if (!onlyLocalChanges) {
- syncTask.addCallbacks(account, callbacksPull.get(account.getId()));
- callbacksPull.put(account.getId(), new ArrayList<>());
- }
- executor.submit(syncTask);
- } catch (NextcloudFilesAppAccountNotFoundException e) {
- Log.e(TAG, "... Could not find " + SingleSignOnAccount.class.getSimpleName() + " for account name " + account.getAccountName());
- e.printStackTrace();
- }
- } else if (!onlyLocalChanges) {
- Log.d(TAG, "... scheduled");
- syncScheduled.put(account.getId(), true);
- if (callbacksPush.containsKey(account.getId()) && callbacksPush.get(account.getId()) != null) {
- final List<ISyncCallback> callbacks = callbacksPush.get(account.getId());
- if (callbacks != null) {
- for (ISyncCallback callback : callbacks) {
- callback.onScheduled();
- }
- } else {
- Log.w(TAG, "List of push-callbacks was set for account \"" + account.getAccountName() + "\" but it was null");
- }
- }
- } else {
- Log.d(TAG, "... do nothing");
- if (callbacksPush.containsKey(account.getId()) && callbacksPush.get(account.getId()) != null) {
- final List<ISyncCallback> callbacks = callbacksPush.get(account.getId());
- if (callbacks != null) {
- for (ISyncCallback callback : callbacks) {
- callback.onScheduled();
- }
- } else {
- Log.w(TAG, "List of push-callbacks was set for account \"" + account.getAccountName() + "\" but it was null");
- }
- }
- }
- }
- }
-
- public void updateNetworkStatus() {
- try {
- final ConnectivityManager connMgr = (ConnectivityManager) context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
-
- if (connMgr == null) {
- throw new NetworkErrorException("ConnectivityManager is null");
- }
-
- final NetworkInfo activeInfo = connMgr.getActiveNetworkInfo();
-
- if (activeInfo == null) {
- throw new NetworkErrorException("NetworkInfo is null");
- }
-
- if (activeInfo.isConnected()) {
- networkConnected = true;
-
- final NetworkInfo networkInfo = connMgr.getNetworkInfo((ConnectivityManager.TYPE_WIFI));
-
- if (networkInfo == null) {
- throw new NetworkErrorException("connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI) is null");
- }
-
- isSyncPossible = !syncOnlyOnWifi || networkInfo.isConnected();
-
- if (isSyncPossible) {
- Log.d(TAG, "Network connection established.");
- } else {
- Log.d(TAG, "Network connected, but not used because only synced on wifi.");
- }
- } else {
- networkConnected = false;
- isSyncPossible = false;
- Log.d(TAG, "No network connection.");
- }
- } catch (NetworkErrorException e) {
- e.printStackTrace();
- networkConnected = false;
- isSyncPossible = false;
- }
- }
-
- @NonNull
- public LiveData<Boolean> getSyncStatus() {
- return distinctUntilChanged(this.syncStatus);
- }
-
- @NonNull
- public LiveData<ArrayList<Throwable>> getSyncErrors() {
- return this.syncErrors;
- }
-}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesServerSyncTask.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesServerSyncTask.java
index dcc44979..2b4e6201 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesServerSyncTask.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesServerSyncTask.java
@@ -39,7 +39,7 @@ abstract class NotesServerSyncTask extends Thread {
@NonNull
private final NotesClient notesClient;
@NonNull
- private final NotesDatabase db;
+ private final NotesRepository repo;
@NonNull
protected final Account localAccount;
@NonNull
@@ -50,10 +50,10 @@ abstract class NotesServerSyncTask extends Thread {
@NonNull
protected final ArrayList<Throwable> exceptions = new ArrayList<>();
- NotesServerSyncTask(@NonNull NotesClient notesClient, @NonNull NotesDatabase db, @NonNull Account localAccount, @NonNull SingleSignOnAccount ssoAccount, boolean onlyLocalChanges) {
+ NotesServerSyncTask(@NonNull NotesClient notesClient, @NonNull NotesRepository repo, @NonNull Account localAccount, @NonNull SingleSignOnAccount ssoAccount, boolean onlyLocalChanges) {
super(TAG);
this.notesClient = notesClient;
- this.db = db;
+ this.repo = repo;
this.localAccount = localAccount;
this.ssoAccount = ssoAccount;
this.onlyLocalChanges = onlyLocalChanges;
@@ -89,7 +89,7 @@ abstract class NotesServerSyncTask extends Thread {
Log.d(TAG, "pushLocalChanges()");
boolean success = true;
- final List<Note> notes = db.getNoteDao().getLocalModifiedNotes(localAccount.getId());
+ final List<Note> notes = repo.getLocalModifiedNotes(localAccount.getId());
for (Note note : notes) {
Log.d(TAG, " Process Local Note: " + note);
try {
@@ -112,10 +112,10 @@ abstract class NotesServerSyncTask extends Thread {
} else {
Log.v(TAG, " ...Note does not have a remoteId yet → create");
remoteNote = notesClient.createNote(ssoAccount, note).getNote();
- db.getNoteDao().updateRemoteId(note.getId(), remoteNote.getRemoteId());
+ repo.updateRemoteId(note.getId(), remoteNote.getRemoteId());
}
// Please note, that db.updateNote() realized an optimistic conflict resolution, which is required for parallel changes of this Note from the UI.
- db.getNoteDao().updateIfNotModifiedLocallyDuringSync(note.getId(), remoteNote.getModified().getTimeInMillis(), remoteNote.getTitle(), remoteNote.getFavorite(), remoteNote.getETag(), remoteNote.getContent(), generateNoteExcerpt(remoteNote.getContent(), remoteNote.getTitle()), note.getContent(), note.getCategory(), note.getFavorite());
+ repo.updateIfNotModifiedLocallyDuringSync(note.getId(), remoteNote.getModified().getTimeInMillis(), remoteNote.getTitle(), remoteNote.getFavorite(), remoteNote.getETag(), remoteNote.getContent(), generateNoteExcerpt(remoteNote.getContent(), remoteNote.getTitle()), note.getContent(), note.getCategory(), note.getFavorite());
break;
case LOCAL_DELETED:
if (note.getRemoteId() == null) {
@@ -133,7 +133,7 @@ abstract class NotesServerSyncTask extends Thread {
}
}
// Please note, that db.deleteNote() realizes an optimistic conflict resolution, which is required for parallel changes of this Note from the UI.
- db.getNoteDao().deleteByNoteId(note.getId(), LOCAL_DELETED);
+ repo.deleteByNoteId(note.getId(), LOCAL_DELETED);
break;
default:
throw new IllegalStateException("Unknown State of Note " + note + ": " + note.getStatus());
@@ -162,10 +162,10 @@ abstract class NotesServerSyncTask extends Thread {
private boolean pullRemoteChanges() {
Log.d(TAG, "pullRemoteChanges() for account " + localAccount.getAccountName());
try {
- final Map<Long, Long> idMap = db.getIdMap(localAccount.getId());
+ final Map<Long, Long> idMap = repo.getIdMap(localAccount.getId());
// FIXME re-reading the localAccount is only a workaround for a not-up-to-date eTag in localAccount.
- final Account accountFromDatabase = db.getAccountDao().getAccountById(localAccount.getId());
+ final Account accountFromDatabase = repo.getAccountById(localAccount.getId());
if (accountFromDatabase == null) {
callbacks.remove(localAccount.getId());
return true;
@@ -186,14 +186,14 @@ abstract class NotesServerSyncTask extends Thread {
Log.v(TAG, " ... found → Update");
Long localId = idMap.get(remoteNote.getRemoteId());
if (localId != null) {
- db.getNoteDao().updateIfNotModifiedLocallyAndAnyRemoteColumnHasChanged(
+ repo.updateIfNotModifiedLocallyAndAnyRemoteColumnHasChanged(
localId, remoteNote.getModified().getTimeInMillis(), remoteNote.getTitle(), remoteNote.getFavorite(), remoteNote.getCategory(), remoteNote.getETag(), remoteNote.getContent(), generateNoteExcerpt(remoteNote.getContent(), remoteNote.getTitle()));
} else {
Log.e(TAG, "Tried to update note from server, but local id of note is null. " + remoteNote);
}
} else {
Log.v(TAG, " ... create");
- db.addNote(localAccount.getId(), remoteNote);
+ repo.addNote(localAccount.getId(), remoteNote);
}
}
Log.d(TAG, " Remove remotely deleted Notes (only those without local changes)");
@@ -201,17 +201,17 @@ abstract class NotesServerSyncTask extends Thread {
for (Map.Entry<Long, Long> entry : idMap.entrySet()) {
if (!remoteIDs.contains(entry.getKey())) {
Log.v(TAG, " ... remove " + entry.getValue());
- db.getNoteDao().deleteByNoteId(entry.getValue(), DBStatus.VOID);
+ repo.deleteByNoteId(entry.getValue(), DBStatus.VOID);
}
}
// update ETag and Last-Modified in order to reduce size of next response
localAccount.setETag(response.getETag());
localAccount.setModified(response.getLastModified());
- db.getAccountDao().updateETag(localAccount.getId(), localAccount.getETag());
- db.getAccountDao().updateModified(localAccount.getId(), localAccount.getModified().getTimeInMillis());
+ repo.updateETag(localAccount.getId(), localAccount.getETag());
+ repo.updateModified(localAccount.getId(), localAccount.getModified().getTimeInMillis());
try {
- if (db.updateApiVersion(localAccount.getId(), response.getSupportedApiVersions())) {
+ if (repo.updateApiVersion(localAccount.getId(), response.getSupportedApiVersions())) {
localAccount.setApiVersion(response.getSupportedApiVersions());
}
} catch (Exception e) {
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/SyncWorker.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/SyncWorker.java
index 1b86e0aa..1d4a8bc7 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/SyncWorker.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/SyncWorker.java
@@ -34,11 +34,11 @@ public class SyncWorker extends Worker {
@NonNull
@Override
public Result doWork() {
- NotesDatabase db = NotesDatabase.getInstance(getApplicationContext());
- for (Account account : db.getAccountDao().getAccounts()) {
+ NotesRepository repo = NotesRepository.getInstance(getApplicationContext());
+ for (Account account : repo.getAccounts()) {
Log.v(TAG, "Starting background synchronization for " + account.getAccountName());
- db.getNoteServerSyncHelper().addCallbackPull(account, () -> Log.v(TAG, "Finished background synchronization for " + account.getAccountName()));
- db.getNoteServerSyncHelper().scheduleSync(account, false);
+ repo.addCallbackPull(account, () -> Log.v(TAG, "Finished background synchronization for " + account.getAccountName()));
+ repo.scheduleSync(account, false);
}
// TODO return result depending on callbackPull
return Result.success();
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java
index 8cc73bf8..45979aa8 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java
@@ -10,7 +10,6 @@ import androidx.room.Update;
import java.util.List;
import java.util.Set;
-import it.niedermann.owncloud.notes.persistence.NotesServerSyncHelper;
import it.niedermann.owncloud.notes.persistence.entity.Account;
import it.niedermann.owncloud.notes.persistence.entity.CategoryWithNotesCount;
import it.niedermann.owncloud.notes.persistence.entity.Note;
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_13_14.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_13_14.java
index 85e02617..3d0147fb 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_13_14.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_13_14.java
@@ -1,7 +1,9 @@
package it.niedermann.owncloud.notes.persistence.migration;
+import android.appwidget.AppWidgetManager;
import android.content.ContentValues;
import android.content.Context;
+import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
@@ -14,19 +16,18 @@ import androidx.sqlite.db.SupportSQLiteDatabase;
import java.util.Map;
import it.niedermann.owncloud.notes.preferences.DarkModeSetting;
+import it.niedermann.owncloud.notes.widget.notelist.NoteListWidget;
+import it.niedermann.owncloud.notes.widget.singlenote.SingleNoteWidget;
public class Migration_13_14 extends Migration {
private static final String TAG = Migration_13_14.class.getSimpleName();
@NonNull
private final Context context;
- @NonNull
- private final Runnable notifyWidgets;
- public Migration_13_14(@NonNull Context context, @NonNull Runnable notifyWidgets) {
+ public Migration_13_14(@NonNull Context context) {
super(13, 14);
this.context = context;
- this.notifyWidgets = notifyWidgets;
}
/**
@@ -86,6 +87,7 @@ public class Migration_13_14 extends Migration {
}
}
editor.apply();
- notifyWidgets.run();
+ context.sendBroadcast(new Intent(context, SingleNoteWidget.class).setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE));
+ context.sendBroadcast(new Intent(context, NoteListWidget.class).setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE));
}
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_15_16.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_15_16.java
index 2732151f..48b7195b 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_15_16.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/migration/Migration_15_16.java
@@ -1,7 +1,9 @@
package it.niedermann.owncloud.notes.persistence.migration;
+import android.appwidget.AppWidgetManager;
import android.content.ContentValues;
import android.content.Context;
+import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.util.Log;
@@ -15,19 +17,18 @@ import androidx.sqlite.db.SupportSQLiteDatabase;
import java.util.Map;
import it.niedermann.owncloud.notes.preferences.DarkModeSetting;
+import it.niedermann.owncloud.notes.widget.notelist.NoteListWidget;
+import it.niedermann.owncloud.notes.widget.singlenote.SingleNoteWidget;
public class Migration_15_16 extends Migration {
private static final String TAG = Migration_15_16.class.getSimpleName();
@NonNull
private final Context context;
- @NonNull
- private final Runnable notifyWidgets;
- public Migration_15_16(@NonNull Context context, @NonNull Runnable notifyWidgets) {
+ public Migration_15_16(@NonNull Context context) {
super(15, 16);
this.context = context;
- this.notifyWidgets = notifyWidgets;
}
/**
@@ -104,6 +105,7 @@ public class Migration_15_16 extends Migration {
}
}
editor.apply();
- notifyWidgets.run();
+ context.sendBroadcast(new Intent(context, SingleNoteWidget.class).setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE));
+ context.sendBroadcast(new Intent(context, NoteListWidget.class).setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE));
}
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListViewModel.java b/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListViewModel.java
index ad3830a4..9ec3e355 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListViewModel.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListViewModel.java
@@ -14,7 +14,7 @@ import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.main.MainActivity;
import it.niedermann.owncloud.notes.main.navigation.NavigationAdapter;
import it.niedermann.owncloud.notes.main.navigation.NavigationItem;
-import it.niedermann.owncloud.notes.persistence.NotesDatabase;
+import it.niedermann.owncloud.notes.persistence.NotesRepository;
import static androidx.lifecycle.Transformations.distinctUntilChanged;
import static androidx.lifecycle.Transformations.map;
@@ -28,20 +28,20 @@ public class NoteListViewModel extends AndroidViewModel {
private static final String TAG = NoteListViewModel.class.getSimpleName();
@NonNull
- private final NotesDatabase db;
+ private final NotesRepository repo;
public NoteListViewModel(@NonNull Application application) {
super(application);
- this.db = NotesDatabase.getInstance(application);
+ this.repo = NotesRepository.getInstance(application);
}
public LiveData<List<NavigationItem>> getAdapterCategories(Long accountId) {
return distinctUntilChanged(
- switchMap(distinctUntilChanged(db.getNoteDao().count$(accountId)), (count) -> {
+ switchMap(distinctUntilChanged(repo.count$(accountId)), (count) -> {
Log.v(TAG, "[getAdapterCategories] countLiveData: " + count);
- return switchMap(distinctUntilChanged(db.getNoteDao().countFavorites$(accountId)), (favoritesCount) -> {
+ return switchMap(distinctUntilChanged(repo.countFavorites$(accountId)), (favoritesCount) -> {
Log.v(TAG, "[getAdapterCategories] getFavoritesCountLiveData: " + favoritesCount);
- return map(distinctUntilChanged(db.getNoteDao().getCategories$(accountId)), fromDatabase -> {
+ return map(distinctUntilChanged(repo.getCategories$(accountId)), fromDatabase -> {
final List<NavigationItem.CategoryNavigationItem> categories = convertToCategoryNavigationItem(getApplication(), fromDatabase);
final List<NavigationItem> items = new ArrayList<>(fromDatabase.size() + 3);
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidget.java b/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidget.java
index 98ebf2fe..7cc84de3 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidget.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidget.java
@@ -13,20 +13,20 @@ import android.widget.RemoteViews;
import java.util.NoSuchElementException;
import it.niedermann.owncloud.notes.R;
-import it.niedermann.owncloud.notes.persistence.NotesDatabase;
+import it.niedermann.owncloud.notes.persistence.NotesRepository;
import it.niedermann.owncloud.notes.persistence.entity.NotesListWidgetData;
public class NoteListWidget extends AppWidgetProvider {
private static final String TAG = NoteListWidget.class.getSimpleName();
static void updateAppWidget(Context context, AppWidgetManager awm, int[] appWidgetIds) {
- final NotesDatabase db = NotesDatabase.getInstance(context);
+ final NotesRepository repo = NotesRepository.getInstance(context);
RemoteViews views;
for (int appWidgetId : appWidgetIds) {
try {
- final NotesListWidgetData data = db.getWidgetNotesListDao().getNoteListWidgetData(appWidgetId);
+ final NotesListWidgetData data = repo.getNoteListWidgetData(appWidgetId);
final Intent serviceIntent = new Intent(context, NoteListWidgetService.class);
serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
@@ -80,10 +80,10 @@ public class NoteListWidget extends AppWidgetProvider {
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
super.onDeleted(context, appWidgetIds);
- final NotesDatabase db = NotesDatabase.getInstance(context);
+ final NotesRepository repo = NotesRepository.getInstance(context);
for (int appWidgetId : appWidgetIds) {
- new Thread(() -> db.getWidgetNotesListDao().removeNoteListWidget(appWidgetId)).start();
+ new Thread(() -> repo.removeNoteListWidget(appWidgetId)).start();
}
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidgetConfigurationActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidgetConfigurationActivity.java
index 2c2aa086..a750ee1e 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidgetConfigurationActivity.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidgetConfigurationActivity.java
@@ -21,11 +21,9 @@ import it.niedermann.owncloud.notes.databinding.ActivityNoteListConfigurationBin
import it.niedermann.owncloud.notes.main.navigation.NavigationAdapter;
import it.niedermann.owncloud.notes.main.navigation.NavigationClickListener;
import it.niedermann.owncloud.notes.main.navigation.NavigationItem;
-import it.niedermann.owncloud.notes.persistence.NotesDatabase;
+import it.niedermann.owncloud.notes.persistence.NotesRepository;
import it.niedermann.owncloud.notes.persistence.entity.Account;
-import it.niedermann.owncloud.notes.persistence.entity.CategoryOptions;
import it.niedermann.owncloud.notes.persistence.entity.NotesListWidgetData;
-import it.niedermann.owncloud.notes.shared.model.CategorySortingMethod;
import static it.niedermann.owncloud.notes.persistence.entity.NotesListWidgetData.MODE_DISPLAY_ALL;
import static it.niedermann.owncloud.notes.persistence.entity.NotesListWidgetData.MODE_DISPLAY_CATEGORY;
@@ -43,14 +41,14 @@ public class NoteListWidgetConfigurationActivity extends LockedActivity {
private ActivityNoteListConfigurationBinding binding;
private NoteListViewModel viewModel;
private NavigationAdapter adapterCategories;
- private NotesDatabase db = null;
+ private NotesRepository repo = null;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setResult(RESULT_CANCELED);
- db = NotesDatabase.getInstance(this);
+ repo = NotesRepository.getInstance(this);
final Bundle extras = getIntent().getExtras();
if (extras != null) {
@@ -107,7 +105,7 @@ public class NoteListWidgetConfigurationActivity extends LockedActivity {
data.setThemeMode(NotesApplication.getAppTheme(getApplicationContext()).getModeId());
new Thread(() -> {
- db.getWidgetNotesListDao().createOrUpdateNoteListWidgetData(data);
+ repo.createOrUpdateNoteListWidgetData(data);
final Intent updateIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE, null, getApplicationContext(), NoteListWidget.class)
.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
@@ -126,7 +124,7 @@ public class NoteListWidgetConfigurationActivity extends LockedActivity {
new Thread(() -> {
try {
- this.localAccount = db.getAccountDao().getAccountByName(SingleAccountHelper.getCurrentSingleSignOnAccount(this).name);
+ this.localAccount = repo.getAccountByName(SingleAccountHelper.getCurrentSingleSignOnAccount(this).name);
} catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
e.printStackTrace();
Toast.makeText(this, R.string.widget_not_logged_in, Toast.LENGTH_LONG).show();
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidgetFactory.java b/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidgetFactory.java
index 7917b25a..b7482233 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidgetFactory.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/notelist/NoteListWidgetFactory.java
@@ -19,7 +19,7 @@ import java.util.List;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.edit.EditNoteActivity;
import it.niedermann.owncloud.notes.main.MainActivity;
-import it.niedermann.owncloud.notes.persistence.NotesDatabase;
+import it.niedermann.owncloud.notes.persistence.NotesRepository;
import it.niedermann.owncloud.notes.persistence.entity.Account;
import it.niedermann.owncloud.notes.persistence.entity.Note;
import it.niedermann.owncloud.notes.persistence.entity.NotesListWidgetData;
@@ -37,7 +37,7 @@ public class NoteListWidgetFactory implements RemoteViewsService.RemoteViewsFact
private final Context context;
private final int appWidgetId;
- private final NotesDatabase db;
+ private final NotesRepository repo;
@NonNull
private final List<Note> dbNotes = new ArrayList<>();
private NotesListWidgetData data;
@@ -45,7 +45,7 @@ public class NoteListWidgetFactory implements RemoteViewsService.RemoteViewsFact
NoteListWidgetFactory(Context context, Intent intent) {
this.context = context;
this.appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
- db = NotesDatabase.getInstance(context);
+ repo = NotesRepository.getInstance(context);
}
@Override
@@ -57,21 +57,21 @@ public class NoteListWidgetFactory implements RemoteViewsService.RemoteViewsFact
public void onDataSetChanged() {
dbNotes.clear();
try {
- data = db.getWidgetNotesListDao().getNoteListWidgetData(appWidgetId);
+ data = repo.getNoteListWidgetData(appWidgetId);
Log.v(TAG, "--- data - " + data);
switch (data.getMode()) {
case MODE_DISPLAY_ALL:
- dbNotes.addAll(db.getNoteDao().searchRecentByModified(data.getAccountId(), "%"));
+ dbNotes.addAll(repo.searchRecentByModified(data.getAccountId(), "%"));
break;
case MODE_DISPLAY_STARRED:
- dbNotes.addAll(db.getNoteDao().searchFavoritesByModified(data.getAccountId(), "%"));
+ dbNotes.addAll(repo.searchFavoritesByModified(data.getAccountId(), "%"));
break;
case MODE_DISPLAY_CATEGORY:
default:
if (data.getCategory() != null) {
- dbNotes.addAll(db.getNoteDao().searchCategoryByModified(data.getAccountId(), "%", data.getCategory()));
+ dbNotes.addAll(repo.searchCategoryByModified(data.getAccountId(), "%", data.getCategory()));
} else {
- dbNotes.addAll(db.getNoteDao().searchUncategorizedByModified(data.getAccountId(), "%"));
+ dbNotes.addAll(repo.searchUncategorizedByModified(data.getAccountId(), "%"));
}
break;
}
@@ -95,7 +95,7 @@ public class NoteListWidgetFactory implements RemoteViewsService.RemoteViewsFact
final RemoteViews note_content;
if (position == 0) {
- final Account localAccount = db.getAccountDao().getAccountById(data.getAccountId());
+ final Account localAccount = repo.getAccountById(data.getAccountId());
final Intent openIntent = new Intent(Intent.ACTION_MAIN).setComponent(new ComponentName(context.getPackageName(), MainActivity.class.getName()));
final Intent createIntent = new Intent(context, EditNoteActivity.class);
final Bundle extras = new Bundle();
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidget.java b/app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidget.java
index 3ad56c76..e208603a 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidget.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidget.java
@@ -11,9 +11,9 @@ import android.util.Log;
import android.widget.RemoteViews;
import it.niedermann.owncloud.notes.R;
-import it.niedermann.owncloud.notes.edit.EditNoteActivity;
import it.niedermann.owncloud.notes.edit.BaseNoteFragment;
-import it.niedermann.owncloud.notes.persistence.NotesDatabase;
+import it.niedermann.owncloud.notes.edit.EditNoteActivity;
+import it.niedermann.owncloud.notes.persistence.NotesRepository;
import it.niedermann.owncloud.notes.persistence.entity.SingleNoteWidgetData;
public class SingleNoteWidget extends AppWidgetProvider {
@@ -22,11 +22,11 @@ public class SingleNoteWidget extends AppWidgetProvider {
static void updateAppWidget(Context context, AppWidgetManager awm, int[] appWidgetIds) {
final Intent templateIntent = new Intent(context, EditNoteActivity.class);
- final NotesDatabase db = NotesDatabase.getInstance(context);
+ final NotesRepository repo = NotesRepository.getInstance(context);
for (int appWidgetId : appWidgetIds) {
- final SingleNoteWidgetData data = db.getWidgetSingleNoteDao().getSingleNoteWidgetData(appWidgetId);
- if(data != null) {
+ final SingleNoteWidgetData data = repo.getSingleNoteWidgetData(appWidgetId);
+ if (data != null) {
templateIntent.putExtra(BaseNoteFragment.PARAM_ACCOUNT_ID, data.getAccountId());
final PendingIntent templatePendingIntent = PendingIntent.getActivity(context, appWidgetId, templateIntent,
@@ -66,10 +66,10 @@ public class SingleNoteWidget extends AppWidgetProvider {
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
- final NotesDatabase db = NotesDatabase.getInstance(context);
+ final NotesRepository repo = NotesRepository.getInstance(context);
for (int appWidgetId : appWidgetIds) {
- new Thread(() -> db.getWidgetSingleNoteDao().removeSingleNoteWidget(appWidgetId)).start();
+ new Thread(() -> repo.removeSingleNoteWidget(appWidgetId)).start();
}
super.onDeleted(context, appWidgetIds);
}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidgetFactory.java b/app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidgetFactory.java
index db26da24..f6ca4a24 100644
--- a/app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidgetFactory.java
+++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidgetFactory.java
@@ -13,7 +13,7 @@ import androidx.annotation.Nullable;
import it.niedermann.android.markdown.MarkdownUtil;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.edit.EditNoteActivity;
-import it.niedermann.owncloud.notes.persistence.NotesDatabase;
+import it.niedermann.owncloud.notes.persistence.NotesRepository;
import it.niedermann.owncloud.notes.persistence.entity.Note;
import it.niedermann.owncloud.notes.persistence.entity.SingleNoteWidgetData;
@@ -22,7 +22,7 @@ public class SingleNoteWidgetFactory implements RemoteViewsService.RemoteViewsFa
private final Context context;
private final int appWidgetId;
- private final NotesDatabase db;
+ private final NotesRepository repo;
@Nullable
private Note note;
@@ -31,7 +31,7 @@ public class SingleNoteWidgetFactory implements RemoteViewsService.RemoteViewsFa
SingleNoteWidgetFactory(Context context, Intent intent) {
this.context = context;
this.appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
- this.db = NotesDatabase.getInstance(context);
+ this.repo = NotesRepository.getInstance(context);
}
@Override
@@ -41,11 +41,11 @@ public class SingleNoteWidgetFactory implements RemoteViewsService.RemoteViewsFa
@Override
public void onDataSetChanged() {
- final SingleNoteWidgetData data = db.getWidgetSingleNoteDao().getSingleNoteWidgetData(appWidgetId);
- if(data != null) {
+ final SingleNoteWidgetData data = repo.getSingleNoteWidgetData(appWidgetId);
+ if (data != null) {
final long noteId = data.getNoteId();
Log.v(TAG, "Fetch note with id " + noteId);
- note = db.getNoteDao().getNoteById(noteId);
+ note = repo.getNoteById(noteId);
if (note == null) {
Log.e(TAG, "Error: note not found");
diff --git a/app/src/test/java/it/niedermann/owncloud/notes/persistence/NotesRepositoryTest.java b/app/src/test/java/it/niedermann/owncloud/notes/persistence/NotesRepositoryTest.java
new file mode 100644
index 00000000..7859b2eb
--- /dev/null
+++ b/app/src/test/java/it/niedermann/owncloud/notes/persistence/NotesRepositoryTest.java
@@ -0,0 +1,151 @@
+package it.niedermann.owncloud.notes.persistence;
+
+import android.content.Context;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule;
+import androidx.room.Room;
+import androidx.test.core.app.ApplicationProvider;
+
+import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Map;
+
+import it.niedermann.owncloud.notes.persistence.entity.Account;
+import it.niedermann.owncloud.notes.persistence.entity.Note;
+import it.niedermann.owncloud.notes.shared.model.Capabilities;
+
+import static it.niedermann.owncloud.notes.persistence.NotesDatabaseTestUtil.getOrAwaitValue;
+import static it.niedermann.owncloud.notes.shared.model.DBStatus.LOCAL_DELETED;
+import static it.niedermann.owncloud.notes.shared.model.DBStatus.LOCAL_EDITED;
+import static it.niedermann.owncloud.notes.shared.model.DBStatus.VOID;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = {Build.VERSION_CODES.P})
+public class NotesRepositoryTest {
+
+ @Rule
+ public InstantTaskExecutorRule instantTaskExecutorRule = new InstantTaskExecutorRule();
+
+ @NonNull
+ private Context context;
+ private NotesRepository repoMock = null;
+ private Account account = null;
+ private Account secondAccount = null;
+
+ @Before
+ public void setupDB() throws NextcloudHttpRequestFailedException {
+ context = ApplicationProvider.getApplicationContext();
+ repoMock = Mockito.mock(NotesRepository.class);
+ final NotesDatabase db = Room
+ .inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), NotesDatabase.class)
+ .allowMainThreadQueries()
+ .build();
+ Mockito.when(repoMock.db.getNoteDao()).thenReturn(db.getNoteDao());
+
+ repoMock.addAccount("https://äöüß.example.com", "彼得", "彼得@äöüß.example.com", new Capabilities("{\"ocs\":{\"meta\":{\"status\":\"ok\",\"statuscode\":200,\"message\":\"OK\"},\"data\":{\"version\":{\"major\":18,\"minor\":0,\"micro\":4,\"string\":\"18.0.4\",\"edition\":\"\",\"extendedSupport\":false},\"capabilities\":{\"core\":{\"pollinterval\":60,\"webdav-root\":\"remote.php\\/webdav\"},\"bruteforce\":{\"delay\":0},\"files\":{\"bigfilechunking\":true,\"blacklisted_files\":[\".htaccess\"],\"directEditing\":{\"url\":\"https:\\/\\/efss.qloud.my\\/ocs\\/v2.php\\/apps\\/files\\/api\\/v1\\/directEditing\",\"etag\":\"ed2b141af2a39b0e42666952ba60988d\"},\"versioning\":true,\"undelete\":true},\"activity\":{\"apiv2\":[\"filters\",\"filters-api\",\"previews\",\"rich-strings\"]},\"ocm\":{\"enabled\":true,\"apiVersion\":\"1.0-proposal1\",\"endPoint\":\"https:\\/\\/efss.qloud.my\\/index.php\\/ocm\",\"resourceTypes\":[{\"name\":\"file\",\"shareTypes\":[\"user\",\"group\"],\"protocols\":{\"webdav\":\"\\/public.php\\/webdav\\/\"}}]},\"deck\":{\"version\":\"0.8.2\"},\"richdocuments\":{\"mimetypes\":[\"application\\/vnd.oasis.opendocument.text\",\"application\\/vnd.oasis.opendocument.spreadsheet\",\"application\\/vnd.oasis.opendocument.graphics\",\"application\\/vnd.oasis.opendocument.presentation\",\"application\\/vnd.lotus-wordpro\",\"application\\/vnd.visio\",\"application\\/vnd.wordperfect\",\"application\\/msonenote\",\"application\\/msword\",\"application\\/rtf\",\"text\\/rtf\",\"application\\/vnd.openxmlformats-officedocument.wordprocessingml.document\",\"application\\/vnd.openxmlformats-officedocument.wordprocessingml.template\",\"application\\/vnd.ms-word.document.macroEnabled.12\",\"application\\/vnd.ms-word.template.macroEnabled.12\",\"application\\/vnd.ms-excel\",\"application\\/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\"application\\/vnd.openxmlformats-officedocument.spreadsheetml.template\",\"application\\/vnd.ms-excel.sheet.macroEnabled.12\",\"application\\/vnd.ms-excel.template.macroEnabled.12\",\"application\\/vnd.ms-excel.addin.macroEnabled.12\",\"application\\/vnd.ms-excel.sheet.binary.macroEnabled.12\",\"application\\/vnd.ms-powerpoint\",\"application\\/vnd.openxmlformats-officedocument.presentationml.presentation\",\"application\\/vnd.openxmlformats-officedocument.presentationml.template\",\"application\\/vnd.openxmlformats-officedocument.presentationml.slideshow\",\"application\\/vnd.ms-powerpoint.addin.macroEnabled.12\",\"application\\/vnd.ms-powerpoint.presentation.macroEnabled.12\",\"application\\/vnd.ms-powerpoint.template.macroEnabled.12\",\"application\\/vnd.ms-powerpoint.slideshow.macroEnabled.12\",\"text\\/csv\"],\"mimetypesNoDefaultOpen\":[\"image\\/svg+xml\",\"application\\/pdf\",\"text\\/plain\",\"text\\/spreadsheet\"],\"collabora\":[],\"direct_editing\":false,\"templates\":false,\"productName\":\"\\u5728\\u7ebf\\u534f\\u4f5c\"},\"dav\":{\"chunking\":\"1.0\"},\"files_sharing\":{\"api_enabled\":true,\"public\":{\"enabled\":true,\"password\":{\"enforced\":true,\"askForOptionalPassword\":false},\"expire_date\":{\"enabled\":true,\"days\":\"7\",\"enforced\":false},\"multiple_links\":true,\"expire_date_internal\":{\"enabled\":false},\"send_mail\":false,\"upload\":true,\"upload_files_drop\":true},\"resharing\":true,\"user\":{\"send_mail\":false,\"expire_date\":{\"enabled\":true}},\"group_sharing\":true,\"group\":{\"enabled\":true,\"expire_date\":{\"enabled\":true}},\"default_permissions\":31,\"federation\":{\"outgoing\":false,\"incoming\":false,\"expire_date\":{\"enabled\":true}},\"sharee\":{\"query_lookup_default\":false},\"sharebymail\":{\"enabled\":true,\"upload_files_drop\":{\"enabled\":true},\"password\":{\"enabled\":true},\"expire_date\":{\"enabled\":true}}},\"external\":{\"v1\":[\"sites\",\"device\",\"groups\",\"redirect\"]},\"notifications\":{\"ocs-endpoints\":[\"list\",\"get\",\"delete\",\"delete-all\",\"icons\",\"rich-strings\",\"action-web\"],\"push\":[\"devices\",\"object-data\",\"delete\"],\"admin-notifications\":[\"ocs\",\"cli\"]},\"password_policy\":{\"minLength\":8,\"enforceNonCommonPassword\":true,\"enforceNumericCharacters\":false,\"enforceSpecialCharacters\":false,\"enforceUpperLowerCase\":false,\"api\":{\"generate\":\"https:\\/\\/efss.qloud.my\\/ocs\\/v2.php\\/apps\\/password_policy\\/api\\/v1\\/generate\",\"validate\":\"https:\\/\\/efss.qloud.my\\/ocs\\/v2.php\\/apps\\/password_policy\\/api\\/v1\\/validate\"}},\"theming\":{\"name\":\"QloudData\",\"url\":\"https:\\/\\/www.qloud.my\\/qloud-data\\/\",\"slogan\":\"Powered by NextCloud\",\"color\":\"#1E4164\",\"color-text\":\"#ffffff\",\"color-element\":\"#1E4164\",\"logo\":\"https:\\/\\/efss.qloud.my\\/index.php\\/apps\\/theming\\/image\\/logo?useSvg=1&v=47\",\"background\":\"https:\\/\\/efss.qloud.my\\/core\\/img\\/background.png?v=47\",\"background-plain\":false,\"background-default\":true,\"logoheader\":\"https:\\/\\/efss.qloud.my\\/index.php\\/apps\\/theming\\/image\\/logo?useSvg=1&v=47\",\"favicon\":\"https:\\/\\/efss.qloud.my\\/index.php\\/apps\\/theming\\/image\\/logo?useSvg=1&v=47\"},\"registration\":{\"enabled\":true,\"apiRoot\":\"\\/ocs\\/v2.php\\/apps\\/registration\\/api\\/v1\\/\",\"apiLevel\":\"v1\"}}}}}", null));
+ account = repoMock.getAccountDao().getAccountByName("彼得@äöüß.example.com");
+
+ repoMock.addAccount("https://example.org", "test", "test@example.org", new Capabilities("{ocs: {}}", null));
+ secondAccount = repoMock.getAccountDao().getAccountByName("test@example.org");
+
+ Arrays.stream(new Note[]{
+ new Note(1, 1001L, Calendar.getInstance(), "美好的一天", "C", "Movies", false, null, VOID, account.getId(), "", 0),
+ new Note(2, null, Calendar.getInstance(), "T", "C", "Movies", false, null, LOCAL_EDITED, account.getId(), "", 0),
+ new Note(3, 1003L, Calendar.getInstance(), "美好的一天", "C", "Movies", false, null, LOCAL_EDITED, account.getId(), "", 0),
+ new Note(4, null, Calendar.getInstance(), "T", "C", "Music", false, null, VOID, account.getId(), "", 0),
+ new Note(5, 1005L, Calendar.getInstance(), "美好的一天", "C", " 兄弟,这真是美好的一天。", false, null, LOCAL_EDITED, account.getId(), "", 0),
+ new Note(6, 1006L, Calendar.getInstance(), "美好的一天", "C", " 兄弟,这真是美好的一天。", false, null, LOCAL_DELETED, account.getId(), "", 0),
+ new Note(7, null, Calendar.getInstance(), "T", "C", "Music", true, null, LOCAL_EDITED, secondAccount.getId(), "", 0),
+ new Note(8, 1008L, Calendar.getInstance(), "美好的一天", "C", "ToDo", true, null, LOCAL_EDITED, secondAccount.getId(), "", 0),
+ new Note(9, 1009L, Calendar.getInstance(), "美好的一天", "C", "ToDo", true, null, LOCAL_DELETED, secondAccount.getId(), "", 0)
+ }).forEach(note -> repoMock.getNoteDao().addNote(note));
+ }
+
+ @After
+ public void closeDb() {
+ repoMock.close();
+ }
+
+ @Test
+ public void testGetIdMap() {
+ final Map<Long, Long> idMapOfFirstAccount = repoMock.getIdMap(account.getId());
+ assertEquals(3, idMapOfFirstAccount.size());
+ assertEquals(Long.valueOf(1L), idMapOfFirstAccount.get(1001L));
+ assertEquals(Long.valueOf(3L), idMapOfFirstAccount.get(1003L));
+ assertEquals(Long.valueOf(5L), idMapOfFirstAccount.get(1005L));
+
+ final Map<Long, Long> idMapOfSecondAccount = repoMock.getIdMap(secondAccount.getId());
+ assertEquals(1, idMapOfSecondAccount.size());
+ assertEquals(Long.valueOf(8L), idMapOfSecondAccount.get(1008L));
+ }
+
+ @Test
+ public void testAddAccount() throws NextcloudHttpRequestFailedException, InterruptedException {
+ final Account createdAccount = getOrAwaitValue(repoMock.addAccount("https://äöüß.example.com", "彼得", "彼得@äöüß.example.com", new Capabilities("{ocs: {}}", null)));
+ assertEquals("https://äöüß.example.com", createdAccount.getUrl());
+ assertEquals("彼得", createdAccount.getUserName());
+ assertEquals("彼得@äöüß.example.com", createdAccount.getAccountName());
+ }
+
+ @Test
+ public void testAddNote() {
+ final Note localNote = new Note(null, Calendar.getInstance(), "Fancy Title", "MyContent", "Samples", false, "123");
+ localNote.setId(99);
+ final Note createdNoteFromLocal = repoMock.addNote(account.getId(), localNote);
+ assertEquals(LOCAL_EDITED, createdNoteFromLocal.getStatus());
+ assertEquals("MyContent", createdNoteFromLocal.getExcerpt());
+
+ final Note createdNoteFromRemote = repoMock.addNote(account.getId(), new Note(null, Calendar.getInstance(), "Fancy Title", "MyContent", "Samples", false, "123"));
+ assertEquals(VOID, createdNoteFromRemote.getStatus());
+ assertEquals("MyContent", createdNoteFromRemote.getExcerpt());
+ }
+
+ @Test
+ public void updateApiVersion() {
+ assertThrows(IllegalArgumentException.class, () -> repoMock.updateApiVersion(account.getId(), ""));
+ assertThrows(IllegalArgumentException.class, () -> repoMock.updateApiVersion(account.getId(), "asdf"));
+ assertThrows(IllegalArgumentException.class, () -> repoMock.updateApiVersion(account.getId(), "{}"));
+
+ repoMock.updateApiVersion(account.getId(), null);
+ assertNull(repoMock.getAccountDao().getAccountById(account.getId()).getApiVersion());
+ repoMock.updateApiVersion(account.getId(), "[]");
+ assertNull(repoMock.getAccountDao().getAccountById(account.getId()).getApiVersion());
+
+ repoMock.updateApiVersion(account.getId(), "[1.0]");
+ assertEquals("[1.0]", repoMock.getAccountDao().getAccountById(account.getId()).getApiVersion());
+ repoMock.updateApiVersion(account.getId(), "[0.2, 1.0]");
+ assertEquals("[0.2, 1.0]", repoMock.getAccountDao().getAccountById(account.getId()).getApiVersion());
+
+ // TODO is this really indented?
+ repoMock.updateApiVersion(account.getId(), "[0.2, abc]");
+ assertEquals("[0.2, abc]", repoMock.getAccountDao().getAccountById(account.getId()).getApiVersion());
+ }
+
+ @Test
+ @Ignore("Need to find a way to stub deleteAndSync method")
+ public void moveNoteToAnotherAccount() throws InterruptedException {
+ final Note noteToMove = repoMock.getNoteDao().getNoteById(1);
+ assertEquals(3, repoMock.getNoteDao().getLocalModifiedNotes(secondAccount.getId()).size());
+ final Note movedNote = getOrAwaitValue(repoMock.moveNoteToAnotherAccount(secondAccount, noteToMove));
+ assertEquals(4, repoMock.getNoteDao().getLocalModifiedNotes(secondAccount.getId()).size());
+ assertEquals(LOCAL_EDITED, movedNote.getStatus());
+ // TODO assert deleteAndSync has been called
+ }
+} \ No newline at end of file