import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Transaction;
import androidx.room.Update;
import net.ktnx.mobileledger.db.Account;
+import net.ktnx.mobileledger.db.AccountValue;
import net.ktnx.mobileledger.db.AccountWithAmounts;
+import net.ktnx.mobileledger.db.DB;
import java.util.ArrayList;
import java.util.List;
+
@Dao
public abstract class AccountDAO extends BaseDAO<Account> {
static public List<String> unbox(List<AccountNameContainer> list) {
return result;
}
- @Insert
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
public abstract long insertSync(Account item);
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ public abstract void insertSync(List<Account> items);
+
+ @Transaction
+ public void insertSync(@NonNull AccountWithAmounts accountWithAmounts) {
+ final AccountValueDAO valueDAO = DB.get()
+ .getAccountValueDAO();
+ Account account = accountWithAmounts.account;
+ Account existingAccount = getByNameSync(account.getProfileId(), account.getName());
+ if (existingAccount != null) {
+ existingAccount.setGeneration(account.getGeneration());
+ account = existingAccount;
+ updateSync(account);
+ }
+ else {
+ long accountId = insertSync(account);
+ account.setId(accountId);
+ }
+ for (AccountValue value : accountWithAmounts.amounts) {
+ value.setAccountId(account.getId());
+ value.setGeneration(account.getGeneration());
+ value.setId(valueDAO.insertSync(value));
+ }
+ }
@Update
public abstract void updateSync(Account item);
@Query("SELECT * FROM accounts WHERE profile_id = :profileId AND name = :accountName")
public abstract LiveData<Account> getByName(long profileId, @NonNull String accountName);
+ @Query("SELECT * FROM accounts WHERE profile_id = :profileId AND name = :accountName")
+ public abstract Account getByNameSync(long profileId, @NonNull String accountName);
+
@Transaction
@Query("SELECT * FROM accounts WHERE profile_id = :profileId AND name = :accountName")
public abstract LiveData<AccountWithAmounts> getByNameWithAmounts(long profileId,
@Query("SELECT * FROM accounts WHERE profile_id = :profileId")
public abstract List<Account> allForProfileSync(long profileId);
+ @Query("SELECT generation FROM accounts WHERE profile_id = :profileId LIMIT 1")
+ protected abstract AccountGenerationContainer getGenerationPOJOSync(long profileId);
+ public long getGenerationSync(long profileId) {
+ AccountGenerationContainer result = getGenerationPOJOSync(profileId);
+
+ if (result == null)
+ return 0;
+ return result.generation;
+ }
+ @Query("DELETE FROM accounts WHERE profile_id = :profileId AND generation <> " +
+ ":currentGeneration")
+ public abstract void purgeOldAccountsSync(long profileId, long currentGeneration);
+
+ @Query("DELETE FROM account_values WHERE EXISTS (SELECT 1 FROM accounts a WHERE a" +
+ ".id=account_values.account_id AND a.profile_id=:profileId) AND generation <> " +
+ ":currentGeneration")
+ public abstract void purgeOldAccountValuesSync(long profileId, long currentGeneration);
+ @Transaction
+ public void storeAccountsSync(List<AccountWithAmounts> accounts, long profileId) {
+ long generation = getGenerationSync(profileId) + 1;
+
+ for (AccountWithAmounts rec : accounts) {
+ rec.account.setGeneration(generation);
+ rec.account.setProfileId(profileId);
+ insertSync(rec);
+ }
+ purgeOldAccountsSync(profileId, generation);
+ purgeOldAccountValuesSync(profileId, generation);
+ }
+
static public class AccountNameContainer {
@ColumnInfo
public String name;
@ColumnInfo
public int ordering;
}
+
+ static class AccountGenerationContainer {
+ @ColumnInfo
+ long generation;
+ public AccountGenerationContainer(long generation) {
+ this.generation = generation;
+ }
+ }
}
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Update;
@Dao
public abstract class AccountValueDAO extends BaseDAO<AccountValue> {
- @Insert
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
public abstract long insertSync(AccountValue item);
@Update
--- /dev/null
+/*
+ * Copyright © 2021 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
+ * under the term of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.dao;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LiveData;
+import androidx.room.ColumnInfo;
+import androidx.room.Dao;
+import androidx.room.Delete;
+import androidx.room.Insert;
+import androidx.room.Query;
+import androidx.room.Update;
+
+import net.ktnx.mobileledger.db.DescriptionHistory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Dao
+public abstract class DescriptionHistoryDAO extends BaseDAO<DescriptionHistory> {
+ static public List<String> unbox(List<DescriptionContainer> list) {
+ ArrayList<String> result = new ArrayList<>(list.size());
+ for (DescriptionContainer item : list) {
+ result.add(item.description);
+ }
+
+ return result;
+ }
+ @Insert
+ public abstract long insertSync(DescriptionHistory item);
+
+ @Update
+ public abstract void updateSync(DescriptionHistory item);
+
+ @Delete
+ public abstract void deleteSync(DescriptionHistory item);
+
+ @Delete
+ public abstract void deleteSync(List<DescriptionHistory> items);
+
+ @Query("DELETE FROM description_history where not exists (select 1 from transactions tr where" +
+ " upper(tr.description)=description_history.description_upper)")
+ public abstract void sweepSync();
+
+ @Query("SELECT * FROM description_history")
+ public abstract LiveData<List<DescriptionHistory>> getAll();
+
+ @Query("SELECT DISTINCT description, CASE WHEN description_upper LIKE :term||'%%' THEN 1 " +
+ " WHEN description_upper LIKE '%%:'||:term||'%%' THEN 2 " +
+ " WHEN description_upper LIKE '%% '||:term||'%%' THEN 3 " +
+ " ELSE 9 END AS ordering " + "FROM description_history " +
+ "WHERE description_upper LIKE '%%'||:term||'%%' " +
+ "ORDER BY ordering, description_upper, rowid ")
+ public abstract List<DescriptionContainer> lookupSync(@NonNull String term);
+
+ static public class DescriptionContainer {
+ @ColumnInfo
+ public String description;
+ @ColumnInfo
+ public int ordering;
+ }
+}
package net.ktnx.mobileledger.dao;
import androidx.room.Dao;
+import androidx.room.Delete;
+import androidx.room.Insert;
+import androidx.room.Query;
+import androidx.room.Update;
import net.ktnx.mobileledger.db.Profile;
@Dao
-abstract class ProfileDAO extends BaseDAO<Profile> {
- @Override
+public abstract class ProfileDAO extends BaseDAO<Profile> {
+ @Insert
abstract long insertSync(Profile item);
- @Override
+ @Update
abstract void updateSync(Profile item);
- @Override
- abstract void deleteSync(Profile item);
+ @Delete
+ public abstract void deleteSync(Profile item);
+
+ @Query("select * from profiles where id = :profileId")
+ public abstract Profile getByIdSync(long profileId);
}
import androidx.room.Update;
import net.ktnx.mobileledger.db.Transaction;
+import net.ktnx.mobileledger.db.TransactionWithAccounts;
import java.util.ArrayList;
import java.util.List;
@Query("SELECT * FROM transactions WHERE id = :id")
public abstract LiveData<Transaction> getById(long id);
+ @androidx.room.Transaction
+ @Query("SELECT * FROM transactions WHERE id = :transactionId")
+ public abstract LiveData<TransactionWithAccounts> getByIdWithAccounts(long transactionId);
+
@Query("SELECT DISTINCT description, CASE WHEN description_upper LIKE :term||'%%' THEN 1 " +
" WHEN description_upper LIKE '%%:'||:term||'%%' THEN 2 " +
" WHEN description_upper LIKE '%% '||:term||'%%' THEN 3 " +
@ColumnInfo
private float value;
@ColumnInfo(defaultValue = "0")
- private int generation = 0;
+ private long generation = 0;
public long getId() {
return id;
}
public void setValue(float value) {
this.value = value;
}
- public int getGeneration() {
+ public long getGeneration() {
return generation;
}
- public void setGeneration(int generation) {
+ public void setGeneration(long generation) {
this.generation = generation;
}
}
import net.ktnx.mobileledger.dao.AccountDAO;
import net.ktnx.mobileledger.dao.AccountValueDAO;
import net.ktnx.mobileledger.dao.CurrencyDAO;
+import net.ktnx.mobileledger.dao.DescriptionHistoryDAO;
import net.ktnx.mobileledger.dao.OptionDAO;
+import net.ktnx.mobileledger.dao.ProfileDAO;
import net.ktnx.mobileledger.dao.TemplateAccountDAO;
import net.ktnx.mobileledger.dao.TemplateHeaderDAO;
import net.ktnx.mobileledger.dao.TransactionDAO;
public abstract TransactionDAO getTransactionDAO();
public abstract OptionDAO getOptionDAO();
+
+ public abstract DescriptionHistoryDAO getDescriptionHistoryDAO();
+
+ public abstract ProfileDAO getProfileDAO();
}
--- /dev/null
+/*
+ * Copyright © 2021 Damyan Ivanov.
+ * This file is part of MoLe.
+ * MoLe is free software: you can distribute it and/or modify it
+ * under the term of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your opinion), any later version.
+ *
+ * MoLe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License terms for details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.db;
+
+import androidx.room.Embedded;
+import androidx.room.Relation;
+
+import java.util.List;
+
+public class TransactionWithAccounts {
+ @Embedded
+ public Transaction transaction;
+ @Relation(parentColumn = "id", entityColumn = "transaction_id")
+ public List<TransactionAccount> accounts;
+}
import android.database.sqlite.SQLiteDatabase;
import android.os.AsyncTask;
import android.os.Bundle;
-import android.text.TextUtils;
import android.util.SparseArray;
import androidx.annotation.Nullable;
import net.ktnx.mobileledger.R;
import net.ktnx.mobileledger.async.DbOpQueue;
import net.ktnx.mobileledger.dao.AccountDAO;
+import net.ktnx.mobileledger.dao.DescriptionHistoryDAO;
import net.ktnx.mobileledger.dao.OptionDAO;
+import net.ktnx.mobileledger.dao.ProfileDAO;
import net.ktnx.mobileledger.dao.TransactionDAO;
+import net.ktnx.mobileledger.db.AccountValue;
+import net.ktnx.mobileledger.db.AccountWithAmounts;
import net.ktnx.mobileledger.db.DB;
import net.ktnx.mobileledger.json.API;
import net.ktnx.mobileledger.ui.profiles.ProfileDetailActivity;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Objects;
try {
int orderNo = 0;
for (MobileLedgerProfile p : Objects.requireNonNull(Data.profiles.getValue())) {
- db.execSQL("update profiles set order_no=? where uuid=?",
+ db.execSQL("update profiles set order_no=? where id=?",
new Object[]{orderNo, p.getId()});
p.orderNo = orderNo;
orderNo++;
});
// debug("accounts", String.format("Stored account '%s' in DB [%s]", acc.getName(), uuid));
}
- public void storeAccountValue(SQLiteDatabase db, int generation, String name, String currency,
- Float amount) {
- if (!TextUtils.isEmpty(currency)) {
- boolean exists;
- try (Cursor c = db.rawQuery("select 1 from currencies where name=?",
- new String[]{currency}))
- {
- exists = c.moveToFirst();
- }
- if (!exists) {
- db.execSQL(
- "insert into currencies(id, name, position, has_gap) values((select max" +
- "(id) from currencies)+1, ?, ?, ?)", new Object[]{currency,
- Objects.requireNonNull(
- Data.currencySymbolPosition.getValue()).toString(),
- Data.currencyGap.getValue()
- });
- }
- }
-
- long accId = findAddAccount(db, name);
-
- db.execSQL("replace into account_values(account_id, " +
- "currency, value, generation) values(?, ?, ?, ?);",
- new Object[]{accId, Misc.emptyIsNull(currency), amount, generation});
- }
- private long findAddAccount(SQLiteDatabase db, String accountName) {
- try (Cursor c = db.rawQuery("select id from accounts where profile_id=? and name=?",
- new String[]{String.valueOf(id), accountName}))
- {
- if (c.moveToFirst())
- return c.getLong(0);
-
- }
-
- try (Cursor c = db.rawQuery(
- "insert into accounts(profile_id, name, name_upper) values(?, ?, ?) returning id",
- new String[]{String.valueOf(id), accountName, accountName.toUpperCase()}))
- {
- c.moveToFirst();
- return c.getInt(0);
- }
- }
public void storeTransaction(SQLiteDatabase db, int generation, LedgerTransaction tr) {
tr.fillDataHash();
// Logger.debug("storeTransaction", String.format(Locale.US, "ID %d", tr.getId()));
setOption(name, String.valueOf(value));
}
public void removeFromDB() {
- SQLiteDatabase db = App.getDatabase();
- debug("db", String.format(Locale.ROOT, "removing profile %d from DB", id));
- db.beginTransactionNonExclusive();
- try {
- Object[] id_param = new Object[]{id};
- db.execSQL("delete from transactions where profile_id=?", id_param);
- db.execSQL("delete from accounts where profile=?", id_param);
- db.execSQL("delete from options where profile=?", id_param);
- db.execSQL("delete from profiles where id=?", id_param);
- db.setTransactionSuccessful();
- }
- finally {
- db.endTransaction();
- }
+ ProfileDAO dao = DB.get()
+ .getProfileDAO();
+ AsyncTask.execute(() -> dao.deleteSync(dao.getByIdSync(id)));
}
public LedgerTransaction loadTransaction(int transactionId) {
LedgerTransaction tr = new LedgerTransaction(transactionId, this.id);
}
return 1;
}
- private int getNextAccountsGeneration(SQLiteDatabase db) {
- try (Cursor c = db.rawQuery("SELECT generation FROM accounts WHERE profile_id=? LIMIT 1",
- new String[]{String.valueOf(id)}))
- {
- if (c.moveToFirst())
- return c.getInt(0) + 1;
- }
- return 1;
- }
- private void deleteNotPresentAccounts(SQLiteDatabase db, int generation) {
- Logger.debug("db/benchmark", "Deleting obsolete accounts");
- db.execSQL("DELETE FROM account_values WHERE (select a.profile_id from accounts a where a" +
- ".id=account_values.account_id)=? AND generation <> ?",
- new Object[]{id, generation});
- db.execSQL("DELETE FROM accounts WHERE profile_id=? AND generation <> ?",
- new Object[]{id, generation});
- Logger.debug("db/benchmark", "Done deleting obsolete accounts");
- }
private void deleteNotPresentTransactions(SQLiteDatabase db, int generation) {
Logger.debug("db/benchmark", "Deleting obsolete transactions");
db.execSQL(
TransactionDAO trnDao = DB.get()
.getTransactionDAO();
trnDao.deleteSync(trnDao.allForProfileSync(id));
+
+ DescriptionHistoryDAO descDao = DB.get()
+ .getDescriptionHistoryDAO();
+ descDao.sweepSync();
}
public void wipeAllData() {
AsyncTask.execute(this::wipeAllDataSync);
SQLiteDatabase db = App.getDatabase();
db.beginTransactionNonExclusive();
try {
- int accountsGeneration = profile.getNextAccountsGeneration(db);
- if (isInterrupted())
- return;
-
int transactionsGeneration = profile.getNextTransactionsGeneration(db);
if (isInterrupted())
return;
- for (LedgerAccount acc : accounts) {
- profile.storeAccount(db, accountsGeneration, acc, false);
- if (isInterrupted())
- return;
- for (LedgerAmount amt : acc.getAmounts()) {
- profile.storeAccountValue(db, accountsGeneration, acc.getName(),
- amt.getCurrency(), amt.getAmount());
- if (isInterrupted())
- return;
- }
- }
-
for (LedgerTransaction tr : transactions) {
profile.storeTransaction(db, transactionsGeneration, tr);
if (isInterrupted())
if (isInterrupted()) {
return;
}
- profile.deleteNotPresentAccounts(db, accountsGeneration);
- if (isInterrupted())
- return;
Map<String, Boolean> unique = new HashMap<>();
finally {
db.endTransaction();
}
+
+ AsyncTask.execute(() -> {
+ List<AccountWithAmounts> list = new ArrayList<>();
+
+ final AccountDAO dao = DB.get()
+ .getAccountDAO();
+
+ for (LedgerAccount acc : accounts) {
+ AccountWithAmounts rec = new AccountWithAmounts();
+ rec.account = acc.toDBO();
+
+ if (isInterrupted())
+ return;
+
+ rec.amounts = new ArrayList<>();
+ for (LedgerAmount amt : acc.getAmounts()) {
+ AccountValue av = new AccountValue();
+ av.setCurrency(amt.getCurrency());
+ av.setValue(amt.getAmount());
+
+ rec.amounts.add(av);
+ }
+
+ list.add(rec);
+ }
+
+ if (isInterrupted())
+ return;
+
+ dao.storeAccountsSync(list, profile.getId());
+ });
}
private void storeDescription(SQLiteDatabase db, int generation, String description,
String descriptionUpper) {
package net.ktnx.mobileledger.ui;
import android.os.AsyncTask;
-import android.os.Build;
import android.text.TextUtils;
-import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import net.ktnx.mobileledger.model.LedgerTransaction;
import net.ktnx.mobileledger.model.MobileLedgerProfile;
import net.ktnx.mobileledger.model.TransactionListItem;
-import net.ktnx.mobileledger.utils.LockHolder;
import net.ktnx.mobileledger.utils.Locker;
import net.ktnx.mobileledger.utils.Logger;
import net.ktnx.mobileledger.utils.MLDB;
import java.util.ArrayList;
import java.util.Date;
-import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
import java.util.Locale;
-import java.util.Map;
import static net.ktnx.mobileledger.utils.Logger.debug;
new MutableLiveData<>();
private final Locker accountsLocker = new Locker();
private final MutableLiveData<String> updateError = new MutableLiveData<>();
- private final Map<String, LedgerAccount> accountMap = new HashMap<>();
private MobileLedgerProfile profile;
- private List<LedgerAccount> allAccounts = new ArrayList<>();
+ private final List<LedgerAccount> allAccounts = new ArrayList<>();
private SimpleDate firstTransactionDate;
private SimpleDate lastTransactionDate;
transient private RetrieveTransactionsTask retrieveTransactionsTask;
transient private Thread displayedAccountsUpdater;
private TransactionsDisplayedFilter displayedTransactionsUpdater;
- public static ArrayList<LedgerAccount> mergeAccountListsFromWeb(List<LedgerAccount> oldList,
- List<LedgerAccount> newList) {
- LedgerAccount oldAcc, newAcc;
- ArrayList<LedgerAccount> merged = new ArrayList<>();
-
- Iterator<LedgerAccount> oldIterator = oldList.iterator();
- Iterator<LedgerAccount> newIterator = newList.iterator();
-
- while (true) {
- if (!oldIterator.hasNext()) {
- // the rest of the incoming are new
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- newIterator.forEachRemaining(merged::add);
- }
- else {
- while (newIterator.hasNext())
- merged.add(newIterator.next());
- }
- break;
- }
- oldAcc = oldIterator.next();
-
- if (!newIterator.hasNext()) {
- // no more incoming accounts. ignore the rest of the old
- break;
- }
- newAcc = newIterator.next();
-
- // ignore now missing old items
- if (oldAcc.getName()
- .compareTo(newAcc.getName()) < 0)
- continue;
-
- // add newly found items
- if (oldAcc.getName()
- .compareTo(newAcc.getName()) > 0)
- {
- merged.add(newAcc);
- continue;
- }
-
- // two items with same account names; forward-merge UI-controlled fields
- // it is important that the result list contains a new LedgerAccount instance
- // so that the change is propagated to the UI
- newAcc.setExpanded(oldAcc.isExpanded());
- newAcc.setAmountsExpanded(oldAcc.amountsExpanded());
- merged.add(newAcc);
- }
-
- return merged;
- }
private void setLastUpdateStamp(long transactionCount) {
debug("db", "Updating transaction value stamp");
Date now = new Date();
accountsLocker.lockForWriting();
return accountsLocker;
}
- public void mergeAccountListFromWeb(List<LedgerAccount> newList) {
-
- try (LockHolder l = accountsLocker.lockForWriting()) {
- allAccounts = mergeAccountListsFromWeb(allAccounts, newList);
- updateAccountsMap(allAccounts);
- }
- }
public LiveData<List<AccountListItem>> getDisplayedAccounts() {
return displayedAccounts;
}
setLastUpdateStamp(transactions.size());
- mergeAccountListFromWeb(accounts);
- updateDisplayedAccounts();
-
updateDisplayedTransactionsFromWeb(transactions);
}
- synchronized public void updateDisplayedAccounts() {
- if (displayedAccountsUpdater != null) {
- displayedAccountsUpdater.interrupt();
- }
- displayedAccountsUpdater = new AccountListDisplayedFilter(this, allAccounts);
- displayedAccountsUpdater.start();
- }
synchronized public void updateDisplayedTransactionsFromWeb(List<LedgerTransaction> list) {
if (displayedTransactionsUpdater != null) {
displayedTransactionsUpdater.interrupt();
displayedTransactionsUpdater = new TransactionsDisplayedFilter(this, list);
displayedTransactionsUpdater.start();
}
- public List<LedgerAccount> getAllAccounts() {
- return allAccounts;
- }
- private void updateAccountsMap(List<LedgerAccount> newAccounts) {
- accountMap.clear();
- for (LedgerAccount acc : newAccounts) {
- accountMap.put(acc.getName(), acc);
- }
- }
- @Nullable
- public LedgerAccount locateAccount(String name) {
- return accountMap.get(name);
- }
public void clearUpdateError() {
updateError.postValue(null);
}
public void clearTransactions() {
displayedTransactions.setValue(new ArrayList<>());
}
- static class AccountListDisplayedFilter extends Thread {
- private final MainModel model;
- private final List<LedgerAccount> list;
- AccountListDisplayedFilter(MainModel model, List<LedgerAccount> list) {
- this.model = model;
- this.list = list;
- }
- @Override
- public void run() {
- List<AccountListItem> newDisplayed = new ArrayList<>();
- Logger.debug("dFilter", "waiting for synchronized block");
- Logger.debug("dFilter", String.format(Locale.US,
- "entered synchronized block (about to examine %d accounts)", list.size()));
- newDisplayed.add(new AccountListItem.Header(Data.lastAccountsUpdateText)); // header
-
- int count = 0;
- for (LedgerAccount a : list) {
- if (isInterrupted())
- return;
-
- if (a.isVisible()) {
- newDisplayed.add(new AccountListItem.Account(a));
- count++;
- }
- }
- if (!isInterrupted()) {
- model.displayedAccounts.postValue(newDisplayed);
- Data.lastUpdateAccountCount.postValue(count);
- }
- Logger.debug("dFilter", "left synchronized block");
- }
- }
static class TransactionsDisplayedFilter extends Thread {
private final MainModel model;