android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@drawable/app_icon_round"
android:supportsRtl="true"
+ android:backupAgent="net.ktnx.mobileledger.backup.MobileLedgerBackupAgent"
tools:ignore="GoogleAppIndexingWarning">
<activity
android:name=".BackupsActivity"
import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;
-import net.ktnx.mobileledger.async.ConfigReader;
-import net.ktnx.mobileledger.async.ConfigWriter;
+import net.ktnx.mobileledger.backup.ConfigReader;
+import net.ktnx.mobileledger.backup.ConfigWriter;
import net.ktnx.mobileledger.databinding.FragmentBackupsBinding;
import net.ktnx.mobileledger.model.Data;
+++ /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.async;
-
-import android.content.Context;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import net.ktnx.mobileledger.utils.Misc;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-
-abstract class ConfigIO extends Thread {
- protected final OnErrorListener onErrorListener;
- protected ParcelFileDescriptor pfd;
- ConfigIO(Context context, Uri uri, OnErrorListener onErrorListener)
- throws FileNotFoundException {
- this.onErrorListener = onErrorListener;
- pfd = context.getContentResolver()
- .openFileDescriptor(uri, getStreamMode());
-
- initStream();
- }
- abstract protected String getStreamMode();
-
- abstract protected void initStream();
-
- abstract protected void processStream() throws IOException;
- @Override
- public void run() {
- try {
- processStream();
- }
- catch (Exception e) {
- Log.e("cfg-json", "Error processing settings as JSON", e);
- if (onErrorListener != null)
- Misc.onMainThread(() -> onErrorListener.error(e));
- }
- finally {
- try {
- pfd.close();
- }
- catch (Exception e) {
- Log.e("cfg-json", "Error closing file descriptor", e);
- }
- }
- }
- protected static class Keys {
- static final String ACCOUNTS = "accounts";
- static final String AMOUNT = "amount";
- static final String AMOUNT_GROUP = "amountGroup";
- static final String API_VER = "apiVersion";
- static final String AUTH_PASS = "authPass";
- static final String AUTH_USER = "authUser";
- static final String CAN_POST = "permitPosting";
- static final String COLOUR = "colour";
- static final String COMMENT = "comment";
- static final String COMMENT_GROUP = "commentMatchGroup";
- static final String COMMODITIES = "commodities";
- static final String CURRENCY = "commodity";
- static final String CURRENCY_GROUP = "commodityGroup";
- static final String CURRENT_PROFILE = "currentProfile";
- static final String DATE_DAY = "dateDay";
- static final String DATE_DAY_GROUP = "dateDayMatchGroup";
- static final String DATE_MONTH = "dateMonth";
- static final String DATE_MONTH_GROUP = "dateMonthMatchGroup";
- static final String DATE_YEAR = "dateYear";
- static final String DATE_YEAR_GROUP = "dateYearMatchGroup";
- static final String DEFAULT_COMMODITY = "defaultCommodity";
- static final String FUTURE_DATES = "futureDates";
- static final String HAS_GAP = "hasGap";
- static final String IS_FALLBACK = "isFallback";
- static final String NAME = "name";
- static final String NAME_GROUP = "nameMatchGroup";
- static final String NEGATE_AMOUNT = "negateAmount";
- static final String POSITION = "position";
- static final String PREF_ACCOUNT = "preferredAccountsFilter";
- static final String PROFILES = "profiles";
- static final String REGEX = "regex";
- static final String SHOW_COMMENTS = "showCommentsByDefault";
- static final String SHOW_COMMODITY = "showCommodityByDefault";
- static final String TEMPLATES = "templates";
- static final String TEST_TEXT = "testText";
- static final String TRANSACTION = "description";
- static final String TRANSACTION_GROUP = "descriptionMatchGroup";
- static final String URL = "url";
- static final String USE_AUTH = "useAuth";
- static final String UUID = "uuid";
- }
-
- abstract static public class OnErrorListener {
- public abstract void error(Exception e);
- }
-}
+++ /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.async;
-
-import android.content.Context;
-import android.net.Uri;
-import android.util.JsonReader;
-import android.util.JsonToken;
-
-import net.ktnx.mobileledger.dao.CurrencyDAO;
-import net.ktnx.mobileledger.dao.ProfileDAO;
-import net.ktnx.mobileledger.dao.TemplateHeaderDAO;
-import net.ktnx.mobileledger.db.Currency;
-import net.ktnx.mobileledger.db.DB;
-import net.ktnx.mobileledger.db.Profile;
-import net.ktnx.mobileledger.db.TemplateAccount;
-import net.ktnx.mobileledger.db.TemplateHeader;
-import net.ktnx.mobileledger.db.TemplateWithAccounts;
-import net.ktnx.mobileledger.model.Data;
-import net.ktnx.mobileledger.utils.Misc;
-
-import java.io.BufferedReader;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.List;
-
-public class ConfigReader extends ConfigIO {
- private final OnDoneListener onDoneListener;
- private JsonReader r;
- public ConfigReader(Context context, Uri uri, OnErrorListener onErrorListener,
- OnDoneListener onDoneListener) throws FileNotFoundException {
- super(context, uri, onErrorListener);
-
- this.onDoneListener = onDoneListener;
- }
- @Override
- protected String getStreamMode() {
- return "r";
- }
- @Override
- protected void initStream() {
- r = new JsonReader(new BufferedReader(
- new InputStreamReader(new FileInputStream(pfd.getFileDescriptor()))));
- }
- @Override
- protected void processStream() throws IOException {
- List<Currency> commodities = null;
- List<Profile> profiles = null;
- List<TemplateWithAccounts> templates = null;
- String currentProfile = null;
- r.beginObject();
- while (r.hasNext()) {
- String item = r.nextName();
- if (r.peek() == JsonToken.NULL) {
- r.nextNull();
- continue;
- }
- switch (item) {
- case Keys.COMMODITIES:
- commodities = readCommodities(r);
- break;
- case Keys.PROFILES:
- profiles = readProfiles(r);
- break;
- case Keys.TEMPLATES:
- templates = readTemplates(r);
- break;
- case Keys.CURRENT_PROFILE:
- currentProfile = r.nextString();
- break;
- default:
- throw new RuntimeException("unexpected top-level item " + item);
- }
- }
- r.endObject();
-
- restoreCommodities(commodities);
- restoreProfiles(profiles);
- restoreTemplates(templates);
-
- if (Data.getProfile() == null) {
- Profile p = null;
- final ProfileDAO dao = DB.get()
- .getProfileDAO();
- if (currentProfile != null)
- p = dao.getByUuidSync(currentProfile);
-
- if (p == null)
- dao.getAnySync();
-
- if (p != null)
- Data.postCurrentProfile(p);
- }
-
- if (onDoneListener != null)
- Misc.onMainThread(onDoneListener::done);
- }
- private void restoreTemplates(List<TemplateWithAccounts> list) {
- if (list == null)
- return;
-
- TemplateHeaderDAO dao = DB.get()
- .getTemplateDAO();
-
- for (TemplateWithAccounts t : list) {
- if (dao.getTemplateWithAccountsByUuidSync(t.header.getUuid()) == null)
- dao.insertSync(t);
- }
- }
- private void restoreProfiles(List<Profile> list) {
- if (list == null)
- return;
-
- ProfileDAO dao = DB.get()
- .getProfileDAO();
-
- for (Profile p : list) {
- if (dao.getByUuidSync(p.getUuid()) == null)
- dao.insert(p);
- }
- }
- private void restoreCommodities(List<Currency> list) {
- if (list == null)
- return;
-
- CurrencyDAO dao = DB.get()
- .getCurrencyDAO();
-
- for (Currency c : list) {
- if (dao.getByNameSync(c.getName()) == null)
- dao.insert(c);
- }
- }
- private TemplateAccount readTemplateAccount(JsonReader r) throws IOException {
- r.beginObject();
- TemplateAccount result = new TemplateAccount(0L, 0L, 0L);
- while (r.peek() != JsonToken.END_OBJECT) {
- String item = r.nextName();
- if (r.peek() == JsonToken.NULL) {
- r.nextNull();
- continue;
- }
- switch (item) {
- case Keys.NAME:
- result.setAccountName(r.nextString());
- break;
- case Keys.NAME_GROUP:
- result.setAccountNameMatchGroup(r.nextInt());
- break;
- case Keys.COMMENT:
- result.setAccountComment(r.nextString());
- break;
- case Keys.COMMENT_GROUP:
- result.setAccountCommentMatchGroup(r.nextInt());
- break;
- case Keys.AMOUNT:
- result.setAmount((float) r.nextDouble());
- break;
- case Keys.AMOUNT_GROUP:
- result.setAmountMatchGroup(r.nextInt());
- break;
- case Keys.NEGATE_AMOUNT:
- result.setNegateAmount(r.nextBoolean());
- break;
- case Keys.CURRENCY:
- result.setCurrency(r.nextLong());
- break;
- case Keys.CURRENCY_GROUP:
- result.setCurrencyMatchGroup(r.nextInt());
- break;
-
- default:
- throw new IllegalStateException("Unexpected template account item: " + item);
- }
- }
- r.endObject();
-
- return result;
- }
- private TemplateWithAccounts readTemplate(JsonReader r) throws IOException {
- r.beginObject();
- String name = null;
- TemplateHeader t = new TemplateHeader(0L, "", "");
- List<TemplateAccount> accounts = new ArrayList<>();
-
- while (r.peek() != JsonToken.END_OBJECT) {
- String item = r.nextName();
- if (r.peek() == JsonToken.NULL) {
- r.nextNull();
- continue;
- }
- switch (item) {
- case Keys.UUID:
- t.setUuid(r.nextString());
- break;
- case Keys.NAME:
- t.setName(r.nextString());
- break;
- case Keys.REGEX:
- t.setRegularExpression(r.nextString());
- break;
- case Keys.TEST_TEXT:
- t.setTestText(r.nextString());
- break;
- case Keys.DATE_YEAR:
- t.setDateYear(r.nextInt());
- break;
- case Keys.DATE_YEAR_GROUP:
- t.setDateYearMatchGroup(r.nextInt());
- break;
- case Keys.DATE_MONTH:
- t.setDateMonth(r.nextInt());
- break;
- case Keys.DATE_MONTH_GROUP:
- t.setDateMonthMatchGroup(r.nextInt());
- break;
- case Keys.DATE_DAY:
- t.setDateDay(r.nextInt());
- break;
- case Keys.DATE_DAY_GROUP:
- t.setDateDayMatchGroup(r.nextInt());
- break;
- case Keys.TRANSACTION:
- t.setTransactionDescription(r.nextString());
- break;
- case Keys.TRANSACTION_GROUP:
- t.setTransactionDescriptionMatchGroup(r.nextInt());
- break;
- case Keys.COMMENT:
- t.setTransactionComment(r.nextString());
- break;
- case Keys.COMMENT_GROUP:
- t.setTransactionCommentMatchGroup(r.nextInt());
- break;
- case Keys.IS_FALLBACK:
- t.setFallback(r.nextBoolean());
- break;
- case Keys.ACCOUNTS:
- r.beginArray();
- while (r.peek() == JsonToken.BEGIN_OBJECT) {
- accounts.add(readTemplateAccount(r));
- }
- r.endArray();
- break;
- default:
- throw new RuntimeException("Unknown template header item: " + item);
- }
- }
- r.endObject();
-
- TemplateWithAccounts result = new TemplateWithAccounts();
- result.header = t;
- result.accounts = accounts;
- return result;
- }
- private List<TemplateWithAccounts> readTemplates(JsonReader r) throws IOException {
- List<TemplateWithAccounts> list = new ArrayList<>();
-
- r.beginArray();
- while (r.peek() == JsonToken.BEGIN_OBJECT) {
- list.add(readTemplate(r));
- }
- r.endArray();
-
- return list;
- }
- private List<Currency> readCommodities(JsonReader r) throws IOException {
- List<Currency> list = new ArrayList<>();
-
- r.beginArray();
- while (r.peek() == JsonToken.BEGIN_OBJECT) {
- Currency c = new Currency();
-
- r.beginObject();
- while (r.peek() != JsonToken.END_OBJECT) {
- final String item = r.nextName();
- if (r.peek() == JsonToken.NULL) {
- r.nextNull();
- continue;
- }
- switch (item) {
- case Keys.NAME:
- c.setName(r.nextString());
- break;
- case Keys.POSITION:
- c.setPosition(r.nextString());
- break;
- case Keys.HAS_GAP:
- c.setHasGap(r.nextBoolean());
- break;
- default:
- throw new RuntimeException("Unknown commodity key: " + item);
- }
- }
- r.endObject();
-
- if (c.getName()
- .isEmpty())
- throw new RuntimeException("Missing commodity name");
-
- list.add(c);
- }
- r.endArray();
-
- return list;
- }
- private List<Profile> readProfiles(JsonReader r) throws IOException {
- List<Profile> list = new ArrayList<>();
- r.beginArray();
- while (r.peek() == JsonToken.BEGIN_OBJECT) {
- Profile p = new Profile();
- r.beginObject();
- while (r.peek() != JsonToken.END_OBJECT) {
- String item = r.nextName();
- if (r.peek() == JsonToken.NULL) {
- r.nextNull();
- continue;
- }
-
- switch (item) {
- case Keys.UUID:
- p.setUuid(r.nextString());
- break;
- case Keys.NAME:
- p.setName(r.nextString());
- break;
- case Keys.URL:
- p.setUrl(r.nextString());
- break;
- case Keys.USE_AUTH:
- p.setUseAuthentication(r.nextBoolean());
- break;
- case Keys.AUTH_USER:
- p.setAuthUser(r.nextString());
- break;
- case Keys.AUTH_PASS:
- p.setAuthPassword(r.nextString());
- break;
- case Keys.API_VER:
- p.setApiVersion(r.nextInt());
- break;
- case Keys.CAN_POST:
- p.setPermitPosting(r.nextBoolean());
- break;
- case Keys.DEFAULT_COMMODITY:
- p.setDefaultCommodity(r.nextString());
- break;
- case Keys.SHOW_COMMODITY:
- p.setShowCommodityByDefault(r.nextBoolean());
- break;
- case Keys.SHOW_COMMENTS:
- p.setShowCommentsByDefault(r.nextBoolean());
- break;
- case Keys.FUTURE_DATES:
- p.setFutureDates(r.nextInt());
- break;
- case Keys.PREF_ACCOUNT:
- p.setPreferredAccountsFilter(r.nextString());
- break;
- case Keys.COLOUR:
- p.setTheme(r.nextInt());
- break;
-
-
- default:
- throw new IllegalStateException("Unexpected profile item: " + item);
- }
- }
- r.endObject();
-
- list.add(p);
- }
- r.endArray();
-
- return list;
- }
- abstract static public class OnDoneListener {
- public abstract void done();
- }
-}
+++ /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.async;
-
-import android.content.Context;
-import android.net.Uri;
-import android.util.JsonWriter;
-
-import net.ktnx.mobileledger.db.Currency;
-import net.ktnx.mobileledger.db.DB;
-import net.ktnx.mobileledger.db.Profile;
-import net.ktnx.mobileledger.db.TemplateAccount;
-import net.ktnx.mobileledger.db.TemplateWithAccounts;
-import net.ktnx.mobileledger.json.API;
-import net.ktnx.mobileledger.model.Data;
-import net.ktnx.mobileledger.utils.Misc;
-
-import java.io.BufferedWriter;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.util.List;
-
-public class ConfigWriter extends ConfigIO {
- private final OnDoneListener onDoneListener;
- private JsonWriter w;
- public ConfigWriter(Context context, Uri uri, OnErrorListener onErrorListener,
- OnDoneListener onDoneListener) throws FileNotFoundException {
- super(context, uri, onErrorListener);
-
- this.onDoneListener = onDoneListener;
- }
- @Override
- protected String getStreamMode() {
- return "w";
- }
- @Override
- protected void initStream() {
- w = new JsonWriter(new BufferedWriter(
- new OutputStreamWriter(new FileOutputStream(pfd.getFileDescriptor()))));
- w.setIndent(" ");
- }
- @Override
- protected void processStream() throws IOException {
- w.beginObject();
- writeCommodities(w);
- writeProfiles(w);
- writeCurrentProfile(w);
- writeConfigTemplates(w);
- w.endObject();
- w.flush();
-
- if (onDoneListener != null)
- Misc.onMainThread(onDoneListener::done);
- }
- private void writeKey(JsonWriter w, String key, String value) throws IOException {
- if (value != null)
- w.name(key)
- .value(value);
- }
- private void writeKey(JsonWriter w, String key, Integer value) throws IOException {
- if (value != null)
- w.name(key)
- .value(value);
- }
- private void writeKey(JsonWriter w, String key, Long value) throws IOException {
- if (value != null)
- w.name(key)
- .value(value);
- }
- private void writeKey(JsonWriter w, String key, Float value) throws IOException {
- if (value != null)
- w.name(key)
- .value(value);
- }
- private void writeKey(JsonWriter w, String key, Boolean value) throws IOException {
- if (value != null)
- w.name(key)
- .value(value);
- }
- private void writeConfigTemplates(JsonWriter w) throws IOException {
- List<TemplateWithAccounts> templates = DB.get()
- .getTemplateDAO()
- .getAllTemplatesWithAccountsSync();
-
- if (templates.isEmpty())
- return;
-
- w.name("templates")
- .beginArray();
- for (TemplateWithAccounts t : templates) {
- w.beginObject();
-
- w.name(Keys.UUID)
- .value(t.header.getUuid());
- w.name(Keys.NAME)
- .value(t.header.getName());
- w.name(Keys.REGEX)
- .value(t.header.getRegularExpression());
- writeKey(w, Keys.TEST_TEXT, t.header.getTestText());
- writeKey(w, Keys.DATE_YEAR, t.header.getDateYear());
- writeKey(w, Keys.DATE_YEAR_GROUP, t.header.getDateYearMatchGroup());
- writeKey(w, Keys.DATE_MONTH, t.header.getDateMonth());
- writeKey(w, Keys.DATE_MONTH_GROUP, t.header.getDateMonthMatchGroup());
- writeKey(w, Keys.DATE_DAY, t.header.getDateDay());
- writeKey(w, Keys.DATE_DAY_GROUP, t.header.getDateDayMatchGroup());
- writeKey(w, Keys.TRANSACTION, t.header.getTransactionDescription());
- writeKey(w, Keys.TRANSACTION_GROUP, t.header.getTransactionDescriptionMatchGroup());
- writeKey(w, Keys.COMMENT, t.header.getTransactionComment());
- writeKey(w, Keys.COMMENT_GROUP, t.header.getTransactionCommentMatchGroup());
- w.name(Keys.IS_FALLBACK)
- .value(t.header.isFallback());
- if (t.accounts.size() > 0) {
- w.name(Keys.ACCOUNTS)
- .beginArray();
- for (TemplateAccount a : t.accounts) {
- w.beginObject();
-
- writeKey(w, Keys.NAME, a.getAccountName());
- writeKey(w, Keys.NAME_GROUP, a.getAccountNameMatchGroup());
- writeKey(w, Keys.COMMENT, a.getAccountComment());
- writeKey(w, Keys.COMMENT_GROUP, a.getAccountCommentMatchGroup());
- writeKey(w, Keys.AMOUNT, a.getAmount());
- writeKey(w, Keys.AMOUNT_GROUP, a.getAmountMatchGroup());
- writeKey(w, Keys.NEGATE_AMOUNT, a.getNegateAmount());
- writeKey(w, Keys.CURRENCY, a.getCurrency());
- writeKey(w, Keys.CURRENCY_GROUP, a.getCurrencyMatchGroup());
-
- w.endObject();
- }
- w.endArray();
- }
-
- w.endObject();
- }
- w.endArray();
- }
- private void writeCommodities(JsonWriter w) throws IOException {
- List<Currency> list = DB.get()
- .getCurrencyDAO()
- .getAllSync();
- if (list.isEmpty())
- return;
- w.name(Keys.COMMODITIES)
- .beginArray();
- for (Currency c : list) {
- w.beginObject();
- writeKey(w, Keys.NAME, c.getName());
- writeKey(w, Keys.POSITION, c.getPosition());
- writeKey(w, Keys.HAS_GAP, c.getHasGap());
- w.endObject();
- }
- w.endArray();
- }
- private void writeProfiles(JsonWriter w) throws IOException {
- List<Profile> profiles = DB.get()
- .getProfileDAO()
- .getAllOrderedSync();
-
- if (profiles.isEmpty())
- return;
-
- w.name(Keys.PROFILES)
- .beginArray();
- for (Profile p : profiles) {
- w.beginObject();
-
- w.name(Keys.NAME)
- .value(p.getName());
- w.name(Keys.UUID)
- .value(p.getUuid());
- w.name(Keys.URL)
- .value(p.getUrl());
- w.name(Keys.USE_AUTH)
- .value(p.useAuthentication());
- if (p.useAuthentication()) {
- w.name(Keys.AUTH_USER)
- .value(p.getAuthUser());
- w.name(Keys.AUTH_PASS)
- .value(p.getAuthPassword());
- }
- if (p.getApiVersion() != API.auto.toInt())
- w.name(Keys.API_VER)
- .value(p.getApiVersion());
- w.name(Keys.CAN_POST)
- .value(p.permitPosting());
- if (p.permitPosting()) {
- String defaultCommodity = p.getDefaultCommodity();
- if (!defaultCommodity.isEmpty())
- w.name(Keys.DEFAULT_COMMODITY)
- .value(defaultCommodity);
- w.name(Keys.SHOW_COMMODITY)
- .value(p.getShowCommodityByDefault());
- w.name(Keys.SHOW_COMMENTS)
- .value(p.getShowCommentsByDefault());
- w.name(Keys.FUTURE_DATES)
- .value(p.getFutureDates());
- w.name(Keys.PREF_ACCOUNT)
- .value(p.getPreferredAccountsFilter());
- }
- w.name(Keys.COLOUR)
- .value(p.getTheme());
-
- w.endObject();
- }
- w.endArray();
- }
- private void writeCurrentProfile(JsonWriter w) throws IOException {
- Profile currentProfile = Data.getProfile();
- if (currentProfile == null)
- return;
-
- w.name(Keys.CURRENT_PROFILE)
- .value(currentProfile.getUuid());
- }
-
- abstract static public class OnDoneListener {
- public abstract void done();
- }
-}
--- /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.backup;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import net.ktnx.mobileledger.utils.Misc;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+abstract class ConfigIO extends Thread {
+ protected final OnErrorListener onErrorListener;
+ protected ParcelFileDescriptor pfd;
+ ConfigIO(Context context, Uri uri, OnErrorListener onErrorListener)
+ throws FileNotFoundException {
+ this.onErrorListener = onErrorListener;
+ pfd = context.getContentResolver()
+ .openFileDescriptor(uri, getStreamMode());
+
+ initStream();
+ }
+ abstract protected String getStreamMode();
+
+ abstract protected void initStream();
+
+ abstract protected void processStream() throws IOException;
+ @Override
+ public void run() {
+ try {
+ processStream();
+ }
+ catch (Exception e) {
+ Log.e("cfg-json", "Error processing settings as JSON", e);
+ if (onErrorListener != null)
+ Misc.onMainThread(() -> onErrorListener.error(e));
+ }
+ finally {
+ try {
+ pfd.close();
+ }
+ catch (Exception e) {
+ Log.e("cfg-json", "Error closing file descriptor", e);
+ }
+ }
+ }
+ protected static class Keys {
+ static final String ACCOUNTS = "accounts";
+ static final String AMOUNT = "amount";
+ static final String AMOUNT_GROUP = "amountGroup";
+ static final String API_VER = "apiVersion";
+ static final String AUTH_PASS = "authPass";
+ static final String AUTH_USER = "authUser";
+ static final String CAN_POST = "permitPosting";
+ static final String COLOUR = "colour";
+ static final String COMMENT = "comment";
+ static final String COMMENT_GROUP = "commentMatchGroup";
+ static final String COMMODITIES = "commodities";
+ static final String CURRENCY = "commodity";
+ static final String CURRENCY_GROUP = "commodityGroup";
+ static final String CURRENT_PROFILE = "currentProfile";
+ static final String DATE_DAY = "dateDay";
+ static final String DATE_DAY_GROUP = "dateDayMatchGroup";
+ static final String DATE_MONTH = "dateMonth";
+ static final String DATE_MONTH_GROUP = "dateMonthMatchGroup";
+ static final String DATE_YEAR = "dateYear";
+ static final String DATE_YEAR_GROUP = "dateYearMatchGroup";
+ static final String DEFAULT_COMMODITY = "defaultCommodity";
+ static final String FUTURE_DATES = "futureDates";
+ static final String HAS_GAP = "hasGap";
+ static final String IS_FALLBACK = "isFallback";
+ static final String NAME = "name";
+ static final String NAME_GROUP = "nameMatchGroup";
+ static final String NEGATE_AMOUNT = "negateAmount";
+ static final String POSITION = "position";
+ static final String PREF_ACCOUNT = "preferredAccountsFilter";
+ static final String PROFILES = "profiles";
+ static final String REGEX = "regex";
+ static final String SHOW_COMMENTS = "showCommentsByDefault";
+ static final String SHOW_COMMODITY = "showCommodityByDefault";
+ static final String TEMPLATES = "templates";
+ static final String TEST_TEXT = "testText";
+ static final String TRANSACTION = "description";
+ static final String TRANSACTION_GROUP = "descriptionMatchGroup";
+ static final String URL = "url";
+ static final String USE_AUTH = "useAuth";
+ static final String UUID = "uuid";
+ }
+
+ abstract static public class OnErrorListener {
+ public abstract void error(Exception e);
+ }
+}
--- /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.backup;
+
+import android.content.Context;
+import android.net.Uri;
+
+import net.ktnx.mobileledger.dao.ProfileDAO;
+import net.ktnx.mobileledger.db.DB;
+import net.ktnx.mobileledger.db.Profile;
+import net.ktnx.mobileledger.model.Data;
+import net.ktnx.mobileledger.utils.Misc;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+public class ConfigReader extends ConfigIO {
+ private final OnDoneListener onDoneListener;
+ private RawConfigReader r;
+ public ConfigReader(Context context, Uri uri, OnErrorListener onErrorListener,
+ OnDoneListener onDoneListener) throws FileNotFoundException {
+ super(context, uri, onErrorListener);
+
+ this.onDoneListener = onDoneListener;
+ }
+ @Override
+ protected String getStreamMode() {
+ return "r";
+ }
+ @Override
+ protected void initStream() {
+ RawConfigReader r = new RawConfigReader(new FileInputStream(pfd.getFileDescriptor()));
+ }
+ @Override
+ protected void processStream() throws IOException {
+ r.readConfig();
+ r.restoreAll();
+ String currentProfile = r.getCurrentProfile();
+
+ if (Data.getProfile() == null) {
+ Profile p = null;
+ final ProfileDAO dao = DB.get()
+ .getProfileDAO();
+ if (currentProfile != null)
+ p = dao.getByUuidSync(currentProfile);
+
+ if (p == null)
+ dao.getAnySync();
+
+ if (p != null)
+ Data.postCurrentProfile(p);
+ }
+
+ if (onDoneListener != null)
+ Misc.onMainThread(onDoneListener::done);
+ }
+ abstract static public class OnDoneListener {
+ public abstract void done();
+ }
+}
--- /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.backup;
+
+import android.content.Context;
+import android.net.Uri;
+
+import net.ktnx.mobileledger.utils.Misc;
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+public class ConfigWriter extends ConfigIO {
+ private final OnDoneListener onDoneListener;
+ private RawConfigWriter w;
+ public ConfigWriter(Context context, Uri uri, OnErrorListener onErrorListener,
+ OnDoneListener onDoneListener) throws FileNotFoundException {
+ super(context, uri, onErrorListener);
+
+ this.onDoneListener = onDoneListener;
+ }
+ @Override
+ protected String getStreamMode() {
+ return "w";
+ }
+ @Override
+ protected void initStream() {
+ w = new RawConfigWriter(new FileOutputStream(pfd.getFileDescriptor()));
+ }
+ @Override
+ protected void processStream() throws IOException {
+ w.writeConfig();
+
+ if (onDoneListener != null)
+ Misc.onMainThread(onDoneListener::done);
+ }
+ abstract static public class OnDoneListener {
+ public abstract void done();
+ }
+}
--- /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.backup;
+
+import android.app.backup.BackupAgentHelper;
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.os.ParcelFileDescriptor;
+
+import net.ktnx.mobileledger.db.DB;
+import net.ktnx.mobileledger.utils.Logger;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+class MobileLedgerBackupAgent extends BackupAgentHelper {
+ private static final int READ_BUF_LEN = 10;
+ public static String SETTINGS_KEY = "settings";
+ @Override
+ public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+ ParcelFileDescriptor newState) throws IOException {
+ super.onBackup(oldState, data, newState);
+ backupSettings(data);
+ newState.close();
+ }
+ private void backupSettings(BackupDataOutput data) throws IOException {
+ Logger.debug ("backup", "Starting cloud backup");
+ ByteArrayOutputStream output = new ByteArrayOutputStream(4096);
+ RawConfigWriter saver = new RawConfigWriter(output);
+ saver.writeConfig();
+ byte[] bytes = output.toByteArray();
+ data.writeEntityHeader(SETTINGS_KEY, bytes.length);
+ data.writeEntityData(bytes, bytes.length);
+ Logger.debug("backup", "Done writing backup data");
+ }
+ @Override
+ public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
+ throws IOException {
+ Logger.debug("restore", "Starting cloud restore");
+ if (data.readNextHeader()) {
+ String key = data.getKey();
+ if (key.equals(SETTINGS_KEY)) {
+ restoreSettings(data);
+ }
+ }
+ }
+ private void restoreSettings(BackupDataInput data) throws IOException {
+ byte[] bytes = new byte[data.getDataSize()];
+ data.readEntityData(bytes, 0, bytes.length);
+ RawConfigReader reader = new RawConfigReader(new ByteArrayInputStream(bytes));
+ reader.readConfig();
+ Logger.debug("restore", "Successfully read restore data. Wiping database");
+ DB.get().deleteAllSync();
+ Logger.debug("restore", "Database wiped");
+ reader.restoreAll();
+ Logger.debug("restore", "All data restored from the cloud");
+ }
+}
--- /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.backup;
+
+import android.util.JsonReader;
+import android.util.JsonToken;
+
+import net.ktnx.mobileledger.backup.ConfigIO.Keys;
+import net.ktnx.mobileledger.dao.CurrencyDAO;
+import net.ktnx.mobileledger.dao.ProfileDAO;
+import net.ktnx.mobileledger.dao.TemplateHeaderDAO;
+import net.ktnx.mobileledger.db.Currency;
+import net.ktnx.mobileledger.db.DB;
+import net.ktnx.mobileledger.db.Profile;
+import net.ktnx.mobileledger.db.TemplateAccount;
+import net.ktnx.mobileledger.db.TemplateHeader;
+import net.ktnx.mobileledger.db.TemplateWithAccounts;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+public class RawConfigReader {
+ private final JsonReader r;
+ private List<Currency> commodities;
+ private List<Profile> profiles;
+ private List<TemplateWithAccounts> templates;
+ private String currentProfile;
+ public RawConfigReader(InputStream inputStream) {
+ r = new JsonReader(new BufferedReader(new InputStreamReader(inputStream)));
+ }
+ public List<Currency> getCommodities() {
+ return commodities;
+ }
+ public List<Profile> getProfiles() {
+ return profiles;
+ }
+ public List<TemplateWithAccounts> getTemplates() {
+ return templates;
+ }
+ public String getCurrentProfile() {
+ return currentProfile;
+ }
+ public void readConfig() throws IOException {
+ commodities = null;
+ profiles = null;
+ templates = null;
+ currentProfile = null;
+ r.beginObject();
+ while (r.hasNext()) {
+ String item = r.nextName();
+ if (r.peek() == JsonToken.NULL) {
+ r.nextNull();
+ continue;
+ }
+ switch (item) {
+ case Keys.COMMODITIES:
+ commodities = readCommodities();
+ break;
+ case Keys.PROFILES:
+ profiles = readProfiles();
+ break;
+ case Keys.TEMPLATES:
+ templates = readTemplates();
+ break;
+ case Keys.CURRENT_PROFILE:
+ currentProfile = r.nextString();
+ break;
+ default:
+ throw new RuntimeException("unexpected top-level item " + item);
+ }
+ }
+ r.endObject();
+ }
+ private TemplateAccount readTemplateAccount() throws IOException {
+ r.beginObject();
+ TemplateAccount result = new TemplateAccount(0L, 0L, 0L);
+ while (r.peek() != JsonToken.END_OBJECT) {
+ String item = r.nextName();
+ if (r.peek() == JsonToken.NULL) {
+ r.nextNull();
+ continue;
+ }
+ switch (item) {
+ case Keys.NAME:
+ result.setAccountName(r.nextString());
+ break;
+ case Keys.NAME_GROUP:
+ result.setAccountNameMatchGroup(r.nextInt());
+ break;
+ case Keys.COMMENT:
+ result.setAccountComment(r.nextString());
+ break;
+ case Keys.COMMENT_GROUP:
+ result.setAccountCommentMatchGroup(r.nextInt());
+ break;
+ case Keys.AMOUNT:
+ result.setAmount((float) r.nextDouble());
+ break;
+ case Keys.AMOUNT_GROUP:
+ result.setAmountMatchGroup(r.nextInt());
+ break;
+ case Keys.NEGATE_AMOUNT:
+ result.setNegateAmount(r.nextBoolean());
+ break;
+ case Keys.CURRENCY:
+ result.setCurrency(r.nextLong());
+ break;
+ case Keys.CURRENCY_GROUP:
+ result.setCurrencyMatchGroup(r.nextInt());
+ break;
+
+ default:
+ throw new IllegalStateException("Unexpected template account item: " + item);
+ }
+ }
+ r.endObject();
+
+ return result;
+ }
+ private TemplateWithAccounts readTemplate(JsonReader r) throws IOException {
+ r.beginObject();
+ String name = null;
+ TemplateHeader t = new TemplateHeader(0L, "", "");
+ List<TemplateAccount> accounts = new ArrayList<>();
+
+ while (r.peek() != JsonToken.END_OBJECT) {
+ String item = r.nextName();
+ if (r.peek() == JsonToken.NULL) {
+ r.nextNull();
+ continue;
+ }
+ switch (item) {
+ case Keys.UUID:
+ t.setUuid(r.nextString());
+ break;
+ case Keys.NAME:
+ t.setName(r.nextString());
+ break;
+ case Keys.REGEX:
+ t.setRegularExpression(r.nextString());
+ break;
+ case Keys.TEST_TEXT:
+ t.setTestText(r.nextString());
+ break;
+ case Keys.DATE_YEAR:
+ t.setDateYear(r.nextInt());
+ break;
+ case Keys.DATE_YEAR_GROUP:
+ t.setDateYearMatchGroup(r.nextInt());
+ break;
+ case Keys.DATE_MONTH:
+ t.setDateMonth(r.nextInt());
+ break;
+ case Keys.DATE_MONTH_GROUP:
+ t.setDateMonthMatchGroup(r.nextInt());
+ break;
+ case Keys.DATE_DAY:
+ t.setDateDay(r.nextInt());
+ break;
+ case Keys.DATE_DAY_GROUP:
+ t.setDateDayMatchGroup(r.nextInt());
+ break;
+ case Keys.TRANSACTION:
+ t.setTransactionDescription(r.nextString());
+ break;
+ case Keys.TRANSACTION_GROUP:
+ t.setTransactionDescriptionMatchGroup(r.nextInt());
+ break;
+ case Keys.COMMENT:
+ t.setTransactionComment(r.nextString());
+ break;
+ case Keys.COMMENT_GROUP:
+ t.setTransactionCommentMatchGroup(r.nextInt());
+ break;
+ case Keys.IS_FALLBACK:
+ t.setFallback(r.nextBoolean());
+ break;
+ case Keys.ACCOUNTS:
+ r.beginArray();
+ while (r.peek() == JsonToken.BEGIN_OBJECT) {
+ accounts.add(readTemplateAccount());
+ }
+ r.endArray();
+ break;
+ default:
+ throw new RuntimeException("Unknown template header item: " + item);
+ }
+ }
+ r.endObject();
+
+ TemplateWithAccounts result = new TemplateWithAccounts();
+ result.header = t;
+ result.accounts = accounts;
+ return result;
+ }
+ private List<TemplateWithAccounts> readTemplates() throws IOException {
+ List<TemplateWithAccounts> list = new ArrayList<>();
+
+ r.beginArray();
+ while (r.peek() == JsonToken.BEGIN_OBJECT) {
+ list.add(readTemplate(r));
+ }
+ r.endArray();
+
+ return list;
+ }
+ private List<Currency> readCommodities() throws IOException {
+ List<Currency> list = new ArrayList<>();
+
+ r.beginArray();
+ while (r.peek() == JsonToken.BEGIN_OBJECT) {
+ Currency c = new Currency();
+
+ r.beginObject();
+ while (r.peek() != JsonToken.END_OBJECT) {
+ final String item = r.nextName();
+ if (r.peek() == JsonToken.NULL) {
+ r.nextNull();
+ continue;
+ }
+ switch (item) {
+ case Keys.NAME:
+ c.setName(r.nextString());
+ break;
+ case Keys.POSITION:
+ c.setPosition(r.nextString());
+ break;
+ case Keys.HAS_GAP:
+ c.setHasGap(r.nextBoolean());
+ break;
+ default:
+ throw new RuntimeException("Unknown commodity key: " + item);
+ }
+ }
+ r.endObject();
+
+ if (c.getName()
+ .isEmpty())
+ throw new RuntimeException("Missing commodity name");
+
+ list.add(c);
+ }
+ r.endArray();
+
+ return list;
+ }
+ private List<Profile> readProfiles() throws IOException {
+ List<Profile> list = new ArrayList<>();
+ r.beginArray();
+ while (r.peek() == JsonToken.BEGIN_OBJECT) {
+ Profile p = new Profile();
+ r.beginObject();
+ while (r.peek() != JsonToken.END_OBJECT) {
+ String item = r.nextName();
+ if (r.peek() == JsonToken.NULL) {
+ r.nextNull();
+ continue;
+ }
+
+ switch (item) {
+ case Keys.UUID:
+ p.setUuid(r.nextString());
+ break;
+ case Keys.NAME:
+ p.setName(r.nextString());
+ break;
+ case Keys.URL:
+ p.setUrl(r.nextString());
+ break;
+ case Keys.USE_AUTH:
+ p.setUseAuthentication(r.nextBoolean());
+ break;
+ case Keys.AUTH_USER:
+ p.setAuthUser(r.nextString());
+ break;
+ case Keys.AUTH_PASS:
+ p.setAuthPassword(r.nextString());
+ break;
+ case Keys.API_VER:
+ p.setApiVersion(r.nextInt());
+ break;
+ case Keys.CAN_POST:
+ p.setPermitPosting(r.nextBoolean());
+ break;
+ case Keys.DEFAULT_COMMODITY:
+ p.setDefaultCommodity(r.nextString());
+ break;
+ case Keys.SHOW_COMMODITY:
+ p.setShowCommodityByDefault(r.nextBoolean());
+ break;
+ case Keys.SHOW_COMMENTS:
+ p.setShowCommentsByDefault(r.nextBoolean());
+ break;
+ case Keys.FUTURE_DATES:
+ p.setFutureDates(r.nextInt());
+ break;
+ case Keys.PREF_ACCOUNT:
+ p.setPreferredAccountsFilter(r.nextString());
+ break;
+ case Keys.COLOUR:
+ p.setTheme(r.nextInt());
+ break;
+
+
+ default:
+ throw new IllegalStateException("Unexpected profile item: " + item);
+ }
+ }
+ r.endObject();
+
+ list.add(p);
+ }
+ r.endArray();
+
+ return list;
+ }
+ public void restoreAll() {
+ restoreCommodities();
+ restoreProfiles();
+ restoreTemplates();
+ }
+ private void restoreTemplates() {
+ if (templates == null)
+ return;
+
+ TemplateHeaderDAO dao = DB.get()
+ .getTemplateDAO();
+
+ for (TemplateWithAccounts t : templates) {
+ if (dao.getTemplateWithAccountsByUuidSync(t.header.getUuid()) == null)
+ dao.insertSync(t);
+ }
+ }
+ private void restoreProfiles() {
+ if (profiles == null)
+ return;
+
+ ProfileDAO dao = DB.get()
+ .getProfileDAO();
+
+ for (Profile p : profiles) {
+ if (dao.getByUuidSync(p.getUuid()) == null)
+ dao.insert(p);
+ }
+ }
+ private void restoreCommodities() {
+ if (commodities == null)
+ return;
+
+ CurrencyDAO dao = DB.get()
+ .getCurrencyDAO();
+
+ for (Currency c : commodities) {
+ if (dao.getByNameSync(c.getName()) == null)
+ dao.insert(c);
+ }
+ }
+}
--- /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.backup;
+
+import android.util.JsonWriter;
+
+import net.ktnx.mobileledger.backup.ConfigIO.Keys;
+import net.ktnx.mobileledger.db.Currency;
+import net.ktnx.mobileledger.db.DB;
+import net.ktnx.mobileledger.db.Profile;
+import net.ktnx.mobileledger.db.TemplateAccount;
+import net.ktnx.mobileledger.db.TemplateWithAccounts;
+import net.ktnx.mobileledger.json.API;
+import net.ktnx.mobileledger.model.Data;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.util.List;
+
+public class RawConfigWriter {
+ private final JsonWriter w;
+ public RawConfigWriter(OutputStream outputStream) {
+ w = new JsonWriter(new BufferedWriter(new OutputStreamWriter(outputStream)));
+ w.setIndent(" ");
+ }
+ public void writeConfig() throws IOException {
+ w.beginObject();
+ writeCommodities();
+ writeProfiles();
+ writeCurrentProfile();
+ writeConfigTemplates();
+ w.endObject();
+ w.flush();
+ }
+ private void writeKey(String key, String value) throws IOException {
+ if (value != null)
+ w.name(key)
+ .value(value);
+ }
+ private void writeKey(String key, Integer value) throws IOException {
+ if (value != null)
+ w.name(key)
+ .value(value);
+ }
+ private void writeKey(String key, Long value) throws IOException {
+ if (value != null)
+ w.name(key)
+ .value(value);
+ }
+ private void writeKey(String key, Float value) throws IOException {
+ if (value != null)
+ w.name(key)
+ .value(value);
+ }
+ private void writeKey(String key, Boolean value) throws IOException {
+ if (value != null)
+ w.name(key)
+ .value(value);
+ }
+ private void writeConfigTemplates() throws IOException {
+ List<TemplateWithAccounts> templates = DB.get()
+ .getTemplateDAO()
+ .getAllTemplatesWithAccountsSync();
+
+ if (templates.isEmpty())
+ return;
+
+ w.name("templates")
+ .beginArray();
+ for (TemplateWithAccounts t : templates) {
+ w.beginObject();
+
+ w.name(Keys.UUID)
+ .value(t.header.getUuid());
+ w.name(Keys.NAME)
+ .value(t.header.getName());
+ w.name(Keys.REGEX)
+ .value(t.header.getRegularExpression());
+ writeKey(Keys.TEST_TEXT, t.header.getTestText());
+ writeKey(ConfigIO.Keys.DATE_YEAR, t.header.getDateYear());
+ writeKey(Keys.DATE_YEAR_GROUP, t.header.getDateYearMatchGroup());
+ writeKey(Keys.DATE_MONTH, t.header.getDateMonth());
+ writeKey(Keys.DATE_MONTH_GROUP, t.header.getDateMonthMatchGroup());
+ writeKey(Keys.DATE_DAY, t.header.getDateDay());
+ writeKey(Keys.DATE_DAY_GROUP, t.header.getDateDayMatchGroup());
+ writeKey(Keys.TRANSACTION, t.header.getTransactionDescription());
+ writeKey(Keys.TRANSACTION_GROUP, t.header.getTransactionDescriptionMatchGroup());
+ writeKey(Keys.COMMENT, t.header.getTransactionComment());
+ writeKey(Keys.COMMENT_GROUP, t.header.getTransactionCommentMatchGroup());
+ w.name(Keys.IS_FALLBACK)
+ .value(t.header.isFallback());
+ if (t.accounts.size() > 0) {
+ w.name(Keys.ACCOUNTS)
+ .beginArray();
+ for (TemplateAccount a : t.accounts) {
+ w.beginObject();
+
+ writeKey(Keys.NAME, a.getAccountName());
+ writeKey(Keys.NAME_GROUP, a.getAccountNameMatchGroup());
+ writeKey(Keys.COMMENT, a.getAccountComment());
+ writeKey(Keys.COMMENT_GROUP, a.getAccountCommentMatchGroup());
+ writeKey(Keys.AMOUNT, a.getAmount());
+ writeKey(Keys.AMOUNT_GROUP, a.getAmountMatchGroup());
+ writeKey(Keys.NEGATE_AMOUNT, a.getNegateAmount());
+ writeKey(Keys.CURRENCY, a.getCurrency());
+ writeKey(Keys.CURRENCY_GROUP, a.getCurrencyMatchGroup());
+
+ w.endObject();
+ }
+ w.endArray();
+ }
+
+ w.endObject();
+ }
+ w.endArray();
+ }
+ private void writeCommodities() throws IOException {
+ List<Currency> list = DB.get()
+ .getCurrencyDAO()
+ .getAllSync();
+ if (list.isEmpty())
+ return;
+ w.name(Keys.COMMODITIES)
+ .beginArray();
+ for (Currency c : list) {
+ w.beginObject();
+ writeKey(Keys.NAME, c.getName());
+ writeKey(Keys.POSITION, c.getPosition());
+ writeKey(Keys.HAS_GAP, c.getHasGap());
+ w.endObject();
+ }
+ w.endArray();
+ }
+ private void writeProfiles() throws IOException {
+ List<Profile> profiles = DB.get()
+ .getProfileDAO()
+ .getAllOrderedSync();
+
+ if (profiles.isEmpty())
+ return;
+
+ w.name(Keys.PROFILES)
+ .beginArray();
+ for (Profile p : profiles) {
+ w.beginObject();
+
+ w.name(Keys.NAME)
+ .value(p.getName());
+ w.name(Keys.UUID)
+ .value(p.getUuid());
+ w.name(Keys.URL)
+ .value(p.getUrl());
+ w.name(Keys.USE_AUTH)
+ .value(p.useAuthentication());
+ if (p.useAuthentication()) {
+ w.name(Keys.AUTH_USER)
+ .value(p.getAuthUser());
+ w.name(Keys.AUTH_PASS)
+ .value(p.getAuthPassword());
+ }
+ if (p.getApiVersion() != API.auto.toInt())
+ w.name(Keys.API_VER)
+ .value(p.getApiVersion());
+ w.name(Keys.CAN_POST)
+ .value(p.permitPosting());
+ if (p.permitPosting()) {
+ String defaultCommodity = p.getDefaultCommodity();
+ if (!defaultCommodity.isEmpty())
+ w.name(Keys.DEFAULT_COMMODITY)
+ .value(defaultCommodity);
+ w.name(Keys.SHOW_COMMODITY)
+ .value(p.getShowCommodityByDefault());
+ w.name(Keys.SHOW_COMMENTS)
+ .value(p.getShowCommentsByDefault());
+ w.name(Keys.FUTURE_DATES)
+ .value(p.getFutureDates());
+ w.name(Keys.PREF_ACCOUNT)
+ .value(p.getPreferredAccountsFilter());
+ }
+ w.name(Keys.COLOUR)
+ .value(p.getTheme());
+
+ w.endObject();
+ }
+ w.endArray();
+ }
+ private void writeCurrentProfile() throws IOException {
+ Profile currentProfile = Data.getProfile();
+ if (currentProfile == null)
+ return;
+
+ w.name(Keys.CURRENT_PROFILE)
+ .value(currentProfile.getUuid());
+ }
+}
import android.app.Activity;
import android.app.AlertDialog;
+import android.app.backup.BackupManager;
import android.graphics.Typeface;
import android.os.Bundle;
import android.text.Editable;
dao.insertLast(profile, null);
}
+ BackupManager.dataChanged(BuildConfig.APPLICATION_ID);
+
Activity activity = getActivity();
if (activity != null)
activity.finish();