Import and export .csv and images as .zip

This commit is contained in:
Sylvia van Os
2021-07-08 22:27:40 +02:00
parent 18c5aa4707
commit a9568d6adb
14 changed files with 179 additions and 113 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,26 @@ 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,6 +327,10 @@ public class Utils {
return BitmapFactory.decodeStream(in);
}
static public Bitmap retrieveCardImage(Context context, int loyaltyCardId, boolean front) {
return retrieveCardImage(context, getCardImageFileName(loyaltyCardId, front));
}
static public Object hashmapGetOrDefault(HashMap hashMap, String key, Object defaultValue) {
Object value = hashMap.get(key);
if (value == null) {

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,25 @@ 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.util.zip.ZipEntry;
import protect.card_locker.DBHelper;
import protect.card_locker.Group;
@@ -18,10 +31,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);
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 +130,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 +148,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()));
} 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");