Merge branch 'feature/raw-sqlite' into dev

This commit is contained in:
2017-06-20 10:43:04 -04:00
44 changed files with 1473 additions and 725 deletions

View File

@@ -64,7 +64,6 @@ dependencies {
implementation 'com.github.paolorotolo:appintro:3.4.0'
implementation 'com.google.dagger:dagger:2.9'
implementation 'com.jakewharton:butterknife:8.6.1-SNAPSHOT'
implementation 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT'
implementation 'org.apmem.tools:layouts:1.10'
implementation 'org.jetbrains:annotations-java5:15.0'
implementation 'com.google.code.gson:gson:2.7'

View File

@@ -115,9 +115,8 @@ public class HabitFixtures
return habit;
}
public void purgeHabits(HabitList habitList)
public synchronized void purgeHabits(HabitList habitList)
{
for (Habit h : habitList)
habitList.remove(h);
habitList.removeAll();
}
}

View File

@@ -22,9 +22,11 @@ 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.*;
@@ -35,25 +37,18 @@ import static org.hamcrest.core.IsEqual.*;
@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 h = component.getModelFactory().buildHabit();
h.setName("Hello world");
h.setId(1000L);
HabitRecord record = new HabitRecord();
record.copyFrom(h);
record.position = 0;
record.save(1000L);
}
@Test
public void testCopyFrom()
{
Habit habit = component.getModelFactory().buildHabit();
habit = component.getModelFactory().buildHabit();
habit.setName("Hello world");
habit.setDescription("Did you greet the world today?");
habit.setColor(1);
@@ -61,7 +56,11 @@ public class HabitRecordTest extends BaseAndroidTest
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);

View File

@@ -22,12 +22,13 @@ package org.isoron.uhabits.models.sqlite;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import com.activeandroid.query.*;
import com.google.common.collect.*;
import org.isoron.androidbase.storage.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.models.sqlite.*;
import org.isoron.uhabits.models.sqlite.records.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import org.junit.rules.*;
import org.junit.runner.*;
@@ -36,6 +37,8 @@ import java.util.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.core.IsEqual.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@SuppressWarnings("JavaDoc")
@RunWith(AndroidJUnit4.class)
@@ -49,6 +52,10 @@ public class SQLiteHabitListTest extends BaseAndroidTest
private ModelFactory modelFactory;
private SQLiteRepository<HabitRecord> repository;
private ModelObservable.Listener listener;
@Override
public void setUp()
{
@@ -57,6 +64,9 @@ public class SQLiteHabitListTest extends BaseAndroidTest
fixtures.purgeHabits(habitList);
modelFactory = component.getModelFactory();
repository =
new SQLiteRepository<>(HabitRecord.class,
DatabaseUtils.openDatabase());
for (int i = 0; i < 10; i++)
{
@@ -68,8 +78,20 @@ public class SQLiteHabitListTest extends BaseAndroidTest
HabitRecord record = new HabitRecord();
record.copyFrom(h);
record.position = i;
record.save(i);
repository.save(record);
}
habitList.reload();
listener = mock(ModelObservable.Listener.class);
habitList.getObservable().addListener(listener);
}
@Override
protected void tearDown() throws Exception
{
habitList.getObservable().removeListener(listener);
super.tearDown();
}
@Test
@@ -77,6 +99,8 @@ public class SQLiteHabitListTest extends BaseAndroidTest
{
Habit habit = modelFactory.buildHabit();
habitList.add(habit);
verify(listener).onModelChange();
exception.expect(IllegalArgumentException.class);
habitList.add(habit);
}
@@ -91,7 +115,7 @@ public class SQLiteHabitListTest extends BaseAndroidTest
habitList.add(habit);
assertThat(habit.getId(), equalTo(12300L));
HabitRecord record = getRecord(12300L);
HabitRecord record = repository.find(12300L);
assertNotNull(record);
assertThat(record.name, equalTo(habit.getName()));
}
@@ -106,7 +130,7 @@ public class SQLiteHabitListTest extends BaseAndroidTest
habitList.add(habit);
assertNotNull(habit.getId());
HabitRecord record = getRecord(habit.getId());
HabitRecord record = repository.find(habit.getId());
assertNotNull(record);
assertThat(record.name, equalTo(habit.getName()));
}
@@ -120,7 +144,7 @@ public class SQLiteHabitListTest extends BaseAndroidTest
@Test
public void testGetAll_withArchived()
{
List<Habit> habits = habitList.toList();
List<Habit> habits = Lists.newArrayList(habitList.iterator());
assertThat(habits.size(), equalTo(10));
assertThat(habits.get(3).getName(), equalTo("habit 3"));
}
@@ -166,12 +190,4 @@ public class SQLiteHabitListTest extends BaseAndroidTest
h2.setId(1000L);
assertThat(habitList.indexOf(h2), equalTo(-1));
}
private HabitRecord getRecord(long id)
{
return new Select()
.from(HabitRecord.class)
.where("id = ?", id)
.executeSingle();
}
}

View File

