Move models.sqlite to uhabits-core

This commit is contained in:
2017-06-20 12:50:57 -04:00
parent 96c1a046d4
commit b96385c4a7
35 changed files with 550 additions and 187 deletions

View File

@@ -23,7 +23,6 @@ package org.isoron.uhabits;
import org.isoron.androidbase.*;
import org.isoron.uhabits.core.*;
import org.isoron.uhabits.core.tasks.*;
import org.isoron.uhabits.models.sqlite.*;
import dagger.*;
@@ -32,7 +31,6 @@ import dagger.*;
AppContextModule.class,
HabitsModule.class,
SingleThreadModule.class,
SQLModelFactory.class
})
public interface AndroidTestComponent extends HabitsApplicationComponent
{

View File

@@ -1,86 +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.models.sqlite;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import org.isoron.androidbase.storage.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.models.sqlite.records.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import org.junit.runner.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.core.IsEqual.*;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class HabitRecordTest extends BaseAndroidTest
{
private Habit habit;
private SQLiteRepository<HabitRecord> sqlite =
new SQLiteRepository<>(HabitRecord.class, DatabaseUtils.openDatabase());
@Before
@Override
public void setUp()
{
super.setUp();
habit = component.getModelFactory().buildHabit();
habit.setName("Hello world");
habit.setDescription("Did you greet the world today?");
habit.setColor(1);
habit.setArchived(true);
habit.setFrequency(Frequency.THREE_TIMES_PER_WEEK);
habit.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY));
habit.setId(1000L);
}
@Test
public void testCopyFrom()
{
HabitRecord rec = new HabitRecord();
rec.copyFrom(habit);
assertThat(rec.name, equalTo(habit.getName()));
assertThat(rec.description, equalTo(habit.getDescription()));
assertThat(rec.color, equalTo(habit.getColor()));
assertThat(rec.archived, equalTo(1));
assertThat(rec.freqDen, equalTo(7));
assertThat(rec.freqNum, equalTo(3));
Reminder reminder = habit.getReminder();
assertThat(rec.reminderDays, equalTo(reminder.getDays().toInteger()));
assertThat(rec.reminderHour, equalTo(reminder.getHour()));
assertThat(rec.reminderMin, equalTo(reminder.getMinute()));
habit.setReminder(null);
rec.copyFrom(habit);
assertThat(rec.reminderMin, equalTo(null));
assertThat(rec.reminderHour, equalTo(null));
assertThat(rec.reminderDays, equalTo(0));
}
}

View File

@@ -24,10 +24,12 @@ import android.test.suitebuilder.annotation.*;
import com.google.common.collect.*;
import org.isoron.androidbase.storage.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.core.db.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.models.sqlite.records.*;
import org.isoron.uhabits.core.models.sqlite.*;
import org.isoron.uhabits.core.models.sqlite.records.*;
import org.isoron.uhabits.database.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import org.junit.rules.*;
@@ -52,7 +54,7 @@ public class SQLiteHabitListTest extends BaseAndroidTest
private ModelFactory modelFactory;
private SQLiteRepository<HabitRecord> repository;
private Repository<HabitRecord> repository;
private ModelObservable.Listener listener;
@@ -64,9 +66,8 @@ public class SQLiteHabitListTest extends BaseAndroidTest
fixtures.purgeHabits(habitList);
modelFactory = component.getModelFactory();
repository =
new SQLiteRepository<>(HabitRecord.class,
DatabaseUtils.openDatabase());
repository = new Repository<>(HabitRecord.class,
new AndroidSQLiteDatabase(DatabaseUtils.openDatabase()));
for (int i = 0; i < 10; i++)
{

View File

@@ -23,11 +23,12 @@ import android.support.annotation.*;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import org.isoron.androidbase.storage.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.core.db.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.models.sqlite.records.*;
import org.isoron.uhabits.core.utils.*;
import org.isoron.uhabits.models.sqlite.records.*;
import org.isoron.uhabits.database.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import org.junit.runner.*;
@@ -50,7 +51,7 @@ public class SQLiteRepetitionListTest extends BaseAndroidTest
private long day;
private SQLiteRepository<RepetitionRecord> sqlite;
private Repository<RepetitionRecord> sqlite;
@Override
public void setUp()
@@ -61,8 +62,8 @@ public class SQLiteRepetitionListTest extends BaseAndroidTest
repetitions = habit.getRepetitions();
today = DateUtils.getStartOfToday();
day = DateUtils.millisecondsInOneDay;
sqlite = new SQLiteRepository<>(RepetitionRecord.class,
DatabaseUtils.openDatabase());
sqlite = new Repository<>(RepetitionRecord.class,
new AndroidSQLiteDatabase(DatabaseUtils.openDatabase()));
}
@Test

