/* * Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com> * * This file is part of Loop Habit Tracker. * * Loop Habit Tracker 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. * * Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>. */ package org.isoron.uhabits.io; import android.support.annotation.*; import org.isoron.uhabits.models.*; import org.isoron.uhabits.utils.*; import java.io.*; import java.text.*; import java.util.*; import java.util.zip.*; /** * Class that exports the application data to CSV files. */ public class HabitsCSVExporter { private List<Habit> selectedHabits; private List<String> generateDirs; private List<String> generateFilenames; private String exportDirName; /** * Delimiter used in a CSV file. */ private final String DELIMITER = ","; @NonNull private final HabitList allHabits; public HabitsCSVExporter(@NonNull HabitList allHabits, @NonNull List<Habit> selectedHabits, @NonNull File dir) { this.allHabits = allHabits; this.selectedHabits = selectedHabits; this.exportDirName = dir.getAbsolutePath() + "/"; generateDirs = new LinkedList<>(); generateFilenames = new LinkedList<>(); } public String writeArchive() throws IOException { String zipFilename; writeHabits(); zipFilename = writeZipFile(); cleanup(); return zipFilename; } private void addFileToZip(ZipOutputStream zos, String filename) throws IOException { FileInputStream fis = new FileInputStream(new File(exportDirName + filename)); ZipEntry ze = new ZipEntry(filename); zos.putNextEntry(ze); int length; byte bytes[] = new byte[1024]; while ((length = fis.read(bytes)) >= 0) zos.write(bytes, 0, length); zos.closeEntry(); fis.close(); } private void cleanup() { for (String filename : generateFilenames) new File(exportDirName + filename).delete(); for (String filename : generateDirs) new File(exportDirName + filename).delete(); new File(exportDirName).delete(); } @NonNull private String sanitizeFilename(String name) { String s = name.replaceAll("[^ a-zA-Z0-9\\._-]+", ""); return s.substring(0, Math.min(s.length(), 100)); } private void writeHabits() throws IOException { String filename = "Habits.csv"; new File(exportDirName).mkdirs(); FileWriter out = new FileWriter(exportDirName + filename); generateFilenames.add(filename); allHabits.writeCSV(out); out.close(); for (Habit h : selectedHabits) { String sane = sanitizeFilename(h.getName()); String habitDirName = String.format("%03d %s", allHabits.indexOf(h) + 1, sane); habitDirName = habitDirName.trim() + "/"; new File(exportDirName + habitDirName).mkdirs(); generateDirs.add(habitDirName); writeScores(habitDirName, h.getScores()); writeCheckmarks(habitDirName, h.getCheckmarks()); } writeMultipleHabits(); } private void writeScores(String habitDirName, ScoreList scores) throws IOException { String path = habitDirName + "Scores.csv"; FileWriter out = new FileWriter(exportDirName + path); generateFilenames.add(path); scores.writeCSV(out); out.close(); } private void writeCheckmarks(String habitDirName, CheckmarkList checkmarks) throws IOException { String filename = habitDirName + "Checkmarks.csv"; FileWriter out = new FileWriter(exportDirName + filename); generateFilenames.add(filename); checkmarks.writeCSV(out); out.close(); } /** * Writes a scores file and a checkmarks file containing scores and checkmarks of every habit. * The first column corresponds to the date. Subsequent columns correspond to a habit. * Habits are taken from the list of selected habits. * Dates are determined from the oldest repetition date to the newest repetition date found in * the list of habits. * * @throws IOException if there was problem writing the files */ private void writeMultipleHabits() throws IOException { String scoresFileName = "Scores.csv"; String checksFileName = "Checkmarks.csv"; generateFilenames.add(scoresFileName); generateFilenames.add(checksFileName); FileWriter scoresWriter = new FileWriter(exportDirName + scoresFileName); FileWriter checksWriter = new FileWriter(exportDirName + checksFileName); writeMultipleHabitsHeader(scoresWriter); writeMultipleHabitsHeader(checksWriter); long[] timeframe = getTimeframe(); long oldest = timeframe[0]; long newest = DateUtils.getStartOfToday(); List<int[]> checkmarks = new ArrayList<>(); List<int[]> scores = new ArrayList<>(); for (Habit h : selectedHabits) { checkmarks.add(h.getCheckmarks().getValues(oldest, newest)); scores.add(h.getScores().getValues(oldest, newest)); } int days = DateUtils.getDaysBetween(oldest, newest); SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat(); for (int i = 0; i <= days; i++) { Date day = new Date(newest - i * DateUtils.millisecondsInOneDay); String date = dateFormat.format(day); StringBuilder sb = new StringBuilder(); sb.append(date).append(DELIMITER); checksWriter.write(sb.toString()); scoresWriter.write(sb.toString()); for(int j = 0; j < selectedHabits.size(); j++) { checksWriter.write(String.valueOf(checkmarks.get(j)[i])); checksWriter.write(DELIMITER); String score = String.format("%.4f", ((float) scores.get(j)[i]) / Score.MAX_VALUE); scoresWriter.write(score); scoresWriter.write(DELIMITER); } checksWriter.write("\n"); scoresWriter.write("\n"); } scoresWriter.close(); checksWriter.close(); } /** * Writes the first row, containing header information, using the given writer. * This consists of the date title and the names of the selected habits. * * @param out the writer to use * @throws IOException if there was a problem writing */ private void writeMultipleHabitsHeader(Writer out) throws IOException { out.write("Date" + DELIMITER); for (Habit h : selectedHabits) { out.write(h.getName()); out.write(DELIMITER); } out.write("\n"); } /** * Gets the overall timeframe of the selected habits. * The timeframe is an array containing the oldest timestamp among the habits and the * newest timestamp among the habits. * Both timestamps are in milliseconds. * * @return the timeframe containing the oldest timestamp and the newest timestamp */ private long[] getTimeframe() { long oldest = Long.MAX_VALUE; long newest = -1; for (Habit h : selectedHabits) { if(h.getRepetitions().getOldest() == null || h.getRepetitions().getNewest() == null) continue; long currOld = h.getRepetitions().getOldest().getTimestamp(); long currNew = h.getRepetitions().getNewest().getTimestamp(); oldest = currOld > oldest ? oldest : currOld; newest = currNew < newest ? newest : currNew; } return new long[]{oldest, newest}; } private String writeZipFile() throws IOException { SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat(); String date = dateFormat.format(DateUtils.getStartOfToday()); String zipFilename = String.format("%s/Loop Habits CSV %s.zip", exportDirName, date); FileOutputStream fos = new FileOutputStream(zipFilename); ZipOutputStream zos = new ZipOutputStream(fos); for (String filename : generateFilenames) addFileToZip(zos, filename); zos.close(); fos.close(); return zipFilename; } }