mirror of https://github.com/iSoron/uhabits.git
parent
ced78e0b1f
commit
79b134164a
@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 Á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.core.io;
|
|
||||||
|
|
||||||
import androidx.annotation.*;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.core.models.*;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AbstractImporter is the base class for all classes that import data from
|
|
||||||
* files into the app.
|
|
||||||
*/
|
|
||||||
public abstract class AbstractImporter
|
|
||||||
{
|
|
||||||
protected final HabitList habitList;
|
|
||||||
|
|
||||||
public AbstractImporter(HabitList habitList)
|
|
||||||
{
|
|
||||||
this.habitList = habitList;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract boolean canHandle(@NonNull File file) throws IOException;
|
|
||||||
|
|
||||||
public abstract void importHabitsFromFile(@NonNull File file) throws IOException;
|
|
||||||
|
|
||||||
public static boolean isSQLite3File(@NonNull File file) throws IOException
|
|
||||||
{
|
|
||||||
FileInputStream fis = new FileInputStream(file);
|
|
||||||
|
|
||||||
byte[] sqliteHeader = "SQLite format 3".getBytes();
|
|
||||||
byte[] buffer = new byte[sqliteHeader.length];
|
|
||||||
|
|
||||||
int count = fis.read(buffer);
|
|
||||||
if(count < sqliteHeader.length) return false;
|
|
||||||
|
|
||||||
return Arrays.equals(buffer, sqliteHeader);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 Á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.core.io
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
abstract class AbstractImporter {
|
||||||
|
abstract fun canHandle(file: File): Boolean
|
||||||
|
abstract fun importHabitsFromFile(file: File)
|
||||||
|
}
|
@ -1,293 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.core.io;
|
|
||||||
|
|
||||||
import androidx.annotation.*;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.core.models.*;
|
|
||||||
import org.isoron.uhabits.core.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(Locale.US, "%03d %s", allHabits.indexOf(h) + 1, sane);
|
|
||||||
habitDirName = habitDirName.trim() + "/";
|
|
||||||
|
|
||||||
new File(exportDirName + habitDirName).mkdirs();
|
|
||||||
generateDirs.add(habitDirName);
|
|
||||||
|
|
||||||
writeScores(habitDirName, h);
|
|
||||||
writeEntries(habitDirName, h.getComputedEntries());
|
|
||||||
}
|
|
||||||
|
|
||||||
writeMultipleHabits();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeScores(String habitDirName, Habit habit)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
String path = habitDirName + "Scores.csv";
|
|
||||||
FileWriter out = new FileWriter(exportDirName + path);
|
|
||||||
generateFilenames.add(path);
|
|
||||||
SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat();
|
|
||||||
|
|
||||||
Timestamp today = DateUtils.getTodayWithOffset();
|
|
||||||
Timestamp oldest = today;
|
|
||||||
List<Entry> known = habit.getComputedEntries().getKnown();
|
|
||||||
if(!known.isEmpty()) oldest = known.get(known.size() - 1).getTimestamp();
|
|
||||||
|
|
||||||
for (Score s : habit.getScores().getByInterval(oldest, today))
|
|
||||||
{
|
|
||||||
String timestamp = dateFormat.format(s.getTimestamp().getUnixTime());
|
|
||||||
String score = String.format(Locale.US, "%.4f", s.getValue());
|
|
||||||
out.write(String.format("%s,%s\n", timestamp, score));
|
|
||||||
}
|
|
||||||
out.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeEntries(String habitDirName, EntryList entries)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
String filename = habitDirName + "Checkmarks.csv";
|
|
||||||
FileWriter out = new FileWriter(exportDirName + filename);
|
|
||||||
generateFilenames.add(filename);
|
|
||||||
|
|
||||||
SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat();
|
|
||||||
for (Entry e : entries.getKnown())
|
|
||||||
{
|
|
||||||
String date = dateFormat.format(e.getTimestamp().toJavaDate());
|
|
||||||
out.write(String.format(Locale.US, "%s,%d\n", date, e.getValue()));
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
Timestamp[] timeframe = getTimeframe();
|
|
||||||
Timestamp oldest = timeframe[0];
|
|
||||||
Timestamp newest = DateUtils.getToday();
|
|
||||||
|
|
||||||
List<int[]> checkmarks = new ArrayList<>();
|
|
||||||
List<ArrayList<Score>> scores = new ArrayList<>();
|
|
||||||
for (Habit h : selectedHabits)
|
|
||||||
{
|
|
||||||
checkmarks.add(h.getComputedEntries().getValues(oldest, newest));
|
|
||||||
scores.add(new ArrayList<>(h.getScores().getByInterval(oldest, newest)));
|
|
||||||
}
|
|
||||||
|
|
||||||
int days = oldest.daysUntil(newest);
|
|
||||||
SimpleDateFormat dateFormat = DateFormats.getCSVDateFormat();
|
|
||||||
for (int i = 0; i <= days; i++)
|
|
||||||
{
|
|
||||||
Date day = newest.minus(i).toJavaDate();
|
|
||||||
|
|
||||||
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(Locale.US, "%.4f", scores.get(j).get(i).getValue());
|
|
||||||
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 Timestamp[] getTimeframe()
|
|
||||||
{
|
|
||||||
Timestamp oldest = Timestamp.ZERO.plus(1000000);
|
|
||||||
Timestamp newest = Timestamp.ZERO;
|
|
||||||
for (Habit h : selectedHabits)
|
|
||||||
{
|
|
||||||
List<Entry> entries = h.getOriginalEntries().getKnown();
|
|
||||||
if (entries.isEmpty()) continue;
|
|
||||||
Timestamp currNew = entries.get(0).getTimestamp();
|
|
||||||
Timestamp currOld = entries.get(entries.size() - 1).getTimestamp();
|
|
||||||
oldest = currOld.isOlderThan(oldest) ? currOld : oldest;
|
|
||||||
newest = currNew.isNewerThan(newest) ? currNew : newest;
|
|
||||||
}
|
|
||||||
return new Timestamp[]{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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,231 @@
|
|||||||
|
/*
|
||||||
|
* 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.core.io
|
||||||
|
|
||||||
|
import org.isoron.uhabits.core.models.EntryList
|
||||||
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.models.HabitList
|
||||||
|
import org.isoron.uhabits.core.models.Score
|
||||||
|
import org.isoron.uhabits.core.models.Timestamp
|
||||||
|
import org.isoron.uhabits.core.utils.DateFormats
|
||||||
|
import org.isoron.uhabits.core.utils.DateUtils
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.FileWriter
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.Writer
|
||||||
|
import java.util.ArrayList
|
||||||
|
import java.util.LinkedList
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.zip.ZipEntry
|
||||||
|
import java.util.zip.ZipOutputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that exports the application data to CSV files.
|
||||||
|
*/
|
||||||
|
class HabitsCSVExporter(
|
||||||
|
private val allHabits: HabitList,
|
||||||
|
private val selectedHabits: List<Habit>,
|
||||||
|
dir: File
|
||||||
|
) {
|
||||||
|
private val generatedDirs = LinkedList<String>()
|
||||||
|
private val generatedFilenames = LinkedList<String>()
|
||||||
|
private val exportDirName: String = dir.absolutePath + "/"
|
||||||
|
private val delimiter = ","
|
||||||
|
|
||||||
|
fun writeArchive(): String {
|
||||||
|
writeHabits()
|
||||||
|
val zipFilename = writeZipFile()
|
||||||
|
cleanup()
|
||||||
|
return zipFilename
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addFileToZip(zos: ZipOutputStream, filename: String) {
|
||||||
|
val fis = FileInputStream(File(exportDirName + filename))
|
||||||
|
val ze = ZipEntry(filename)
|
||||||
|
zos.putNextEntry(ze)
|
||||||
|
var length: Int
|
||||||
|
val bytes = ByteArray(1024)
|
||||||
|
while (fis.read(bytes).also { length = it } >= 0) zos.write(bytes, 0, length)
|
||||||
|
zos.closeEntry()
|
||||||
|
fis.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun cleanup() {
|
||||||
|
for (filename in generatedFilenames) File(exportDirName + filename).delete()
|
||||||
|
for (filename in generatedDirs) File(exportDirName + filename).delete()
|
||||||
|
File(exportDirName).delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sanitizeFilename(name: String): String {
|
||||||
|
val s = name.replace("[^ a-zA-Z0-9\\._-]+".toRegex(), "")
|
||||||
|
return s.substring(0, Math.min(s.length, 100))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeHabits() {
|
||||||
|
val filename = "Habits.csv"
|
||||||
|
File(exportDirName).mkdirs()
|
||||||
|
val out = FileWriter(exportDirName + filename)
|
||||||
|
generatedFilenames.add(filename)
|
||||||
|
allHabits.writeCSV(out)
|
||||||
|
out.close()
|
||||||
|
for (h in selectedHabits) {
|
||||||
|
val sane = sanitizeFilename(h.name)
|
||||||
|
var habitDirName = String.format(Locale.US, "%03d %s", allHabits.indexOf(h) + 1, sane)
|
||||||
|
habitDirName = habitDirName.trim() + "/"
|
||||||
|
File(exportDirName + habitDirName).mkdirs()
|
||||||
|
generatedDirs.add(habitDirName)
|
||||||
|
writeScores(habitDirName, h)
|
||||||
|
writeEntries(habitDirName, h.computedEntries)
|
||||||
|
}
|
||||||
|
writeMultipleHabits()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeScores(habitDirName: String, habit: Habit) {
|
||||||
|
val path = habitDirName + "Scores.csv"
|
||||||
|
val out = FileWriter(exportDirName + path)
|
||||||
|
generatedFilenames.add(path)
|
||||||
|
val dateFormat = DateFormats.getCSVDateFormat()
|
||||||
|
val today = DateUtils.getTodayWithOffset()
|
||||||
|
var oldest = today
|
||||||
|
val known = habit.computedEntries.getKnown()
|
||||||
|
if (known.isNotEmpty()) oldest = known[known.size - 1].timestamp
|
||||||
|
for ((timestamp1, value) in habit.scores.getByInterval(oldest, today)) {
|
||||||
|
val timestamp = dateFormat.format(timestamp1.unixTime)
|
||||||
|
val score = String.format(Locale.US, "%.4f", value)
|
||||||
|
out.write(String.format("%s,%s\n", timestamp, score))
|
||||||
|
}
|
||||||
|
out.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeEntries(habitDirName: String, entries: EntryList) {
|
||||||
|
val filename = habitDirName + "Checkmarks.csv"
|
||||||
|
val out = FileWriter(exportDirName + filename)
|
||||||
|
generatedFilenames.add(filename)
|
||||||
|
val dateFormat = DateFormats.getCSVDateFormat()
|
||||||
|
for ((timestamp, value) in entries.getKnown()) {
|
||||||
|
val date = dateFormat.format(timestamp.toJavaDate())
|
||||||
|
out.write(String.format(Locale.US, "%s,%d\n", date, value))
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
private fun writeMultipleHabits() {
|
||||||
|
val scoresFileName = "Scores.csv"
|
||||||
|
val checksFileName = "Checkmarks.csv"
|
||||||
|
generatedFilenames.add(scoresFileName)
|
||||||
|
generatedFilenames.add(checksFileName)
|
||||||
|
|
||||||
|
val scoresWriter = FileWriter(exportDirName + scoresFileName)
|
||||||
|
val checksWriter = FileWriter(exportDirName + checksFileName)
|
||||||
|
writeMultipleHabitsHeader(scoresWriter)
|
||||||
|
writeMultipleHabitsHeader(checksWriter)
|
||||||
|
|
||||||
|
val timeframe = getTimeframe()
|
||||||
|
val oldest = timeframe[0]
|
||||||
|
val newest = DateUtils.getToday()
|
||||||
|
val checkmarks: MutableList<IntArray> = ArrayList()
|
||||||
|
val scores: MutableList<ArrayList<Score>> = ArrayList()
|
||||||
|
for (habit in selectedHabits) {
|
||||||
|
checkmarks.add(habit.computedEntries.getValues(oldest, newest))
|
||||||
|
scores.add(ArrayList(habit.scores.getByInterval(oldest, newest)))
|
||||||
|
}
|
||||||
|
|
||||||
|
val days = oldest.daysUntil(newest)
|
||||||
|
val dateFormat = DateFormats.getCSVDateFormat()
|
||||||
|
for (i in 0..days) {
|
||||||
|
val day = newest.minus(i).toJavaDate()
|
||||||
|
val date = dateFormat.format(day)
|
||||||
|
val sb = StringBuilder()
|
||||||
|
sb.append(date).append(delimiter)
|
||||||
|
checksWriter.write(sb.toString())
|
||||||
|
scoresWriter.write(sb.toString())
|
||||||
|
for (j in selectedHabits.indices) {
|
||||||
|
checksWriter.write(checkmarks[j][i].toString())
|
||||||
|
checksWriter.write(delimiter)
|
||||||
|
val score = String.format(Locale.US, "%.4f", scores[j][i].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
|
||||||
|
*/
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun writeMultipleHabitsHeader(out: Writer) {
|
||||||
|
out.write("Date$delimiter")
|
||||||
|
for (habit in selectedHabits) {
|
||||||
|
out.write(habit.name)
|
||||||
|
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 fun getTimeframe(): Array<Timestamp> {
|
||||||
|
var oldest = Timestamp.ZERO.plus(1000000)
|
||||||
|
var newest = Timestamp.ZERO
|
||||||
|
for (habit in selectedHabits) {
|
||||||
|
val entries = habit.originalEntries.getKnown()
|
||||||
|
if (entries.isEmpty()) continue
|
||||||
|
val currNew = entries[0].timestamp
|
||||||
|
val currOld = entries[entries.size - 1].timestamp
|
||||||
|
oldest = if (currOld.isOlderThan(oldest)) currOld else oldest
|
||||||
|
newest = if (currNew.isNewerThan(newest)) currNew else newest
|
||||||
|
}
|
||||||
|
return arrayOf(oldest, newest)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeZipFile(): String {
|
||||||
|
val dateFormat = DateFormats.getCSVDateFormat()
|
||||||
|
val date = dateFormat.format(DateUtils.getStartOfToday())
|
||||||
|
val zipFilename = String.format("%s/Loop Habits CSV %s.zip", exportDirName, date)
|
||||||
|
val fos = FileOutputStream(zipFilename)
|
||||||
|
val zos = ZipOutputStream(fos)
|
||||||
|
for (filename in generatedFilenames) addFileToZip(zos, filename)
|
||||||
|
zos.close()
|
||||||
|
fos.close()
|
||||||
|
return zipFilename
|
||||||
|
}
|
||||||
|
}
|
@ -1,216 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 Á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.core.io;
|
|
||||||
|
|
||||||
import androidx.annotation.*;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.core.database.*;
|
|
||||||
import org.isoron.uhabits.core.models.*;
|
|
||||||
import org.isoron.uhabits.core.utils.*;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import javax.inject.*;
|
|
||||||
|
|
||||||
import static org.isoron.uhabits.core.models.Entry.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class that imports database files exported by Rewire.
|
|
||||||
*/
|
|
||||||
public class RewireDBImporter extends AbstractImporter
|
|
||||||
{
|
|
||||||
private ModelFactory modelFactory;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private final DatabaseOpener opener;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public RewireDBImporter(@NonNull HabitList habits,
|
|
||||||
@NonNull ModelFactory modelFactory,
|
|
||||||
@NonNull DatabaseOpener opener)
|
|
||||||
{
|
|
||||||
super(habits);
|
|
||||||
this.modelFactory = modelFactory;
|
|
||||||
this.opener = opener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canHandle(@NonNull File file) throws IOException
|
|
||||||
{
|
|
||||||
if (!isSQLite3File(file)) return false;
|
|
||||||
|
|
||||||
Database db = opener.open(file);
|
|
||||||
Cursor c = db.query("select count(*) from SQLITE_MASTER " +
|
|
||||||
"where name='CHECKINS' or name='UNIT'");
|
|
||||||
|
|
||||||
boolean result = (c.moveToNext() && c.getInt(0) == 2);
|
|
||||||
|
|
||||||
c.close();
|
|
||||||
db.close();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void importHabitsFromFile(@NonNull File file) throws IOException
|
|
||||||
{
|
|
||||||
Database db = opener.open(file);
|
|
||||||
db.beginTransaction();
|
|
||||||
createHabits(db);
|
|
||||||
db.setTransactionSuccessful();
|
|
||||||
db.endTransaction();
|
|
||||||
db.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createHabits(Database db)
|
|
||||||
{
|
|
||||||
Cursor c = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
c = db.query("select _id, name, description, schedule, " +
|
|
||||||
"active_days, repeating_count, days, period " +
|
|
||||||
"from habits");
|
|
||||||
if (!c.moveToNext()) return;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
int id = c.getInt(0);
|
|
||||||
String name = c.getString(1);
|
|
||||||
String description = c.getString(2);
|
|
||||||
int schedule = c.getInt(3);
|
|
||||||
String activeDays = c.getString(4);
|
|
||||||
int repeatingCount = c.getInt(5);
|
|
||||||
int days = c.getInt(6);
|
|
||||||
int periodIndex = c.getInt(7);
|
|
||||||
|
|
||||||
Habit habit = modelFactory.buildHabit();
|
|
||||||
habit.setName(name);
|
|
||||||
habit.setDescription(description == null ? "" : description);
|
|
||||||
|
|
||||||
int periods[] = { 7, 31, 365 };
|
|
||||||
int numerator, denominator;
|
|
||||||
|
|
||||||
switch (schedule)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
numerator = activeDays.split(",").length;
|
|
||||||
denominator = 7;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
numerator = days;
|
|
||||||
denominator = (periods[periodIndex]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
numerator = 1;
|
|
||||||
denominator = repeatingCount;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException();
|
|
||||||
}
|
|
||||||
|
|
||||||
habit.setFrequency(new Frequency(numerator, denominator));
|
|
||||||
habitList.add(habit);
|
|
||||||
|
|
||||||
createReminder(db, habit, id);
|
|
||||||
createCheckmarks(db, habit, id);
|
|
||||||
|
|
||||||
} while (c.moveToNext());
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (c != null) c.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createCheckmarks(@NonNull Database db,
|
|
||||||
@NonNull Habit habit,
|
|
||||||
int rewireHabitId)
|
|
||||||
{
|
|
||||||
Cursor c = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
String[] params = { Integer.toString(rewireHabitId) };
|
|
||||||
c = db.query(
|
|
||||||
"select distinct date from checkins where habit_id=? and type=2",
|
|
||||||
params);
|
|
||||||
if (!c.moveToNext()) return;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
String date = c.getString(0);
|
|
||||||
int year = Integer.parseInt(date.substring(0, 4));
|
|
||||||
int month = Integer.parseInt(date.substring(4, 6));
|
|
||||||
int day = Integer.parseInt(date.substring(6, 8));
|
|
||||||
|
|
||||||
GregorianCalendar cal = DateUtils.getStartOfTodayCalendar();
|
|
||||||
cal.set(year, month - 1, day);
|
|
||||||
|
|
||||||
habit.getOriginalEntries().add(new Entry(new Timestamp(cal), YES_MANUAL));
|
|
||||||
} while (c.moveToNext());
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (c != null) c.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createReminder(Database db, Habit habit, int rewireHabitId)
|
|
||||||
{
|
|
||||||
String[] params = { Integer.toString(rewireHabitId) };
|
|
||||||
Cursor c = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
c = db.query(
|
|
||||||
"select time, active_days from reminders where habit_id=? limit 1",
|
|
||||||
params);
|
|
||||||
|
|
||||||
if (!c.moveToNext()) return;
|
|
||||||
int rewireReminder = Integer.parseInt(c.getString(0));
|
|
||||||
if (rewireReminder <= 0 || rewireReminder >= 1440) return;
|
|
||||||
|
|
||||||
boolean reminderDays[] = new boolean[7];
|
|
||||||
|
|
||||||
String activeDays[] = c.getString(1).split(",");
|
|
||||||
for (String d : activeDays)
|
|
||||||
{
|
|
||||||
int idx = (Integer.parseInt(d) + 1) % 7;
|
|
||||||
reminderDays[idx] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
int hour = rewireReminder / 60;
|
|
||||||
int minute = rewireReminder % 60;
|
|
||||||
WeekdayList days = new WeekdayList(reminderDays);
|
|
||||||
|
|
||||||
Reminder reminder = new Reminder(hour, minute, days);
|
|
||||||
habit.setReminder(reminder);
|
|
||||||
habitList.update(habit);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (c != null) c.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 Á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.core.io
|
||||||
|
|
||||||
|
import org.isoron.uhabits.core.database.Cursor
|
||||||
|
import org.isoron.uhabits.core.database.Database
|
||||||
|
import org.isoron.uhabits.core.database.DatabaseOpener
|
||||||
|
import org.isoron.uhabits.core.models.Entry
|
||||||
|
import org.isoron.uhabits.core.models.Frequency
|
||||||
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.models.HabitList
|
||||||
|
import org.isoron.uhabits.core.models.ModelFactory
|
||||||
|
import org.isoron.uhabits.core.models.Reminder
|
||||||
|
import org.isoron.uhabits.core.models.Timestamp
|
||||||
|
import org.isoron.uhabits.core.models.WeekdayList
|
||||||
|
import org.isoron.uhabits.core.utils.DateUtils
|
||||||
|
import org.isoron.uhabits.core.utils.isSQLite3File
|
||||||
|
import java.io.File
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that imports database files exported by Rewire.
|
||||||
|
*/
|
||||||
|
class RewireDBImporter
|
||||||
|
@Inject constructor(
|
||||||
|
private val habitList: HabitList,
|
||||||
|
private val modelFactory: ModelFactory,
|
||||||
|
private val opener: DatabaseOpener
|
||||||
|
) : AbstractImporter() {
|
||||||
|
|
||||||
|
override fun canHandle(file: File): Boolean {
|
||||||
|
if (!file.isSQLite3File()) return false
|
||||||
|
val db = opener.open(file)
|
||||||
|
val c = db.query(
|
||||||
|
"select count(*) from SQLITE_MASTER " +
|
||||||
|
"where name='CHECKINS' or name='UNIT'"
|
||||||
|
)
|
||||||
|
val result = c.moveToNext() && c.getInt(0) == 2
|
||||||
|
c.close()
|
||||||
|
db.close()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun importHabitsFromFile(file: File) {
|
||||||
|
val db = opener.open(file)
|
||||||
|
db.beginTransaction()
|
||||||
|
createHabits(db)
|
||||||
|
db.setTransactionSuccessful()
|
||||||
|
db.endTransaction()
|
||||||
|
db.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createHabits(db: Database) {
|
||||||
|
var c: Cursor? = null
|
||||||
|
try {
|
||||||
|
c = db.query(
|
||||||
|
"select _id, name, description, schedule, " +
|
||||||
|
"active_days, repeating_count, days, period " +
|
||||||
|
"from habits"
|
||||||
|
)
|
||||||
|
if (!c.moveToNext()) return
|
||||||
|
do {
|
||||||
|
val id = c.getInt(0)!!
|
||||||
|
val name = c.getString(1)
|
||||||
|
val description = c.getString(2)
|
||||||
|
val schedule = c.getInt(3)!!
|
||||||
|
val activeDays = c.getString(4)
|
||||||
|
val repeatingCount = c.getInt(5)!!
|
||||||
|
val days = c.getInt(6)!!
|
||||||
|
val periodIndex = c.getInt(7)!!
|
||||||
|
|
||||||
|
val habit = modelFactory.buildHabit()
|
||||||
|
habit.name = name!!
|
||||||
|
habit.description = description ?: ""
|
||||||
|
val periods = intArrayOf(7, 31, 365)
|
||||||
|
var numerator: Int
|
||||||
|
var denominator: Int
|
||||||
|
when (schedule) {
|
||||||
|
0 -> {
|
||||||
|
numerator = activeDays!!.split(",").toTypedArray().size
|
||||||
|
denominator = 7
|
||||||
|
}
|
||||||
|
1 -> {
|
||||||
|
numerator = days
|
||||||
|
denominator = periods[periodIndex]
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
numerator = 1
|
||||||
|
denominator = repeatingCount
|
||||||
|
}
|
||||||
|
else -> throw IllegalStateException()
|
||||||
|
}
|
||||||
|
habit.frequency = Frequency(numerator, denominator)
|
||||||
|
habitList.add(habit)
|
||||||
|
createReminder(db, habit, id)
|
||||||
|
createCheckmarks(db, habit, id)
|
||||||
|
} while (c.moveToNext())
|
||||||
|
} finally {
|
||||||
|
c?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createCheckmarks(
|
||||||
|
db: Database,
|
||||||
|
habit: Habit,
|
||||||
|
rewireHabitId: Int
|
||||||
|
) {
|
||||||
|
var c: Cursor? = null
|
||||||
|
try {
|
||||||
|
c = db.query(
|
||||||
|
"select distinct date from checkins where habit_id=? and type=2",
|
||||||
|
rewireHabitId.toString(),
|
||||||
|
)
|
||||||
|
if (!c.moveToNext()) return
|
||||||
|
do {
|
||||||
|
val date = c.getString(0)
|
||||||
|
val year = date!!.substring(0, 4).toInt()
|
||||||
|
val month = date.substring(4, 6).toInt()
|
||||||
|
val day = date.substring(6, 8).toInt()
|
||||||
|
val cal = DateUtils.getStartOfTodayCalendar()
|
||||||
|
cal[year, month - 1] = day
|
||||||
|
habit.originalEntries.add(Entry(Timestamp(cal), Entry.YES_MANUAL))
|
||||||
|
} while (c.moveToNext())
|
||||||
|
} finally {
|
||||||
|
c?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createReminder(db: Database, habit: Habit, rewireHabitId: Int) {
|
||||||
|
var c: Cursor? = null
|
||||||
|
try {
|
||||||
|
c = db.query(
|
||||||
|
"select time, active_days from reminders where habit_id=? limit 1",
|
||||||
|
rewireHabitId.toString(),
|
||||||
|
)
|
||||||
|
if (!c.moveToNext()) return
|
||||||
|
val rewireReminder = c.getInt(0)!!
|
||||||
|
if (rewireReminder <= 0 || rewireReminder >= 1440) return
|
||||||
|
val reminderDays = BooleanArray(7)
|
||||||
|
val activeDays = c.getString(1)!!.split(",").toTypedArray()
|
||||||
|
for (d in activeDays) {
|
||||||
|
val idx = (d.toInt() + 1) % 7
|
||||||
|
reminderDays[idx] = true
|
||||||
|
}
|
||||||
|
val hour = rewireReminder / 60
|
||||||
|
val minute = rewireReminder % 60
|
||||||
|
val days = WeekdayList(reminderDays)
|
||||||
|
val reminder = Reminder(hour, minute, days)
|
||||||
|
habit.reminder = reminder
|
||||||
|
habitList.update(habit)
|
||||||
|
} finally {
|
||||||
|
c?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,145 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2017 Á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.core.io;
|
|
||||||
|
|
||||||
import androidx.annotation.*;
|
|
||||||
|
|
||||||
import org.isoron.uhabits.core.database.*;
|
|
||||||
import org.isoron.uhabits.core.models.*;
|
|
||||||
import org.isoron.uhabits.core.utils.*;
|
|
||||||
|
|
||||||
import java.io.*;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import javax.inject.*;
|
|
||||||
|
|
||||||
import static org.isoron.uhabits.core.models.Entry.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class that imports data from database files exported by Tickmate.
|
|
||||||
*/
|
|
||||||
public class TickmateDBImporter extends AbstractImporter
|
|
||||||
{
|
|
||||||
private ModelFactory modelFactory;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private final DatabaseOpener opener;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public TickmateDBImporter(@NonNull HabitList habits,
|
|
||||||
@NonNull ModelFactory modelFactory,
|
|
||||||
@NonNull DatabaseOpener opener)
|
|
||||||
{
|
|
||||||
super(habits);
|
|
||||||
this.modelFactory = modelFactory;
|
|
||||||
this.opener = opener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canHandle(@NonNull File file) throws IOException
|
|
||||||
{
|
|
||||||
if (!isSQLite3File(file)) return false;
|
|
||||||
|
|
||||||
Database db = opener.open(file);
|
|
||||||
Cursor c = db.query("select count(*) from SQLITE_MASTER " +
|
|
||||||
"where name='tracks' or name='track2groups'");
|
|
||||||
|
|
||||||
boolean result = (c.moveToNext() && c.getInt(0) == 2);
|
|
||||||
|
|
||||||
c.close();
|
|
||||||
db.close();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void importHabitsFromFile(@NonNull File file) throws IOException
|
|
||||||
{
|
|
||||||
final Database db = opener.open(file);
|
|
||||||
db.beginTransaction();
|
|
||||||
createHabits(db);
|
|
||||||
db.setTransactionSuccessful();
|
|
||||||
db.endTransaction();
|
|
||||||
db.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createCheckmarks(@NonNull Database db,
|
|
||||||
@NonNull Habit habit,
|
|
||||||
int tickmateTrackId)
|
|
||||||
{
|
|
||||||
Cursor c = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
String[] params = {Integer.toString(tickmateTrackId)};
|
|
||||||
c = db.query(
|
|
||||||
"select distinct year, month, day from ticks where _track_id=?",
|
|
||||||
params);
|
|
||||||
if (!c.moveToNext()) return;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
int year = c.getInt(0);
|
|
||||||
int month = c.getInt(1);
|
|
||||||
int day = c.getInt(2);
|
|
||||||
|
|
||||||
GregorianCalendar cal = DateUtils.getStartOfTodayCalendar();
|
|
||||||
cal.set(year, month, day);
|
|
||||||
|
|
||||||
habit.getOriginalEntries().add(new Entry(new Timestamp(cal), YES_MANUAL));
|
|
||||||
} while (c.moveToNext());
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (c != null) c.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createHabits(Database db)
|
|
||||||
{
|
|
||||||
Cursor c = null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
c = db.query("select _id, name, description from tracks",
|
|
||||||
new String[0]);
|
|
||||||
if (!c.moveToNext()) return;
|
|
||||||
|
|
||||||
do
|
|
||||||
{
|
|
||||||
int id = c.getInt(0);
|
|
||||||
String name = c.getString(1);
|
|
||||||
String description = c.getString(2);
|
|
||||||
|
|
||||||
Habit habit = modelFactory.buildHabit();
|
|
||||||
habit.setName(name);
|
|
||||||
habit.setDescription(description == null ? "" : description);
|
|
||||||
habit.setFrequency(Frequency.DAILY);
|
|
||||||
habitList.add(habit);
|
|
||||||
|
|
||||||
createCheckmarks(db, habit, id);
|
|
||||||
|
|
||||||
} while (c.moveToNext());
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (c != null) c.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 Á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.core.io
|
||||||
|
|
||||||
|
import org.isoron.uhabits.core.database.Cursor
|
||||||
|
import org.isoron.uhabits.core.database.Database
|
||||||
|
import org.isoron.uhabits.core.database.DatabaseOpener
|
||||||
|
import org.isoron.uhabits.core.models.Entry
|
||||||
|
import org.isoron.uhabits.core.models.Frequency
|
||||||
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.models.HabitList
|
||||||
|
import org.isoron.uhabits.core.models.ModelFactory
|
||||||
|
import org.isoron.uhabits.core.models.Timestamp
|
||||||
|
import org.isoron.uhabits.core.utils.DateUtils
|
||||||
|
import org.isoron.uhabits.core.utils.isSQLite3File
|
||||||
|
import java.io.File
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that imports data from database files exported by Tickmate.
|
||||||
|
*/
|
||||||
|
class TickmateDBImporter @Inject constructor(
|
||||||
|
private val habitList: HabitList,
|
||||||
|
private val modelFactory: ModelFactory,
|
||||||
|
private val opener: DatabaseOpener
|
||||||
|
) : AbstractImporter() {
|
||||||
|
|
||||||
|
override fun canHandle(file: File): Boolean {
|
||||||
|
if (!file.isSQLite3File()) return false
|
||||||
|
val db = opener.open(file)
|
||||||
|
val c = db.query(
|
||||||
|
"select count(*) from SQLITE_MASTER " +
|
||||||
|
"where name='tracks' or name='track2groups'"
|
||||||
|
)
|
||||||
|
val result = c.moveToNext() && c.getInt(0) == 2
|
||||||
|
c.close()
|
||||||
|
db.close()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun importHabitsFromFile(file: File) {
|
||||||
|
val db = opener.open(file)
|
||||||
|
db.beginTransaction()
|
||||||
|
createHabits(db)
|
||||||
|
db.setTransactionSuccessful()
|
||||||
|
db.endTransaction()
|
||||||
|
db.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createCheckmarks(
|
||||||
|
db: Database,
|
||||||
|
habit: Habit,
|
||||||
|
tickmateTrackId: Int
|
||||||
|
) {
|
||||||
|
var c: Cursor? = null
|
||||||
|
try {
|
||||||
|
c = db.query(
|
||||||
|
"select distinct year, month, day from ticks where _track_id=?",
|
||||||
|
tickmateTrackId.toString(),
|
||||||
|
)
|
||||||
|
if (!c.moveToNext()) return
|
||||||
|
do {
|
||||||
|
val year = c.getInt(0)!!
|
||||||
|
val month = c.getInt(1)!!
|
||||||
|
val day = c.getInt(2)!!
|
||||||
|
val cal = DateUtils.getStartOfTodayCalendar()
|
||||||
|
cal[year, month] = day
|
||||||
|
habit.originalEntries.add(Entry(Timestamp(cal), Entry.YES_MANUAL))
|
||||||
|
} while (c.moveToNext())
|
||||||
|
} finally {
|
||||||
|
c?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createHabits(db: Database) {
|
||||||
|
var c: Cursor? = null
|
||||||
|
try {
|
||||||
|
c = db.query("select _id, name, description from tracks")
|
||||||
|
if (!c.moveToNext()) return
|
||||||
|
do {
|
||||||
|
val id = c.getInt(0)!!
|
||||||
|
val name = c.getString(1)
|
||||||
|
val description = c.getString(2)
|
||||||
|
val habit = modelFactory.buildHabit()
|
||||||
|
habit.name = name!!
|
||||||
|
habit.description = description ?: ""
|
||||||
|
habit.frequency = Frequency.DAILY
|
||||||
|
habitList.add(habit)
|
||||||
|
createCheckmarks(db, habit, id)
|
||||||
|
} while (c.moveToNext())
|
||||||
|
} finally {
|
||||||
|
c?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2016-2020 Á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.core.utils
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
|
|
||||||
|
fun File.isSQLite3File(): Boolean {
|
||||||
|
val fis = FileInputStream(this)
|
||||||
|
val sqliteHeader = "SQLite format 3".toByteArray()
|
||||||
|
val buffer = ByteArray(sqliteHeader.size)
|
||||||
|
val count = fis.read(buffer)
|
||||||
|
return if (count < sqliteHeader.size) false else buffer.contentEquals(sqliteHeader)
|
||||||
|
}
|
Loading…
Reference in new issue