@@ -23,19 +23,19 @@ import android.support.annotation.*;
import android.support.test.runner.*;
import android.test.suitebuilder.annotation.*;
import com.activeandroid.query.*;
import org.isoron.androidbase.storage.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.utils.*;
import org.isoron.uhabits.models.sqlite.records.*;
import org.isoron.uhabits.utils.*;
import org.junit.*;
import org.junit.runner.*;
import java.util.*;
import static android.support.test.espresso.matcher.ViewMatchers.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.hamcrest.core.IsEqual.*;
import static org.isoron.uhabits.core.models.Checkmark.CHECKED_EXPLICITLY;
@RunWith(AndroidJUnit4.class)
@@ -50,6 +50,8 @@ public class SQLiteRepetitionListTest extends BaseAndroidTest
private long day;
private SQLiteRepository<RepetitionRecord> sqlite;
@Override
public void setUp()
{
@@ -59,6 +61,8 @@ public class SQLiteRepetitionListTest extends BaseAndroidTest
repetitions = habit.getRepetitions();
today = DateUtils.getStartOfToday();
day = DateUtils.millisecondsInOneDay;
sqlite = new SQLiteRepository<>(RepetitionRecord.class,
DatabaseUtils.openDatabase());
}
@Test
@@ -130,15 +134,13 @@ public class SQLiteRepetitionListTest extends BaseAndroidTest
@Nullable
private RepetitionRecord getByTimestamp(long timestamp)
{
return selectByTimestamp(timestamp).executeSingle();
}
String query = "where habit = ? and timestamp = ?";
@NonNull
private From selectByTimestamp(long timestamp)
{
return new Select()
.from(RepetitionRecord.class)
.where("habit = ?", habit.getId())
.and("timestamp = ?", timestamp);
String params[] = {
Long.toString(habit.getId()),
Long.toString(timestamp)
};
return sqlite.findFirst(query, params);
}
}

View File

