Merge pull request #258 from TheLastProject/feature/catimaImportExportZip

Import and export .csv and images as .zip
This commit is contained in:
Sylvia van Os
2021-07-09 22:26:13 +02:00
committed by GitHub
18 changed files with 303 additions and 234 deletions

View File

@@ -78,8 +78,8 @@ public class ImportExportActivity extends AppCompatActivity
// Check that there is a file manager available
final Intent intentCreateDocumentAction = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intentCreateDocumentAction.addCategory(Intent.CATEGORY_OPENABLE);
intentCreateDocumentAction.setType("text/csv");
intentCreateDocumentAction.putExtra(Intent.EXTRA_TITLE, "Catima.csv");
intentCreateDocumentAction.setType("application/zip");
intentCreateDocumentAction.putExtra(Intent.EXTRA_TITLE, "Catima.zip");
Button exportButton = findViewById(R.id.exportButton);
exportButton.setOnClickListener(new View.OnClickListener()

View File

@@ -77,7 +77,7 @@ class ImportExportTask extends AsyncTask<Void, Void, ImportExportResult>
try
{
OutputStreamWriter writer = new OutputStreamWriter(stream, Charset.forName("UTF-8"));
result = MultiFormatExporter.exportData(context, db, writer, format);
result = MultiFormatExporter.exportData(context, db, stream, format);
writer.close();
}
catch (IOException e)

View File

@@ -33,6 +33,7 @@ import java.util.Currency;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import androidx.core.graphics.ColorUtils;
@@ -234,30 +235,6 @@ public class Utils {
return bos.toByteArray();
}
static public Bitmap byteArrayToBitmap(byte[] byteArray) {
if (byteArray == null) {
return null;
}
return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);
}
static public String bitmapToBase64(Bitmap bitmap) {
if (bitmap == null) {
return null;
}
return Base64.encodeToString(bitmapToByteArray(bitmap), Base64.URL_SAFE);
}
static public Bitmap base64ToBitmap(String base64) {
if (base64 == null) {
return null;
}
return byteArrayToBitmap(Base64.decode(base64, Base64.URL_SAFE));
}
static public Bitmap resizeBitmap(Bitmap bitmap) {
if (bitmap == null) {
return null;
@@ -307,7 +284,7 @@ public class Utils {
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
static private String getCardImageFileName(int loyaltyCardId, boolean front) {
static public String getCardImageFileName(int loyaltyCardId, boolean front) {
StringBuilder cardImageFileNameBuilder = new StringBuilder();
cardImageFileNameBuilder.append("card_");
@@ -323,23 +300,25 @@ public class Utils {
return cardImageFileNameBuilder.toString();
}
static public void saveCardImage(Context context, Bitmap bitmap, int loyaltyCardId, boolean front) throws FileNotFoundException {
String fileName = getCardImageFileName(loyaltyCardId, front);
static public void saveCardImage(Context context, Bitmap bitmap, String fileName) throws FileNotFoundException {
if (bitmap == null) {
context.deleteFile(fileName);
return;
}
FileOutputStream out = context.openFileOutput(getCardImageFileName(loyaltyCardId, front), Context.MODE_PRIVATE);
FileOutputStream out = context.openFileOutput(fileName, Context.MODE_PRIVATE);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
}
static public Bitmap retrieveCardImage(Context context, int loyaltyCardId, boolean front) {
static public void saveCardImage(Context context, Bitmap bitmap, int loyaltyCardId, boolean front) throws FileNotFoundException {
saveCardImage(context, bitmap, getCardImageFileName(loyaltyCardId, front));
}
static public Bitmap retrieveCardImage(Context context, String fileName) {
FileInputStream in;
try {
in = context.openFileInput(getCardImageFileName(loyaltyCardId, front));
in = context.openFileInput(fileName);
} catch (FileNotFoundException e) {
return null;
}
@@ -347,11 +326,19 @@ public class Utils {
return BitmapFactory.decodeStream(in);
}
static public Object hashmapGetOrDefault(HashMap hashMap, String key, Object defaultValue) {
Object value = hashMap.get(key);
static public Bitmap retrieveCardImage(Context context, int loyaltyCardId, boolean front) {
return retrieveCardImage(context, getCardImageFileName(loyaltyCardId, front));
}
static public Object hashmapGetOrDefault(HashMap hashMap, Object key, Object defaultValue, Class keyType) {
Object value = hashMap.get(keyType.cast(key));
if (value == null) {
return defaultValue;
}
return value;
}
static public Object hashmapGetOrDefault(HashMap hashMap, String key, Object defaultValue) {
return hashmapGetOrDefault(hashMap, key, defaultValue, String.class);
}
}

View File

@@ -0,0 +1,36 @@
package protect.card_locker;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import net.lingala.zip4j.io.inputstream.ZipInputStream;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
public class ZipUtils {
static public String read(ZipInputStream zipInputStream) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
Reader reader = new BufferedReader(new InputStreamReader(zipInputStream, Charset.forName(StandardCharsets.UTF_8.name())));
int c;
while ((c = reader.read()) != -1) {
stringBuilder.append((char) c);
}
return stringBuilder.toString();
}
static public Bitmap readImage(ZipInputStream zipInputStream) {
return BitmapFactory.decodeStream(zipInputStream);
}
static public JSONObject readJSON(ZipInputStream zipInputStream) throws IOException, JSONException {
return new JSONObject(read(zipInputStream));
}
}

View File

@@ -1,38 +1,10 @@
package protect.card_locker.importexport;
import android.graphics.Bitmap;
import org.apache.commons.csv.CSVRecord;
import protect.card_locker.FormatException;
import protect.card_locker.Utils;
public class CSVHelpers {
static String IMAGE_FRONT = "frontimage";
static String IMAGE_BACK = "backimage";
/**
* Extract an image from the items array. The index into the array
* is determined by looking up the index in the fields map using the
* "key" as the key. If no such key exists, defaultValue is returned
* if it is not null. Otherwise, a FormatException is thrown.
*/
static Bitmap extractImage(String key, CSVRecord record)
{
if(record.isMapped(key))
{
String value = record.get(key);
if (value.isEmpty()) {
return null;
}
return Utils.base64ToBitmap(value);
}
return null;
}
/**
* Extract a string from the items array. The index into the array
* is determined by looking up the index in the fields map using the

View File

@@ -2,12 +2,26 @@ package protect.card_locker.importexport;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.io.outputstream.ZipOutputStream;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.util.InternalZipConstants;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.zip.ZipEntry;
import protect.card_locker.DBHelper;
import protect.card_locker.Group;
@@ -18,10 +32,65 @@ import protect.card_locker.Utils;
* Class for exporting the database into CSV (Comma Separate Values)
* format.
*/
public class CsvExporter implements Exporter
public class CatimaExporter implements Exporter
{
public void exportData(Context context, DBHelper db, OutputStreamWriter output) throws IOException, InterruptedException
public void exportData(Context context, DBHelper db, OutputStream output) throws IOException, InterruptedException
{
// Necessary vars
int readLen;
byte[] readBuffer = new byte[InternalZipConstants.BUFF_SIZE];
// Create zip output stream
ZipOutputStream zipOutputStream = new ZipOutputStream(output);
// Generate CSV
ByteArrayOutputStream catimaOutputStream = new ByteArrayOutputStream();
OutputStreamWriter catimaOutputStreamWriter = new OutputStreamWriter(catimaOutputStream, StandardCharsets.UTF_8);
writeCSV(db, catimaOutputStreamWriter);
// Add CSV to zip file
ZipParameters csvZipParameters = new ZipParameters();
csvZipParameters.setFileNameInZip("catima.csv");
zipOutputStream.putNextEntry(csvZipParameters);
InputStream csvInputStream = new ByteArrayInputStream(catimaOutputStream.toByteArray());
while ((readLen = csvInputStream.read(readBuffer)) != -1) {
zipOutputStream.write(readBuffer, 0, readLen);
}
zipOutputStream.closeEntry();
// Loop over all cards again
Cursor cardCursor = db.getLoyaltyCardCursor();
while(cardCursor.moveToNext())
{
// For each card
LoyaltyCard card = LoyaltyCard.toLoyaltyCard(cardCursor);
// Prepare looping over both front and back image
boolean[] frontValues = new boolean[2];
frontValues[0] = true;
frontValues[1] = false;
// For each image
for (boolean front : frontValues) {
// If it exists, add to the .zip file
Bitmap image = Utils.retrieveCardImage(context, card.id, front);
if (image != null) {
ZipParameters imageZipParameters = new ZipParameters();
imageZipParameters.setFileNameInZip(Utils.getCardImageFileName(card.id, front));
zipOutputStream.putNextEntry(imageZipParameters);
InputStream imageInputStream = new ByteArrayInputStream(Utils.bitmapToByteArray(image));
while ((readLen = imageInputStream.read(readBuffer)) != -1) {
zipOutputStream.write(readBuffer, 0, readLen);
}
zipOutputStream.closeEntry();
}
}
}
zipOutputStream.close();
}
private void writeCSV(DBHelper db, OutputStreamWriter output) throws IOException, InterruptedException {
CSVPrinter printer = new CSVPrinter(output, CSVFormat.RFC4180);
// Print the version
@@ -62,9 +131,7 @@ public class CsvExporter implements Exporter
DBHelper.LoyaltyCardDbIds.BARCODE_ID,
DBHelper.LoyaltyCardDbIds.BARCODE_TYPE,
DBHelper.LoyaltyCardDbIds.HEADER_COLOR,
DBHelper.LoyaltyCardDbIds.STAR_STATUS,
CSVHelpers.IMAGE_FRONT,
CSVHelpers.IMAGE_BACK);
DBHelper.LoyaltyCardDbIds.STAR_STATUS);
Cursor cardCursor = db.getLoyaltyCardCursor();
@@ -82,9 +149,7 @@ public class CsvExporter implements Exporter
card.barcodeId,
card.barcodeType,
card.headerColor,
card.starStatus,
Utils.bitmapToBase64(Utils.retrieveCardImage(context, card.id, true)),
Utils.bitmapToBase64(Utils.retrieveCardImage(context, card.id, false)));
card.starStatus);
if(Thread.currentThread().isInterrupted())
{

View File

@@ -5,11 +5,15 @@ 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;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -24,6 +28,7 @@ import protect.card_locker.DBHelper;
import protect.card_locker.FormatException;
import protect.card_locker.Group;
import protect.card_locker.Utils;
import protect.card_locker.ZipUtils;
/**
* Class for importing a database from CSV (Comma Separate Values)
@@ -32,10 +37,33 @@ import protect.card_locker.Utils;
* 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 CsvImporter implements Importer
public class CatimaImporter implements Importer
{
public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, InterruptedException
{
public void importData(Context context, DBHelper db, InputStream input, char[] password) throws IOException, FormatException, InterruptedException {
// First, check if this is a zip file
ZipInputStream zipInputStream = new ZipInputStream(input);
LocalFileHeader localFileHeader = zipInputStream.getNextEntry();
if (localFileHeader == null) {
// This is not a zip file, try importing as bare CSV
input.reset();
importCSV(context, db, input);
return;
}
importZipFile(context, db, zipInputStream, localFileHeader);
}
public void importZipFile(Context context, DBHelper db, ZipInputStream input, LocalFileHeader localFileHeader) throws IOException, FormatException, InterruptedException {
String fileName = localFileHeader.getFileName();
if (fileName.equals("catima.csv")) {
importCSV(context, db, new ByteArrayInputStream(ZipUtils.read(input).getBytes(StandardCharsets.UTF_8)));
} else {
Utils.saveCardImage(context, ZipUtils.readImage(input), fileName);
}
}
public void importCSV(Context context, DBHelper db, InputStream input) throws IOException, FormatException, InterruptedException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
bufferedReader.mark(100);
@@ -278,9 +306,6 @@ public class CsvImporter implements Importer
if (starStatus != 1) starStatus = 0;
helper.insertLoyaltyCard(database, id, store, note, expiry, balance, balanceType, cardId, barcodeId, barcodeType, headerColor, starStatus);
Utils.saveCardImage(context, CSVHelpers.extractImage(CSVHelpers.IMAGE_FRONT, record), id, true);
Utils.saveCardImage(context, CSVHelpers.extractImage(CSVHelpers.IMAGE_BACK, record), id, false);
}
/**

View File

@@ -3,6 +3,7 @@ package protect.card_locker.importexport;
import android.content.Context;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import protect.card_locker.DBHelper;
@@ -17,5 +18,5 @@ public interface Exporter
* Export the database to the output stream in a given format.
* @throws IOException
*/
void exportData(Context context, DBHelper db, OutputStreamWriter output) throws IOException, InterruptedException;
void exportData(Context context, DBHelper db, OutputStream output) throws IOException, InterruptedException;
}

View File

@@ -4,6 +4,7 @@ import android.content.Context;
import android.util.Log;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import protect.card_locker.DBHelper;
@@ -22,14 +23,14 @@ public class MultiFormatExporter
* another ImportExportResult otherwise. If not Success, partial data may have been
* written to the output stream, and it should be discarded.
*/
public static ImportExportResult exportData(Context context, DBHelper db, OutputStreamWriter output, DataFormat format)
public static ImportExportResult exportData(Context context, DBHelper db, OutputStream output, DataFormat format)
{
Exporter exporter = null;
switch(format)
{
case Catima:
exporter = new CsvExporter();
exporter = new CatimaExporter();
break;
default:
Log.e(TAG, "Failed to export data, unknown format " + format.name());

View File

@@ -36,7 +36,7 @@ public class MultiFormatImporter
switch(format)
{
case Catima:
importer = new CsvImporter();
importer = new CatimaImporter();
break;
case Fidme:
importer = new FidmeImporter();
@@ -60,7 +60,7 @@ public class MultiFormatImporter
{
return ImportExportResult.BadPassword;
}
catch(IOException | FormatException | InterruptedException | JSONException | ParseException e)
catch(IOException | FormatException | InterruptedException | JSONException | ParseException | NullPointerException e)
{
Log.e(TAG, "Failed to import data", e);
}

View File

@@ -30,6 +30,7 @@ import java.util.List;
import protect.card_locker.DBHelper;
import protect.card_locker.FormatException;
import protect.card_locker.Utils;
import protect.card_locker.ZipUtils;
/**
* Class for importing a database from CSV (Comma Separate Values)
@@ -83,7 +84,7 @@ public class StocardImporter implements Importer
if (nameParts.length == cardBaseName.length + 1) {
// Ignore the .txt file
if (fileName.endsWith(".json")) {
JSONObject jsonObject = readJSON(zipInputStream);
JSONObject jsonObject = ZipUtils.readJSON(zipInputStream);
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
loyaltyCardHashMap,
@@ -115,7 +116,7 @@ public class StocardImporter implements Importer
loyaltyCardHashMap,
cardName,
"note",
readJSON(zipInputStream)
ZipUtils.readJSON(zipInputStream)
.getString("content")
);
} else if (fileName.endsWith("/images/front.png")) {
@@ -123,14 +124,14 @@ public class StocardImporter implements Importer
loyaltyCardHashMap,
cardName,
"frontImage",
readImage(zipInputStream)
ZipUtils.readImage(zipInputStream)
);
} else if (fileName.endsWith("/images/back.png")) {
loyaltyCardHashMap = appendToLoyaltyCardHashMap(
loyaltyCardHashMap,
cardName,
"backImage",
readImage(zipInputStream)
ZipUtils.readImage(zipInputStream)
);
}
}
@@ -188,24 +189,6 @@ public class StocardImporter implements Importer
return true;
}
private String read(ZipInputStream zipInputStream) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
Reader reader = new BufferedReader(new InputStreamReader(zipInputStream, Charset.forName(StandardCharsets.UTF_8.name())));
int c;
while ((c = reader.read()) != -1) {
stringBuilder.append((char) c);
}
return stringBuilder.toString();
}
private Bitmap readImage(ZipInputStream zipInputStream) {
return BitmapFactory.decodeStream(zipInputStream);
}
private JSONObject readJSON(ZipInputStream zipInputStream) throws IOException, JSONException {
return new JSONObject(read(zipInputStream));
}
private HashMap<String, HashMap<String, Object>> appendToLoyaltyCardHashMap(HashMap<String, HashMap<String, Object>> loyaltyCardHashMap, String cardID, String key, Object value) {
HashMap<String, Object> loyaltyCardData = loyaltyCardHashMap.get(cardID);
if (loyaltyCardData == null) {
@@ -220,7 +203,7 @@ public class StocardImporter implements Importer
private HashMap<String, String> parseProviders(ZipInputStream zipInputStream) throws IOException, JSONException {
// FIXME: This is probably completely wrong, but it works for the one and only test file I have
JSONObject jsonObject = readJSON(zipInputStream);
JSONObject jsonObject = ZipUtils.readJSON(zipInputStream);
JSONArray providerIdList = jsonObject.getJSONArray("provider_id_list");
JSONArray providerList = jsonObject.getJSONArray("provider_list");

View File

@@ -1,66 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="192dp"
android:height="192dp"
android:viewportWidth="50.8"
android:viewportHeight="50.8">
<path
android:pathData="M14.3354,20.1954l20.7318,-9.2304l5.7612,12.9398l-20.7318,9.2304z"
android:strokeLineJoin="miter"
android:strokeWidth="0.529167"
android:fillColor="#f0f0f0"
android:strokeColor="#c80000"/>
<path
android:pathData="M14.8755,10.9648l23.2041,10.3311l-6.8874,15.4694l-23.2041,-10.3311z"
android:strokeLineJoin="miter"
android:strokeWidth="0.529167"
android:fillColor="#f0f0f0"
android:strokeColor="#c80000"/>
<path
android:pathData="M16.5599,16.1348l26.5459,7.6119l-4.5489,15.8639l-26.5459,-7.6119z"
android:strokeLineJoin="round"
android:strokeWidth="1.5875"
android:fillColor="#c80000"
android:strokeColor="#c80000"
android:fillType="evenOdd"/>
<path
android:pathData="M12.011,15.4955h27.6157v16.5032h-27.6157z"
android:strokeLineJoin="round"
android:strokeWidth="1.5875"
android:fillColor="#ff0000"
android:strokeColor="#ff0000"
android:fillType="evenOdd"/>
<path
android:pathData="M7.8471,23.7471a4.3659,8.5899 0,1 0,8.7317 0a4.3659,8.5899 0,1 0,-8.7317 0z"
android:strokeLineJoin="round"
android:strokeWidth="0.91078"
android:fillColor="#ff0000"
android:strokeColor="#ff0000"/>
<path
android:pathData="m24.4983,25.781a1.6711,1.6711 0,0 1,-1.3809 1.6457,1.6711 1.6711,0 0,1 -1.8605,-1.0741"
android:strokeLineJoin="round"
android:strokeWidth="0.529167"
android:fillColor="#00000000"
android:strokeColor="#f0f0f0"
android:strokeLineCap="round"/>
<path
android:pathData="m27.7991,26.333a1.6711,1.6711 0,0 1,-1.8605 1.0741,1.6711 1.6711,0 0,1 -1.3809,-1.6457"
android:strokeLineJoin="round"
android:strokeWidth="0.529167"
android:fillColor="#00000000"
android:strokeColor="#f0f0f0"
android:strokeLineCap="round"/>
<path
android:pathData="m16.0606,22.271 l2.6458,-2.6458 2.6458,2.6458"
android:strokeLineJoin="miter"
android:strokeWidth="0.529167"
android:fillColor="#00000000"
android:strokeColor="#f0f0f0"
android:strokeLineCap="butt"/>
<path
android:pathData="m27.7023,22.271 l2.6458,-2.6458 2.6458,2.6458"
android:strokeLineJoin="miter"
android:strokeWidth="0.529167"
android:fillColor="#00000000"
android:strokeColor="#f0f0f0"
android:strokeLineCap="butt"/>
</vector>

View File

@@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M20,3h-1L19,1h-2v2L7,3L7,1L5,1v2L4,3c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,5c0,-1.1 -0.9,-2 -2,-2zM20,21L4,21L4,8h16v13z"/>
</vector>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/darker_gray"/>
<size android:height="1dp"/>
</shape>

View File

@@ -29,6 +29,7 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
@@ -38,6 +39,7 @@ import java.util.Arrays;
import java.util.Calendar;
import java.util.Currency;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import androidx.core.content.res.ResourcesCompat;
@@ -333,7 +335,7 @@ public class ImportExportTest
OutputStreamWriter outStream = new OutputStreamWriter(outData);
// Export data to CSV format
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima);
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima);
assertEquals(ImportExportResult.Success, result);
outStream.close();
@@ -364,7 +366,7 @@ public class ImportExportTest
OutputStreamWriter outStream = new OutputStreamWriter(outData);
// Export data to CSV format
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima);
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima);
assertEquals(ImportExportResult.Success, result);
outStream.close();
@@ -437,7 +439,7 @@ public class ImportExportTest
OutputStreamWriter outStream = new OutputStreamWriter(outData);
// Export data to CSV format
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima);
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima);
assertEquals(ImportExportResult.Success, result);
outStream.close();
@@ -481,7 +483,7 @@ public class ImportExportTest
OutputStreamWriter outStream = new OutputStreamWriter(outData);
// Export into CSV data
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima);
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima);
assertEquals(ImportExportResult.Success, result);
outStream.close();
@@ -512,7 +514,7 @@ public class ImportExportTest
OutputStreamWriter outStream = new OutputStreamWriter(outData);
// Export data to CSV format
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outStream, DataFormat.Catima);
ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima);
assertEquals(ImportExportResult.Success, result);
TestHelpers.getEmptyDb(activity);
@@ -867,46 +869,106 @@ public class ImportExportTest
}
@Test
public void exportV2() throws FileNotFoundException
public void exportImportV2Zip() throws FileNotFoundException
{
db.insertGroup("Example");
// Prepare images
BitmapDrawable launcher = (BitmapDrawable) ResourcesCompat.getDrawableForDensity(activity.getResources(), R.mipmap.ic_launcher, DisplayMetrics.DENSITY_XXXHIGH, activity.getTheme());
BitmapDrawable roundLauncher = (BitmapDrawable) ResourcesCompat.getDrawableForDensity(activity.getResources(), R.mipmap.ic_launcher_round, DisplayMetrics.DENSITY_XXXHIGH, activity.getTheme());
Bitmap frontImage = launcher.getBitmap();
Bitmap backImage = roundLauncher.getBitmap();
Bitmap launcherBitmap = launcher.getBitmap();
Bitmap roundLauncherBitmap = roundLauncher.getBitmap();
int loyaltyCard = (int) db.insertLoyaltyCard("Card 1", "Note 1", new Date(1618053234), new BigDecimal("100"), Currency.getInstance("USD"), "1234", "5432", BarcodeFormat.QR_CODE, 1, 0);
// Set up cards and groups
HashMap<Integer, LoyaltyCard> loyaltyCardHashMap = new HashMap<>();
HashMap<Integer, List<Group>> loyaltyCardGroups = new HashMap<>();
HashMap<Integer, Bitmap> loyaltyCardFrontImages = new HashMap<>();
HashMap<Integer, Bitmap> loyaltyCardBackImages = new HashMap<>();
Utils.saveCardImage(activity.getApplicationContext(), Utils.resizeBitmap(frontImage), loyaltyCard, true);
Utils.saveCardImage(activity.getApplicationContext(), Utils.resizeBitmap(backImage), loyaltyCard, false);
// Create card 1
int loyaltyCardId = (int) db.insertLoyaltyCard("Card 1", "Note 1", new Date(1618053234), new BigDecimal("100"), Currency.getInstance("USD"), "1234", "5432", BarcodeFormat.QR_CODE, 1, 0);
loyaltyCardHashMap.put(loyaltyCardId, db.getLoyaltyCard(loyaltyCardId));
db.insertGroup("One");
List<Group> groups = Arrays.asList(db.getGroup("One"));
db.setLoyaltyCardGroups(loyaltyCardId, groups);
loyaltyCardGroups.put(loyaltyCardId, groups);
Utils.saveCardImage(activity.getApplicationContext(), launcherBitmap, loyaltyCardId, true);
Utils.saveCardImage(activity.getApplicationContext(), roundLauncherBitmap, loyaltyCardId, false);
loyaltyCardFrontImages.put(loyaltyCardId, launcherBitmap);
loyaltyCardBackImages.put(loyaltyCardId, roundLauncherBitmap);
db.setLoyaltyCardGroups(loyaltyCard, Arrays.asList(db.getGroup("Example")));
// Create card 2
loyaltyCardId = (int) db.insertLoyaltyCard("Card 2", "", null, new BigDecimal(0), null, "123456", null, null, 2, 1);
loyaltyCardHashMap.put(loyaltyCardId, db.getLoyaltyCard(loyaltyCardId));
// Export everything
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
MultiFormatExporter.exportData(activity.getApplicationContext(), db, outputStream, DataFormat.Catima);
MultiFormatExporter.exportData(activity.getApplicationContext(), db, outputStreamWriter, DataFormat.Catima);
// Wipe database
TestHelpers.getEmptyDb(activity);
String outputCsv = "2\r\n" +
"\r\n" +
"_id\r\n" +
"Example\r\n" +
"\r\n" +
"_id,store,note,expiry,balance,balancetype,cardid,barcodeid,barcodetype,headercolor,starstatus,frontimage,backimage\r\n" +
"1,Card 1,Note 1,1618053234,100,USD,1234,5432,QR_CODE,1,0,\"iVBORw0KGgoAAAANSUhEUgAAAgAAAAIAAQAAAADcA-lXAAAANklEQVR42u3BAQEAAACCIP-vbkhA\n" +
"AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8G4IAAAFjdVCkAAAAAElFTkSuQmCC\n\",\"iVBORw0KGgoAAAANSUhEUgAAAgAAAAIAAQAAAADcA-lXAAAANklEQVR42u3BAQEAAACCIP-vbkhA\n" +
"AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8G4IAAAFjdVCkAAAAAElFTkSuQmCC\n\"\r\n" +
"\r\n" +
"cardId,groupId\r\n" +
"1,Example\r\n";
// Import everything
MultiFormatImporter.importData(activity.getApplicationContext(), db, new ByteArrayInputStream(outputStream.toByteArray()), DataFormat.Catima, null);
assertEquals(outputCsv, outputStream.toString());
// Ensure everything is there
assertEquals(loyaltyCardHashMap.size(), db.getLoyaltyCardCount());
for (Integer loyaltyCardID : loyaltyCardHashMap.keySet()) {
LoyaltyCard loyaltyCard = loyaltyCardHashMap.get(loyaltyCardID);
LoyaltyCard dbLoyaltyCard = db.getLoyaltyCard(loyaltyCardID);
assertEquals(loyaltyCard.id, dbLoyaltyCard.id);
assertEquals(loyaltyCard.store, dbLoyaltyCard.store);
assertEquals(loyaltyCard.note, dbLoyaltyCard.note);
assertEquals(loyaltyCard.expiry, dbLoyaltyCard.expiry);
assertEquals(loyaltyCard.balance, dbLoyaltyCard.balance);
assertEquals(loyaltyCard.cardId, dbLoyaltyCard.cardId);
assertEquals(loyaltyCard.barcodeId, dbLoyaltyCard.barcodeId);
assertEquals(loyaltyCard.starStatus, dbLoyaltyCard.starStatus);
assertEquals(loyaltyCard.barcodeType, dbLoyaltyCard.barcodeType);
assertEquals(loyaltyCard.balanceType, dbLoyaltyCard.balanceType);
assertEquals(loyaltyCard.headerColor, dbLoyaltyCard.headerColor);
List<Group> emptyGroup = new ArrayList<>();
assertEquals(
groupsToGroupNames(
(List<Group>) Utils.hashmapGetOrDefault(
loyaltyCardGroups,
loyaltyCardID,
emptyGroup,
Integer.class
)
),
groupsToGroupNames(
db.getLoyaltyCardGroups(
loyaltyCardID
)
)
);
Bitmap expectedFrontImage = loyaltyCardFrontImages.get(loyaltyCardID);
Bitmap expectedBackImage = loyaltyCardBackImages.get(loyaltyCardID);
Bitmap actualFrontImage = Utils.retrieveCardImage(activity.getApplicationContext(), Utils.getCardImageFileName(loyaltyCardID, true));
Bitmap actualBackImage = Utils.retrieveCardImage(activity.getApplicationContext(), Utils.getCardImageFileName(loyaltyCardID, false));
if (expectedFrontImage != null) {
assertTrue(expectedFrontImage.sameAs(actualFrontImage));
} else {
assertNull(actualFrontImage);
}
if (expectedBackImage != null) {
assertTrue(expectedBackImage.sameAs(actualBackImage));
} else {
assertNull(actualBackImage);
}
}
}
@Test
public void importV2()
public void importV2CSV()
{
String csvText = "2\n" +
"\n" +
@@ -915,16 +977,14 @@ public class ImportExportTest
"Food\n" +
"Fashion\n" +
"\n" +
"_id,store,note,expiry,balance,balancetype,cardid,barcodeid,headercolor,barcodetype,starstatus,frontimage,backimage\n" +
"1,Card 1,Note 1,1618053234,100,USD,1234,5432,1,QR_CODE,0,\"iVBORw0KGgoAAAANSUhEUgAAAgAAAAIAAQAAAADcA-lXAAAANklEQVR42u3BAQEAAACCIP-vbkhA\n" +
"AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8G4IAAAFjdVCkAAAAAElFTkSuQmCC\n\",\"iVBORw0KGgoAAAANSUhEUgAAAgAAAAIAAQAAAADcA-lXAAAANklEQVR42u3BAQEAAACCIP-vbkhA\n" +
"AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8G4IAAAFjdVCkAAAAAElFTkSuQmCC\n\"\r\n" +
"8,Clothes Store,Note about store,,0,,a,,-5317,,0,,\n" +
"2,Department Store,,1618041729,0,,A,,-9977996,,0,,\n" +
"3,Grocery Store,,,150,,dhd,,-9977996,,0,,\n" +
"4,Pharmacy,,,0,,dhshsvshs,,-10902850,,1,,\n" +
"5,Restaurant,Note about restaurant here,,0,,98765432,23456,-10902850,CODE_128,0,,\n" +
"6,Shoe Store,,,12.50,EUR,a,-5317,,AZTEC,0,,\n" +
"_id,store,note,expiry,balance,balancetype,cardid,barcodeid,headercolor,barcodetype,starstatus\n" +
"1,Card 1,Note 1,1618053234,100,USD,1234,5432,1,QR_CODE,0,\r\n" +
"8,Clothes Store,Note about store,,0,,a,,-5317,,0,\n" +
"2,Department Store,,1618041729,0,,A,,-9977996,,0,\n" +
"3,Grocery Store,,,150,,dhd,,-9977996,,0,\n" +
"4,Pharmacy,,,0,,dhshsvshs,,-10902850,,1,\n" +
"5,Restaurant,Note about restaurant here,,0,,98765432,23456,-10902850,CODE_128,0,\n" +
"6,Shoe Store,,,12.50,EUR,a,-5317,,AZTEC,0,\n" +
"\n" +
"cardId,groupId\n" +
"8,Fashion\n" +
@@ -958,12 +1018,6 @@ public class ImportExportTest
assertEquals(Arrays.asList(8, 6), db.getGroupCardIds("Fashion"));
// Check all cards
BitmapDrawable launcher = (BitmapDrawable) ResourcesCompat.getDrawableForDensity(activity.getResources(), R.mipmap.ic_launcher, DisplayMetrics.DENSITY_XXXHIGH, activity.getTheme());
BitmapDrawable roundLauncher = (BitmapDrawable) ResourcesCompat.getDrawableForDensity(activity.getResources(), R.mipmap.ic_launcher_round, DisplayMetrics.DENSITY_XXXHIGH, activity.getTheme());
Bitmap frontImage = launcher.getBitmap();
Bitmap backImage = roundLauncher.getBitmap();
LoyaltyCard card1 = db.getLoyaltyCard(1);
assertEquals("Card 1", card1.store);
@@ -976,8 +1030,8 @@ public class ImportExportTest
assertEquals(BarcodeFormat.QR_CODE, card1.barcodeType);
assertEquals(1, (long) card1.headerColor);
assertEquals(0, card1.starStatus);
assertTrue(Utils.resizeBitmap(frontImage).sameAs(Utils.retrieveCardImage(activity.getApplicationContext(), card1.id, true)));
assertTrue(Utils.resizeBitmap(backImage).sameAs(Utils.retrieveCardImage(activity.getApplicationContext(), card1.id, false)));
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card1.id, true));
assertEquals(null, Utils.retrieveCardImage(activity.getApplicationContext(), card1.id, false));
LoyaltyCard card8 = db.getLoyaltyCard(8);
@@ -1074,7 +1128,7 @@ public class ImportExportTest
@Test
public void importFidme() {
InputStream inputStream = getClass().getResourceAsStream("fidme-export-request-2021-06-29-17-28-20.zip");
InputStream inputStream = getClass().getResourceAsStream("fidme.zip");
// Import the Fidme data
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Fidme, null);
@@ -1122,14 +1176,14 @@ public class ImportExportTest
@Test
public void importStocard() throws IOException {
InputStream inputStream = getClass().getResourceAsStream("50e33e49-cfa0-49ad-a297-c1d655f72b01-sync.zip");
InputStream inputStream = getClass().getResourceAsStream("stocard.zip");
// Import the Stocard data
ImportExportResult result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Stocard, null);
assertEquals(ImportExportResult.BadPassword, result);
assertEquals(0, db.getLoyaltyCardCount());
inputStream = getClass().getResourceAsStream("50e33e49-cfa0-49ad-a297-c1d655f72b01-sync.zip");
inputStream = getClass().getResourceAsStream("stocard.zip");
result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inputStream, DataFormat.Stocard, "da811b40a4dac56f0cbb2d99b21bbb9a".toCharArray());
assertEquals(ImportExportResult.Success, result);

View File

@@ -1,11 +1,33 @@
package protect.card_locker;
import android.app.Activity;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
public class TestHelpers {
static public DBHelper getEmptyDb(Activity activity) {
DBHelper db = new DBHelper(activity);
// Make sure no files remain
Cursor cursor = db.getLoyaltyCardCursor();
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
int cardID = cursor.getColumnIndex(DBHelper.LoyaltyCardDbIds.ID);
try {
Utils.saveCardImage(activity.getApplicationContext(), null, cardID, true);
} catch (FileNotFoundException ignored) {}
try {
Utils.saveCardImage(activity.getApplicationContext(), null, cardID, false);
} catch (FileNotFoundException ignored) {}
cursor.moveToNext();
}
// Make sure DB is empty
SQLiteDatabase database = db.getWritableDatabase();
database.execSQL("delete from " + DBHelper.LoyaltyCardDbIds.TABLE);