mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 09:08:52 -06:00
Improve performance of toggleCheckmark
This commit is contained in:
@@ -25,6 +25,7 @@ import android.test.suitebuilder.annotation.SmallTest;
|
|||||||
import org.isoron.uhabits.BaseTest;
|
import org.isoron.uhabits.BaseTest;
|
||||||
import org.isoron.uhabits.helpers.DateHelper;
|
import org.isoron.uhabits.helpers.DateHelper;
|
||||||
import org.isoron.uhabits.models.Habit;
|
import org.isoron.uhabits.models.Habit;
|
||||||
|
import org.isoron.uhabits.models.Repetition;
|
||||||
import org.isoron.uhabits.unit.HabitFixtures;
|
import org.isoron.uhabits.unit.HabitFixtures;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
@@ -37,6 +38,7 @@ import java.util.GregorianCalendar;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static junit.framework.Assert.assertFalse;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
|
||||||
@@ -174,4 +176,16 @@ public class RepetitionListTest extends BaseTest
|
|||||||
from = to - 5 * DateHelper.millisecondsInOneDay;
|
from = to - 5 * DateHelper.millisecondsInOneDay;
|
||||||
assertThat(habit.repetitions.count(from, to), equalTo(3));
|
assertThat(habit.repetitions.count(from, to), equalTo(3));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getOldest()
|
||||||
|
{
|
||||||
|
long expectedOldestTimestamp = DateHelper.getStartOfToday() - 9 * DateHelper.millisecondsInOneDay;
|
||||||
|
|
||||||
|
assertThat(habit.repetitions.getOldestTimestamp(), equalTo(expectedOldestTimestamp));
|
||||||
|
|
||||||
|
Repetition oldest = habit.repetitions.getOldest();
|
||||||
|
assertFalse(oldest == null);
|
||||||
|
assertThat(oldest.timestamp, equalTo(expectedOldestTimestamp));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,11 +20,13 @@
|
|||||||
package org.isoron.uhabits.helpers;
|
package org.isoron.uhabits.helpers;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
|
|
||||||
import com.activeandroid.ActiveAndroid;
|
import com.activeandroid.ActiveAndroid;
|
||||||
|
import com.activeandroid.Cache;
|
||||||
import com.activeandroid.Configuration;
|
import com.activeandroid.Configuration;
|
||||||
|
|
||||||
import org.isoron.uhabits.BuildConfig;
|
import org.isoron.uhabits.BuildConfig;
|
||||||
@@ -163,4 +165,20 @@ public class DatabaseHelper
|
|||||||
|
|
||||||
ActiveAndroid.initialize(dbConfig);
|
ActiveAndroid.initialize(dbConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static long longQuery(String query, String args[])
|
||||||
|
{
|
||||||
|
Cursor c = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
c = Cache.openDatabase().rawQuery(query, args);
|
||||||
|
if (!c.moveToFirst()) return 0;
|
||||||
|
return c.getLong(0);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if(c != null) c.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ package org.isoron.uhabits.models;
|
|||||||
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteStatement;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import com.activeandroid.ActiveAndroid;
|
|
||||||
import com.activeandroid.Cache;
|
import com.activeandroid.Cache;
|
||||||
import com.activeandroid.query.Delete;
|
import com.activeandroid.query.Delete;
|
||||||
import com.activeandroid.query.Select;
|
import com.activeandroid.query.Select;
|
||||||
@@ -134,10 +134,9 @@ public class CheckmarkList
|
|||||||
*/
|
*/
|
||||||
protected void computeAll()
|
protected void computeAll()
|
||||||
{
|
{
|
||||||
Repetition oldestRep = habit.repetitions.getOldest();
|
long fromTimestamp = habit.repetitions.getOldestTimestamp();
|
||||||
if(oldestRep == null) return;
|
if(fromTimestamp == 0) return;
|
||||||
|
|
||||||
Long fromTimestamp = oldestRep.timestamp;
|
|
||||||
Long toTimestamp = DateHelper.getStartOfToday();
|
Long toTimestamp = DateHelper.getStartOfToday();
|
||||||
|
|
||||||
compute(fromTimestamp, toTimestamp);
|
compute(fromTimestamp, toTimestamp);
|
||||||
@@ -150,9 +149,9 @@ public class CheckmarkList
|
|||||||
* @param from timestamp for the beginning of the interval
|
* @param from timestamp for the beginning of the interval
|
||||||
* @param to timestamp for the end of the interval
|
* @param to timestamp for the end of the interval
|
||||||
*/
|
*/
|
||||||
protected void compute(long from, long to)
|
protected void compute(long from, final long to)
|
||||||
{
|
{
|
||||||
long day = DateHelper.millisecondsInOneDay;
|
final long day = DateHelper.millisecondsInOneDay;
|
||||||
|
|
||||||
Checkmark newestCheckmark = findNewest();
|
Checkmark newestCheckmark = findNewest();
|
||||||
if(newestCheckmark != null)
|
if(newestCheckmark != null)
|
||||||
@@ -165,9 +164,9 @@ public class CheckmarkList
|
|||||||
.selectFromTo(fromExtended, to)
|
.selectFromTo(fromExtended, to)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
int nDays = (int) ((to - from) / day) + 1;
|
final int nDays = (int) ((to - from) / day) + 1;
|
||||||
int nDaysExtended = (int) ((to - fromExtended) / day) + 1;
|
int nDaysExtended = (int) ((to - fromExtended) / day) + 1;
|
||||||
int checks[] = new int[nDaysExtended];
|
final int checks[] = new int[nDaysExtended];
|
||||||
|
|
||||||
for (Repetition rep : reps)
|
for (Repetition rep : reps)
|
||||||
{
|
{
|
||||||
@@ -187,24 +186,38 @@ public class CheckmarkList
|
|||||||
checks[i] = Checkmark.CHECKED_IMPLICITLY;
|
checks[i] = Checkmark.CHECKED_IMPLICITLY;
|
||||||
}
|
}
|
||||||
|
|
||||||
ActiveAndroid.beginTransaction();
|
|
||||||
|
long timestamps[] = new long[nDays];
|
||||||
|
for (int i = 0; i < nDays; i++)
|
||||||
|
timestamps[i] = to - i * day;
|
||||||
|
|
||||||
|
insert(timestamps, checks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insert(long timestamps[], int values[])
|
||||||
|
{
|
||||||
|
String query = "insert into Checkmarks(habit, timestamp, value) values (?,?,?)";
|
||||||
|
|
||||||
|
SQLiteDatabase db = Cache.openDatabase();
|
||||||
|
db.beginTransaction();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
for (int i = 0; i < nDays; i++)
|
SQLiteStatement statement = db.compileStatement(query);
|
||||||
|
statement.bindString(1, habit.getId().toString());
|
||||||
|
|
||||||
|
for (int i = 0; i < timestamps.length; i++)
|
||||||
{
|
{
|
||||||
Checkmark c = new Checkmark();
|
statement.bindLong(2, timestamps[i]);
|
||||||
c.habit = habit;
|
statement.bindLong(3, values[i]);
|
||||||
c.timestamp = to - i * day;
|
statement.execute();
|
||||||
c.value = checks[i];
|
|
||||||
c.save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ActiveAndroid.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
ActiveAndroid.endTransaction();
|
db.endTransaction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,9 @@ import com.activeandroid.Cache;
|
|||||||
import com.activeandroid.query.Delete;
|
import com.activeandroid.query.Delete;
|
||||||
import com.activeandroid.query.From;
|
import com.activeandroid.query.From;
|
||||||
import com.activeandroid.query.Select;
|
import com.activeandroid.query.Select;
|
||||||
|
import com.activeandroid.util.SQLiteUtils;
|
||||||
|
|
||||||
|
import org.isoron.uhabits.helpers.DatabaseHelper;
|
||||||
import org.isoron.uhabits.helpers.DateHelper;
|
import org.isoron.uhabits.helpers.DateHelper;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -96,22 +98,21 @@ public class RepetitionList
|
|||||||
timestamp = DateHelper.getStartOfDay(timestamp);
|
timestamp = DateHelper.getStartOfDay(timestamp);
|
||||||
|
|
||||||
if (contains(timestamp))
|
if (contains(timestamp))
|
||||||
{
|
|
||||||
delete(timestamp);
|
delete(timestamp);
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
insert(timestamp);
|
||||||
Repetition rep = new Repetition();
|
|
||||||
rep.habit = habit;
|
|
||||||
rep.timestamp = timestamp;
|
|
||||||
rep.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
habit.scores.invalidateNewerThan(timestamp);
|
habit.scores.invalidateNewerThan(timestamp);
|
||||||
habit.checkmarks.deleteNewerThan(timestamp);
|
habit.checkmarks.deleteNewerThan(timestamp);
|
||||||
habit.streaks.deleteNewerThan(timestamp);
|
habit.streaks.deleteNewerThan(timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void insert(long timestamp)
|
||||||
|
{
|
||||||
|
String[] args = { habit.getId().toString(), Long.toString(timestamp) };
|
||||||
|
SQLiteUtils.execSql("insert into Repetitions(habit, timestamp) values (?,?)", args);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the oldest repetition for the habit. If there is no repetition, returns null.
|
* Returns the oldest repetition for the habit. If there is no repetition, returns null.
|
||||||
* Repetitions in the future are discarded.
|
* Repetitions in the future are discarded.
|
||||||
@@ -124,6 +125,21 @@ public class RepetitionList
|
|||||||
return (Repetition) select().limit(1).executeSingle();
|
return (Repetition) select().limit(1).executeSingle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the timestamp of the oldest repetition. If there are no repetitions, returns zero.
|
||||||
|
* Repetitions in the future are discarded.
|
||||||
|
*
|
||||||
|
* @return timestamp of the oldest repetition
|
||||||
|
*/
|
||||||
|
public long getOldestTimestamp()
|
||||||
|
{
|
||||||
|
String[] args = { habit.getId().toString(), Long.toString(DateHelper.getStartOfToday()) };
|
||||||
|
String query = "select timestamp from Repetitions where habit = ? and timestamp <= ? " +
|
||||||
|
"order by timestamp limit 1";
|
||||||
|
|
||||||
|
return DatabaseHelper.longQuery(query, args);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the total number of repetitions for each month, from the first repetition until
|
* Returns the total number of repetitions for each month, from the first repetition until
|
||||||
* today, grouped by day of week. The repetitions are returned in a HashMap. The key is the
|
* today, grouped by day of week. The repetitions are returned in a HashMap. The key is the
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ package org.isoron.uhabits.models;
|
|||||||
|
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteStatement;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
@@ -28,8 +29,8 @@ import com.activeandroid.Cache;
|
|||||||
import com.activeandroid.query.Delete;
|
import com.activeandroid.query.Delete;
|
||||||
import com.activeandroid.query.From;
|
import com.activeandroid.query.From;
|
||||||
import com.activeandroid.query.Select;
|
import com.activeandroid.query.Select;
|
||||||
|
import com.activeandroid.util.SQLiteUtils;
|
||||||
|
|
||||||
import org.isoron.uhabits.helpers.DatabaseHelper;
|
|
||||||
import org.isoron.uhabits.helpers.DateHelper;
|
import org.isoron.uhabits.helpers.DateHelper;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -104,10 +105,9 @@ public class ScoreList
|
|||||||
*/
|
*/
|
||||||
private void computeAll()
|
private void computeAll()
|
||||||
{
|
{
|
||||||
Repetition oldestRep = habit.repetitions.getOldest();
|
long fromTimestamp = habit.repetitions.getOldestTimestamp();
|
||||||
if(oldestRep == null) return;
|
if(fromTimestamp == 0) return;
|
||||||
|
|
||||||
long fromTimestamp = oldestRep.timestamp;
|
|
||||||
long toTimestamp = DateHelper.getStartOfToday();
|
long toTimestamp = DateHelper.getStartOfToday();
|
||||||
compute(fromTimestamp, toTimestamp);
|
compute(fromTimestamp, toTimestamp);
|
||||||
}
|
}
|
||||||
@@ -136,28 +136,51 @@ public class ScoreList
|
|||||||
from = newestScore.timestamp + day;
|
from = newestScore.timestamp + day;
|
||||||
|
|
||||||
final int checkmarkValues[] = habit.checkmarks.getValues(from, to);
|
final int checkmarkValues[] = habit.checkmarks.getValues(from, to);
|
||||||
final int firstScore = newestScoreValue;
|
|
||||||
final long beginning = from;
|
final long beginning = from;
|
||||||
|
|
||||||
DatabaseHelper.executeAsTransaction(new DatabaseHelper.Command()
|
|
||||||
|
int lastScore = newestScoreValue;
|
||||||
|
int size = checkmarkValues.length;
|
||||||
|
|
||||||
|
long timestamps[] = new long[size];
|
||||||
|
long values[] = new long[size];
|
||||||
|
|
||||||
|
for (int i = 0; i < checkmarkValues.length; i++)
|
||||||
{
|
{
|
||||||
@Override
|
int checkmarkValue = checkmarkValues[checkmarkValues.length - i - 1];
|
||||||
public void execute()
|
lastScore = Score.compute(freq, lastScore, checkmarkValue);
|
||||||
|
timestamps[i] = beginning + day * i;
|
||||||
|
values[i] = lastScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
insert(timestamps, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insert(long timestamps[], long values[])
|
||||||
|
{
|
||||||
|
String query = "insert into Score(habit, timestamp, score) values (?,?,?)";
|
||||||
|
|
||||||
|
SQLiteDatabase db = Cache.openDatabase();
|
||||||
|
db.beginTransaction();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SQLiteStatement statement = db.compileStatement(query);
|
||||||
|
statement.bindString(1, habit.getId().toString());
|
||||||
|
|
||||||
|
for (int i = 0; i < timestamps.length; i++)
|
||||||
{
|
{
|
||||||
int lastScore = firstScore;
|
statement.bindLong(2, timestamps[i]);
|
||||||
|
statement.bindLong(3, values[i]);
|
||||||
for (int i = 0; i < checkmarkValues.length; i++)
|
statement.execute();
|
||||||
{
|
|
||||||
int checkmarkValue = checkmarkValues[checkmarkValues.length - i - 1];
|
|
||||||
|
|
||||||
Score s = new Score();
|
|
||||||
s.habit = habit;
|
|
||||||
s.timestamp = beginning + day * i;
|
|
||||||
s.score = lastScore = Score.compute(freq, lastScore, checkmarkValue);
|
|
||||||
s.save();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
db.endTransaction();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -185,9 +208,9 @@ public class ScoreList
|
|||||||
*/
|
*/
|
||||||
public int getValue(long timestamp)
|
public int getValue(long timestamp)
|
||||||
{
|
{
|
||||||
Score s = get(timestamp);
|
computeAll();
|
||||||
if(s == null) return 0;
|
String[] args = { habit.getId().toString(), Long.toString(timestamp) };
|
||||||
else return s.score;
|
return SQLiteUtils.intQuery("select score from Score where habit = ? and timestamp = ?", args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user