diff --git a/app/src/main/java/protect/card_locker/importexport/FidmeImporter.java b/app/src/main/java/protect/card_locker/importexport/FidmeImporter.java index b7b64560b..9275ff203 100644 --- a/app/src/main/java/protect/card_locker/importexport/FidmeImporter.java +++ b/app/src/main/java/protect/card_locker/importexport/FidmeImporter.java @@ -19,10 +19,13 @@ import java.io.StringReader; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.text.ParseException; +import java.util.ArrayList; +import java.util.List; import protect.card_locker.CatimaBarcode; import protect.card_locker.DBHelper; import protect.card_locker.FormatException; +import protect.card_locker.LoyaltyCard; import protect.card_locker.Utils; /** @@ -33,6 +36,14 @@ import protect.card_locker.Utils; * A header is expected for the each table showing the names of the columns. */ public class FidmeImporter implements Importer { + public static class ImportedData { + public final List cards; + + ImportedData(final List cards) { + this.cards = cards; + } + } + public void importData(Context context, SQLiteDatabase database, File inputFile, char[] password) throws IOException, FormatException, JSONException, ParseException { InputStream input = new FileInputStream(inputFile); // We actually retrieve a .zip file @@ -57,10 +68,14 @@ public class FidmeImporter implements Importer { } final CSVParser fidmeParser = new CSVParser(new StringReader(loyaltyCards.toString()), CSVFormat.RFC4180.builder().setDelimiter(';').setHeader().build()); + ImportedData importedData = new ImportedData(new ArrayList<>()); try { for (CSVRecord record : fidmeParser) { - importLoyaltyCard(context, database, record); + LoyaltyCard card = importLoyaltyCard(context, record); + if (card != null) { + importedData.cards.add(card); + } if (Thread.currentThread().isInterrupted()) { throw new InterruptedException(); @@ -74,14 +89,15 @@ public class FidmeImporter implements Importer { zipInputStream.close(); input.close(); + + saveAndDeduplicate(database, importedData); } /** * Import a single loyalty card into the database using the given * session. */ - private void importLoyaltyCard(Context context, SQLiteDatabase database, CSVRecord record) - throws FormatException { + private LoyaltyCard importLoyaltyCard(Context context, CSVRecord record) throws FormatException { // A loyalty card export from Fidme contains the following fields: // Retailer (store name) // Program (program name) @@ -117,7 +133,7 @@ public class FidmeImporter implements Importer { // Fidme deletes the card id if a card is expired // Because Catima considers the card id a required field, we ignore these expired cards // https://github.com/CatimaLoyalty/Android/issues/1005 - return; + return null; } // Sadly, Fidme exports don't contain the card type @@ -132,6 +148,17 @@ public class FidmeImporter implements Importer { // TODO: Front and back image - DBHelper.insertLoyaltyCard(database, store, note, null, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, headerColor, starStatus, null,archiveStatus); + // use -1 for the ID, it will be ignored when inserting the card into the DB + return new LoyaltyCard(-1, store, note, null, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, headerColor, starStatus, Utils.getUnixTime(), DBHelper.DEFAULT_ZOOM_LEVEL, archiveStatus); + } + + public void saveAndDeduplicate(SQLiteDatabase database, final ImportedData data) { + // This format does not have IDs that can cause conflicts + // Proper deduplication for all formats will be implemented later + for (LoyaltyCard card : data.cards) { + // Do not use card.id which is set to -1 + DBHelper.insertLoyaltyCard(database, card.store, card.note, card.validFrom, card.expiry, card.balance, card.balanceType, + card.cardId, card.barcodeId, card.barcodeType, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus); + } } } \ No newline at end of file diff --git a/app/src/main/java/protect/card_locker/importexport/StocardImporter.java b/app/src/main/java/protect/card_locker/importexport/StocardImporter.java index df0d79451..280aa647a 100644 --- a/app/src/main/java/protect/card_locker/importexport/StocardImporter.java +++ b/app/src/main/java/protect/card_locker/importexport/StocardImporter.java @@ -24,12 +24,16 @@ import java.io.InputStreamReader; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.text.ParseException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; +import java.util.Map; import protect.card_locker.CatimaBarcode; import protect.card_locker.DBHelper; import protect.card_locker.FormatException; import protect.card_locker.ImageLocationType; +import protect.card_locker.LoyaltyCard; import protect.card_locker.R; import protect.card_locker.Utils; import protect.card_locker.ZipUtils; @@ -42,11 +46,30 @@ import protect.card_locker.ZipUtils; * A header is expected for the each table showing the names of the columns. */ public class StocardImporter implements Importer { + public static class ZIPData { + public final HashMap> loyaltyCardHashMap; + public final HashMap> providers; + + ZIPData(final HashMap> loyaltyCardHashMap, final HashMap> providers) { + this.loyaltyCardHashMap = loyaltyCardHashMap; + this.providers = providers; + } + } + + public static class ImportedData { + public final List cards; + public final Map> images; + + ImportedData(final List cards, final Map> images) { + this.cards = cards; + this.images = images; + } + } + private static final String TAG = "Catima"; public void importData(Context context, SQLiteDatabase database, File inputFile, char[] password) throws IOException, FormatException, JSONException, ParseException { - HashMap> loyaltyCardHashMap = new HashMap<>(); - HashMap> providers = new HashMap<>(); + ZIPData zipData = new ZIPData(new HashMap<>(), new HashMap<>()); final CSVParser parser = new CSVParser(new InputStreamReader(context.getResources().openRawResource(R.raw.stocard_stores), StandardCharsets.UTF_8), CSVFormat.RFC4180.builder().setHeader().build()); @@ -56,7 +79,7 @@ public class StocardImporter implements Importer { recordData.put("name", record.get("name")); recordData.put("barcodeFormat", record.get("barcodeFormat")); - providers.put(record.get("_id"), recordData); + zipData.providers.put(record.get("_id"), recordData); } parser.close(); @@ -66,6 +89,21 @@ public class StocardImporter implements Importer { InputStream input = new FileInputStream(inputFile); ZipInputStream zipInputStream = new ZipInputStream(input, password); + zipData = importZIP(zipInputStream, zipData); + zipInputStream.close(); + input.close(); + + if (zipData.loyaltyCardHashMap.keySet().size() == 0) { + throw new FormatException("Couldn't find any loyalty cards in this Stocard export."); + } + + ImportedData importedData = importLoyaltyCardHashMap(context, zipData); + saveAndDeduplicate(context, database, importedData); + } + + public ZIPData importZIP(ZipInputStream zipInputStream, final ZIPData zipData) throws IOException, JSONException { + HashMap> loyaltyCardHashMap = zipData.loyaltyCardHashMap; + HashMap> providers = zipData.providers; String[] providersFileName = null; String[] customProvidersBaseName = null; @@ -197,11 +235,14 @@ public class StocardImporter implements Importer { } } - if (loyaltyCardHashMap.keySet().size() == 0) { - throw new FormatException("Couldn't find any loyalty cards in this Stocard export."); - } + return new ZIPData(loyaltyCardHashMap, providers); + } - for (HashMap loyaltyCardData : loyaltyCardHashMap.values()) { + public ImportedData importLoyaltyCardHashMap(Context context, final ZIPData zipData) { + ImportedData importedData = new ImportedData(new ArrayList<>(), new HashMap<>()); + int tempID = 0; + + for (Map loyaltyCardData : zipData.loyaltyCardHashMap.values()) { String providerId = (String) loyaltyCardData.get("_providerId"); if (providerId == null) { @@ -209,7 +250,7 @@ public class StocardImporter implements Importer { continue; } - HashMap providerData = providers.get(providerId); + HashMap providerData = zipData.providers.get(providerId); String store = providerData != null ? providerData.get("name").toString() : providerId; String note = (String) Utils.mapGetOrDefault(loyaltyCardData, "note", ""); @@ -233,22 +274,39 @@ public class StocardImporter implements Importer { headerColor = Utils.getHeaderColorFromImage(cardIcon, headerColor); } - long loyaltyCardInternalId = DBHelper.insertLoyaltyCard(database, store, note, null, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, headerColor, 0, null,0); + LoyaltyCard card = new LoyaltyCard(tempID, store, note, null, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, headerColor, 0, Utils.getUnixTime(), DBHelper.DEFAULT_ZOOM_LEVEL, 0); + importedData.cards.add(card); + + Map images = new HashMap<>(); if (cardIcon != null) { - Utils.saveCardImage(context, cardIcon, (int) loyaltyCardInternalId, ImageLocationType.icon); + images.put(ImageLocationType.icon, cardIcon); } - if (loyaltyCardData.containsKey("frontImage")) { - Utils.saveCardImage(context, (Bitmap) loyaltyCardData.get("frontImage"), (int) loyaltyCardInternalId, ImageLocationType.front); + images.put(ImageLocationType.front, (Bitmap) loyaltyCardData.get("frontImage")); } if (loyaltyCardData.containsKey("backImage")) { - Utils.saveCardImage(context, (Bitmap) loyaltyCardData.get("backImage"), (int) loyaltyCardInternalId, ImageLocationType.back); + images.put(ImageLocationType.back, (Bitmap) loyaltyCardData.get("backImage")); } + + importedData.images.put(tempID, images); + tempID++; } - zipInputStream.close(); - input.close(); + return importedData; + } + + public void saveAndDeduplicate(Context context, SQLiteDatabase database, final ImportedData data) throws IOException { + // This format does not have IDs that can cause conflicts + // Proper deduplication for all formats will be implemented later + for (LoyaltyCard card : data.cards) { + // card.id is temporary and only used to index the images Map + long id = DBHelper.insertLoyaltyCard(database, card.store, card.note, card.validFrom, card.expiry, card.balance, card.balanceType, + card.cardId, card.barcodeId, card.barcodeType, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus); + for (Map.Entry entry : data.images.get(card.id).entrySet()) { + Utils.saveCardImage(context, entry.getValue(), (int) id, entry.getKey()); + } + } } private boolean startsWith(String[] full, String[] start, int minExtraLength) { diff --git a/app/src/main/java/protect/card_locker/importexport/VoucherVaultImporter.java b/app/src/main/java/protect/card_locker/importexport/VoucherVaultImporter.java index 08454626b..b45d81efe 100644 --- a/app/src/main/java/protect/card_locker/importexport/VoucherVaultImporter.java +++ b/app/src/main/java/protect/card_locker/importexport/VoucherVaultImporter.java @@ -21,13 +21,16 @@ import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Currency; import java.util.Date; +import java.util.List; import java.util.TimeZone; import protect.card_locker.CatimaBarcode; import protect.card_locker.DBHelper; import protect.card_locker.FormatException; +import protect.card_locker.LoyaltyCard; import protect.card_locker.Utils; /** @@ -38,6 +41,14 @@ import protect.card_locker.Utils; * A header is expected for the each table showing the names of the columns. */ public class VoucherVaultImporter implements Importer { + public static class ImportedData { + public final List cards; + + ImportedData(final List cards) { + this.cards = cards; + } + } + public void importData(Context context, SQLiteDatabase database, File inputFile, char[] password) throws IOException, FormatException, JSONException, ParseException { InputStream input = new FileInputStream(inputFile); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)); @@ -49,6 +60,16 @@ public class VoucherVaultImporter implements Importer { } JSONArray jsonArray = new JSONArray(sb.toString()); + bufferedReader.close(); + input.close(); + + ImportedData importedData = importJSON(jsonArray); + saveAndDeduplicate(database, importedData); + } + + public ImportedData importJSON(JSONArray jsonArray) throws FormatException, JSONException, ParseException { + ImportedData importedData = new ImportedData(new ArrayList<>()); + // See https://github.com/tim-smart/vouchervault/issues/4#issuecomment-788226503 for more info for (int i = 0; i < jsonArray.length(); i++) { JSONObject jsonCard = jsonArray.getJSONObject(i); @@ -129,10 +150,20 @@ public class VoucherVaultImporter implements Importer { throw new FormatException("Unknown colour type found: " + colorFromJSON); } - DBHelper.insertLoyaltyCard(database, store, "", null, expiry, balance, balanceType, cardId, null, barcodeType, headerColor, 0, Utils.getUnixTime(),0); + // use -1 for the ID, it will be ignored when inserting the card into the DB + importedData.cards.add(new LoyaltyCard(-1, store, "", null, expiry, balance, balanceType, cardId, null, barcodeType, headerColor, 0, Utils.getUnixTime(), DBHelper.DEFAULT_ZOOM_LEVEL, 0)); } - bufferedReader.close(); - input.close(); + return importedData; + } + + public void saveAndDeduplicate(SQLiteDatabase database, final ImportedData data) { + // This format does not have IDs that can cause conflicts + // Proper deduplication for all formats will be implemented later + for (LoyaltyCard card : data.cards) { + // Do not use card.id which is set to -1 + DBHelper.insertLoyaltyCard(database, card.store, card.note, card.validFrom, card.expiry, card.balance, card.balanceType, + card.cardId, card.barcodeId, card.barcodeType, card.headerColor, card.starStatus, card.lastUsed, card.archiveStatus); + } } } \ No newline at end of file