@@ -0,0 +1,189 @@
/*
* 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.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.utils.*;
import org.junit.*;
import org.junit.runner.*;
import static org.hamcrest.core.IsEqual.*;
import static org.junit.Assert.assertThat;
@RunWith(AndroidJUnit4.class)
@MediumTest
public class SQLiteRepositoryTest extends BaseAndroidTest
{
private SQLiteRepository<ThingRecord> repository;
private SQLiteDatabase db;
@Before
@Override
public void setUp()
{
super.setUp();
this.db = DatabaseUtils.openDatabase();
repository = new SQLiteRepository<>(ThingRecord.class, db);
db.execSQL("drop table if exists tests");
db.execSQL("create table tests(" +
"id integer not null primary key autoincrement, " +
"color_number integer not null, score float not null, " +
"name string not null)");
}
@Test
public void testFind() throws Exception
{
db.execSQL("insert into tests(id, color_number, name, score) " +
"values (10, 20, 'hello', 8.0)");
ThingRecord record = repository.find(10L);
assertNotNull(record);
assertThat(record.id, equalTo(10L));
assertThat(record.color, equalTo(20));
assertThat(record.name, equalTo("hello"));
assertThat(record.score, equalTo(8.0));
}
@Test
public void testSave_withId() throws Exception
{
ThingRecord record = new ThingRecord();
record.id = 50L;
record.color = 10;
record.name = "hello";
record.score = 5.0;
repository.save(record);
assertThat(record, equalTo(repository.find(50L)));
record.name = "world";
record.score = 128.0;
repository.save(record);
assertThat(record, equalTo(repository.find(50L)));
}
@Test
public void testSave_withoutId() throws Exception
{
ThingRecord r1 = new ThingRecord();
r1.color = 10;
r1.name = "hello";
r1.score = 16.0;
repository.save(r1);
ThingRecord r2 = new ThingRecord();
r2.color = 20;
r2.name = "world";
r2.score = 2.0;
repository.save(r2);
assertThat(r1.id, equalTo(1L));
assertThat(r2.id, equalTo(2L));
}
@Test
public void testRemove() throws Exception
{
ThingRecord rec1 = new ThingRecord();
rec1.color = 10;
rec1.name = "hello";
rec1.score = 16.0;
repository.save(rec1);
ThingRecord rec2 = new ThingRecord();
rec2.color = 20;
rec2.name = "world";
rec2.score = 32.0;
repository.save(rec2);
long id = rec1.id;
assertThat(rec1, equalTo(repository.find(id)));
assertThat(rec2, equalTo(repository.find(rec2.id)));
repository.remove(rec1);
assertThat(rec1.id, equalTo(null));
assertNull(repository.find(id));
assertThat(rec2, equalTo(repository.find(rec2.id)));
repository.remove(rec1); // should have no effect
assertNull(repository.find(id));
}
}
@Table(name = "tests")
class ThingRecord
{
@Column
public Long id;
@Column
public String name;
@Column(name = "color_number")
public Integer color;
@Column
public Double score;
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ThingRecord record = (ThingRecord) o;
return new EqualsBuilder()
.append(id, record.id)
.append(name, record.name)
.append(color, record.color)
.isEquals();
}
@Override
public int hashCode()
{
return new HashCodeBuilder(17, 37)
.append(id)
.append(name)
.append(color)
.toHashCode();
}
@Override
public String toString()
{
return new ToStringBuilder(this)
.append("id", id)
.append("name", name)
.append("color", color)
.toString();
}
}

View File

@@ -1,3 +1,3 @@
delete from Score;
delete from Streak;
delete from Checkmarks;
delete from Checkmarks;

View File

@@ -1 +1 @@
alter table habits add column reminder_days integer not null default 127;
alter table Habits add column reminder_days integer not null default 127;

View File

@@ -1,4 +1,4 @@
create index idx_score_habit_timestamp on score(habit, timestamp);
create index idx_checkmark_habit_timestamp on checkmarks(habit, timestamp);
create index idx_repetitions_habit_timestamp on repetitions(habit, timestamp);
create index idx_streak_habit_end on streak(habit, end);
create index idx_score_habit_timestamp on Score(habit, timestamp);
create index idx_checkmark_habit_timestamp on Checkmarks(habit, timestamp);
create index idx_repetitions_habit_timestamp on Repetitions(habit, timestamp);
create index idx_streak_habit_end on Streak(habit, end);

View File

@@ -1,5 +1,11 @@
DROP TABLE Score;
CREATE TABLE Score (Id INTEGER PRIMARY KEY AUTOINCREMENT, habit INTEGER REFERENCES Habits(Id), score REAL, timestamp INTEGER);
CREATE INDEX idx_score_habit_timestamp on score(habit, timestamp);
delete from Streak;
delete from Checkmarks;
drop table Score;
create table Score (
id integer primary key autoincrement,
habit integer references habits(id),
score real,
timestamp integer);
create index idx_score_habit_timestamp on Score(habit, timestamp);
delete from streak;
delete from checkmarks;

View File

@@ -0,0 +1,6 @@
create table Events (
id integer primary key autoincrement,
timestamp integer,
message text,
server_id integer
);

View File

@@ -1,2 +0,0 @@
alter table habits add column reminder_hour integer;
alter table habits add column reminder_min integer;

View File

@@ -1 +0,0 @@
alter table habits add column highlight integer not null default 0;

View File

@@ -1 +0,0 @@
alter table habits add column archived integer not null default 0;

View File

@@ -0,0 +1,41 @@
create table Habits (
id integer primary key autoincrement,
archived integer,
color integer,
description text,
freq_den integer,
freq_num integer,
highlight integer,
name text,
position integer,
reminder_hour integer,
reminder_min integer
);
create table Checkmarks (
id integer primary key autoincrement,
habit integer references habits(id),
timestamp integer,
value integer
);
create table Repetitions (
id integer primary key autoincrement,
habit integer references habits(id),
timestamp integer
);
create table Streak (
id integer primary key autoincrement,
end integer,
habit integer references habits(id),
length integer,
start integer
);
create table Score (
id integer primary key autoincrement,
habit integer references habits(id),
score integer,
timestamp integer
);

View File

@@ -22,8 +22,6 @@ package org.isoron.uhabits;
import android.app.*;
import android.content.*;
import com.activeandroid.*;
import org.isoron.androidbase.*;
import org.isoron.uhabits.core.preferences.*;
import org.isoron.uhabits.core.reminders.*;
@@ -92,13 +90,13 @@ public class HabitsApplication extends Application
try
{
DatabaseUtils.initializeActiveAndroid(context);
DatabaseUtils.initializeDatabase(context);
}
catch (InvalidDatabaseVersionException e)
{
File db = DatabaseUtils.getDatabaseFile(context);
db.renameTo(new File(db.getAbsolutePath() + ".invalid"));
DatabaseUtils.initializeActiveAndroid(context);
DatabaseUtils.initializeDatabase(context);
}
widgetUpdater = component.getWidgetUpdater();
@@ -124,8 +122,6 @@ public class HabitsApplication extends Application
public void onTerminate()
{
context = null;
ActiveAndroid.dispose();
reminderScheduler.stopListening();
widgetUpdater.stopListening();
notificationTray.stopListening();

View File

@@ -0,0 +1,54 @@
/*
* 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;
import android.content.*;
import android.database.sqlite.*;
import org.isoron.androidbase.storage.*;
public class HabitsDatabaseOpener extends BaseSQLiteOpenHelper
{
private final int version;
public HabitsDatabaseOpener(Context context,
String databaseFilename,
int version)
{
super(context, databaseFilename, version);
this.version = version;
}
@Override
public void onCreate(SQLiteDatabase db)
{
onUpgrade(db, 8, version);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
if(oldVersion < 8) throw new UnsupportedDatabaseVersionException();
super.onUpgrade(db, oldVersion, newVersion);
}
}

View File

@@ -21,7 +21,6 @@ package org.isoron.uhabits.io;
import android.support.annotation.*;
import com.activeandroid.*;
import com.opencsv.*;
import org.isoron.uhabits.core.models.*;
@@ -32,6 +31,8 @@ import java.util.*;
import javax.inject.*;
import static org.isoron.uhabits.utils.DatabaseUtils.executeAsTransaction;
/**
* Class that imports data from HabitBull CSV files.
*/
@@ -59,16 +60,7 @@ public class HabitBullCSVImporter extends AbstractImporter
@Override
public void importHabitsFromFile(@NonNull final File file) throws IOException
{
ActiveAndroid.beginTransaction();
try
{
parseFile(file);
ActiveAndroid.setTransactionSuccessful();
}
finally
{
ActiveAndroid.endTransaction();
}
executeAsTransaction(() -> parseFile(file));
}
private void parseFile(@NonNull File file) throws IOException

View File

