From: Damyan Ivanov Date: Sun, 12 Sep 2021 11:30:56 +0000 (+0300) Subject: provide cloud backup functionality X-Git-Tag: v0.20.2~4 X-Git-Url: https://git.ktnx.net/?p=mobile-ledger.git;a=commitdiff_plain;h=41d543229f3231469247e10ec6c197920c0e8bc4 provide cloud backup functionality --- diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a8d20d90..e3ec6ee1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -30,6 +30,7 @@ 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"> . - */ - -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); - } -} diff --git a/app/src/main/java/net/ktnx/mobileledger/async/ConfigReader.java b/app/src/main/java/net/ktnx/mobileledger/async/ConfigReader.java deleted file mode 100644 index a894f92f..00000000 --- a/app/src/main/java/net/ktnx/mobileledger/async/ConfigReader.java +++ /dev/null @@ -1,398 +0,0 @@ -/* - * 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 . - */ - -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 commodities = null; - List profiles = null; - List 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 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 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 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 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 readTemplates(JsonReader r) throws IOException { - List list = new ArrayList<>(); - - r.beginArray(); - while (r.peek() == JsonToken.BEGIN_OBJECT) { - list.add(readTemplate(r)); - } - r.endArray(); - - return list; - } - private List readCommodities(JsonReader r) throws IOException { - List 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 readProfiles(JsonReader r) throws IOException { - List 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(); - } -} diff --git a/app/src/main/java/net/ktnx/mobileledger/async/ConfigWriter.java b/app/src/main/java/net/ktnx/mobileledger/async/ConfigWriter.java deleted file mode 100644 index f72a2913..00000000 --- a/app/src/main/java/net/ktnx/mobileledger/async/ConfigWriter.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * 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 . - */ - -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 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 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 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(); - } -} diff --git a/app/src/main/java/net/ktnx/mobileledger/backup/ConfigIO.java b/app/src/main/java/net/ktnx/mobileledger/backup/ConfigIO.java new file mode 100644 index 00000000..ec7a9499 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/backup/ConfigIO.java @@ -0,0 +1,111 @@ +/* + * 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 . + */ + +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); + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/backup/ConfigReader.java b/app/src/main/java/net/ktnx/mobileledger/backup/ConfigReader.java new file mode 100644 index 00000000..7959da51 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/backup/ConfigReader.java @@ -0,0 +1,76 @@ +/* + * 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 . + */ + +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(); + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/backup/ConfigWriter.java b/app/src/main/java/net/ktnx/mobileledger/backup/ConfigWriter.java new file mode 100644 index 00000000..b1a2814e --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/backup/ConfigWriter.java @@ -0,0 +1,56 @@ +/* + * 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 . + */ + +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(); + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/backup/MobileLedgerBackupAgent.java b/app/src/main/java/net/ktnx/mobileledger/backup/MobileLedgerBackupAgent.java new file mode 100644 index 00000000..ea09e514 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/backup/MobileLedgerBackupAgent.java @@ -0,0 +1,74 @@ +/* + * 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 . + */ + +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"); + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/backup/RawConfigReader.java b/app/src/main/java/net/ktnx/mobileledger/backup/RawConfigReader.java new file mode 100644 index 00000000..45a593d7 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/backup/RawConfigReader.java @@ -0,0 +1,377 @@ +/* + * 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 . + */ + +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 commodities; + private List profiles; + private List templates; + private String currentProfile; + public RawConfigReader(InputStream inputStream) { + r = new JsonReader(new BufferedReader(new InputStreamReader(inputStream))); + } + public List getCommodities() { + return commodities; + } + public List getProfiles() { + return profiles; + } + public List 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 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 readTemplates() throws IOException { + List list = new ArrayList<>(); + + r.beginArray(); + while (r.peek() == JsonToken.BEGIN_OBJECT) { + list.add(readTemplate(r)); + } + r.endArray(); + + return list; + } + private List readCommodities() throws IOException { + List 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 readProfiles() throws IOException { + List 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); + } + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/backup/RawConfigWriter.java b/app/src/main/java/net/ktnx/mobileledger/backup/RawConfigWriter.java new file mode 100644 index 00000000..751a74a0 --- /dev/null +++ b/app/src/main/java/net/ktnx/mobileledger/backup/RawConfigWriter.java @@ -0,0 +1,212 @@ +/* + * 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 . + */ + +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 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 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 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()); + } +} diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailFragment.java b/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailFragment.java index fce5967f..ab0d07d9 100644 --- a/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailFragment.java +++ b/app/src/main/java/net/ktnx/mobileledger/ui/profiles/ProfileDetailFragment.java @@ -19,6 +19,7 @@ package net.ktnx.mobileledger.ui.profiles; 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; @@ -408,6 +409,8 @@ public class ProfileDetailFragment extends Fragment { dao.insertLast(profile, null); } + BackupManager.dataChanged(BuildConfig.APPLICATION_ID); + Activity activity = getActivity(); if (activity != null) activity.finish();