View File

@@ -19,13 +19,14 @@
package org.isoron.uhabits.models.sqlite;
import android.database.sqlite.*;
import android.database.sqlite.SQLiteDatabase;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import org.apache.commons.lang3.builder.*;
import org.isoron.androidbase.storage.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.core.db.*;
import org.isoron.uhabits.database.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import org.junit.runner.*;
@@ -37,7 +38,7 @@ import static org.junit.Assert.assertThat;
@MediumTest
public class SQLiteRepositoryTest extends BaseAndroidTest
{
private SQLiteRepository<ThingRecord> repository;
private Repository<ThingRecord> repository;
private SQLiteDatabase db;
@@ -47,7 +48,8 @@ public class SQLiteRepositoryTest extends BaseAndroidTest
{
super.setUp();
this.db = DatabaseUtils.openDatabase();
repository = new SQLiteRepository<>(ThingRecord.class, db);
repository = new Repository<>(ThingRecord.class,
new AndroidSQLiteDatabase((db)));
db.execSQL("drop table if exists tests");
db.execSQL("create table tests(" +

View File

@@ -27,7 +27,7 @@ import org.isoron.uhabits.core.preferences.*;
import org.isoron.uhabits.core.reminders.*;
import org.isoron.uhabits.core.tasks.*;
import org.isoron.uhabits.core.ui.*;
import org.isoron.uhabits.models.sqlite.*;
import org.isoron.uhabits.database.*;
import org.isoron.uhabits.utils.*;
import org.isoron.uhabits.widgets.*;

View File

@@ -33,7 +33,6 @@ import org.isoron.uhabits.core.ui.screens.habits.list.*;
import org.isoron.uhabits.core.utils.*;
import org.isoron.uhabits.intents.*;
import org.isoron.uhabits.io.*;
import org.isoron.uhabits.models.sqlite.*;
import org.isoron.uhabits.sync.*;
import org.isoron.uhabits.tasks.*;
import org.isoron.uhabits.widgets.*;
@@ -42,7 +41,9 @@ import dagger.*;
@AppScope
@Component(modules = {
AppContextModule.class, HabitsModule.class, AndroidTaskRunner.class, SQLModelFactory.class
AppContextModule.class,
HabitsModule.class,
AndroidTaskRunner.class,
})
public interface HabitsApplicationComponent
{

View File

@@ -24,7 +24,7 @@ package org.isoron.uhabits;
import android.content.*;
import android.database.sqlite.*;
import org.isoron.androidbase.storage.*;
import org.isoron.uhabits.database.*;
public class HabitsDatabaseOpener extends BaseSQLiteOpenHelper

View File

@@ -22,13 +22,16 @@ package org.isoron.uhabits;
import org.isoron.uhabits.core.*;
import org.isoron.uhabits.core.commands.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.models.sqlite.*;
import org.isoron.uhabits.core.preferences.*;
import org.isoron.uhabits.core.reminders.*;
import org.isoron.uhabits.core.tasks.*;
import org.isoron.uhabits.core.ui.*;
import org.isoron.uhabits.database.*;
import org.isoron.uhabits.intents.*;
import org.isoron.uhabits.notifications.*;
import org.isoron.uhabits.preferences.*;
import org.isoron.uhabits.utils.*;
import dagger.*;
@@ -69,5 +72,19 @@ public class HabitsModule
{
return new WidgetPreferences(storage);
}
@Provides
public ModelFactory getModelFactory()
{
return new SQLModelFactory(
new AndroidSQLiteDatabase(DatabaseUtils.openDatabase()));
}
@Provides
@AppScope
public HabitList getHabitList(SQLiteHabitList list)
{
return list;
}
}

View File

@@ -0,0 +1,70 @@
/*
* 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.database;
import org.isoron.uhabits.core.db.*;
public class AndroidCursor implements Cursor
{
private android.database.Cursor cursor;
public AndroidCursor(android.database.Cursor cursor)
{
this.cursor = cursor;
}
@Override
public void close()
{
cursor.close();
}
@Override
public boolean moveToNext()
{
return cursor.moveToNext();
}
@Override
public Integer getInt(int index)
{
return cursor.getInt(index);
}
@Override
public Long getLong(int index)
{
return cursor.getLong(index);
}
@Override
public Double getDouble(int index)
{
return cursor.getDouble(index);
}
@Override
public String getString(int index)
{
return cursor.getString(index);
}
}

View File

@@ -0,0 +1,113 @@
/*
* 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.database;
import android.content.*;
import android.database.sqlite.*;
import org.isoron.uhabits.core.db.*;
import java.util.*;
public class AndroidSQLiteDatabase implements Database
{
private final SQLiteDatabase db;
public AndroidSQLiteDatabase(SQLiteDatabase db)
{
this.db = db;
}
@Override
public Cursor select(String query, String... params)
{
return new AndroidCursor(db.rawQuery(query, params));
}
@Override
public void execSQL(String query, Object... params)
{
db.execSQL(query, params);
}
@Override
public void beginTransaction()
{
db.beginTransaction();
}
@Override
public void setTransactionSuccessful()
{
db.setTransactionSuccessful();
}
@Override
public void endTransaction()
{
db.endTransaction();
}
@Override
public int update(String tableName,
Map<String, Object> map,
String where,
String... params)
{
ContentValues values = mapToContentValues(map);
return db.update(tableName, values, where, params);
}
@Override
public Long insert(String tableName, Map<String, Object> map)
{
ContentValues values = mapToContentValues(map);
return db.insert(tableName, null, values);
}
@Override
public void delete(String tableName, String where, String... params)
{
db.delete(tableName, where, params);
}
private ContentValues mapToContentValues(Map<String, Object> map)
{
ContentValues values = new ContentValues();
for (Map.Entry<String, Object> entry : map.entrySet())
{
if (entry.getValue() == null) continue;
if (entry.getValue() instanceof Integer)
values.put(entry.getKey(), (Integer) entry.getValue());
else if (entry.getValue() instanceof Long)
values.put(entry.getKey(), (Long) entry.getValue());
else if (entry.getValue() instanceof Double)
values.put(entry.getKey(), (Double) entry.getValue());
else if (entry.getValue() instanceof String)
values.put(entry.getKey(), (String) entry.getValue());
else throw new IllegalStateException(
"unsupported type: " + entry.getValue());
}
return values;
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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.database;
import android.content.*;
import android.database.sqlite.*;
import org.isoron.androidbase.*;
import java.io.*;
import java.util.*;
public class BaseSQLiteOpenHelper extends SQLiteOpenHelper
{
private final Context context;
private final int version;
public BaseSQLiteOpenHelper(@AppContext Context context,
String databaseFilename,
int version)
{
super(context, databaseFilename, null, version);
this.context = context;
this.version = version;
}
@Override
public void onCreate(SQLiteDatabase db)
{
executeMigrations(db, -1, version);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
executeMigrations(db, oldVersion, newVersion);
}
private void executeMigrations(SQLiteDatabase db,
int oldVersion,
int newVersion)
{
try
{
for (int v = oldVersion + 1; v <= newVersion; v++)
{
String fname = String.format(Locale.US, "migrations/%d.sql", v);
InputStream stream = context.getAssets().open(fname);
for (String command : SQLParser.parse(stream))
db.execSQL(command);
}
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
throw new UnsupportedDatabaseVersionException();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
@@ -15,9 +15,11 @@
*
* 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.models.sqlite;
package org.isoron.uhabits.database;
public class InconsistentDatabaseException extends RuntimeException
{

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
@@ -15,9 +15,11 @@
*
* 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.models.sqlite;
package org.isoron.uhabits.database;
public class InvalidDatabaseVersionException extends RuntimeException
{

View File

@@ -0,0 +1,168 @@
/*
* 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.database;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
class Tokenizer {
private final InputStream mStream;
private boolean mIsNext;
private int mCurrent;
public Tokenizer(final InputStream in) {
this.mStream = in;
}
public boolean hasNext() throws IOException {
if (!this.mIsNext) {
this.mIsNext = true;
this.mCurrent = this.mStream.read();
}
return this.mCurrent != -1;
}
public int next() throws IOException {
if (!this.mIsNext) {
this.mCurrent = this.mStream.read();
}
this.mIsNext = false;
return this.mCurrent;
}
public boolean skip(final String s) throws IOException {
if (s == null || s.length() == 0) {
return false;
}
if (s.charAt(0) != this.mCurrent) {
return false;
}
final int len = s.length();
this.mStream.mark(len - 1);
for (int n = 1; n < len; n++) {
final int value = this.mStream.read();
if (value != s.charAt(n)) {
this.mStream.reset();
return false;
}
}
return true;
}
}
public class SQLParser {
public final static int STATE_NONE = 0;
public final static int STATE_STRING = 1;
public final static int STATE_COMMENT = 2;
public final static int STATE_COMMENT_BLOCK = 3;
public static List<String> parse(final InputStream stream) throws IOException {
final BufferedInputStream buffer = new BufferedInputStream(stream);
final List<String> commands = new ArrayList<String>();
final StringBuffer sb = new StringBuffer();
try {
final Tokenizer tokenizer = new Tokenizer(buffer);
int state = STATE_NONE;
while (tokenizer.hasNext()) {
final char c = (char) tokenizer.next();
if (state == STATE_COMMENT_BLOCK) {
if (tokenizer.skip("*/")) {
state = STATE_NONE;
}
continue;
} else if (state == STATE_COMMENT) {
if (isNewLine(c)) {
state = STATE_NONE;
}
continue;
} else if (state == STATE_NONE && tokenizer.skip("/*")) {
state = STATE_COMMENT_BLOCK;
continue;
} else if (state == STATE_NONE && tokenizer.skip("--")) {
state = STATE_COMMENT;
continue;
} else if (state == STATE_NONE && c == ';') {
final String command = sb.toString().trim();
commands.add(command);
sb.setLength(0);
continue;
} else if (state == STATE_NONE && c == '\'') {
state = STATE_STRING;
} else if (state == STATE_STRING && c == '\'') {
state = STATE_NONE;
}
if (state == STATE_NONE || state == STATE_STRING) {
if (state == STATE_NONE && isWhitespace(c)) {
if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ') {
sb.append(' ');
}
} else {
sb.append(c);
}
}
}
} finally {
buffer.close();
}
if (sb.length() > 0) {
commands.add(sb.toString().trim());
}
return commands;
}
private static boolean isNewLine(final char c) {
return c == '\r' || c == '\n';
}
private static boolean isWhitespace(final char c) {
return c == '\r' || c == '\n' || c == '\t' || c == ' ';
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
@@ -15,9 +15,12 @@
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*
*
*/
/**
* Provides SQLite implementations of the core models.
*/
package org.isoron.uhabits.models.sqlite;
package org.isoron.uhabits.database;
public class UnsupportedDatabaseVersionException extends RuntimeException
{
}

View File

@@ -1,76 +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.models.sqlite;
import org.isoron.uhabits.core.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.models.memory.*;
import dagger.*;
/**
* Factory that provides models backed by an SQLite database.
*/
@Module
public class SQLModelFactory implements ModelFactory
{
@Provides
public static ModelFactory provideModelFactory()
{
return new SQLModelFactory();
}
@Provides
@AppScope
public static HabitList provideHabitList(ModelFactory modelFactory)
{
return new SQLiteHabitList(modelFactory);
}
@Override
public CheckmarkList buildCheckmarkList(Habit habit)
{
return new MemoryCheckmarkList(habit);
}
@Override
public HabitList buildHabitList()
{
return SQLiteHabitList.getInstance(provideModelFactory());
}
@Override
public RepetitionList buildRepetitionList(Habit habit)
{
return new SQLiteRepetitionList(habit);
}
@Override
public ScoreList buildScoreList(Habit habit)
{
return new MemoryScoreList(habit);
}
@Override
public StreakList buildStreakList(Habit habit)
{
return new MemoryStreakList(habit);
}
}

View File

@@ -1,266 +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.models.sqlite;
import android.database.sqlite.*;
import android.support.annotation.*;
import org.isoron.androidbase.storage.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.models.memory.*;
import org.isoron.uhabits.models.sqlite.records.*;
import org.isoron.uhabits.utils.*;
import java.util.*;
import static org.isoron.uhabits.utils.DatabaseUtils.executeAsTransaction;
/**
* Implementation of a {@link HabitList} that is backed by SQLite.
*/
public class SQLiteHabitList extends HabitList
{
private static SQLiteHabitList instance;
@NonNull
private final SQLiteRepository<HabitRecord> repository;
@NonNull
private final ModelFactory modelFactory;
@NonNull
private final MemoryHabitList list;
private boolean loaded = false;
public SQLiteHabitList(@NonNull ModelFactory modelFactory)
{
super();
this.modelFactory = modelFactory;
this.list = new MemoryHabitList();
repository =
new SQLiteRepository<>(HabitRecord.class, DatabaseUtils.openDatabase());
}
private void loadRecords()
{
if(loaded) return;
loaded = true;
list.removeAll();
List<HabitRecord> records = repository.findAll("order by position");
for (HabitRecord rec : records)
{
Habit h = modelFactory.buildHabit();
rec.copyTo(h);
list.add(h);
}
}
public static SQLiteHabitList getInstance(
@NonNull ModelFactory modelFactory)
{
if (instance == null) instance = new SQLiteHabitList(modelFactory);
return instance;
}
@Override
public synchronized void add(@NonNull Habit habit)
{
loadRecords();
list.add(habit);
HabitRecord record = new HabitRecord();
record.copyFrom(habit);
record.position = list.indexOf(habit);
repository.save(record);
getObservable().notifyListeners();
}
@Override
@Nullable
public Habit getById(long id)
{
loadRecords();
return list.getById(id);
}
@Override
@NonNull
public Habit getByPosition(int position)
{
loadRecords();
return list.getByPosition(position);
}
@NonNull
@Override
public HabitList getFiltered(HabitMatcher filter)
{
loadRecords();
return list.getFiltered(filter);
}
@Override
@NonNull
public Order getOrder()
{
return list.getOrder();
}
@Override
public void setOrder(@NonNull Order order)
{
list.setOrder(order);
}
@Override
public int indexOf(@NonNull Habit h)
{
loadRecords();
return list.indexOf(h);
}
@Override
public Iterator<Habit> iterator()
{
loadRecords();
return list.iterator();
}
private void rebuildOrder()
{
// List<Habit> habits = toList();
//
// int i = 0;
// for (Habit h : habits)
// {
// HabitRecord record = repository.find(h.getId());
// if (record == null)
// throw new RuntimeException("habit not in database");
//
// record.position = i++;
// repository.save(record);
// }
//
// update(habits);
}
@Override
public synchronized void remove(@NonNull Habit habit)
{
loadRecords();
list.remove(habit);
HabitRecord record = repository.find(habit.getId());
if (record == null) throw new RuntimeException("habit not in database");
executeAsTransaction(() ->
{
((SQLiteRepetitionList) habit.getRepetitions()).removeAll();
repository.remove(record);
});
rebuildOrder();
getObservable().notifyListeners();
}
@Override
public synchronized void removeAll()
{
list.removeAll();
SQLiteDatabase db = DatabaseUtils.openDatabase();
db.execSQL("delete from habits");
db.execSQL("delete from repetitions");
getObservable().notifyListeners();
}
@Override
public synchronized void reorder(@NonNull Habit from, @NonNull Habit to)
{
loadRecords();
list.reorder(from, to);
HabitRecord fromRecord = repository.find(from.getId());
HabitRecord toRecord = repository.find(to.getId());
if (fromRecord == null)
throw new RuntimeException("habit not in database");
if (toRecord == null)
throw new RuntimeException("habit not in database");
Integer fromPos = fromRecord.position;
Integer toPos = toRecord.position;
SQLiteDatabase db = DatabaseUtils.openDatabase();
if (toPos < fromPos)
{
db.execSQL("update habits set position = position + 1 " +
"where position >= ? and position < ?",
new String[]{ toPos.toString(), fromPos.toString() });
}
else
{
db.execSQL("update habits set position = position - 1 " +
"where position > ? and position <= ?",
new String[]{ fromPos.toString(), toPos.toString() });
}
fromRecord.position = toPos;
repository.save(fromRecord);
update(from);
getObservable().notifyListeners();
}
@Override
public void repair()
{
loadRecords();
rebuildOrder();
}
@Override
public int size()
{
loadRecords();
return list.size();
}
@Override
public synchronized void update(List<Habit> habits)
{
loadRecords();
for (Habit h : habits)
{
HabitRecord record = repository.find(h.getId());
if (record == null)
throw new RuntimeException("habit not in database");
record.copyFrom(h);
repository.save(record);
}
getObservable().notifyListeners();
}
public void reload()
{
loaded = false;
}
}

View File

@@ -1,147 +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.models.sqlite;
import android.support.annotation.*;
import android.support.annotation.Nullable;
import org.isoron.androidbase.storage.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.models.memory.*;
import org.isoron.uhabits.models.sqlite.records.*;
import org.isoron.uhabits.utils.*;
import org.jetbrains.annotations.*;
import java.util.*;
/**
* Implementation of a {@link RepetitionList} that is backed by SQLite.
*/
public class SQLiteRepetitionList extends RepetitionList
{
private final SQLiteRepository<RepetitionRecord> repository;
private final MemoryRepetitionList list;
private boolean loaded = false;
public SQLiteRepetitionList(@NonNull Habit habit)
{
super(habit);
repository = new SQLiteRepository<>(RepetitionRecord.class,
DatabaseUtils.openDatabase());
list = new MemoryRepetitionList(habit);
}
private void loadRecords()
{
if (loaded) return;
loaded = true;
check(habit.getId());
List<RepetitionRecord> records =
repository.findAll("where habit = ? order by timestamp",
habit.getId().toString());
for (RepetitionRecord rec : records)
list.add(rec.toRepetition());
}
@Override
public void add(Repetition rep)
{
loadRecords();
list.add(rep);
check(habit.getId());
RepetitionRecord record = new RepetitionRecord();
record.habit_id = habit.getId();
record.copyFrom(rep);
repository.save(record);
observable.notifyListeners();
}
@Override
public List<Repetition> getByInterval(long timeFrom, long timeTo)
{
loadRecords();
return list.getByInterval(timeFrom, timeTo);
}
@Override
@Nullable
public Repetition getByTimestamp(long timestamp)
{
loadRecords();
return list.getByTimestamp(timestamp);
}
@Override
public Repetition getOldest()
{
loadRecords();
return list.getOldest();
}
@Override
public Repetition getNewest()
{
loadRecords();
return list.getNewest();
}
@Override
public void remove(@NonNull Repetition repetition)
{
loadRecords();
list.remove(repetition);
check(habit.getId());
repository.execSQL(
"delete from repetitions where habit = ? and timestamp = ?",
habit.getId());
observable.notifyListeners();
}
public void removeAll()
{
loadRecords();
list.removeAll();
check(habit.getId());
repository.execSQL("delete from repetitions where habit = ?",
habit.getId());
}
@Override
public long getTotalCount()
{
loadRecords();
return list.getTotalCount();
}
public void reload()
{
loaded = false;
}
@Contract("null -> fail")
private void check(Long value)
{
if (value == null) throw new RuntimeException("null check failed");
}
}

View File

@@ -1,202 +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.models.sqlite.records;
import org.apache.commons.lang3.builder.*;
import org.isoron.androidbase.storage.*;
import org.isoron.uhabits.core.models.*;
/**
* The SQLite database record corresponding to a {@link Habit}.
*/
@Table(name = "habits")
public class HabitRecord
{
@Column
public String description;
@Column
public String name;
@Column(name = "freq_num")
public Integer freqNum;
@Column(name = "freq_den")
public Integer freqDen;
@Column
public Integer color;
@Column
public Integer position;
@Column(name = "reminder_hour")
public Integer reminderHour;
@Column(name = "reminder_min")
public Integer reminderMin;
@Column(name = "reminder_days")
public Integer reminderDays;
@Column
public Integer highlight;
@Column
public Integer archived;
@Column
public Integer type;
@Column(name = "target_value")
public Double targetValue;
@Column(name = "target_type")
public Integer targetType;
@Column
public String unit;
@Column
public Long id;
public void copyFrom(Habit model)
{
this.id = model.getId();
this.name = model.getName();
this.description = model.getDescription();
this.highlight = 0;
this.color = model.getColor();
this.archived = model.isArchived() ? 1 : 0;
this.type = model.getType();
this.targetType = model.getTargetType();
this.targetValue = model.getTargetValue();
this.unit = model.getUnit();
Frequency freq = model.getFrequency();
this.freqNum = freq.getNumerator();
this.freqDen = freq.getDenominator();
this.reminderDays = 0;
this.reminderMin = null;
this.reminderHour = null;
if (model.hasReminder())
{
Reminder reminder = model.getReminder();
this.reminderHour = reminder.getHour();
this.reminderMin = reminder.getMinute();
this.reminderDays = reminder.getDays().toInteger();
}
}
public void copyTo(Habit habit)
{
habit.setId(this.id);
habit.setName(this.name);
habit.setDescription(this.description);
habit.setFrequency(new Frequency(this.freqNum, this.freqDen));
habit.setColor(this.color);
habit.setArchived(this.archived != 0);
habit.setType(this.type);
habit.setTargetType(this.targetType);
habit.setTargetValue(this.targetValue);
habit.setUnit(this.unit);
if (reminderHour != null && reminderMin != null)
{
habit.setReminder(new Reminder(reminderHour, reminderMin,
new WeekdayList(reminderDays)));
}
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HabitRecord that = (HabitRecord) o;
return new EqualsBuilder()
.appendSuper(super.equals(o))
.append(freqNum, that.freqNum)
.append(freqDen, that.freqDen)
.append(color, that.color)
.append(position, that.position)
.append(reminderDays, that.reminderDays)
.append(highlight, that.highlight)
.append(archived, that.archived)
.append(type, that.type)
.append(targetValue, that.targetValue)
.append(targetType, that.targetType)
.append(name, that.name)
.append(description, that.description)
.append(reminderHour, that.reminderHour)
.append(reminderMin, that.reminderMin)
.append(unit, that.unit)
.isEquals();
}
@Override
public int hashCode()
{
return new HashCodeBuilder(17, 37)
.appendSuper(super.hashCode())
.append(name)
.append(description)
.append(freqNum)
.append(freqDen)
.append(color)
.append(position)
.append(reminderHour)
.append(reminderMin)
.append(reminderDays)
.append(highlight)
.append(archived)
.append(type)
.append(targetValue)
.append(targetType)
.append(unit)
.toHashCode();
}
@Override
public String toString()
{
return new ToStringBuilder(this)
.append("name", name)
.append("description", description)
.append("freqNum", freqNum)
.append("freqDen", freqDen)
.append("color", color)
.append("position", position)
.append("reminderHour", reminderHour)
.append("reminderMin", reminderMin)
.append("reminderDays", reminderDays)
.append("highlight", highlight)
.append("archived", archived)
.append("type", type)
.append("targetValue", targetValue)
.append("targetType", targetType)
.append("unit", unit)
.toString();
}
}

View File

@@ -1,55 +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.models.sqlite.records;
import org.isoron.androidbase.storage.*;
import org.isoron.uhabits.core.models.*;
/**
* The SQLite database record corresponding to a {@link Repetition}.
*/
@Table(name = "Repetitions")
public class RepetitionRecord
{
public HabitRecord habit;
@Column(name = "habit")
public Long habit_id;
@Column
public Long timestamp;
@Column
public Integer value;
@Column
public Long id;
public void copyFrom(Repetition repetition)
{
timestamp = repetition.getTimestamp();
value = repetition.getValue();
}
public Repetition toRepetition()
{
return new Repetition(timestamp, value);
}
}

View File

@@ -1,23 +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/>.
*/
/**
* Provides classes that represent rows in the SQLite database.
*/
package org.isoron.uhabits.models.sqlite.records;

View File

@@ -21,7 +21,7 @@ package org.isoron.uhabits.sync;
import android.support.annotation.*;
import org.isoron.androidbase.storage.*;
import org.isoron.uhabits.core.db.*;
@Table(name = "Events")
public class Event

View File

@@ -23,11 +23,12 @@ import android.support.annotation.*;
import android.util.*;
import org.isoron.androidbase.*;
import org.isoron.androidbase.storage.*;
import org.isoron.uhabits.BuildConfig;
import org.isoron.uhabits.core.*;
import org.isoron.uhabits.core.commands.*;
import org.isoron.uhabits.core.db.*;
import org.isoron.uhabits.core.preferences.*;
import org.isoron.uhabits.database.*;
import org.isoron.uhabits.utils.*;
import org.json.*;
@@ -97,7 +98,7 @@ public class SyncManager implements CommandRunner.Listener
private SSLContextProvider sslProvider;
private final SQLiteRepository<Event> repository;
private final Repository<Event> repository;
@Inject
public SyncManager(@NonNull SSLContextProvider sslProvider,
@@ -113,8 +114,8 @@ public class SyncManager implements CommandRunner.Listener
this.commandParser = commandParser;
this.isListening = false;
repository =
new SQLiteRepository<>(Event.class, DatabaseUtils.openDatabase());
repository = new Repository<>(Event.class,
new AndroidSQLiteDatabase(DatabaseUtils.openDatabase()));
pendingConfirmation = new LinkedList<>();
pendingEmit = new LinkedList<>(repository.findAll("order by timestamp"));

View File

@@ -26,7 +26,7 @@ import android.support.annotation.*;
import org.isoron.androidbase.utils.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.core.utils.*;
import org.isoron.uhabits.models.sqlite.*;
import org.isoron.uhabits.database.*;
import java.io.*;
import java.text.*;
@@ -36,6 +36,7 @@ public abstract class DatabaseUtils
@Nullable
private static HabitsDatabaseOpener opener = null;
@Deprecated
public static void executeAsTransaction(Callback callback)
{
try (SQLiteDatabase db = openDatabase())