SQLiteDatabase db = MLDB.getDatabase();
db.beginTransaction();
try {
- for (LedgerAccount acc : params[0].accountList) {
- Log.d("CAT", String.format("Setting %s to %s", acc.getName(),
- acc.isHiddenByStarToBe() ? "hidden" : "starred"));
- db.execSQL("UPDATE accounts SET hidden=? WHERE profile=? AND name=?",
- new Object[]{acc.isHiddenByStarToBe() ? 1 : 0, profile, acc.getName()});
-
- acc.setHiddenByStar(acc.isHiddenByStarToBe());
- if (!params[0].showOnlyStarred || !acc.isHiddenByStar()) newList.add(acc);
+ for (int i = 0; i < params[0].accountList.size(); i++ ){
+ LedgerAccount acc = params[0].accountList.get(i);
+ Log.d("CAT", String.format("Setting %s to %s", acc.getName(),
+ acc.isHiddenByStarToBe() ? "hidden" : "starred"));
+ db.execSQL("UPDATE accounts SET hidden=? WHERE profile=? AND name=?",
+ new Object[]{acc.isHiddenByStarToBe() ? 1 : 0, profile, acc.getName()
+ });
+
+ acc.setHiddenByStar(acc.isHiddenByStarToBe());
+ if (!params[0].showOnlyStarred || !acc.isHiddenByStar()) newList.add(acc);
+ db.setTransactionSuccessful();
}
- db.setTransactionSuccessful();
}
finally {
db.endTransaction();
package net.ktnx.mobileledger.async;
import net.ktnx.mobileledger.model.LedgerAccount;
-
-import java.util.List;
+import net.ktnx.mobileledger.utils.ObservableList;
public class CommitAccountsTaskParams {
- List<LedgerAccount> accountList;
+ ObservableList<LedgerAccount> accountList;
boolean showOnlyStarred;
- public CommitAccountsTaskParams(List<LedgerAccount> accountList, boolean showOnlyStarred) {
+ public CommitAccountsTaskParams(ObservableList<LedgerAccount> accountList, boolean showOnlyStarred) {
this.accountList = accountList;
this.showOnlyStarred = showOnlyStarred;
}
// state of the database
db.setTransactionSuccessful();
db.endTransaction();
- Data.accounts.set(accountList);
+ Data.accounts.setList(accountList);
db.beginTransaction();
continue;
}
}
publishProgress(progress);
SQLiteDatabase db = MLDB.getDatabase();
+ ArrayList<LedgerAccount> accountList = new ArrayList<>();
+ boolean listFilledOK = false;
try (InputStream resp = http.getInputStream()) {
if (http.getResponseCode() != 200)
throw new IOException(String.format("HTTP error %d", http.getResponseCode()));
profile.markAccountsAsNotPresent(db);
AccountListParser parser = new AccountListParser(resp);
- ArrayList<LedgerAccount> accountList = new ArrayList<>();
LedgerAccount prevAccount = null;
profile.deleteNotPresentAccounts(db);
throwIfCancelled();
db.setTransactionSuccessful();
- Data.accounts.set(accountList);
+ listFilledOK = true;
}
finally {
db.endTransaction();
}
}
+ // should not be set in the DB transaction, because of a possible deadlock
+ // with the main and DbOpQueueRunner threads
+ if (listFilledOK) Data.accounts.setList(accountList);
+
return true;
}
private boolean retrieveTransactionList(MobileLedgerProfile profile)
default:
throw new HTTPException(http.getResponseCode(), http.getResponseMessage());
}
- try (SQLiteDatabase db = MLDB.getDatabase()) {
- try (InputStream resp = http.getInputStream()) {
- if (http.getResponseCode() != 200)
- throw new IOException(String.format("HTTP error %d", http.getResponseCode()));
- throwIfCancelled();
- db.beginTransaction();
- try {
- profile.markTransactionsAsNotPresent(db);
+ SQLiteDatabase db = MLDB.getDatabase();
+ try (InputStream resp = http.getInputStream()) {
+ if (http.getResponseCode() != 200)
+ throw new IOException(String.format("HTTP error %d", http.getResponseCode()));
+ throwIfCancelled();
+ db.beginTransaction();
+ try {
+ profile.markTransactionsAsNotPresent(db);
- int matchedTransactionsCount = 0;
- TransactionListParser parser = new TransactionListParser(resp);
+ int matchedTransactionsCount = 0;
+ TransactionListParser parser = new TransactionListParser(resp);
- int processedTransactionCount = 0;
+ int processedTransactionCount = 0;
- while (true) {
- throwIfCancelled();
- ParsedLedgerTransaction parsedTransaction = parser.nextTransaction();
- throwIfCancelled();
- if (parsedTransaction == null) break;
- LedgerTransaction transaction = parsedTransaction.asLedgerTransaction();
- if (transaction.existsInDb(db)) {
- profile.markTransactionAsPresent(db, transaction);
- matchedTransactionsCount++;
-
- if (matchedTransactionsCount == MATCHING_TRANSACTIONS_LIMIT) {
- profile.markTransactionsBeforeTransactionAsPresent(db, transaction);
- progress.setTotal(progress.getProgress());
- publishProgress(progress);
- db.setTransactionSuccessful();
- profile.setLastUpdateStamp();
- return true;
- }
- }
- else {
- profile.storeTransaction(db, transaction);
- matchedTransactionsCount = 0;
- progress.setTotal(maxTransactionId);
+ while (true) {
+ throwIfCancelled();
+ ParsedLedgerTransaction parsedTransaction = parser.nextTransaction();
+ throwIfCancelled();
+ if (parsedTransaction == null) break;
+ LedgerTransaction transaction = parsedTransaction.asLedgerTransaction();
+ if (transaction.existsInDb(db)) {
+ profile.markTransactionAsPresent(db, transaction);
+ matchedTransactionsCount++;
+
+ if (matchedTransactionsCount == MATCHING_TRANSACTIONS_LIMIT) {
+ profile.markTransactionsBeforeTransactionAsPresent(db, transaction);
+ progress.setTotal(progress.getProgress());
+ publishProgress(progress);
+ db.setTransactionSuccessful();
+ profile.setLastUpdateStamp();
+ return true;
}
-
- if ((progress.getTotal() == Progress.INDETERMINATE) ||
- (progress.getTotal() < transaction.getId()))
- progress.setTotal(transaction.getId());
-
- progress.setProgress(++processedTransactionCount);
- publishProgress(progress);
+ }
+ else {
+ profile.storeTransaction(db, transaction);
+ matchedTransactionsCount = 0;
+ progress.setTotal(maxTransactionId);
}
- throwIfCancelled();
- profile.deleteNotPresentTransactions(db);
- throwIfCancelled();
- db.setTransactionSuccessful();
- profile.setLastUpdateStamp();
- }
- finally {
- db.endTransaction();
+ if ((progress.getTotal() == Progress.INDETERMINATE) ||
+ (progress.getTotal() < transaction.getId()))
+ progress.setTotal(transaction.getId());
+
+ progress.setProgress(++processedTransactionCount);
+ publishProgress(progress);
}
+
+ throwIfCancelled();
+ profile.deleteNotPresentTransactions(db);
+ throwIfCancelled();
+ db.setTransactionSuccessful();
+ profile.setLastUpdateStamp();
+ }
+ finally {
+ db.endTransaction();
}
}
public final class Data {
public static TransactionList transactions = new TransactionList();
- public static ObservableValue<ArrayList<LedgerAccount>> accounts =
- new ObservableValue<>(new ArrayList<>());
+ public static ObservableList<LedgerAccount> accounts =
+ new ObservableList<>(new ArrayList<>());
public static ObservableAtomicInteger backgroundTaskCount = new ObservableAtomicInteger(0);
public static ObservableValue<Date> lastUpdateDate = new ObservableValue<>();
public static ObservableValue<MobileLedgerProfile> profile = new ObservableValue<>();
profile.set(newProfile);
}
public static int getProfileIndex(MobileLedgerProfile profile) {
- List<MobileLedgerProfile> list = profiles.getList();
+ for (int i = 0; i < profiles.size(); i++) {
+ MobileLedgerProfile p = profiles.get(i);
+ if (p.equals(profile)) return i;
- for (int i = 0; i < list.size(); i++) {
- MobileLedgerProfile p = list.get(i);
- if (p.equals(profile)) return i;
}
return -1;
if (level == 0) return true;
- return isVisible(Data.accounts.get());
+ return isVisible(Data.accounts);
}
- public boolean isVisible(ArrayList<LedgerAccount> list) {
+ public boolean isVisible(List<LedgerAccount> list) {
for (LedgerAccount acc : list) {
if (acc.isParentOf(this)) {
if (!acc.isExpanded()) return false;
db.beginTransaction();
try {
int orderNo = 0;
- for (MobileLedgerProfile p : Data.profiles.getList()) {
- db.execSQL("update profiles set order_no=? where uuid=?",
- new Object[]{orderNo, p.getUuid()});
- p.orderNo = orderNo;
- orderNo++;
+ for (int i = 0; i < Data.profiles.size(); i++) {
+ MobileLedgerProfile p = Data.profiles.get(i);
+ db.execSQL("update profiles set order_no=? where uuid=?",
+ new Object[]{orderNo, p.getUuid()});
+ p.orderNo = orderNo;
+ orderNo++;
}
db.setTransactionSuccessful();
}
import net.ktnx.mobileledger.model.Data;
import net.ktnx.mobileledger.model.LedgerAccount;
-import java.util.List;
-
import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.recyclerview.widget.RecyclerView;
}
public void onBindViewHolder(@NonNull LedgerRowHolder holder, int position) {
- List<LedgerAccount> accounts = Data.accounts.get();
- if (position < accounts.size()) {
- LedgerAccount acc = accounts.get(position);
+ if (position < Data.accounts.size()) {
+ LedgerAccount acc = Data.accounts.get(position);
Context ctx = holder.row.getContext();
Resources rm = ctx.getResources();
@Override
public int getItemCount() {
- return Data.accounts.get().size() + 1;
+ return Data.accounts.size() + 1;
}
public void startSelection() {
- for (LedgerAccount acc : Data.accounts.get()) acc.setHiddenByStarToBe(acc.isHiddenByStar());
+ for (int i = 0; i < Data.accounts.size(); i++ ) {
+ LedgerAccount acc = Data.accounts.get(i);
+ acc.setHiddenByStarToBe(acc.isHiddenByStar());
+ }
this.selectionActive = true;
notifyDataSetChanged();
}
}
public void selectItem(int position) {
- LedgerAccount acc = Data.accounts.get().get(position);
+ LedgerAccount acc = Data.accounts.get(position);
acc.toggleHiddenToBe();
toggleChildrenOf(acc, acc.isHiddenByStarToBe(), position);
notifyItemChanged(position);
}
void toggleChildrenOf(LedgerAccount parent, boolean hiddenToBe, int parentPosition) {
int i = parentPosition + 1;
- for (LedgerAccount acc : Data.accounts.get()) {
+ for (int j = 0; j < Data.accounts.size(); j++) {
+ LedgerAccount acc = Data.accounts.get(j);
if (acc.getName().startsWith(parent.getName() + ":")) {
acc.setHiddenByStarToBe(hiddenToBe);
notifyItemChanged(i);
static void commitSelections(Context context) {
CAT task = new CAT();
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
- new CommitAccountsTaskParams(Data.accounts.get(), Data.optShowOnlyStarred.get()));
+ new CommitAccountsTaskParams(Data.accounts, Data.optShowOnlyStarred.get()));
}
static public void scheduleAccountListReload() {
if (Data.profile.get() == null) return;
super.onPostExecute(list);
if (list != null) {
Log.d("acc", "setting updated account list");
- Data.accounts.set(list);
+ Data.accounts.setList(list);
}
}
}
super.onPostExecute(list);
if (list != null) {
Log.d("acc", "setting new account list");
- Data.accounts.set(list);
+ Data.accounts.setList(list);
}
}
}
import java.lang.ref.WeakReference;
import java.text.DateFormat;
-import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Observable;
profile = MobileLedgerProfile.loadAllFromDB(profileUUID);
- if (Data.profiles.getList().isEmpty()) {
+ if (Data.profiles.isEmpty()) {
findViewById(R.id.no_profiles_layout).setVisibility(View.VISIBLE);
findViewById(R.id.pager_layout).setVisibility(View.GONE);
return;
// removing all child accounts from the view
int start = -1, count = 0;
- int i = 0;
- final ArrayList<LedgerAccount> accountList = Data.accounts.get();
- for (LedgerAccount a : accountList) {
- if (acc.isParentOf(a)) {
- if (start == -1) {
- start = i;
+ for (int i = 0; i < Data.accounts.size(); i++) {
+ if (acc.isParentOf(Data.accounts.get(i))) {
+// Log.d("accounts", String.format("Found a child '%s' at position %d",
+// Data.accounts.get(i).getName(), i));
+ if (start == -1) {
+ start = i;
+ }
+ count++;
}
- count++;
- }
- else {
- if (start != -1) {
- break;
+ else {
+ if (start != -1) {
+// Log.d("accounts",
+// String.format("Found a non-child '%s' at position %d",
+// Data.accounts.get(i).getName(), i));
+ break;
+ }
}
}
- i++;
- }
- if (start != -1) {
- for (int j = 0; j < count; j++) {
- Log.d("accounts", String.format("Removing item %d: %s", start + j,
- accountList.get(start).getName()));
- accountList.remove(start);
- }
+ if (start != -1) {
+ for (int j = 0; j < count; j++) {
+// Log.d("accounts", String.format("Removing item %d: %s", start + j,
+// Data.accounts.get(start).getName()));
+ Data.accounts.removeQuietly(start);
+ }
- mAccountSummaryFragment.modelAdapter.notifyItemRangeRemoved(start, count);
+ mAccountSummaryFragment.modelAdapter
+ .notifyItemRangeRemoved(start, count);
}
}
else {
Log.d("accounts", String.format("Expanding account '%s'", acc.getName()));
arrow.setRotation(180);
animator.rotationBy(-180);
- ArrayList<LedgerAccount> accounts = Data.accounts.get();
List<LedgerAccount> children =
Data.profile.get().loadVisibleChildAccountsOf(acc);
- int parentPos = accounts.indexOf(acc);
- if (parentPos == -1) throw new RuntimeException(
- "Can't find index of clicked account " + acc.getName());
- accounts.addAll(parentPos + 1, children);
- mAccountSummaryFragment.modelAdapter
- .notifyItemRangeInserted(parentPos + 1, children.size());
+ int parentPos = Data.accounts.indexOf(acc);
+ if (parentPos != -1) {
+ // may have disappeared in a concurrent refresh operation
+ Data.accounts.addAllQuietly(parentPos + 1, children);
+ mAccountSummaryFragment.modelAdapter
+ .notifyItemRangeInserted(parentPos + 1, children.size());
+ }
}
break;
case R.id.account_row_acc_amounts:
String profileUUID = c.getString(0);
int transactionId = c.getInt(1);
- List<MobileLedgerProfile> profiles = Data.profiles.getList();
- MobileLedgerProfile profile = null;
- for (int i = 0; i < profiles.size(); i++) {
- MobileLedgerProfile p = profiles.get(i);
- if (p.getUuid().equals(profileUUID)) {
- profile = p;
- break;
+ LedgerTransaction tr;
+ MobileLedgerProfile profile = null;
+ for (int i = 0; i < Data.profiles.size(); i++) {
+ MobileLedgerProfile p = Data.profiles.get(i);
+ if (p.getUuid().equals(profileUUID)) {
+ profile = p;
+ break;
}
}
- if (profile == null) throw new RuntimeException(String.format(
- "Unable to find profile %s, which is supposed to contain " +
- "transaction %d with description %s", profileUUID, transactionId, description));
+ if (profile == null) throw new RuntimeException(String.format(
+ "Unable to find profile %s, which is supposed to contain " +
+ "transaction %d with description %s", profileUUID, transactionId,
+ description));
- LedgerTransaction tr = profile.loadTransaction(transactionId);
+ tr = profile.loadTransaction(transactionId);
int i = 0;
table = findViewById(R.id.new_transaction_accounts_table);
ArrayList<LedgerTransactionAccount> accounts = tr.getAccounts();
MobileLedgerProfile.storeProfilesOrder();
// first profile ever?
- if (Data.profiles.getList().size() == 1) Data.profile.set(mProfile);
+ if (Data.profiles.size() == 1) Data.profile.set(mProfile);
}
Activity activity = getActivity();
public boolean onMove(@NonNull RecyclerView recyclerView,
@NonNull RecyclerView.ViewHolder viewHolder,
@NonNull RecyclerView.ViewHolder target) {
- Collections.swap(Data.profiles.getList(), viewHolder.getAdapterPosition(),
+ Collections.swap(Data.profiles, viewHolder.getAdapterPosition(),
target.getAdapterPosition());
MobileLedgerProfile.storeProfilesOrder();
notifyItemMoved(viewHolder.getAdapterPosition(), target.getAdapterPosition());
package net.ktnx.mobileledger.utils;
import android.os.Build;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
import android.util.Log;
import java.util.Collection;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;
-public class ObservableList<T> extends Observable {
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+public class ObservableList<T> extends Observable implements List<T> {
private List<T> list;
public ObservableList(List<T> list) {
this.list = list;
public boolean contains(@Nullable Object o) {
return list.contains(o);
}
+ @NonNull
public Iterator<T> iterator() {
return list.iterator();
}
if (result) forceNotify();
return result;
}
+ public T removeQuietly(int index) {
+ return list.remove(index);
+ }
public boolean containsAll(@NonNull Collection<?> c) {
return list.containsAll(c);
}
if (result) forceNotify();
return result;
}
+ public boolean addAllQuietly(int index, Collection<? extends T> c) {
+ return list.addAll(index, c);
+ }
public boolean removeAll(@NonNull Collection<?> c) {
boolean result = list.removeAll(c);
if (result) forceNotify();