2 * Copyright © 2021 Damyan Ivanov.
3 * This file is part of MoLe.
4 * MoLe is free software: you can distribute it and/or modify it
5 * under the term of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your opinion), any later version.
9 * MoLe is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License terms for details.
14 * You should have received a copy of the GNU General Public License
15 * along with MoLe. If not, see <https://www.gnu.org/licenses/>.
18 package net.ktnx.mobileledger.model;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.res.Resources;
23 import android.database.Cursor;
24 import android.database.sqlite.SQLiteDatabase;
25 import android.os.AsyncTask;
26 import android.os.Bundle;
27 import android.text.TextUtils;
28 import android.util.SparseArray;
30 import androidx.annotation.Nullable;
31 import androidx.room.Transaction;
33 import net.ktnx.mobileledger.App;
34 import net.ktnx.mobileledger.R;
35 import net.ktnx.mobileledger.async.DbOpQueue;
36 import net.ktnx.mobileledger.dao.AccountDAO;
37 import net.ktnx.mobileledger.dao.OptionDAO;
38 import net.ktnx.mobileledger.dao.TransactionDAO;
39 import net.ktnx.mobileledger.db.DB;
40 import net.ktnx.mobileledger.json.API;
41 import net.ktnx.mobileledger.ui.profiles.ProfileDetailActivity;
42 import net.ktnx.mobileledger.ui.profiles.ProfileDetailFragment;
43 import net.ktnx.mobileledger.utils.Logger;
44 import net.ktnx.mobileledger.utils.Misc;
45 import net.ktnx.mobileledger.utils.SimpleDate;
47 import org.jetbrains.annotations.Contract;
49 import java.util.ArrayList;
50 import java.util.HashMap;
51 import java.util.List;
52 import java.util.Locale;
54 import java.util.Objects;
56 import static net.ktnx.mobileledger.utils.Logger.debug;
58 public final class MobileLedgerProfile {
59 // N.B. when adding new fields, update the copy-constructor below
60 private final long id;
62 private boolean permitPosting;
63 private boolean showCommentsByDefault;
64 private boolean showCommodityByDefault;
65 private String defaultCommodity;
66 private String preferredAccountsFilter;
68 private boolean authEnabled;
69 private String authUserName;
70 private String authPassword;
72 private int orderNo = -1;
73 private API apiVersion = API.auto;
74 private FutureDates futureDates = FutureDates.None;
75 private boolean accountsLoaded;
76 private boolean transactionsLoaded;
77 private HledgerVersion detectedVersion;
78 // N.B. when adding new fields, update the copy-constructor below
79 transient private AccountAndTransactionListSaver accountAndTransactionListSaver;
80 public MobileLedgerProfile(long id) {
83 public MobileLedgerProfile(MobileLedgerProfile origin) {
86 permitPosting = origin.permitPosting;
87 showCommentsByDefault = origin.showCommentsByDefault;
88 showCommodityByDefault = origin.showCommodityByDefault;
89 preferredAccountsFilter = origin.preferredAccountsFilter;
91 authEnabled = origin.authEnabled;
92 authUserName = origin.authUserName;
93 authPassword = origin.authPassword;
94 themeHue = origin.themeHue;
95 orderNo = origin.orderNo;
96 futureDates = origin.futureDates;
97 apiVersion = origin.apiVersion;
98 defaultCommodity = origin.defaultCommodity;
99 accountsLoaded = origin.accountsLoaded;
100 transactionsLoaded = origin.transactionsLoaded;
101 if (origin.detectedVersion != null)
102 detectedVersion = new HledgerVersion(origin.detectedVersion);
104 // loads all profiles into Data.profiles
105 // returns the profile with the given UUID
106 public static MobileLedgerProfile loadAllFromDB(long currentProfileId) {
107 MobileLedgerProfile result = null;
108 ArrayList<MobileLedgerProfile> list = new ArrayList<>();
109 SQLiteDatabase db = App.getDatabase();
110 try (Cursor cursor = db.rawQuery("SELECT id, name, url, use_authentication, auth_user, " +
111 "auth_password, permit_posting, theme, order_no, " +
112 "preferred_accounts_filter, future_dates, api_version, " +
113 "show_commodity_by_default, default_commodity, " +
114 "show_comments_by_default, detected_version_pre_1_19, " +
115 "detected_version_major, detected_version_minor FROM " +
116 "profiles order by order_no", null))
118 while (cursor.moveToNext()) {
119 MobileLedgerProfile item = new MobileLedgerProfile(cursor.getLong(0));
120 item.setName(cursor.getString(1));
121 item.setUrl(cursor.getString(2));
122 item.setAuthEnabled(cursor.getInt(3) == 1);
123 item.setAuthUserName(cursor.getString(4));
124 item.setAuthPassword(cursor.getString(5));
125 item.setPostingPermitted(cursor.getInt(6) == 1);
126 item.setThemeId(cursor.getInt(7));
127 item.orderNo = cursor.getInt(8);
128 item.setPreferredAccountsFilter(cursor.getString(9));
129 item.setFutureDates(cursor.getInt(10));
130 item.setApiVersion(cursor.getInt(11));
131 item.setShowCommodityByDefault(cursor.getInt(12) == 1);
132 item.setDefaultCommodity(cursor.getString(13));
133 item.setShowCommentsByDefault(cursor.getInt(14) == 1);
135 boolean pre_1_20 = cursor.getInt(15) == 1;
136 int major = cursor.getInt(16);
137 int minor = cursor.getInt(17);
139 if (!pre_1_20 && major == 0 && minor == 0) {
140 item.detectedVersion = null;
143 item.detectedVersion = new HledgerVersion(true);
146 item.detectedVersion = new HledgerVersion(major, minor);
150 if (item.getId() == currentProfileId)
154 Data.profiles.postValue(list);
157 public static void storeProfilesOrder() {
158 SQLiteDatabase db = App.getDatabase();
159 db.beginTransactionNonExclusive();
162 for (MobileLedgerProfile p : Objects.requireNonNull(Data.profiles.getValue())) {
163 db.execSQL("update profiles set order_no=? where uuid=?",
164 new Object[]{orderNo, p.getId()});
168 db.setTransactionSuccessful();
174 static public void startEditProfileActivity(Context context, MobileLedgerProfile profile) {
175 Intent intent = new Intent(context, ProfileDetailActivity.class);
176 Bundle args = new Bundle();
177 if (profile != null) {
178 int index = Data.getProfileIndex(profile);
180 intent.putExtra(ProfileDetailFragment.ARG_ITEM_ID, index);
182 intent.putExtras(args);
183 context.startActivity(intent, args);
185 public HledgerVersion getDetectedVersion() {
186 return detectedVersion;
188 public void setDetectedVersion(HledgerVersion detectedVersion) {
189 this.detectedVersion = detectedVersion;
191 @Contract(value = "null -> false", pure = true)
193 public boolean equals(@Nullable Object obj) {
198 if (obj.getClass() != this.getClass())
201 MobileLedgerProfile p = (MobileLedgerProfile) obj;
204 if (!name.equals(p.name))
206 if (permitPosting != p.permitPosting)
208 if (showCommentsByDefault != p.showCommentsByDefault)
210 if (showCommodityByDefault != p.showCommodityByDefault)
212 if (!Objects.equals(defaultCommodity, p.defaultCommodity))
214 if (!Objects.equals(preferredAccountsFilter, p.preferredAccountsFilter))
216 if (!Objects.equals(url, p.url))
218 if (authEnabled != p.authEnabled)
220 if (!Objects.equals(authUserName, p.authUserName))
222 if (!Objects.equals(authPassword, p.authPassword))
224 if (themeHue != p.themeHue)
226 if (apiVersion != p.apiVersion)
228 if (!Objects.equals(detectedVersion, p.detectedVersion))
230 return futureDates == p.futureDates;
232 public boolean getShowCommentsByDefault() {
233 return showCommentsByDefault;
235 public void setShowCommentsByDefault(boolean newValue) {
236 this.showCommentsByDefault = newValue;
238 public boolean getShowCommodityByDefault() {
239 return showCommodityByDefault;
241 public void setShowCommodityByDefault(boolean showCommodityByDefault) {
242 this.showCommodityByDefault = showCommodityByDefault;
244 public String getDefaultCommodity() {
245 return defaultCommodity;
247 public void setDefaultCommodity(String defaultCommodity) {
248 this.defaultCommodity = defaultCommodity;
250 public void setDefaultCommodity(CharSequence defaultCommodity) {
251 if (defaultCommodity == null)
252 this.defaultCommodity = null;
254 this.defaultCommodity = String.valueOf(defaultCommodity);
256 public API getApiVersion() {
259 public void setApiVersion(API apiVersion) {
260 this.apiVersion = apiVersion;
262 public void setApiVersion(int apiVersion) {
263 this.apiVersion = API.valueOf(apiVersion);
265 public FutureDates getFutureDates() {
268 public void setFutureDates(int anInt) {
269 futureDates = FutureDates.valueOf(anInt);
271 public void setFutureDates(FutureDates futureDates) {
272 this.futureDates = futureDates;
274 public String getPreferredAccountsFilter() {
275 return preferredAccountsFilter;
277 public void setPreferredAccountsFilter(String preferredAccountsFilter) {
278 this.preferredAccountsFilter = preferredAccountsFilter;
280 public void setPreferredAccountsFilter(CharSequence preferredAccountsFilter) {
281 setPreferredAccountsFilter(String.valueOf(preferredAccountsFilter));
283 public boolean isPostingPermitted() {
284 return permitPosting;
286 public void setPostingPermitted(boolean permitPosting) {
287 this.permitPosting = permitPosting;
289 public long getId() {
292 public String getName() {
295 public void setName(CharSequence text) {
296 setName(String.valueOf(text));
298 public void setName(String name) {
301 public String getUrl() {
304 public void setUrl(CharSequence text) {
305 setUrl(String.valueOf(text));
307 public void setUrl(String url) {
310 public boolean isAuthEnabled() {
313 public void setAuthEnabled(boolean authEnabled) {
314 this.authEnabled = authEnabled;
316 public String getAuthUserName() {
319 public void setAuthUserName(CharSequence text) {
320 setAuthUserName(String.valueOf(text));
322 public void setAuthUserName(String authUserName) {
323 this.authUserName = authUserName;
325 public String getAuthPassword() {
328 public void setAuthPassword(CharSequence text) {
329 setAuthPassword(String.valueOf(text));
331 public void setAuthPassword(String authPassword) {
332 this.authPassword = authPassword;
334 public void storeInDB() {
335 SQLiteDatabase db = App.getDatabase();
336 db.beginTransactionNonExclusive();
338 // debug("profiles", String.format("Storing profile in DB: uuid=%s, name=%s, " +
339 // "url=%s, permit_posting=%s, authEnabled=%s, " +
340 // "themeHue=%d", uuid, name, url,
341 // permitPosting ? "TRUE" : "FALSE", authEnabled ? "TRUE" : "FALSE", themeHue));
342 db.execSQL("REPLACE INTO profiles(id, name, permit_posting, url, " +
343 "use_authentication, auth_user, auth_password, theme, order_no, " +
344 "preferred_accounts_filter, future_dates, api_version, " +
345 "show_commodity_by_default, default_commodity, show_comments_by_default," +
346 "detected_version_pre_1_19, detected_version_major, " +
347 "detected_version_minor) " +
348 "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
349 new Object[]{id, name, permitPosting, url, authEnabled,
350 authEnabled ? authUserName : null,
351 authEnabled ? authPassword : null, themeHue, orderNo,
352 preferredAccountsFilter, futureDates.toInt(), apiVersion.toInt(),
353 showCommodityByDefault, defaultCommodity, showCommentsByDefault,
354 (detectedVersion != null) && detectedVersion.isPre_1_20_1(),
355 (detectedVersion == null) ? 0 : detectedVersion.getMajor(),
356 (detectedVersion == null) ? 0 : detectedVersion.getMinor()
358 db.setTransactionSuccessful();
364 public void storeAccount(SQLiteDatabase db, int generation, LedgerAccount acc,
365 boolean storeUiFields) {
366 // replace into is a bad idea because it would reset hidden to its default value
367 // we like the default, but for new accounts only
368 String sql = "update accounts set generation = ?";
369 List<Object> params = new ArrayList<>();
370 params.add(generation);
372 sql += ", expanded=?";
373 params.add(acc.isExpanded() ? 1 : 0);
375 sql += " where profile_id=? and name=?";
377 params.add(acc.getName());
378 db.execSQL(sql, params.toArray());
380 db.execSQL("insert into accounts(profile_id, name, name_upper, parent_name, level, " +
381 "expanded, generation) select ?,?,?,?,?,0,? where (select changes() = 0)",
382 new Object[]{id, acc.getName(), acc.getName().toUpperCase(), acc.getParentName(),
383 acc.getLevel(), generation
385 // debug("accounts", String.format("Stored account '%s' in DB [%s]", acc.getName(), uuid));
387 public void storeAccountValue(SQLiteDatabase db, int generation, String name, String currency,
389 if (!TextUtils.isEmpty(currency)) {
391 try (Cursor c = db.rawQuery("select 1 from currencies where name=?",
392 new String[]{currency}))
394 exists = c.moveToFirst();
398 "insert into currencies(id, name, position, has_gap) values((select max" +
399 "(id) from currencies)+1, ?, ?, ?)", new Object[]{currency,
400 Objects.requireNonNull(
401 Data.currencySymbolPosition.getValue()).toString(),
402 Data.currencyGap.getValue()
407 long accId = findAddAccount(db, name);
409 db.execSQL("replace into account_values(account_id, " +
410 "currency, value, generation) values(?, ?, ?, ?);",
411 new Object[]{accId, Misc.emptyIsNull(currency), amount, generation});
413 private long findAddAccount(SQLiteDatabase db, String accountName) {
414 try (Cursor c = db.rawQuery("select id from accounts where profile_id=? and name=?",
415 new String[]{String.valueOf(id), accountName}))
422 try (Cursor c = db.rawQuery(
423 "insert into accounts(profile_id, name, name_upper) values(?, ?, ?) returning id",
424 new String[]{String.valueOf(id), accountName, accountName.toUpperCase()}))
430 public void storeTransaction(SQLiteDatabase db, int generation, LedgerTransaction tr) {
432 // Logger.debug("storeTransaction", String.format(Locale.US, "ID %d", tr.getId()));
433 SimpleDate d = tr.getDate();
434 db.execSQL("UPDATE transactions SET year=?, month=?, day=?, description=?, comment=?, " +
435 "data_hash=?, generation=? WHERE profile_id=? AND ledger_id=?",
436 new Object[]{d.year, d.month, d.day, tr.getDescription(), tr.getComment(),
437 tr.getDataHash(), generation, id, tr.getId()
440 "INSERT INTO transactions(profile_id, ledger_id, year, month, day, description, " +
441 "comment, data_hash, generation) " +
442 "select ?,?,?,?,?,?,?,?,? WHERE (select changes() = 0)",
443 new Object[]{id, tr.getId(), tr.getDate().year, tr.getDate().month,
444 tr.getDate().day, tr.getDescription(), tr.getComment(),
445 tr.getDataHash(), generation
448 int accountOrderNo = 1;
449 for (LedgerTransactionAccount item : tr.getAccounts()) {
450 db.execSQL("UPDATE transaction_accounts SET account_name=?, amount=?, currency=?, " +
451 "comment=?, generation=? " + "WHERE transaction_id=? AND order_no=?",
452 new Object[]{item.getAccountName(), item.getAmount(),
453 Misc.nullIsEmpty(item.getCurrency()), item.getComment(),
454 generation, tr.getId(), accountOrderNo
456 db.execSQL("INSERT INTO transaction_accounts(transaction_id, " +
457 "order_no, account_name, amount, currency, comment, generation) " +
458 "select ?, ?, ?, ?, ?, ?, ? WHERE (select changes() = 0)",
459 new Object[]{tr.getId(), accountOrderNo, item.getAccountName(),
460 item.getAmount(), Misc.nullIsEmpty(item.getCurrency()),
461 item.getComment(), generation
466 // debug("profile", String.format("Transaction %d stored", tr.getId()));
468 public String getOption(String name, String default_value) {
469 SQLiteDatabase db = App.getDatabase();
470 try (Cursor cursor = db.rawQuery(
471 "select value from options where profile_id = ? and name=?",
472 new String[]{String.valueOf(id), name}))
474 if (cursor.moveToFirst()) {
475 String result = cursor.getString(0);
477 if (result == null) {
478 debug("profile", "returning default value for " + name);
479 result = default_value;
482 debug("profile", String.format("option %s=%s", name, result));
487 return default_value;
489 catch (Exception e) {
490 debug("db", "returning default value for " + name, e);
491 return default_value;
494 public long getLongOption(String name, long default_value) {
496 String result = getOption(name, "");
497 if ((result == null) || result.isEmpty()) {
498 debug("profile", String.format("Returning default value for option %s", name));
499 longResult = default_value;
503 longResult = Long.parseLong(result);
504 debug("profile", String.format("option %s=%s", name, result));
506 catch (Exception e) {
507 debug("profile", String.format("Returning default value for option %s", name), e);
508 longResult = default_value;
514 public void setOption(String name, String value) {
515 debug("profile", String.format("setting option %s=%s", name, value));
516 DbOpQueue.add("insert or replace into options(profile_id, name, value) values(?, ?, ?);",
517 new String[]{String.valueOf(id), name, value});
519 public void setLongOption(String name, long value) {
520 setOption(name, String.valueOf(value));
522 public void removeFromDB() {
523 SQLiteDatabase db = App.getDatabase();
524 debug("db", String.format(Locale.ROOT, "removing profile %d from DB", id));
525 db.beginTransactionNonExclusive();
527 Object[] id_param = new Object[]{id};
528 db.execSQL("delete from transactions where profile_id=?", id_param);
529 db.execSQL("delete from accounts where profile=?", id_param);
530 db.execSQL("delete from options where profile=?", id_param);
531 db.execSQL("delete from profiles where id=?", id_param);
532 db.setTransactionSuccessful();
538 public LedgerTransaction loadTransaction(int transactionId) {
539 LedgerTransaction tr = new LedgerTransaction(transactionId, this.id);
540 tr.loadData(App.getDatabase());
544 public int getThemeHue() {
545 // debug("profile", String.format("Profile.getThemeHue() returning %d", themeHue));
546 return this.themeHue;
548 public void setThemeHue(Object o) {
549 setThemeId(Integer.parseInt(String.valueOf(o)));
551 public void setThemeId(int themeHue) {
552 // debug("profile", String.format("Profile.setThemeHue(%d) called", themeHue));
553 this.themeHue = themeHue;
555 public int getNextTransactionsGeneration(SQLiteDatabase db) {
556 try (Cursor c = db.rawQuery(
557 "SELECT generation FROM transactions WHERE profile_id=? LIMIT 1",
558 new String[]{String.valueOf(id)}))
561 return c.getInt(0) + 1;
565 private int getNextAccountsGeneration(SQLiteDatabase db) {
566 try (Cursor c = db.rawQuery("SELECT generation FROM accounts WHERE profile_id=? LIMIT 1",
567 new String[]{String.valueOf(id)}))
570 return c.getInt(0) + 1;
574 private void deleteNotPresentAccounts(SQLiteDatabase db, int generation) {
575 Logger.debug("db/benchmark", "Deleting obsolete accounts");
576 db.execSQL("DELETE FROM account_values WHERE (select a.profile_id from accounts a where a" +
577 ".id=account_values.account_id)=? AND generation <> ?",
578 new Object[]{id, generation});
579 db.execSQL("DELETE FROM accounts WHERE profile_id=? AND generation <> ?",
580 new Object[]{id, generation});
581 Logger.debug("db/benchmark", "Done deleting obsolete accounts");
583 private void deleteNotPresentTransactions(SQLiteDatabase db, int generation) {
584 Logger.debug("db/benchmark", "Deleting obsolete transactions");
586 "DELETE FROM transaction_accounts WHERE (select t.profile_id from transactions t " +
587 "where t.id=transaction_accounts.transaction_id)=? AND generation" + " <> ?",
588 new Object[]{id, generation});
589 db.execSQL("DELETE FROM transactions WHERE profile_id=? AND generation <> ?",
590 new Object[]{id, generation});
591 Logger.debug("db/benchmark", "Done deleting obsolete transactions");
594 public void wipeAllDataSync() {
595 OptionDAO optDao = DB.get()
597 optDao.deleteSync(optDao.allForProfileSync(id));
599 AccountDAO accDao = DB.get()
601 accDao.deleteSync(accDao.allForProfileSync(id));
603 TransactionDAO trnDao = DB.get()
604 .getTransactionDAO();
605 trnDao.deleteSync(trnDao.allForProfileSync(id));
607 public void wipeAllData() {
608 AsyncTask.execute(this::wipeAllDataSync);
610 public List<Currency> getCurrencies() {
611 SQLiteDatabase db = App.getDatabase();
613 ArrayList<Currency> result = new ArrayList<>();
615 try (Cursor c = db.rawQuery("SELECT c.id, c.name, c.position, c.has_gap FROM currencies c",
618 while (c.moveToNext()) {
619 Currency currency = new Currency(c.getInt(0), c.getString(1),
620 Currency.Position.valueOf(c.getString(2)), c.getInt(3) == 1);
621 result.add(currency);
627 Currency loadCurrencyByName(String name) {
628 SQLiteDatabase db = App.getDatabase();
629 Currency result = tryLoadCurrencyByName(db, name);
631 throw new RuntimeException(String.format("Unable to load currency '%s'", name));
634 private Currency tryLoadCurrencyByName(SQLiteDatabase db, String name) {
635 try (Cursor cursor = db.rawQuery(
636 "SELECT c.id, c.name, c.position, c.has_gap FROM currencies c WHERE c.name=?",
639 if (cursor.moveToFirst()) {
640 return new Currency(cursor.getInt(0), cursor.getString(1),
641 Currency.Position.valueOf(cursor.getString(2)), cursor.getInt(3) == 1);
646 public void storeAccountAndTransactionListAsync(List<LedgerAccount> accounts,
647 List<LedgerTransaction> transactions) {
648 if (accountAndTransactionListSaver != null)
649 accountAndTransactionListSaver.interrupt();
651 accountAndTransactionListSaver =
652 new AccountAndTransactionListSaver(this, accounts, transactions);
653 accountAndTransactionListSaver.start();
655 private Currency tryLoadCurrencyById(SQLiteDatabase db, int id) {
656 try (Cursor cursor = db.rawQuery(
657 "SELECT c.id, c.name, c.position, c.has_gap FROM currencies c WHERE c.id=?",
658 new String[]{String.valueOf(id)}))
660 if (cursor.moveToFirst()) {
661 return new Currency(cursor.getInt(0), cursor.getString(1),
662 Currency.Position.valueOf(cursor.getString(2)), cursor.getInt(3) == 1);
667 public Currency loadCurrencyById(int id) {
668 SQLiteDatabase db = App.getDatabase();
669 Currency result = tryLoadCurrencyById(db, id);
671 throw new RuntimeException(String.format("Unable to load currency with id '%d'", id));
675 public enum FutureDates {
676 None(0), OneWeek(7), TwoWeeks(14), OneMonth(30), TwoMonths(60), ThreeMonths(90),
677 SixMonths(180), OneYear(365), All(-1);
678 private static final SparseArray<FutureDates> map = new SparseArray<>();
681 for (FutureDates item : FutureDates.values()) {
682 map.put(item.value, item);
686 private final int value;
687 FutureDates(int value) {
690 public static FutureDates valueOf(int i) {
691 return map.get(i, None);
696 public String getText(Resources resources) {
699 return resources.getString(R.string.future_dates_7);
701 return resources.getString(R.string.future_dates_14);
703 return resources.getString(R.string.future_dates_30);
705 return resources.getString(R.string.future_dates_60);
707 return resources.getString(R.string.future_dates_90);
709 return resources.getString(R.string.future_dates_180);
711 return resources.getString(R.string.future_dates_365);
713 return resources.getString(R.string.future_dates_all);
715 return resources.getString(R.string.future_dates_none);
720 private static class AccountAndTransactionListSaver extends Thread {
721 private final MobileLedgerProfile profile;
722 private final List<LedgerAccount> accounts;
723 private final List<LedgerTransaction> transactions;
724 AccountAndTransactionListSaver(MobileLedgerProfile profile, List<LedgerAccount> accounts,
725 List<LedgerTransaction> transactions) {
726 this.accounts = accounts;
727 this.transactions = transactions;
728 this.profile = profile;
730 public int getNextDescriptionsGeneration(SQLiteDatabase db) {
732 try (Cursor c = db.rawQuery("SELECT generation FROM description_history LIMIT 1",
735 if (c.moveToFirst()) {
736 generation = c.getInt(0) + 1;
741 void deleteNotPresentDescriptions(SQLiteDatabase db, int generation) {
742 Logger.debug("db/benchmark", "Deleting obsolete descriptions");
743 db.execSQL("DELETE FROM description_history WHERE generation <> ?",
744 new Object[]{generation});
745 db.execSQL("DELETE FROM description_history WHERE generation <> ?",
746 new Object[]{generation});
747 Logger.debug("db/benchmark", "Done deleting obsolete descriptions");
751 SQLiteDatabase db = App.getDatabase();
752 db.beginTransactionNonExclusive();
754 int accountsGeneration = profile.getNextAccountsGeneration(db);
758 int transactionsGeneration = profile.getNextTransactionsGeneration(db);
762 for (LedgerAccount acc : accounts) {
763 profile.storeAccount(db, accountsGeneration, acc, false);
766 for (LedgerAmount amt : acc.getAmounts()) {
767 profile.storeAccountValue(db, accountsGeneration, acc.getName(),
768 amt.getCurrency(), amt.getAmount());
774 for (LedgerTransaction tr : transactions) {
775 profile.storeTransaction(db, transactionsGeneration, tr);
780 profile.deleteNotPresentTransactions(db, transactionsGeneration);
781 if (isInterrupted()) {
784 profile.deleteNotPresentAccounts(db, accountsGeneration);
788 Map<String, Boolean> unique = new HashMap<>();
790 debug("descriptions", "Starting refresh");
791 int descriptionsGeneration = getNextDescriptionsGeneration(db);
792 try (Cursor c = db.rawQuery("SELECT distinct description from transactions",
795 while (c.moveToNext()) {
796 String description = c.getString(0);
797 String descriptionUpper = description.toUpperCase();
798 if (unique.containsKey(descriptionUpper))
801 storeDescription(db, descriptionsGeneration, description, descriptionUpper);
803 unique.put(descriptionUpper, true);
806 deleteNotPresentDescriptions(db, descriptionsGeneration);
808 db.setTransactionSuccessful();
814 private void storeDescription(SQLiteDatabase db, int generation, String description,
815 String descriptionUpper) {
816 db.execSQL("UPDATE description_history SET description=?, generation=? WHERE " +
817 "description_upper=?", new Object[]{description, generation, descriptionUpper
820 "INSERT INTO description_history(description, description_upper, generation) " +
821 "select ?,?,? WHERE (select changes() = 0)",
822 new Object[]{description, descriptionUpper, generation