diff --git a/app/build.gradle b/app/build.gradle index b6b1fc1a1..28bcb3456 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -84,6 +84,7 @@ dependencies { implementation 'com.jaredrummler:colorpicker:1.1.0' implementation 'com.google.guava:guava:30.1.1-jre' implementation 'com.github.invissvenska:NumberPickerPreference:1.0.2' + implementation 'net.lingala.zip4j:zip4j:2.8.0' // SpotBugs implementation 'io.wcm.tooling.spotbugs:io.wcm.tooling.spotbugs.annotations:1.0.0' diff --git a/app/src/main/java/protect/card_locker/ImportExportActivity.java b/app/src/main/java/protect/card_locker/ImportExportActivity.java index 2445605a9..27fa2baa3 100644 --- a/app/src/main/java/protect/card_locker/ImportExportActivity.java +++ b/app/src/main/java/protect/card_locker/ImportExportActivity.java @@ -27,6 +27,8 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; +import protect.card_locker.importexport.DataFormat; +import protect.card_locker.importexport.ImportExportResult; public class ImportExportActivity extends AppCompatActivity { @@ -162,19 +164,19 @@ public class ImportExportActivity extends AppCompatActivity builder.show(); } - private void startImport(final InputStream target, final Uri targetUri, final DataFormat dataFormat) + private void startImport(final InputStream target, final Uri targetUri, final DataFormat dataFormat, final char[] password) { ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() { @Override - public void onTaskComplete(boolean success) + public void onTaskComplete(ImportExportResult result) { - onImportComplete(success, targetUri); + onImportComplete(result, targetUri); } }; importExporter = new ImportExportTask(ImportExportActivity.this, - dataFormat, target, listener); + dataFormat, target, password, listener); importExporter.execute(); } @@ -183,9 +185,9 @@ public class ImportExportActivity extends AppCompatActivity ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() { @Override - public void onTaskComplete(boolean success) + public void onTaskComplete(ImportExportResult result) { - onExportComplete(success, targetUri); + onExportComplete(result, targetUri); } }; @@ -245,20 +247,23 @@ public class ImportExportActivity extends AppCompatActivity return super.onOptionsItemSelected(item); } - private void onImportComplete(boolean success, Uri path) + private void onImportComplete(ImportExportResult result, Uri path) { AlertDialog.Builder builder = new AlertDialog.Builder(this); - if(success) + int messageId; + + if(result == ImportExportResult.Success) { builder.setTitle(R.string.importSuccessfulTitle); + messageId = R.string.importSuccessful; } else { builder.setTitle(R.string.importFailedTitle); + messageId = R.string.importFailed; } - int messageId = success ? R.string.importSuccessful : R.string.importFailed; final String message = getResources().getString(messageId); builder.setMessage(message); @@ -274,20 +279,23 @@ public class ImportExportActivity extends AppCompatActivity builder.create().show(); } - private void onExportComplete(boolean success, final Uri path) + private void onExportComplete(ImportExportResult result, final Uri path) { AlertDialog.Builder builder = new AlertDialog.Builder(this); - if(success) + int messageId; + + if(result == ImportExportResult.Success) { builder.setTitle(R.string.exportSuccessfulTitle); + messageId = R.string.exportSuccessful; } else { builder.setTitle(R.string.exportFailedTitle); + messageId = R.string.exportFailed; } - int messageId = success ? R.string.exportSuccessful : R.string.exportFailed; final String message = getResources().getString(messageId); builder.setMessage(message); @@ -300,7 +308,7 @@ public class ImportExportActivity extends AppCompatActivity } }); - if(success) + if(result == ImportExportResult.Success) { final CharSequence sendLabel = ImportExportActivity.this.getResources().getText(R.string.sendLabel); @@ -389,7 +397,7 @@ public class ImportExportActivity extends AppCompatActivity Log.e(TAG, "Starting file import with: " + uri.toString()); - startImport(reader, uri, importDataFormat); + startImport(reader, uri, importDataFormat, null); } } catch(FileNotFoundException e) @@ -397,11 +405,11 @@ public class ImportExportActivity extends AppCompatActivity Log.e(TAG, "Failed to import/export file: " + uri.toString(), e); if (requestCode == CHOOSE_EXPORT_LOCATION) { - onExportComplete(false, uri); + onExportComplete(ImportExportResult.GenericFailure, uri); } else { - onImportComplete(false, uri); + onImportComplete(ImportExportResult.GenericFailure, uri); } } } diff --git a/app/src/main/java/protect/card_locker/ImportExportTask.java b/app/src/main/java/protect/card_locker/ImportExportTask.java index 96ad7bf39..a86057353 100644 --- a/app/src/main/java/protect/card_locker/ImportExportTask.java +++ b/app/src/main/java/protect/card_locker/ImportExportTask.java @@ -13,10 +13,12 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.Charset; +import protect.card_locker.importexport.DataFormat; +import protect.card_locker.importexport.ImportExportResult; import protect.card_locker.importexport.MultiFormatExporter; import protect.card_locker.importexport.MultiFormatImporter; -class ImportExportTask extends AsyncTask +class ImportExportTask extends AsyncTask { private static final String TAG = "Catima"; @@ -25,6 +27,7 @@ class ImportExportTask extends AsyncTask private DataFormat format; private OutputStream outputStream; private InputStream inputStream; + private char[] password; private TaskCompleteListener listener; private ProgressDialog progress; @@ -46,7 +49,7 @@ class ImportExportTask extends AsyncTask /** * Constructor which will setup a task for importing from the given InputStream. */ - ImportExportTask(Activity activity, DataFormat format, InputStream input, + ImportExportTask(Activity activity, DataFormat format, InputStream input, char[] password, TaskCompleteListener listener) { super(); @@ -54,24 +57,22 @@ class ImportExportTask extends AsyncTask this.doImport = true; this.format = format; this.inputStream = input; + this.password = password; this.listener = listener; } - private boolean performImport(Context context, InputStream stream, DBHelper db) + private ImportExportResult performImport(Context context, InputStream stream, DBHelper db, char[] password) { - boolean result = false; + ImportExportResult importResult = MultiFormatImporter.importData(context, db, stream, format, password); + Log.i(TAG, "Import result: " + importResult.name()); - result = MultiFormatImporter.importData(context, db, stream, format); - - Log.i(TAG, "Import result: " + result); - - return result; + return importResult; } - private boolean performExport(Context context, OutputStream stream, DBHelper db) + private ImportExportResult performExport(Context context, OutputStream stream, DBHelper db) { - boolean result = false; + ImportExportResult result = ImportExportResult.GenericFailure; try { @@ -106,14 +107,14 @@ class ImportExportTask extends AsyncTask progress.show(); } - protected Boolean doInBackground(Void... nothing) + protected ImportExportResult doInBackground(Void... nothing) { final DBHelper db = new DBHelper(activity); - boolean result; + ImportExportResult result; if(doImport) { - result = performImport(activity.getApplicationContext(), inputStream, db); + result = performImport(activity.getApplicationContext(), inputStream, db, password); } else { @@ -123,7 +124,7 @@ class ImportExportTask extends AsyncTask return result; } - protected void onPostExecute(Boolean result) + protected void onPostExecute(ImportExportResult result) { listener.onTaskComplete(result); @@ -138,7 +139,7 @@ class ImportExportTask extends AsyncTask } interface TaskCompleteListener { - void onTaskComplete(boolean success); + void onTaskComplete(ImportExportResult result); } } diff --git a/app/src/main/java/protect/card_locker/importexport/CsvImporter.java b/app/src/main/java/protect/card_locker/importexport/CsvImporter.java index a7c5876f7..292a21fec 100644 --- a/app/src/main/java/protect/card_locker/importexport/CsvImporter.java +++ b/app/src/main/java/protect/card_locker/importexport/CsvImporter.java @@ -37,7 +37,7 @@ import protect.card_locker.Utils; */ public class CsvImporter implements Importer { - public void importData(Context context, DBHelper db, InputStream input) throws IOException, FormatException, InterruptedException + public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, InterruptedException { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)); diff --git a/app/src/main/java/protect/card_locker/DataFormat.java b/app/src/main/java/protect/card_locker/importexport/DataFormat.java similarity index 57% rename from app/src/main/java/protect/card_locker/DataFormat.java rename to app/src/main/java/protect/card_locker/importexport/DataFormat.java index c030653a7..26e97e745 100644 --- a/app/src/main/java/protect/card_locker/DataFormat.java +++ b/app/src/main/java/protect/card_locker/importexport/DataFormat.java @@ -1,9 +1,10 @@ -package protect.card_locker; +package protect.card_locker.importexport; public enum DataFormat { Catima, Fidme, + Stocard, VoucherVault ; } 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 943f44d49..770ee1108 100644 --- a/app/src/main/java/protect/card_locker/importexport/FidmeImporter.java +++ b/app/src/main/java/protect/card_locker/importexport/FidmeImporter.java @@ -5,6 +5,9 @@ import android.database.sqlite.SQLiteDatabase; import com.google.zxing.BarcodeFormat; +import net.lingala.zip4j.io.inputstream.ZipInputStream; +import net.lingala.zip4j.model.LocalFileHeader; + import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVParser; import org.apache.commons.csv.CSVRecord; @@ -16,8 +19,6 @@ import java.io.StringReader; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.text.ParseException; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; import protect.card_locker.DBHelper; import protect.card_locker.FormatException; @@ -31,18 +32,18 @@ import protect.card_locker.FormatException; */ public class FidmeImporter implements Importer { - public void importData(Context context, DBHelper db, InputStream input) throws IOException, FormatException, JSONException, ParseException { + public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, JSONException, ParseException { // We actually retrieve a .zip file - ZipInputStream zipInputStream = new ZipInputStream(input); + ZipInputStream zipInputStream = new ZipInputStream(input, password); StringBuilder loyaltyCards = new StringBuilder(); byte[] buffer = new byte[1024]; int read = 0; - ZipEntry zipEntry; + LocalFileHeader localFileHeader; - while ((zipEntry = zipInputStream.getNextEntry()) != null) { - if (zipEntry.getName().equals("loyalty_programs.csv")) { + while ((localFileHeader = zipInputStream.getNextEntry()) != null) { + if (localFileHeader.getFileName().equals("loyalty_programs.csv")) { while ((read = zipInputStream.read(buffer, 0, 1024)) >= 0) { loyaltyCards.append(new String(buffer, 0, read, StandardCharsets.UTF_8)); } diff --git a/app/src/main/java/protect/card_locker/importexport/ImportExportResult.java b/app/src/main/java/protect/card_locker/importexport/ImportExportResult.java new file mode 100644 index 000000000..9a8d71c08 --- /dev/null +++ b/app/src/main/java/protect/card_locker/importexport/ImportExportResult.java @@ -0,0 +1,9 @@ +package protect.card_locker.importexport; + +public enum ImportExportResult +{ + Success, + GenericFailure, + BadPassword + ; +} diff --git a/app/src/main/java/protect/card_locker/importexport/Importer.java b/app/src/main/java/protect/card_locker/importexport/Importer.java index 9dec306e4..fe47d05fe 100644 --- a/app/src/main/java/protect/card_locker/importexport/Importer.java +++ b/app/src/main/java/protect/card_locker/importexport/Importer.java @@ -23,5 +23,5 @@ public interface Importer * @throws IOException * @throws FormatException */ - void importData(Context context, DBHelper db, InputStream input) throws IOException, FormatException, InterruptedException, JSONException, ParseException; + void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, InterruptedException, JSONException, ParseException; } diff --git a/app/src/main/java/protect/card_locker/importexport/MultiFormatExporter.java b/app/src/main/java/protect/card_locker/importexport/MultiFormatExporter.java index f61636cc6..f020370d2 100644 --- a/app/src/main/java/protect/card_locker/importexport/MultiFormatExporter.java +++ b/app/src/main/java/protect/card_locker/importexport/MultiFormatExporter.java @@ -7,7 +7,6 @@ import java.io.IOException; import java.io.OutputStreamWriter; import protect.card_locker.DBHelper; -import protect.card_locker.DataFormat; public class MultiFormatExporter { @@ -19,11 +18,11 @@ public class MultiFormatExporter * * The output stream is closed on success. * - * @return true if the database was successfully exported, - * false otherwise. If false, partial data may have been + * @return ImportExportResult.Success if the database was successfully exported, + * another ImportExportResult otherwise. If not Success, partial data may have been * written to the output stream, and it should be discarded. */ - public static boolean exportData(Context context, DBHelper db, OutputStreamWriter output, DataFormat format) + public static ImportExportResult exportData(Context context, DBHelper db, OutputStreamWriter output, DataFormat format) { Exporter exporter = null; @@ -42,7 +41,7 @@ public class MultiFormatExporter try { exporter.exportData(context, db, output); - return true; + return ImportExportResult.Success; } catch(IOException e) { @@ -53,12 +52,12 @@ public class MultiFormatExporter Log.e(TAG, "Failed to export data", e); } - return false; + return ImportExportResult.GenericFailure; } else { Log.e(TAG, "Unsupported data format exported: " + format.name()); - return false; + return ImportExportResult.GenericFailure; } } } diff --git a/app/src/main/java/protect/card_locker/importexport/MultiFormatImporter.java b/app/src/main/java/protect/card_locker/importexport/MultiFormatImporter.java index 34c620c74..835bb26df 100644 --- a/app/src/main/java/protect/card_locker/importexport/MultiFormatImporter.java +++ b/app/src/main/java/protect/card_locker/importexport/MultiFormatImporter.java @@ -3,6 +3,8 @@ package protect.card_locker.importexport; import android.content.Context; import android.util.Log; +import net.lingala.zip4j.exception.ZipException; + import org.json.JSONException; import java.io.IOException; @@ -10,7 +12,6 @@ import java.io.InputStream; import java.text.ParseException; import protect.card_locker.DBHelper; -import protect.card_locker.DataFormat; import protect.card_locker.FormatException; public class MultiFormatImporter @@ -24,11 +25,11 @@ public class MultiFormatImporter * The input stream is not closed, and doing so is the * responsibility of the caller. * - * @return true if the database was successfully imported, - * false otherwise. If false, no data was written to + * @return ImportExportResult.Success if the database was successfully imported, + * or another result otherwise. If no Success, no data was written to * the database. */ - public static boolean importData(Context context, DBHelper db, InputStream input, DataFormat format) + public static ImportExportResult importData(Context context, DBHelper db, InputStream input, DataFormat format, char[] password) { Importer importer = null; @@ -40,6 +41,9 @@ public class MultiFormatImporter case Fidme: importer = new FidmeImporter(); break; + case Stocard: + importer = new StocardImporter(); + break; case VoucherVault: importer = new VoucherVaultImporter(); break; @@ -49,8 +53,12 @@ public class MultiFormatImporter { try { - importer.importData(context, db, input); - return true; + importer.importData(context, db, input, password); + return ImportExportResult.Success; + } + catch(ZipException e) + { + return ImportExportResult.BadPassword; } catch(IOException | FormatException | InterruptedException | JSONException | ParseException e) { @@ -62,6 +70,7 @@ public class MultiFormatImporter { Log.e(TAG, "Unsupported data format imported: " + format.name()); } - return false; + + return ImportExportResult.GenericFailure; } } diff --git a/app/src/main/java/protect/card_locker/importexport/StocardImporter.java b/app/src/main/java/protect/card_locker/importexport/StocardImporter.java new file mode 100644 index 000000000..a25c3fa03 --- /dev/null +++ b/app/src/main/java/protect/card_locker/importexport/StocardImporter.java @@ -0,0 +1,149 @@ +package protect.card_locker.importexport; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.util.Log; + +import com.google.zxing.BarcodeFormat; + +import net.lingala.zip4j.io.inputstream.ZipInputStream; +import net.lingala.zip4j.model.LocalFileHeader; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVRecord; +import org.json.JSONException; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.StringReader; +import java.math.BigDecimal; +import java.text.ParseException; + +import protect.card_locker.DBHelper; +import protect.card_locker.FormatException; + +/** + * Class for importing a database from CSV (Comma Separate Values) + * formatted data. + * + * The database's loyalty cards are expected to appear in the CSV data. + * A header is expected for the each table showing the names of the columns. + */ +public class StocardImporter implements Importer +{ + public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, JSONException, ParseException { + LocalFileHeader localFileHeader; + + // We actually retrieve a .zip file + ZipInputStream zipInputStream = new ZipInputStream(input, password); + + StringBuilder loyaltyCards = new StringBuilder(); + byte[] buffer = new byte[1024]; + int read = 0; + + while ((localFileHeader = zipInputStream.getNextEntry()) != null) { + Log.w("STO", localFileHeader.getFileName()); + //File extractedFile = new File(localFileHeader.getFileName()); + //if (localFileHeader.isDirectory()) { + // localFileHeader = zipInputStream.getNextEntry(localFileHeader); + //} + //if (!localFileHeader.isDirectory()) { + // File extractedFile = new File(localFileHeader.getFileName()); + // OutputStream outputStream = new FileOutputStream(extractedFile); + // while ((read = zipInputStream.read(buffer)) != -1) { + // outputStream.write(buffer, 0, read); + // } + //} + } + + if (loyaltyCards.length() == 0) { + throw new FormatException("Couldn't find loyalty_programs.csv in zip file or it is empty"); + } + + SQLiteDatabase database = db.getWritableDatabase(); + database.beginTransaction(); + + final CSVParser fidmeParser = new CSVParser(new StringReader(loyaltyCards.toString()), CSVFormat.RFC4180.withDelimiter(';').withHeader()); + + try { + for (CSVRecord record : fidmeParser) { + importLoyaltyCard(database, db, record); + + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedException(); + } + } + } catch (IllegalArgumentException | IllegalStateException | InterruptedException e) { + throw new FormatException("Issue parsing CSV data", e); + } finally { + fidmeParser.close(); + } + + database.setTransactionSuccessful(); + database.endTransaction(); + database.close(); + + zipInputStream.close(); + } + + /** + * Import a single loyalty card into the database using the given + * session. + */ + private void importLoyaltyCard(SQLiteDatabase database, DBHelper helper, CSVRecord record) + throws IOException, FormatException + { + // A loyalty card export from Fidme contains the following fields: + // Retailer (store name) + // Program (program name) + // Added at (YYYY-MM-DD HH:MM:SS UTC) + // Reference (card ID) + // Firstname (card holder first name) + // Lastname (card holder last name) + + // The store is called Retailer + String store = CSVHelpers.extractString("Retailer", record, ""); + + if (store.isEmpty()) + { + throw new FormatException("No store listed, but is required"); + } + + // There seems to be no note field in the CSV? So let's combine other fields instead... + String program = CSVHelpers.extractString("Program", record, "").trim(); + String addedAt = CSVHelpers.extractString("Added At", record, "").trim(); + String firstName = CSVHelpers.extractString("Firstname", record, "").trim(); + String lastName = CSVHelpers.extractString("Lastname", record, "").trim(); + + String combinedName = String.format("%s %s", firstName, lastName).trim(); + + StringBuilder noteBuilder = new StringBuilder(); + if (!program.isEmpty()) noteBuilder.append(program).append('\n'); + if (!addedAt.isEmpty()) noteBuilder.append(addedAt).append('\n'); + if (!combinedName.isEmpty()) noteBuilder.append(combinedName).append('\n'); + String note = noteBuilder.toString().trim(); + + // The ID is called reference + String cardId = CSVHelpers.extractString("Reference", record, ""); + if(cardId.isEmpty()) + { + throw new FormatException("No card ID listed, but is required"); + } + + // Sadly, Fidme exports don't contain the card type + // I guess they have an online DB of all the different companies and what type they use + // TODO: Hook this into our own loyalty card DB if we ever get one + BarcodeFormat barcodeType = null; + + // No favourite data in the export either + int starStatus = 0; + + // TODO: Front and back image + + helper.insertLoyaltyCard(database, store, note, null, BigDecimal.valueOf(0), null, cardId, null, barcodeType, null, starStatus); + } +} \ No newline at end of file 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 950ec0bc3..42c99d5ff 100644 --- a/app/src/main/java/protect/card_locker/importexport/VoucherVaultImporter.java +++ b/app/src/main/java/protect/card_locker/importexport/VoucherVaultImporter.java @@ -35,7 +35,7 @@ import protect.card_locker.FormatException; */ public class VoucherVaultImporter implements Importer { - public void importData(Context context, DBHelper db, InputStream input) throws IOException, FormatException, JSONException, ParseException { + public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, JSONException, ParseException { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)); StringBuilder sb = new StringBuilder(); diff --git a/app/src/test/java/protect/card_locker/ImportExportTest.java b/app/src/test/java/protect/card_locker/ImportExportTest.java index 9c4f35a41..9bf598339 100644 --- a/app/src/test/java/protect/card_locker/ImportExportTest.java +++ b/app/src/test/java/protect/card_locker/ImportExportTest.java @@ -1,17 +1,12 @@ package protect.card_locker; import android.app.Activity; -import android.content.res.Resources; import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; import android.os.Environment; import android.util.DisplayMetrics; -import android.util.Log; import com.google.zxing.BarcodeFormat; @@ -24,7 +19,6 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import org.robolectric.annotation.LooperMode; import org.robolectric.shadows.ShadowLog; -import org.robolectric.util.Util; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -45,9 +39,9 @@ import java.util.Currency; import java.util.Date; import java.util.List; -import javax.annotation.Resource; - import androidx.core.content.res.ResourcesCompat; +import protect.card_locker.importexport.DataFormat; +import protect.card_locker.importexport.ImportExportResult; import protect.card_locker.importexport.MultiFormatExporter; import protect.card_locker.importexport.MultiFormatImporter; @@ -338,8 +332,8 @@ public class ImportExportTest OutputStreamWriter outStream = new OutputStreamWriter(outData); // Export data to CSV format - boolean result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima); - assertTrue(result); + ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima); + assertEquals(ImportExportResult.Success, result); outStream.close(); TestHelpers.getEmptyDb(activity); @@ -347,8 +341,8 @@ public class ImportExportTest ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray()); // Import the CSV data - result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, DataFormat.Catima); - assertTrue(result); + result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, DataFormat.Catima, null); + assertEquals(ImportExportResult.Success, result); assertEquals(NUM_CARDS, db.getLoyaltyCardCount()); @@ -369,8 +363,8 @@ public class ImportExportTest OutputStreamWriter outStream = new OutputStreamWriter(outData); // Export data to CSV format - boolean result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima); - assertTrue(result); + ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima); + assertEquals(ImportExportResult.Success, result); outStream.close(); TestHelpers.getEmptyDb(activity); @@ -378,8 +372,8 @@ public class ImportExportTest ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray()); // Import the CSV data - result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, DataFormat.Catima); - assertTrue(result); + result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, DataFormat.Catima, null); + assertEquals(ImportExportResult.Success, result); assertEquals(NUM_CARDS, db.getLoyaltyCardCount()); @@ -442,8 +436,8 @@ public class ImportExportTest OutputStreamWriter outStream = new OutputStreamWriter(outData); // Export data to CSV format - boolean result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima); - assertTrue(result); + ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima); + assertEquals(ImportExportResult.Success, result); outStream.close(); TestHelpers.getEmptyDb(activity); @@ -451,8 +445,8 @@ public class ImportExportTest ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray()); // Import the CSV data - result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, DataFormat.Catima); - assertTrue(result); + result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, DataFormat.Catima, null); + assertEquals(ImportExportResult.Success, result); assertEquals(NUM_CARDS, db.getLoyaltyCardCount()); assertEquals(NUM_GROUPS, db.getGroupCount()); @@ -486,15 +480,15 @@ public class ImportExportTest OutputStreamWriter outStream = new OutputStreamWriter(outData); // Export into CSV data - boolean result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima); - assertTrue(result); + ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima); + assertEquals(ImportExportResult.Success, result); outStream.close(); ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray()); // Import the CSV data on top of the existing database - result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, DataFormat.Catima); - assertTrue(result); + result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, DataFormat.Catima, null); + assertEquals(ImportExportResult.Success, result); assertEquals(NUM_CARDS, db.getLoyaltyCardCount()); @@ -517,8 +511,8 @@ public class ImportExportTest OutputStreamWriter outStream = new OutputStreamWriter(outData); // Export data to CSV format - boolean result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima); - assertTrue(result); + ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima); + assertEquals(ImportExportResult.Success, result); TestHelpers.getEmptyDb(activity); @@ -531,8 +525,8 @@ public class ImportExportTest ByteArrayInputStream inData = new ByteArrayInputStream((outData.toString() + corruptEntry).getBytes()); // Attempt to import the data - result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, format); - assertEquals(false, result); + result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, format, null); + assertEquals(ImportExportResult.GenericFailure, result); assertEquals(0, db.getLoyaltyCardCount()); @@ -542,11 +536,11 @@ public class ImportExportTest class TestTaskCompleteListener implements ImportExportTask.TaskCompleteListener { - Boolean success; + ImportExportResult result; - public void onTaskComplete(boolean success) + public void onTaskComplete(ImportExportResult result) { - this.success = success; + this.result = result; } } @@ -572,8 +566,8 @@ public class ImportExportTest Robolectric.flushBackgroundThreadScheduler(); // Check that the listener was executed - assertNotNull(listener.success); - assertEquals(true, listener.success); + assertNotNull(listener.result); + assertEquals(ImportExportResult.Success, listener.result); TestHelpers.getEmptyDb(activity); @@ -583,15 +577,15 @@ public class ImportExportTest FileInputStream fileStream = new FileInputStream(exportFile); - task = new ImportExportTask(activity, DataFormat.Catima, fileStream, listener); + task = new ImportExportTask(activity, DataFormat.Catima, fileStream, null, listener); task.execute(); // Actually run the task to completion Robolectric.flushBackgroundThreadScheduler(); // Check that the listener was executed - assertNotNull(listener.success); - assertEquals(true, listener.success); + assertNotNull(listener.result); + assertEquals(ImportExportResult.Success, listener.result); assertEquals(NUM_CARDS, db.getLoyaltyCardCount()); @@ -617,8 +611,8 @@ public class ImportExportTest ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8)); // Import the CSV data - boolean result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima); - assertTrue(result); + ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null); + assertEquals(ImportExportResult.Success, result); assertEquals(1, db.getLoyaltyCardCount()); LoyaltyCard card = db.getLoyaltyCard(1); @@ -655,8 +649,8 @@ public class ImportExportTest ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8)); // Import the CSV data - boolean result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima); - assertTrue(result); + ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null); + assertEquals(ImportExportResult.Success, result); assertEquals(1, db.getLoyaltyCardCount()); LoyaltyCard card = db.getLoyaltyCard(1); @@ -693,8 +687,8 @@ public class ImportExportTest ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8)); // Import the CSV data - boolean result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima); - assertEquals(false, result); + ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null); + assertEquals(ImportExportResult.GenericFailure, result); assertEquals(0, db.getLoyaltyCardCount()); TestHelpers.getEmptyDb(activity); @@ -718,8 +712,8 @@ public class ImportExportTest ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8)); // Import the CSV data - boolean result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima); - assertEquals(true, result); + ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null); + assertEquals(ImportExportResult.Success, result); assertEquals(1, db.getLoyaltyCardCount()); LoyaltyCard card = db.getLoyaltyCard(1); @@ -756,8 +750,8 @@ public class ImportExportTest ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8)); // Import the CSV data - boolean result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima); - assertEquals(true, result); + ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null); + assertEquals(ImportExportResult.Success, result); assertEquals(1, db.getLoyaltyCardCount()); LoyaltyCard card = db.getLoyaltyCard(1); @@ -794,8 +788,8 @@ public class ImportExportTest ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8)); // Import the CSV data - boolean result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima); - assertEquals(true, result); + ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null); + assertEquals(ImportExportResult.Success, result); assertEquals(1, db.getLoyaltyCardCount()); LoyaltyCard card = db.getLoyaltyCard(1); @@ -832,8 +826,8 @@ public class ImportExportTest ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8)); // Import the CSV data - boolean result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima); - assertTrue(result); + ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null); + assertEquals(ImportExportResult.Success, result); assertEquals(1, db.getLoyaltyCardCount()); csvText = ""; @@ -851,8 +845,8 @@ public class ImportExportTest inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8)); // Import the CSV data - result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima); - assertTrue(result); + result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null); + assertEquals(ImportExportResult.Success, result); assertEquals(1, db.getLoyaltyCardCount()); LoyaltyCard card = db.getLoyaltyCard(1); @@ -941,8 +935,8 @@ public class ImportExportTest ByteArrayInputStream inputStream = new ByteArrayInputStream(csvText.getBytes(StandardCharsets.UTF_8)); // Import the CSV data - boolean result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima); - assertEquals(true, result); + ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Catima, null); + assertEquals(ImportExportResult.Success, result); assertEquals(7, db.getLoyaltyCardCount()); assertEquals(3, db.getGroupCount()); @@ -1082,8 +1076,8 @@ public class ImportExportTest InputStream inputStream = getClass().getResourceAsStream("fidme-export-request-2021-06-29-17-28-20.zip"); // Import the Fidme data - boolean result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Fidme); - assertTrue(result); + ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Fidme, null); + assertEquals(ImportExportResult.Success, result); assertEquals(3, db.getLoyaltyCardCount()); LoyaltyCard card = db.getLoyaltyCard(1); @@ -1125,6 +1119,58 @@ public class ImportExportTest TestHelpers.getEmptyDb(activity); } + @Test + public void importStocard() throws IOException { + InputStream inputStream = getClass().getResourceAsStream("50e33e49-cfa0-49ad-a297-c1d655f72b01-sync.zip"); + + // Import the Stocard data + ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Stocard, null); + assertEquals(ImportExportResult.BadPassword, result); + assertEquals(0, db.getLoyaltyCardCount()); + + result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Stocard, "da811b40a4dac56f0cbb2d99b21bbb9a".toCharArray()); + assertEquals(ImportExportResult.Success, result); + assertEquals(3, db.getLoyaltyCardCount()); + + LoyaltyCard card = db.getLoyaltyCard(1); + + assertEquals("Air Miles", card.store); + assertEquals("", card.note); + assertEquals(null, card.expiry); + assertEquals(new BigDecimal("0"), card.balance); + assertEquals(null, card.balanceType); + assertEquals("7649484", card.cardId); + assertEquals(null, card.barcodeId); + assertEquals(null, card.barcodeType); + assertEquals(0, card.starStatus); + + card = db.getLoyaltyCard(2); + + assertEquals("Gamma", card.store); + assertEquals("", card.note); + assertEquals(null, card.expiry); + assertEquals(new BigDecimal("0"), card.balance); + assertEquals(null, card.balanceType); + assertEquals("55555", card.cardId); + assertEquals(null, card.barcodeId); + assertEquals(null, card.barcodeType); + assertEquals(0, card.starStatus); + + card = db.getLoyaltyCard(3); + + assertEquals("Jö", card.store); + assertEquals("", card.note); + assertEquals(null, card.expiry); + assertEquals(new BigDecimal("0"), card.balance); + assertEquals(null, card.balanceType); + assertEquals("20975646", card.cardId); + assertEquals("(01)09010374000019(21)02097564604859211217(10)01231287693", card.barcodeId); + assertEquals(null, card.barcodeType); + assertEquals(0, card.starStatus); + + TestHelpers.getEmptyDb(activity); + } + @Test public void importVoucherVault() throws IOException, FormatException, JSONException, ParseException { String jsonText = "[\n" + @@ -1153,8 +1199,8 @@ public class ImportExportTest ByteArrayInputStream inputStream = new ByteArrayInputStream(jsonText.getBytes(StandardCharsets.UTF_8)); // Import the Voucher Vault data - boolean result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.VoucherVault); - assertTrue(result); + ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.VoucherVault, null); + assertEquals(ImportExportResult.Success, result); assertEquals(2, db.getLoyaltyCardCount()); LoyaltyCard card = db.getLoyaltyCard(1); diff --git a/app/src/test/res/protect/card_locker/50e33e49-cfa0-49ad-a297-c1d655f72b01-sync.zip b/app/src/test/res/protect/card_locker/50e33e49-cfa0-49ad-a297-c1d655f72b01-sync.zip new file mode 100644 index 000000000..763d6f816 Binary files /dev/null and b/app/src/test/res/protect/card_locker/50e33e49-cfa0-49ad-a297-c1d655f72b01-sync.zip differ