@@ -25,8 +25,6 @@ import android.database.sqlite.*;
import android.support.annotation.*;
import android.util.*;
import com.activeandroid.*;
import org.isoron.androidbase.*;
import org.isoron.androidbase.utils.*;
import org.isoron.uhabits.BuildConfig;
@@ -89,9 +87,9 @@ public class LoopDBImporter extends AbstractImporter
@Override
public void importHabitsFromFile(@NonNull File file) throws IOException
{
ActiveAndroid.dispose();
DatabaseUtils.dispose();
File originalDB = DatabaseUtils.getDatabaseFile(context);
FileUtils.copy(file, originalDB);
DatabaseUtils.initializeActiveAndroid(context);
DatabaseUtils.initializeDatabase(context);
}
}

View File

@@ -39,9 +39,9 @@ public class SQLModelFactory implements ModelFactory
@Provides
@AppScope
public static HabitList provideHabitList()
public static HabitList provideHabitList(ModelFactory modelFactory)
{
return SQLiteHabitList.getInstance(provideModelFactory());
return new SQLiteHabitList(modelFactory);
}
@Override

View File

@@ -19,55 +19,60 @@
package org.isoron.uhabits.models.sqlite;
import android.database.sqlite.*;
import android.support.annotation.*;
import com.activeandroid.query.*;
import com.activeandroid.util.*;
import org.apache.commons.lang3.*;
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 HashMap<Long, Habit> cache;
private static SQLiteHabitList instance;
@NonNull
private final SQLiteUtils<HabitRecord> sqlite;
private final SQLiteRepository<HabitRecord> repository;
@NonNull
private final ModelFactory modelFactory;
@NonNull
private Order order;
private final MemoryHabitList list;
private boolean loaded = false;
public SQLiteHabitList(@NonNull ModelFactory modelFactory)
{
super();
this.modelFactory = modelFactory;
this.list = new MemoryHabitList();
if (cache == null) cache = new HashMap<>();
sqlite = new SQLiteUtils<>(HabitRecord.class);
order = Order.BY_POSITION;
repository =
new SQLiteRepository<>(HabitRecord.class, DatabaseUtils.openDatabase());
}
protected SQLiteHabitList(@NonNull ModelFactory modelFactory,
@NonNull HabitMatcher filter,
@NonNull Order order)
private void loadRecords()
{
super(filter);
this.modelFactory = modelFactory;
if(loaded) return;
loaded = true;
if (cache == null) cache = new HashMap<>();
sqlite = new SQLiteUtils<>(HabitRecord.class);
this.order = order;
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(
@@ -78,127 +83,123 @@ public class SQLiteHabitList extends HabitList
}
@Override
public void add(@NonNull Habit habit)
public synchronized void add(@NonNull Habit habit)
{
if (cache.containsValue(habit))
throw new IllegalArgumentException("habit already added");
loadRecords();
list.add(habit);
HabitRecord record = new HabitRecord();
record.copyFrom(habit);
record.position = size();
record.position = list.indexOf(habit);
repository.save(record);
Long id = habit.getId();
if (id == null) id = record.save();
else record.save(id);
if (id < 0)
throw new IllegalArgumentException("habit could not be saved");
habit.setId(id);
cache.put(id, habit);
getObservable().notifyListeners();
}
@Override
@Nullable
public Habit getById(long id)
{
if (!cache.containsKey(id))
{
HabitRecord record = HabitRecord.get(id);
if (record == null) return null;
Habit habit = modelFactory.buildHabit();
record.copyTo(habit);
cache.put(id, habit);
}
return cache.get(id);
loadRecords();
return list.getById(id);
}
@Override
@NonNull
public Habit getByPosition(int position)
{
return toList().get(position);
loadRecords();
return list.getByPosition(position);
}
@NonNull
@Override
public HabitList getFiltered(HabitMatcher filter)
{
return new SQLiteHabitList(modelFactory, filter, order);
loadRecords();
return list.getFiltered(filter);
}
@Override
@NonNull
public Order getOrder()
{
return order;
return list.getOrder();
}
@Override
public void setOrder(@NonNull Order order)
{
this.order = order;
list.setOrder(order);
}
@Override
public int indexOf(@NonNull Habit h)
{
return toList().indexOf(h);
loadRecords();
return list.indexOf(h);
}
@Override
public Iterator<Habit> iterator()
{
return Collections.unmodifiableCollection(toList()).iterator();
loadRecords();
return list.iterator();
}
public void rebuildOrder()
private void rebuildOrder()
{
List<Habit> habits = toList();
int i = 0;
for (Habit h : habits)
{
HabitRecord record = HabitRecord.get(h.getId());
if (record == null)
throw new RuntimeException("habit not in database");
record.position = i++;
record.save();
}
update(habits);
// 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 void remove(@NonNull Habit habit)
public synchronized void remove(@NonNull Habit habit)
{
if (!cache.containsKey(habit.getId()))
throw new RuntimeException("habit not in cache");
loadRecords();
list.remove(habit);
cache.remove(habit.getId());
HabitRecord record = HabitRecord.get(habit.getId());
HabitRecord record = repository.find(habit.getId());
if (record == null) throw new RuntimeException("habit not in database");
record.cascadeDelete();
executeAsTransaction(() ->
{
((SQLiteRepetitionList) habit.getRepetitions()).removeAll();
repository.remove(record);
});
rebuildOrder();
getObservable().notifyListeners();
}
@Override
public void removeAll()
public synchronized void removeAll()
{
sqlite.query("delete from repetitions", null);
sqlite.query("delete from habits", null);
list.removeAll();
SQLiteDatabase db = DatabaseUtils.openDatabase();
db.execSQL("delete from habits");
db.execSQL("delete from repetitions");
getObservable().notifyListeners();
}
@Override
public synchronized void reorder(Habit from, Habit to)
public synchronized void reorder(@NonNull Habit from, @NonNull Habit to)
{
if (from == to) return;
loadRecords();
list.reorder(from, to);
HabitRecord fromRecord = HabitRecord.get(from.getId());
HabitRecord toRecord = HabitRecord.get(to.getId());
HabitRecord fromRecord = repository.find(from.getId());
HabitRecord toRecord = repository.find(to.getId());
if (fromRecord == null)
throw new RuntimeException("habit not in database");
@@ -207,128 +208,59 @@ public class SQLiteHabitList extends HabitList
Integer fromPos = fromRecord.position;
Integer toPos = toRecord.position;
Log.d("SQLiteHabitList",
String.format("reorder: %d %d", fromPos, toPos));
SQLiteDatabase db = DatabaseUtils.openDatabase();
if (toPos < fromPos)
{
new Update(HabitRecord.class)
.set("position = position + 1")
.where("position >= ? and position < ?", toPos, fromPos)
.execute();
db.execSQL("update habits set position = position + 1 " +
"where position >= ? and position < ?",
new String[]{ toPos.toString(), fromPos.toString() });
}
else
{
new Update(HabitRecord.class)
.set("position = position - 1")
.where("position > ? and position <= ?", fromPos, toPos)
.execute();
db.execSQL("update habits set position = position - 1 " +
"where position > ? and position <= ?",
new String[]{ fromPos.toString(), toPos.toString() });
}
fromRecord.position = toPos;
fromRecord.save();
repository.save(fromRecord);
update(from);
getObservable().notifyListeners();
}
@Override
public void repair()
{
super.repair();
loadRecords();
rebuildOrder();
}
@Override
public int size()
{
return toList().size();
loadRecords();
return list.size();
}
@Override
public void update(List<Habit> habits)
public synchronized void update(List<Habit> habits)
{
loadRecords();
for (Habit h : habits)
{
HabitRecord record = HabitRecord.get(h.getId());
HabitRecord record = repository.find(h.getId());
if (record == null)
throw new RuntimeException("habit not in database");
record.copyFrom(h);
record.save();
}
}
public synchronized List<Habit> toList()
{
String query = buildSelectQuery();
List<HabitRecord> recordList = sqlite.query(query, null);
List<Habit> habits = new LinkedList<>();
for (HabitRecord record : recordList)
{
Habit habit = getById(record.getId());
if (habit == null) continue;
if (!filter.matches(habit)) continue;
habits.add(habit);
repository.save(record);
}
if(order == Order.BY_SCORE)
{
Collections.sort(habits, (lhs, rhs) -> {
double s1 = lhs.getScores().getTodayValue();
double s2 = rhs.getScores().getTodayValue();
return Double.compare(s2, s1);
});
}
return habits;
getObservable().notifyListeners();
}
private void appendOrderBy(StringBuilder query)
public void reload()
{
switch (order)
{
case BY_POSITION:
query.append("order by position ");
break;
case BY_NAME:
case BY_SCORE:
query.append("order by name ");
break;
case BY_COLOR:
query.append("order by color, name ");
break;
default:
throw new IllegalStateException();
}
}
private void appendSelect(StringBuilder query)
{
query.append(HabitRecord.SELECT);
}
private void appendWhere(StringBuilder query)
{
ArrayList<Object> where = new ArrayList<>();
if (filter.isReminderRequired()) where.add("reminder_hour is not null");
if (!filter.isArchivedAllowed()) where.add("archived = 0");
if (where.isEmpty()) return;
query.append("where ");
query.append(StringUtils.join(where, " and "));
query.append(" ");
}
private String buildSelectQuery()
{
StringBuilder query = new StringBuilder();
appendSelect(query);
appendWhere(query);
appendOrderBy(query);
return query.toString();
loaded = false;
}
}

View File

@@ -19,16 +19,14 @@
package org.isoron.uhabits.models.sqlite;
import android.database.*;
import android.database.sqlite.*;
import android.support.annotation.*;
import android.support.annotation.Nullable;
import com.activeandroid.*;
import com.activeandroid.query.*;
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.*;
@@ -38,165 +36,112 @@ import java.util.*;
*/
public class SQLiteRepetitionList extends RepetitionList
{
private final SQLiteRepository<RepetitionRecord> repository;
private final SQLiteUtils<RepetitionRecord> sqlite;
private final MemoryRepetitionList list;
@Nullable
private HabitRecord habitRecord;
private SQLiteStatement addStatement;
public static final String ADD_QUERY =
"insert into repetitions(habit, timestamp, value) " +
"values (?,?,?)";
private boolean loaded = false;
public SQLiteRepetitionList(@NonNull Habit habit)
{
super(habit);
sqlite = new SQLiteUtils<>(RepetitionRecord.class);
SQLiteDatabase db = Cache.openDatabase();
addStatement = db.compileStatement(ADD_QUERY);
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());
}
/**
* Adds a repetition to the global SQLite database.
* <p>
* Given a repetition, this creates and saves the corresponding
* RepetitionRecord to the database.
*
* @param rep the repetition to be added
*/
@Override
public void add(Repetition rep)
{
loadRecords();
list.add(rep);
check(habit.getId());
addStatement.bindLong(1, habit.getId());
addStatement.bindLong(2, rep.getTimestamp());
addStatement.bindLong(3, rep.getValue());
addStatement.execute();
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)
{
check(habit.getId());
String query = "select habit, timestamp, value " +
"from Repetitions " +
"where habit = ? and timestamp >= ? and timestamp <= ? " +
"order by timestamp";
String params[] = {
Long.toString(habit.getId()),
Long.toString(timeFrom),
Long.toString(timeTo)
};
List<RepetitionRecord> records = sqlite.query(query, params);
return toRepetitions(records);
loadRecords();
return list.getByInterval(timeFrom, timeTo);
}
@Override
@Nullable
public Repetition getByTimestamp(long timestamp)
{
check(habit.getId());
String query = "select habit, timestamp, value " +
"from Repetitions " +
"where habit = ? and timestamp = ? " +
"limit 1";
String params[] =
{ Long.toString(habit.getId()), Long.toString(timestamp) };
RepetitionRecord record = sqlite.querySingle(query, params);
if (record == null) return null;
record.habit = habitRecord;
return record.toRepetition();
loadRecords();
return list.getByTimestamp(timestamp);
}
@Override
public Repetition getOldest()
{
check(habit.getId());
String query = "select habit, timestamp, value " +
"from Repetitions " +
"where habit = ? " +
"order by timestamp asc " +
"limit 1";
String params[] = { Long.toString(habit.getId()) };
RepetitionRecord record = sqlite.querySingle(query, params);
if (record == null) return null;
record.habit = habitRecord;
return record.toRepetition();
loadRecords();
return list.getOldest();
}
@Override
public Repetition getNewest()
{
check(habit.getId());
String query = "select habit, timestamp, value " +
"from Repetitions " +
"where habit = ? " +
"order by timestamp desc " +
"limit 1";
String params[] = { Long.toString(habit.getId()) };
RepetitionRecord record = sqlite.querySingle(query, params);
if (record == null) return null;
record.habit = habitRecord;
return record.toRepetition();
loadRecords();
return list.getNewest();
}
@Override
public void remove(@NonNull Repetition repetition)
{
new Delete()
.from(RepetitionRecord.class)
.where("habit = ?", habit.getId())
.and("timestamp = ?", repetition.getTimestamp())
.execute();
loadRecords();
list.remove(repetition);
check(habit.getId());
repository.execSQL(
"delete from repetitions where habit = ? and timestamp = ?",
habit.getId());
observable.notifyListeners();
}
@Contract("null -> fail")
private void check(Long id)
{
if (id == null) throw new RuntimeException("habit is not saved");
if (habitRecord != null) return;
habitRecord = HabitRecord.get(id);
if (habitRecord == null) throw new RuntimeException("habit not found");
}
@NonNull
private List<Repetition> toRepetitions(
@NonNull List<RepetitionRecord> records)
public void removeAll()
{
loadRecords();
list.removeAll();
check(habit.getId());
List<Repetition> reps = new LinkedList<>();
for (RepetitionRecord record : records)
{
record.habit = habitRecord;
reps.add(record.toRepetition());
}
return reps;
repository.execSQL("delete from repetitions where habit = ?",
habit.getId());
}
@Override
public long getTotalCount()
{
SQLiteDatabase db = Cache.openDatabase();
loadRecords();
return list.getTotalCount();
}
return DatabaseUtils.queryNumEntries(db, "Repetitions",
"habit=?", new String[] { Long.toString(habit.getId()) });
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,84 +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.*;
import android.database.sqlite.*;
import android.support.annotation.*;
import com.activeandroid.*;
import org.isoron.uhabits.models.sqlite.records.*;
import java.util.*;
public class SQLiteUtils<T extends SQLiteRecord>
{
private Class klass;
public SQLiteUtils(Class klass)
{
this.klass = klass;
}
@NonNull
public List<T> query(String query, String params[])
{
SQLiteDatabase db = Cache.openDatabase();
try (Cursor c = db.rawQuery(query, params))
{
return cursorToMultipleRecords(c);
}
}
@Nullable
public T querySingle(String query, String params[])
{
SQLiteDatabase db = Cache.openDatabase();
try(Cursor c = db.rawQuery(query, params))
{
if (!c.moveToNext()) return null;
return cursorToSingleRecord(c);
}
}
@NonNull
private List<T> cursorToMultipleRecords(Cursor c)
{
List<T> records = new LinkedList<>();
while (c.moveToNext()) records.add(cursorToSingleRecord(c));
return records;
}
@NonNull
private T cursorToSingleRecord(Cursor c)
{
try
{
T record = (T) klass.newInstance();
record.copyFrom(c);
return record;
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
}

View File

@@ -19,122 +19,67 @@
package org.isoron.uhabits.models.sqlite.records;
import android.annotation.*;
import android.database.*;
import android.support.annotation.*;
import com.activeandroid.*;
import com.activeandroid.annotation.*;
import com.activeandroid.query.*;
import com.activeandroid.util.*;
import org.apache.commons.lang3.builder.*;
import org.isoron.androidbase.storage.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.utils.DatabaseUtils;
import java.lang.reflect.*;
/**
* The SQLite database record corresponding to a {@link Habit}.
*/
@Table(name = "Habits")
public class HabitRecord extends Model implements SQLiteRecord
@Table(name = "habits")
public class HabitRecord
{
public static String SELECT =
"select id, color, description, freq_den, freq_num, " +
"name, position, reminder_hour, reminder_min, " +
"highlight, archived, reminder_days, type, target_type, " +
"target_value, unit from habits ";
@Column(name = "name")
public String name;
@Column(name = "description")
@Column
public String description;
@Column
public String name;
@Column(name = "freq_num")
public int freqNum;
public Integer freqNum;
@Column(name = "freq_den")
public int freqDen;
public Integer freqDen;
@Column(name = "color")
public int color;
@Column
public Integer color;
@Column(name = "position")
public int position;
@Column
public Integer position;
@Nullable
@Column(name = "reminder_hour")
public Integer reminderHour;
@Nullable
@Column(name = "reminder_min")
public Integer reminderMin;
@Column(name = "reminder_days")
public int reminderDays;
public Integer reminderDays;
@Column(name = "highlight")
public int highlight;
@Column
public Integer highlight;
@Column(name = "archived")
public int archived;
@Column
public Integer archived;
@Column(name = "type")
public int type;
@Column
public Integer type;
@Column(name = "target_value")
public double targetValue;
public Double targetValue;
@Column(name = "target_type")
public int targetType;
public Integer targetType;
@Column(name = "unit")
@Column
public String unit;
public HabitRecord()
{
}
@Nullable
public static HabitRecord get(long id)
{
return HabitRecord.load(HabitRecord.class, id);
}
/**
* Changes the id of a habit on the database.
*
* @param oldId the original id
* @param newId the new id
*/
@SuppressLint("DefaultLocale")
public static void updateId(long oldId, long newId)
{
SQLiteUtils.execSql(
String.format("update Habits set Id = %d where Id = %d", newId,
oldId));
}
/**
* Deletes the habit and all data associated to it, including checkmarks,
* repetitions and scores.
*/
public void cascadeDelete()
{
Long id = getId();
DatabaseUtils.executeAsTransaction(() -> {
new Delete()
.from(RepetitionRecord.class)
.where("habit = ?", id)
.execute();
delete();
});
}
@Column
public Long id;
public void copyFrom(Habit model)
{
this.id = model.getId();
this.name = model.getName();
this.description = model.getDescription();
this.highlight = 0;
@@ -161,35 +106,14 @@ public class HabitRecord extends Model implements SQLiteRecord
}
}
@Override
public void copyFrom(Cursor c)
{
setId(c.getLong(0));
color = c.getInt(1);
description = c.getString(2);
freqDen = c.getInt(3);
freqNum = c.getInt(4);
name = c.getString(5);
position = c.getInt(6);
reminderHour = c.getInt(7);
reminderMin = c.getInt(8);
highlight = c.getInt(9);
archived = c.getInt(10);
reminderDays = c.getInt(11);
type = c.getInt(12);
targetType = c.getInt(13);
targetValue = c.getDouble(14);
unit = c.getString(15);
}
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.setId(this.getId());
habit.setType(this.type);
habit.setTargetType(this.targetType);
habit.setTargetValue(this.targetValue);
@@ -202,28 +126,77 @@ public class HabitRecord extends Model implements SQLiteRecord
}
}
/**
* Saves the habit on the database, and assigns the specified id to it.
*
* @param id the id that the habit should receive
*/
public void save(long id)
@Override
public boolean equals(Object o)
{
save();
updateId(getId(), id);
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();
}
private void setId(Long id)
@Override
public int hashCode()
{
try
{
Field f = (Model.class).getDeclaredField("mId");
f.setAccessible(true);
f.set(this, id);
}
catch (Exception e)
{
throw new RuntimeException(e);
}
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

@@ -19,32 +19,28 @@
package org.isoron.uhabits.models.sqlite.records;
import android.database.*;
import com.activeandroid.*;
import com.activeandroid.annotation.*;
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 extends Model implements SQLiteRecord
public class RepetitionRecord
{
@Column(name = "habit")
public HabitRecord habit;
@Column(name = "timestamp")
@Column(name = "habit")
public Long habit_id;
@Column
public Long timestamp;
@Column(name = "value")
public int value;
@Column
public Integer value;
public static RepetitionRecord get(Long id)
{
return RepetitionRecord.load(RepetitionRecord.class, id);
}
@Column
public Long id;
public void copyFrom(Repetition repetition)
{
@@ -52,13 +48,6 @@ public class RepetitionRecord extends Model implements SQLiteRecord
value = repetition.getValue();
}
@Override
public void copyFrom(Cursor c)
{
timestamp = c.getLong(1);
value = c.getInt(2);
}
public Repetition toRepetition()
{
return new Repetition(timestamp, value);

View File

@@ -1,27 +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 android.database.*;
public interface SQLiteRecord
{
void copyFrom(Cursor c);
}

View File

@@ -19,18 +19,17 @@
package org.isoron.uhabits.sync;
import android.support.annotation.NonNull;
import android.support.annotation.*;
import com.activeandroid.Model;
import com.activeandroid.annotation.Column;
import com.activeandroid.annotation.Table;
import com.activeandroid.query.Select;
import java.util.List;
import org.isoron.androidbase.storage.*;
@Table(name = "Events")
public class Event extends Model
public class Event
{
@Nullable
@Column
public Long id;
@NonNull
@Column(name = "timestamp")
public Long timestamp;
@@ -56,10 +55,4 @@ public class Event extends Model
this.timestamp = timestamp;
this.message = message;
}
@NonNull
public static List<Event> getAll()
{
return new Select().from(Event.class).orderBy("timestamp").execute();
}
}

View File

@@ -23,10 +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.preferences.*;
import org.isoron.uhabits.utils.*;
import org.json.*;
import java.net.*;
@@ -95,6 +97,8 @@ public class SyncManager implements CommandRunner.Listener
private SSLContextProvider sslProvider;
private final SQLiteRepository<Event> repository;
@Inject
public SyncManager(@NonNull SSLContextProvider sslProvider,
@NonNull Preferences prefs,
@@ -109,8 +113,10 @@ public class SyncManager implements CommandRunner.Listener
this.commandParser = commandParser;
this.isListening = false;
repository =
new SQLiteRepository<>(Event.class, DatabaseUtils.openDatabase());
pendingConfirmation = new LinkedList<>();
pendingEmit = new LinkedList<>(Event.getAll());
pendingEmit = new LinkedList<>(repository.findAll("order by timestamp"));
groupKey = prefs.getSyncKey();
clientId = prefs.getSyncClientId();
@@ -129,7 +135,7 @@ public class SyncManager implements CommandRunner.Listener
JSONObject msg = toJSONObject(command.toJson());
Long now = new Date().getTime();
Event e = new Event(command.getId(), now, msg.toString());
e.save();
repository.save(e);
Log.i("SyncManager", "Adding to outbox: " + msg.toString());
@@ -337,7 +343,7 @@ public class SyncManager implements CommandRunner.Listener
{
Log.i("SyncManager", "Pending command confirmed");
pendingConfirmation.remove(e);
e.delete();
repository.remove(e);
return;
}
}

View File

@@ -20,33 +20,40 @@
package org.isoron.uhabits.utils;
import android.content.*;
import android.database.sqlite.*;
import android.support.annotation.*;
import com.activeandroid.*;
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.models.sqlite.records.*;
import org.isoron.uhabits.sync.*;
import java.io.*;
import java.text.*;
public abstract class DatabaseUtils
{
@Nullable
private static HabitsDatabaseOpener opener = null;
public static void executeAsTransaction(Callback callback)
{
ActiveAndroid.beginTransaction();
try
try (SQLiteDatabase db = openDatabase())
{
callback.execute();
ActiveAndroid.setTransactionSuccessful();
}
finally
{
ActiveAndroid.endTransaction();
db.beginTransaction();
try
{
callback.execute();
db.setTransactionSuccessful();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
finally
{
db.endTransaction();
}
}
}
@@ -71,28 +78,24 @@ public abstract class DatabaseUtils
}
@SuppressWarnings("unchecked")
public static void initializeActiveAndroid(Context context)
public static void initializeDatabase(Context context)
{
Configuration dbConfig = new Configuration.Builder(context)
.setDatabaseName(getDatabaseFilename())
.setDatabaseVersion(BuildConfig.databaseVersion)
.addModelClasses(HabitRecord.class, RepetitionRecord.class,
Event.class).create();
try
{
ActiveAndroid.initialize(dbConfig);
opener = new HabitsDatabaseOpener(context, getDatabaseFilename(),
BuildConfig.databaseVersion);
}
catch (RuntimeException e)
{
if(e.getMessage().contains("downgrade"))
if (e.getMessage().contains("downgrade"))
throw new InvalidDatabaseVersionException();
else throw e;
}
}
@SuppressWarnings("ResultOfMethodCallIgnored")
public static String saveDatabaseCopy(Context context, File dir) throws IOException
public static String saveDatabaseCopy(Context context, File dir)
throws IOException
{
SimpleDateFormat dateFormat = DateFormats.getBackupDateFormat();
String date = dateFormat.format(DateUtils.getLocalTime());
@@ -106,8 +109,20 @@ public abstract class DatabaseUtils
return dbCopy.getAbsolutePath();
}
@NonNull
public static SQLiteDatabase openDatabase()
{
if(opener == null) throw new IllegalStateException();
return opener.getWritableDatabase();
}
public static void dispose()
{
opener = null;
}
public interface Callback
{
void execute();
void execute() throws Exception;
}
}

View File

@@ -25,14 +25,15 @@ import android.os.*;
import android.support.annotation.*;
import android.widget.*;
import com.activeandroid.util.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.preferences.*;
import static android.appwidget.AppWidgetManager.*;
import static org.isoron.androidbase.utils.InterfaceUtils.*;
import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT;
import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH;
import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT;
import static android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH;
import static org.isoron.androidbase.utils.InterfaceUtils.dpToPixels;
public abstract class BaseWidgetProvider extends AppWidgetProvider
{
@@ -109,7 +110,7 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider
}
catch (HabitNotFoundException e)
{
Log.e("BaseWidgetProvider", e);
e.printStackTrace();
}
}
}