diff --git a/app/build.gradle b/app/build.gradle index 2921e8b81..695bd53a3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -35,6 +35,7 @@ dependencies { compile 'com.android.support:support-v4:23.1.1' compile 'com.github.paolorotolo:appintro:3.4.0' compile 'org.apmem.tools:layouts:1.10@aar' + compile 'com.opencsv:opencsv:3.7' compile project(':libs:drag-sort-listview:library') compile files('libs/ActiveAndroid.jar') diff --git a/app/src/main/java/org/isoron/helpers/ColorHelper.java b/app/src/main/java/org/isoron/helpers/ColorHelper.java index de49d654d..091471699 100644 --- a/app/src/main/java/org/isoron/helpers/ColorHelper.java +++ b/app/src/main/java/org/isoron/helpers/ColorHelper.java @@ -91,4 +91,9 @@ public class ColorHelper hsv[index] = newValue; return Color.HSVToColor(hsv); } + + public static String toHTML(int color) + { + return String.format("#%06X", 0xFFFFFF & color); + } } \ No newline at end of file diff --git a/app/src/main/java/org/isoron/helpers/DateHelper.java b/app/src/main/java/org/isoron/helpers/DateHelper.java index f89a3ef7b..97bc17082 100644 --- a/app/src/main/java/org/isoron/helpers/DateHelper.java +++ b/app/src/main/java/org/isoron/helpers/DateHelper.java @@ -24,6 +24,7 @@ import android.text.format.DateFormat; import org.isoron.uhabits.R; +import java.text.SimpleDateFormat; import java.util.Date; import java.util.GregorianCalendar; import java.util.Locale; @@ -96,6 +97,14 @@ public class DateHelper return df.format(date); } + public static SimpleDateFormat getCSVDateFormat() + { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + + return dateFormat; + } + public static String formatHeaderDate(GregorianCalendar day) { String dayOfMonth = Integer.toString(day.get(GregorianCalendar.DAY_OF_MONTH)); diff --git a/app/src/main/java/org/isoron/uhabits/dialogs/HabitSelectionCallback.java b/app/src/main/java/org/isoron/uhabits/dialogs/HabitSelectionCallback.java index 47a23cf28..f7c615353 100644 --- a/app/src/main/java/org/isoron/uhabits/dialogs/HabitSelectionCallback.java +++ b/app/src/main/java/org/isoron/uhabits/dialogs/HabitSelectionCallback.java @@ -42,7 +42,7 @@ import org.isoron.uhabits.commands.ChangeHabitColorCommand; import org.isoron.uhabits.commands.DeleteHabitsCommand; import org.isoron.uhabits.commands.UnarchiveHabitsCommand; import org.isoron.uhabits.fragments.EditHabitFragment; -import org.isoron.uhabits.io.CSVExporter; +import org.isoron.uhabits.io.HabitsExporter; import org.isoron.uhabits.loaders.HabitListLoader; import org.isoron.uhabits.models.Habit; @@ -226,7 +226,7 @@ public class HabitSelectionCallback implements ActionMode.Callback { new AsyncTask() { - String filename; + String archiveFilename; @Override protected void onPreExecute() @@ -241,15 +241,19 @@ public class HabitSelectionCallback implements ActionMode.Callback @Override protected void onPostExecute(Void aVoid) { - if(filename != null) + if(archiveFilename != null) { Intent intent = new Intent(); intent.setAction(Intent.ACTION_SEND); intent.setType("application/zip"); - intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(filename))); + intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(archiveFilename))); activity.startActivity(intent); } + else + { + activity.showToast(R.string.could_not_export); + } if(progressBar != null) progressBar.setVisibility(View.GONE); @@ -258,8 +262,10 @@ public class HabitSelectionCallback implements ActionMode.Callback @Override protected Void doInBackground(Void... params) { - CSVExporter exporter = new CSVExporter(activity, selectedHabits); - filename = exporter.writeArchive(); + String dirName = String.format("%s/export/", activity.getExternalCacheDir()); + HabitsExporter exporter = new HabitsExporter(selectedHabits, dirName); + archiveFilename = exporter.writeArchive(); + return null; } }.execute(); diff --git a/app/src/main/java/org/isoron/uhabits/io/CSVExporter.java b/app/src/main/java/org/isoron/uhabits/io/CSVExporter.java deleted file mode 100644 index d5690e0a1..000000000 --- a/app/src/main/java/org/isoron/uhabits/io/CSVExporter.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2016 Álinson Santos Xavier - * - * 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 . - */ - -package org.isoron.uhabits.io; - -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.util.Log; - -import com.activeandroid.Cache; - -import org.isoron.helpers.DateHelper; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.models.Score; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.FileWriter; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.TimeZone; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -public class CSVExporter -{ - private List habits; - private Context context; - private java.text.DateFormat dateFormat; - - private List generateDirs; - private List generateFilenames; - - private String basePath; - - public CSVExporter(Context context, List habits) - { - this.habits = habits; - this.context = context; - generateDirs = new LinkedList<>(); - generateFilenames = new LinkedList<>(); - - basePath = String.format("%s/export/", context.getFilesDir()); - - dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.US); - dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - } - - public String formatDate(long timestamp) - { - return dateFormat.format(new Date(timestamp)); - } - - public String formatScore(int score) - { - return String.format("%.2f", ((float) score) / Score.MAX_VALUE); - } - - private void writeScores(String dirPath, Habit habit) throws IOException - { - String path = dirPath + "scores.csv"; - FileWriter out = new FileWriter(basePath + path); - generateFilenames.add(path); - - String query = "select timestamp, score from score where habit = ? order by timestamp"; - String params[] = { habit.getId().toString() }; - - SQLiteDatabase db = Cache.openDatabase(); - Cursor cursor = db.rawQuery(query, params); - - if(!cursor.moveToFirst()) return; - - do - { - String timestamp = formatDate(cursor.getLong(0)); - String score = formatScore(cursor.getInt(1)); - out.write(String.format("%s,%s\n", timestamp, score)); - - } while(cursor.moveToNext()); - - out.close(); - cursor.close(); - } - - private void writeCheckmarks(String dirPath, Habit habit) throws IOException - { - String path = dirPath + "checkmarks.csv"; - FileWriter out = new FileWriter(basePath + path); - generateFilenames.add(path); - - String query = "select timestamp, value from checkmarks where habit = ? order by timestamp"; - String params[] = { habit.getId().toString() }; - - SQLiteDatabase db = Cache.openDatabase(); - Cursor cursor = db.rawQuery(query, params); - - if(!cursor.moveToFirst()) return; - - do - { - String timestamp = formatDate(cursor.getLong(0)); - Integer value = cursor.getInt(1); - out.write(String.format("%s,%d\n", timestamp, value)); - - } while(cursor.moveToNext()); - - out.close(); - cursor.close(); - } - - private void writeFiles(Habit habit) throws IOException - { - String path = String.format("%s/", habit.name); - new File(basePath + path).mkdirs(); - generateDirs.add(path); - - writeScores(path, habit); - writeCheckmarks(path, habit); - } - - private void writeZipFile(String zipFilename) throws IOException - { - FileOutputStream fos = new FileOutputStream(zipFilename); - ZipOutputStream zos = new ZipOutputStream(fos); - - for(String filename : generateFilenames) - addFileToZip(zos, filename); - - zos.close(); - fos.close(); - } - - private void addFileToZip(ZipOutputStream zos, String filename) throws IOException - { - FileInputStream fis = new FileInputStream(new File(basePath + 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(basePath + filename).delete(); - - for(String filename : generateDirs) - new File(basePath + filename).delete(); - - new File(basePath).delete(); - } - - public String writeArchive() - { - String date = formatDate(DateHelper.getStartOfToday()); - - File dir = context.getExternalCacheDir(); - - if(dir == null) - { - Log.e("CSVExporter", "No suitable directory found."); - return null; - } - - String zipFilename = String.format("%s/habits-%s.zip", dir, date); - - try - { - for (Habit h : habits) - writeFiles(h); - - writeZipFile(zipFilename); - cleanup(); - } - catch (IOException e) - { - e.printStackTrace(); - return null; - } - - return zipFilename; - } - - -} diff --git a/app/src/main/java/org/isoron/uhabits/io/HabitsExporter.java b/app/src/main/java/org/isoron/uhabits/io/HabitsExporter.java new file mode 100644 index 000000000..c3674220e --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/io/HabitsExporter.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2016 Álinson Santos Xavier + * + * 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 . + */ + +package org.isoron.uhabits.io; + +import org.isoron.helpers.DateHelper; +import org.isoron.uhabits.models.CheckmarkList; +import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.ScoreList; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.LinkedList; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +public class HabitsExporter +{ + private List habits; + + private List generateDirs; + private List generateFilenames; + + private String exportDirName; + + public HabitsExporter(List habits, String exportDirName) + { + this.habits = habits; + this.exportDirName = exportDirName; + + if(!this.exportDirName.endsWith("/")) + this.exportDirName += "/"; + + generateDirs = new LinkedList<>(); + generateFilenames = new LinkedList<>(); + } + + private void writeHabits() throws IOException + { + String filename = "habits.csv"; + new File(exportDirName).mkdirs(); + FileWriter out = new FileWriter(exportDirName + filename); + generateFilenames.add(filename); + Habit.writeCSV(habits, out); + out.close(); + + for(Habit h : habits) + { + String habitDirName = String.format("%s/", h.name); + new File(exportDirName + habitDirName).mkdirs(); + generateDirs.add(habitDirName); + + writeScores(habitDirName, h.scores); + writeCheckmarks(habitDirName, h.checkmarks); + } + } + + 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(); + } + + private String writeZipFile() throws IOException + { + SimpleDateFormat dateFormat = DateHelper.getCSVDateFormat(); + String date = dateFormat.format(DateHelper.getStartOfToday()); + String zipFilename = String.format("%s/habits-%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; + } + + 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(); + } + + public String writeArchive() + { + String zipFilename; + + try + { + writeHabits(); + zipFilename = writeZipFile(); + cleanup(); + } + catch (IOException e) + { + e.printStackTrace(); + return null; + } + + return zipFilename; + } + + 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(); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java index d4fd67617..99448f335 100644 --- a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java +++ b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java @@ -31,6 +31,10 @@ import com.activeandroid.query.Select; import org.isoron.helpers.DateHelper; +import java.io.IOException; +import java.io.Writer; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.List; public class CheckmarkList @@ -229,4 +233,27 @@ public class CheckmarkList if(today != null) return today.value; else return Checkmark.UNCHECKED; } + + public void writeCSV(Writer out) throws IOException + { + SimpleDateFormat dateFormat = DateHelper.getCSVDateFormat(); + + String query = "select timestamp, value from checkmarks where habit = ? order by timestamp"; + String params[] = { habit.getId().toString() }; + + SQLiteDatabase db = Cache.openDatabase(); + Cursor cursor = db.rawQuery(query, params); + + if(!cursor.moveToFirst()) return; + + do + { + String timestamp = dateFormat.format(new Date(cursor.getLong(0))); + Integer value = cursor.getInt(1); + out.write(String.format("%s,%d\n", timestamp, value)); + + } while(cursor.moveToNext()); + + cursor.close(); + } } diff --git a/app/src/main/java/org/isoron/uhabits/models/Habit.java b/app/src/main/java/org/isoron/uhabits/models/Habit.java index cc89521e4..e93d73abc 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Habit.java +++ b/app/src/main/java/org/isoron/uhabits/models/Habit.java @@ -20,6 +20,7 @@ package org.isoron.uhabits.models; import android.annotation.SuppressLint; +import android.graphics.Color; import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -33,10 +34,17 @@ import com.activeandroid.query.From; import com.activeandroid.query.Select; import com.activeandroid.query.Update; import com.activeandroid.util.SQLiteUtils; +import com.opencsv.CSVReader; +import com.opencsv.CSVWriter; import org.isoron.helpers.ColorHelper; import org.isoron.helpers.DateHelper; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.util.Collections; +import java.util.LinkedList; import java.util.List; import java.util.Locale; @@ -470,4 +478,38 @@ public class Habit extends Model reminderMin = null; reminderDays = DateHelper.ALL_WEEK_DAYS; } + + public static void writeCSV(List habits, Writer out) throws IOException + { + CSVWriter csv = new CSVWriter(out); + + for(Habit habit : habits) + { + String[] cols = { habit.name, habit.description, Integer.toString(habit.freqNum), + Integer.toString(habit.freqDen), ColorHelper.toHTML(habit.color) }; + csv.writeAll(Collections.singletonList(cols)); + } + + csv.close(); + } + + public List parseCSV(Reader in) + { + CSVReader csv = new CSVReader(in); + List habits = new LinkedList<>(); + + for(String cols[] : csv) + { + Habit habit = new Habit(); + + habit.name = cols[0]; + habit.description = cols[1]; + habit.freqNum = Integer.parseInt(cols[2]); + habit.freqDen = Integer.parseInt(cols[3]); + habit.color = Color.parseColor(cols[4]); + habits.add(habit); + } + + return habits; + } } diff --git a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java index 1a267c301..23c10ec24 100644 --- a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java +++ b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java @@ -24,7 +24,6 @@ import android.database.sqlite.SQLiteDatabase; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import com.activeandroid.ActiveAndroid; import com.activeandroid.Cache; import com.activeandroid.query.Delete; import com.activeandroid.query.From; @@ -33,6 +32,11 @@ import com.activeandroid.query.Select; import org.isoron.helpers.ActiveAndroidHelper; import org.isoron.helpers.DateHelper; +import java.io.IOException; +import java.io.Writer; +import java.text.SimpleDateFormat; +import java.util.Date; + public class ScoreList { @NonNull @@ -278,4 +282,27 @@ public class ScoreList if(score != null) return score.getStarStatus(); else return Score.EMPTY_STAR; } + + public void writeCSV(Writer out) throws IOException + { + SimpleDateFormat dateFormat = DateHelper.getCSVDateFormat(); + + String query = "select timestamp, score from score where habit = ? order by timestamp"; + String params[] = { habit.getId().toString() }; + + SQLiteDatabase db = Cache.openDatabase(); + Cursor cursor = db.rawQuery(query, params); + + if(!cursor.moveToFirst()) return; + + do + { + String timestamp = dateFormat.format(new Date(cursor.getLong(0))); + String score = String.format("%.2f", ((float) cursor.getInt(1)) / Score.MAX_VALUE); + out.write(String.format("%s,%s\n", timestamp, score)); + + } while(cursor.moveToNext()); + + cursor.close(); + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3417ff2d1..b1933b4b2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -138,4 +138,5 @@ 5 times per week Custom … Help & FAQ + Failed to export data. \ No newline at end of file