Convert remaining core.io classes to Kotlin

pull/699/head
Alinson S. Xavier 5 years ago
parent ced78e0b1f
commit 79b134164a

@ -21,5 +21,5 @@ package org.isoron.uhabits.core.database
import java.io.File import java.io.File
interface DatabaseOpener { interface DatabaseOpener {
fun open(file: File): Database? fun open(file: File): Database
} }

@ -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)
}

@ -18,7 +18,6 @@
*/ */
package org.isoron.uhabits.core.io package org.isoron.uhabits.core.io
import org.isoron.uhabits.core.models.HabitList
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
@ -28,12 +27,11 @@ import javax.inject.Inject
*/ */
class GenericImporter class GenericImporter
@Inject constructor( @Inject constructor(
habits: HabitList,
loopDBImporter: LoopDBImporter, loopDBImporter: LoopDBImporter,
rewireDBImporter: RewireDBImporter, rewireDBImporter: RewireDBImporter,
tickmateDBImporter: TickmateDBImporter, tickmateDBImporter: TickmateDBImporter,
habitBullCSVImporter: HabitBullCSVImporter, habitBullCSVImporter: HabitBullCSVImporter,
) : AbstractImporter(habits) { ) : AbstractImporter() {
var importers: List<AbstractImporter> = listOf( var importers: List<AbstractImporter> = listOf(
loopDBImporter, loopDBImporter,

@ -37,9 +37,9 @@ import javax.inject.Inject
*/ */
class HabitBullCSVImporter class HabitBullCSVImporter
@Inject constructor( @Inject constructor(
habits: HabitList, private val habitList: HabitList,
private val modelFactory: ModelFactory, private val modelFactory: ModelFactory,
) : AbstractImporter(habits) { ) : AbstractImporter() {
override fun canHandle(file: File): Boolean { override fun canHandle(file: File): Boolean {
val reader = BufferedReader(FileReader(file)) val reader = BufferedReader(FileReader(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
}
}

@ -33,6 +33,7 @@ import org.isoron.uhabits.core.models.ModelFactory
import org.isoron.uhabits.core.models.Timestamp import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.core.models.sqlite.records.EntryRecord import org.isoron.uhabits.core.models.sqlite.records.EntryRecord
import org.isoron.uhabits.core.models.sqlite.records.HabitRecord import org.isoron.uhabits.core.models.sqlite.records.HabitRecord
import org.isoron.uhabits.core.utils.isSQLite3File
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
@ -46,12 +47,12 @@ class LoopDBImporter
@AppScope val opener: DatabaseOpener, @AppScope val opener: DatabaseOpener,
@AppScope val runner: CommandRunner, @AppScope val runner: CommandRunner,
@AppScope logging: Logging, @AppScope logging: Logging,
) : AbstractImporter(habitList) { ) : AbstractImporter() {
private val logger = logging.getLogger("LoopDBImporter") private val logger = logging.getLogger("LoopDBImporter")
override fun canHandle(file: File): Boolean { override fun canHandle(file: File): Boolean {
if (!isSQLite3File(file)) return false if (!file.isSQLite3File()) return false
val db = opener.open(file)!! val db = opener.open(file)!!
var canHandle = true var canHandle = true
val c = db.query("select count(*) from SQLITE_MASTER where name='Habits' or name='Repetitions'") val c = db.query("select count(*) from SQLITE_MASTER where name='Habits' or name='Repetitions'")

@ -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()
}
}
}

@ -65,7 +65,7 @@ public class ExportCSVTask implements Task
exporter = new HabitsCSVExporter(habitList, selectedHabits, outputDir); exporter = new HabitsCSVExporter(habitList, selectedHabits, outputDir);
archiveFilename = exporter.writeArchive(); archiveFilename = exporter.writeArchive();
} }
catch (IOException e) catch (Exception e)
{ {
e.printStackTrace(); e.printStackTrace();
} }

@ -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)
}

@ -133,8 +133,8 @@ public class ImportTest extends BaseUnitTest
assertTrue(file.exists()); assertTrue(file.exists());
assertTrue(file.canRead()); assertTrue(file.canRead());
GenericImporter importer = new GenericImporter(habitList, GenericImporter importer = new GenericImporter(
new LoopDBImporter(habitList, modelFactory, databaseOpener, commandRunner, new StandardLogging()), new LoopDBImporter(habitList, modelFactory, databaseOpener, commandRunner, new StandardLogging()),
new RewireDBImporter(habitList, modelFactory, databaseOpener), new RewireDBImporter(habitList, modelFactory, databaseOpener),
new TickmateDBImporter(habitList, modelFactory, databaseOpener), new TickmateDBImporter(habitList, modelFactory, databaseOpener),
new HabitBullCSVImporter(habitList, modelFactory)); new HabitBullCSVImporter(habitList, modelFactory));

Loading…
Cancel
Save