/* This file is part of BeepMe. BeepMe is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. BeepMe is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with BeepMe. If not, see <http://www.gnu.org/licenses/>. Copyright 2012-2014 Michael Glanznig http://beepme.yourexp.at */ package com.glanznig.beepme.data; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.FilenameFilter; import java.io.IOException; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import com.glanznig.beepme.BeeperApp; import com.glanznig.beepme.R; import com.glanznig.beepme.db.SampleTable; import com.glanznig.beepme.db.StorageHandler; import com.glanznig.beepme.helper.AsyncImageScaler; import com.glanznig.beepme.helper.PhotoUtils; import com.glanznig.beepme.view.MainActivity; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.Bitmap; import android.os.Bundle; import android.os.Environment; import android.support.v4.app.NotificationCompat; import android.support.v4.app.TaskStackBuilder; import android.util.Log; import au.com.bytecode.opencsv.CSVWriter; public class DataExporter { private static final String EXPORT_PREFIX = "beepme_data_"; private static final String EXPORT_DIR = "export"; private static final String TEMP_DIR_NAME = "tmp"; private static final String TAG = "DataExporter"; private static final int BUFFER = 2048; private static final int NOTIFICATION_ID = 1438; Context ctx; public DataExporter(Context context) { ctx = context; } private File writeDataCSV(File tempDir) { SampleTable st = new SampleTable(ctx.getApplicationContext()); List<Sample> sampleList = st.getSamples(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH); File csvFile = new File(tempDir, "data.csv"); try { CSVWriter writer = new CSVWriter(new FileWriter(csvFile), ';'); writer.writeNext("Timestamp#Title#Description#Photo#Tags".split("#")); Iterator<Sample> i = sampleList.iterator(); while (i.hasNext()) { Sample item = i.next(); ArrayList<String> list = new ArrayList<String>(); list.add(dateFormat.format(item.getTimestamp())); if (item.getTitle() != null) { list.add(item.getTitle()); } else { list.add(""); } if (item.getDescription() != null) { list.add(item.getDescription()); } else { list.add(""); } if (item.getPhotoUri() != null) { File photo = new File(item.getPhotoUri()); list.add(photo.getName()); } else { list.add(""); } List<Tag> tags = st.getTagsOfSample(item.getId()); if (tags!= null && tags.size() > 0) { Iterator<Tag> it = tags.iterator(); String tagString = it.next().getName(); while (it.hasNext()) { tagString += ", "; tagString += it.next().getName(); } list.add(tagString); } else { list.add(""); } String[] listArray = new String[list.size()]; listArray = list.toArray(listArray); writer.writeNext(listArray); } writer.close(); } catch(IOException ioe) { Log.e(TAG, "error writing data csv file."); return null; } return csvFile; } private File writeHistoryCSV(File tempDir) { BeeperApp app = (BeeperApp)ctx.getApplicationContext(); List<Bundle> statList = Statistics.getStats(ctx, app.getTimerProfile()); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH); File csvFile = new File(tempDir, "history.csv"); try { CSVWriter writer = new CSVWriter(new FileWriter(csvFile), ';'); writer.writeNext("Date#Accepted#Declined#Elapsed".split("#")); Iterator<Bundle> i = statList.iterator(); while (i.hasNext()) { Bundle item = i.next(); ArrayList<String> list = new ArrayList<String>(); list.add(dateFormat.format(new Date(item.getLong("timestamp")))); if (item.containsKey("acceptedSamples")) { list.add(String.valueOf(item.getInt("acceptedSamples", 0))); } else { list.add("0"); } if (item.containsKey("declinedSamples")) { list.add(String.valueOf(item.getInt("declinedSamples", 0))); } else { list.add("0"); } if (item.containsKey("uptimeDuration")) { long uptimeDur = item.getLong("uptimeDuration") / 1000; String timeActive = String.format("%02d:%02d:%02d", uptimeDur/3600, (uptimeDur%3600)/60, (uptimeDur%60)); list.add(timeActive); } else { list.add("00:00:00"); } String[] listArray = new String[list.size()]; listArray = list.toArray(listArray); writer.writeNext(listArray); } writer.close(); } catch(IOException ioe) { Log.e(TAG, "error writing history csv file."); return null; } return csvFile; } public String exportToZipFile(Bundle opts) { boolean exportPhotos = opts.getBoolean("photoExport", true); boolean exportRaw = opts.getBoolean("rawExport", false); int densityFactor = opts.getInt("densityFactor", 1); //external storage is ready and writable - can be used if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) { File exportDir = ctx.getExternalFilesDir(EXPORT_DIR); if (!exportDir.exists()) { exportDir.mkdirs(); } BeeperApp app = (BeeperApp)ctx.getApplicationContext(); String exportFilename = EXPORT_PREFIX; if (app.getPreferences().isTestMode()) { exportFilename += "testmode_"; } exportFilename += new SimpleDateFormat("yyyyMMddHHmmss").format(Calendar.getInstance().getTime()) + ".zip"; File exportFile = new File(exportDir, exportFilename); ArrayList<File> fileList = new ArrayList<File>(); String archive = null; String dbName; File picDir = ctx.getExternalFilesDir(Environment.DIRECTORY_PICTURES); if (app.getPreferences().isTestMode()) { dbName = StorageHandler.getTestModeDatabaseName(); picDir = new File(picDir, PhotoUtils.TEST_MODE_DIR); } else { dbName = StorageHandler.getProductionDatabaseName(); picDir = new File(picDir, PhotoUtils.NORMAL_MODE_DIR); } if (exportRaw) { fileList.add(ctx.getDatabasePath(dbName)); } // create temp dir for CSV and photos File tempDir = new File(exportDir, TEMP_DIR_NAME); tempDir.mkdirs(); File dataCSV = writeDataCSV(tempDir); File historyCSV = writeHistoryCSV(tempDir); if (dataCSV != null && dataCSV.exists()) { fileList.add(dataCSV); } if (historyCSV != null && historyCSV.exists()) { fileList.add(historyCSV); } if (exportPhotos) { if (picDir.exists()) { FilenameFilter filter = new FilenameFilter() { public boolean accept(File directory, String fileName) { return fileName.endsWith(".jpg"); } }; File[] photos = picDir.listFiles(filter); if (densityFactor > 1) { // downscale photos for (int i = 0; i < photos.length; i++) { String srcUri = photos[i].getAbsolutePath(); File destPhoto = new File(srcUri); destPhoto = new File(tempDir, destPhoto.getName()); String destUri = destPhoto.getAbsolutePath(); Bundle dim = PhotoUtils.getPhotoDimensions(srcUri); int width = dim.getInt("width"); int height = dim.getInt("height"); if (width > 0 && height > 0) { Bitmap scaledPhoto = PhotoUtils.scalePhoto(ctx, srcUri, destUri, (int)Math.round(width / Math.sqrt((double)densityFactor)), (int)Math.round(height / Math.sqrt((double)densityFactor))); if (scaledPhoto != null) { scaledPhoto.recycle(); fileList.add(destPhoto); } } } archive = zipFiles(exportFile, fileList); } else { for (int i = 0; i < photos.length; i++) { fileList.add(photos[i]); } archive = zipFiles(exportFile, fileList); } } } else { archive = zipFiles(exportFile, fileList); } // remove temp dir and contents File[] tempFiles; tempFiles = tempDir.listFiles(); for (int i=0; i < tempFiles.length; i++) { tempFiles[i].delete(); } tempDir.delete(); return archive; } return null; } private String zipFiles(File zipFile, List<File> fileList) { String path = null; if (zipFile != null && fileList != null) { try { BufferedInputStream bufIn = null; FileOutputStream zipFOutStream = new FileOutputStream(zipFile); ZipOutputStream outStream = new ZipOutputStream(new BufferedOutputStream(zipFOutStream)); byte data[] = new byte[BUFFER]; Iterator<File> i = fileList.iterator(); while (i.hasNext()) { File f = i.next(); FileInputStream fIn = new FileInputStream(f); bufIn = new BufferedInputStream(fIn, BUFFER); ZipEntry entry = new ZipEntry(f.getName()); outStream.putNextEntry(entry); int count; while ((count = bufIn.read(data, 0, BUFFER)) != -1) { outStream.write(data, 0, count); } bufIn.close(); } outStream.close(); path = zipFile.getAbsolutePath(); } catch(Exception e) { Log.e(TAG, "error while zipping.", e); } } return path; } public double getArchiveSize(Bundle opts, int densityFactor) { BeeperApp app = (BeeperApp)ctx.getApplicationContext(); File db; double archiveSize = 0; boolean exportPhotos = opts.getBoolean("photoExport", true); boolean exportRaw = opts.getBoolean("rawExport", false); if (!app.getPreferences().isTestMode()) { db = app.getDatabasePath(StorageHandler.getTestModeDatabaseName()); } else { db = app.getDatabasePath(StorageHandler.getProductionDatabaseName()); } if (db != null) { if (exportRaw) { archiveSize += db.length() + db.length() / 2; } else { archiveSize += db.length() / 2; } } if (exportPhotos) { File[] photoList = PhotoUtils.getPhotos(ctx); if (photoList != null) { int count; double photoOverallSize = 0; for (count = 0; count < photoList.length; count++) { photoOverallSize += photoList[count].length(); } double photoAvgSize; if (count > 0) { photoAvgSize = photoOverallSize / count; } else { photoAvgSize = 0; } if (densityFactor == 1) { archiveSize += photoOverallSize; } else { archiveSize += photoAvgSize / densityFactor * count; } } } return archiveSize; } public String getReadableArchiveSize(Bundle opts, int densityFactor) { double size = getArchiveSize(opts, densityFactor); return getReadableFileSize(size, 0); } public static String getReadableFileSize(double size, int decimals) { if(size <= 0) { return "0 KB"; } final String[] units = new String[] { "B", "KB", "MB", "GB", "TB" }; int digitGroups = (int) (Math.log10(size)/Math.log10(1024)); NumberFormat numFormat = DecimalFormat.getInstance(); numFormat.setMaximumFractionDigits(decimals); return numFormat.format(size/Math.pow(1024, digitGroups)) + " " + units[digitGroups]; } }