Skip to content

Commit

Permalink
Implement per-book session tracking (date opened, session duration, C…
Browse files Browse the repository at this point in the history
…SV), calculate average reading time per session, average pages read per session, geometer#261
  • Loading branch information
liquiddandruff committed Jan 28, 2015
1 parent 6ac19bc commit 7ddf8ad
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ final class SQLiteBooksDatabase extends BooksDatabase {

SQLiteBooksDatabase(Context context) {
myDatabase = context.openOrCreateDatabase("books.db", Context.MODE_PRIVATE, null);
exportDatabase("FBReader.books.db");
//exportDatabase("FBReader.books.db", false);
migrate();
}

public boolean exportDatabase(String dir) {
public boolean exportDatabase(String dir, boolean reverse) {
try {
File dataDir = Environment.getDataDirectory();
File sdDir = Environment.getExternalStorageDirectory();
Expand All @@ -67,7 +67,10 @@ public boolean exportDatabase(String dir) {
if (currentDB.exists()) {
FileChannel src = new FileInputStream(currentDB).getChannel();
FileChannel dst = new FileOutputStream(exportDB).getChannel();
dst.transferFrom(src, 0, src.size());
if(reverse)
src.transferFrom(dst, 0, dst.size());
else
dst.transferFrom(src, 0, src.size());
src.close();
dst.close();
return true;
Expand Down Expand Up @@ -361,7 +364,7 @@ protected Map<Long,Book> loadBooks(FileInfoSet infos, boolean existing) {
}

cursor = myDatabase.rawQuery(
"SELECT book_id,date_added,date_opened,date_closed,pages_turned,total_time_spent " +
"SELECT book_id,date_added,date_opened,sessions,pages_turned,total_time_spent " +
"FROM BookStatistics",
null
);
Expand All @@ -374,7 +377,7 @@ protected Map<Long,Book> loadBooks(FileInfoSet infos, boolean existing) {
cursor.getLong(0),
cursor.getLong(1),
cursor.getLong(2),
cursor.getLong(3),
cursor.getString(3),
(int)cursor.getLong(4),
(int)cursor.getLong(5))
);
Expand Down Expand Up @@ -577,7 +580,7 @@ protected List<String> listLabels(long bookId) {
"SELECT Labels.name FROM Labels" +
" INNER JOIN BookLabel ON BookLabel.label_id=Labels.label_id" +
" WHERE BookLabel.book_id=?",
new String[] { String.valueOf(bookId) }
new String[]{String.valueOf(bookId)}
);
final LinkedList<String> names = new LinkedList<String>();
while (cursor.moveToNext()) {
Expand Down Expand Up @@ -988,15 +991,15 @@ protected void deleteBookmark(Bookmark bookmark) {
protected BookStatistics getBookStatistics(long bookId) {
BookStatistics bookStatistics;
Cursor cursor = myDatabase.rawQuery(
"SELECT book_id,date_added,date_opened,date_closed,pages_turned,total_time_spent FROM" +
" BookStatistics WHERE book_id = " + bookId, null
"SELECT book_id,date_added,date_opened,sessions,pages_turned,total_time_spent FROM" +
" BookStatistics WHERE book_id = " + bookId, null
);
if(cursor.moveToNext()) {
bookStatistics = new BookStatistics(
cursor.getLong(0), // book_id
cursor.getLong(1), // date_added
cursor.getLong(2), // date_opened
cursor.getLong(3), // date_closed
cursor.getString(3), // sessions
(int)cursor.getLong(4), // pages_turned
(int)cursor.getLong(5)); // total_time_spent
} else {
Expand All @@ -1009,15 +1012,16 @@ protected BookStatistics getBookStatistics(long bookId) {

@Override
protected void saveBookStatistics(BookStatistics bookStatistics) {
Log.d("checkSAVE", "saveBookStatistics");
final SQLiteStatement statement = get(
"INSERT OR REPLACE INTO BookStatistics " +
"(book_id,date_added,date_opened,date_closed,pages_turned,total_time_spent) " +
"(book_id,date_added,date_opened,sessions,pages_turned,total_time_spent) " +
"VALUES (?,?,?,?,?,?)"
);
statement.bindLong(1, bookStatistics.getBookID());
statement.bindLong(2, bookStatistics.getDateAdded());
statement.bindLong(3, bookStatistics.getDateOpened());
statement.bindLong(4, bookStatistics.getDateClosed());
statement.bindString(4, bookStatistics.getSesssions());
statement.bindLong(5, bookStatistics.getPagesTurned());
statement.bindLong(6, bookStatistics.getTotalTimeSpent());
statement.execute();
Expand Down Expand Up @@ -1609,7 +1613,7 @@ private void updateTables29() {
"book_id INTEGER PRIMARY KEY REFERENCES Books(book_id)," +
"date_added INTEGER NOT NULL DEFAULT -1," +
"date_opened INTEGER," +
"date_closed INTEGER," +
"sessions TEXT(350)," +
"pages_turned INTEGER," +
"total_time_spent INTEGER)");
Cursor cursor = myDatabase.rawQuery("SELECT * FROM BookStatistics", null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,33 @@

import android.util.Log;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public final class BookStatistics {
private final long myBookID;
private final long myDateAdded;
private long myDateOpened;
private long myDateClosed;
private String mySessions;
private int myPagesTurned;
private int myTotalTimeSpent;

public final int SESSION_SIZE = 350;
public final int SESSION_RESERVED = 4;

public BookStatistics(long book_id, long date_added, long date_opened,
long date_closed, int pages_turned, int total_time_spent) {
String sessions, int pages_turned, int total_time_spent) {
myBookID = book_id;
myDateAdded = date_added;
myDateOpened = date_opened;
myDateClosed = date_closed;
mySessions = sessions;
myPagesTurned = pages_turned;
myTotalTimeSpent = total_time_spent;
}

public BookStatistics(long book_id) {
this(book_id, System.currentTimeMillis(), -1, -1, 0, 0);
this(book_id, System.currentTimeMillis(), -1, "", 0, 0);
}

public void startSession(long date_opened) {
Expand All @@ -49,10 +56,27 @@ public void startSession(long date_opened) {
}

public void endSession(long date_closed) {
myDateClosed = date_closed;
int elapsed = (int) (myDateClosed - myDateOpened);
Log.d("check4", "endSession: " + date_closed + "\tID: " + System.identityHashCode(this) + "\n" + toString() + " \t " + elapsed);
myTotalTimeSpent += elapsed > 0 ? elapsed : 0;
int elapsedMS = (int) (date_closed - myDateOpened);
// endSession() is called twice (once from FBReader.onPause(), and again from
// ZLApplication.closeWindow(), so ignore the second call
if(elapsedMS <= 0)
return;
// Intervals longer than 10 seconds qualify as a reading session
if(elapsedMS / 1000 > 10) {
// Last SESSION_RESERVED characters used to count total number of sessions
final String reserved = String.format("%0" + SESSION_RESERVED + "d", getNumberOfSessions() + 1);
// Junk last SESSION_RESERVED characters
mySessions = mySessions.substring(0, Math.max(0, mySessions.length() - SESSION_RESERVED));
// The date stored is accurate to minutes, session length is accurate to seconds
// The session date's time of epoch is the book's added date
final String newSession = String.valueOf((myDateOpened - myDateAdded) / 1000 / 60) + "," + (elapsedMS / 1000);
mySessions = newSession + (mySessions.length() == 0 ? "" : "," + mySessions);
// Throw away oldest sessions if needed to keep within SESSION_SIZE
mySessions = mySessions.substring(0, Math.min(mySessions.length(), SESSION_SIZE - SESSION_RESERVED)) + reserved;
}
Log.d("check4", "endSession: " + date_closed + "\tID: " + System.identityHashCode(this) + " \t elapsed: " + elapsedMS + "\n" + toString());
myDateOpened = date_closed;
myTotalTimeSpent += elapsedMS;
}

public void incrementPagesTurned() {
Expand All @@ -71,8 +95,26 @@ public long getDateOpened() {
return myDateOpened;
}

public long getDateClosed() {
return myDateClosed;
public String getSesssions() {
return mySessions;
}

public int getNumberOfSessions() {
final int len = mySessions.length();
return len < SESSION_RESERVED ? 0 : Integer.valueOf(mySessions.substring(len - SESSION_RESERVED, len));
}

public Map<Long, Integer> getProcessedSessions() {
if(mySessions.equals("")) return null;
// Junk last SESSION_RESERVED characters
final String sessions = mySessions.substring(0, Math.max(0, mySessions.length() - SESSION_RESERVED));
String[] parts = sessions.split(",");
// Junk last CSV if odd
final int max = parts.length % 2 == 0 ? parts.length : parts.length - 1;
Map<Long, Integer> dateSessionMap = new HashMap<Long, Integer>();
for(int i = 0; i < max; i += 2)
dateSessionMap.put(Integer.valueOf(parts[i]) * 60 * 1000 + myDateAdded, Integer.valueOf(parts[i + 1]));
return dateSessionMap;
}

public int getPagesTurned() {
Expand All @@ -85,10 +127,11 @@ public int getTotalTimeSpent() {

public String toString() {
return "book_id: " + myBookID +
"\tdate_added: " + myDateAdded +
"\tdate_opened: " + myDateOpened +
"\tdate_closed: " + myDateClosed +
"\tpages_turned: " + myPagesTurned +
"\tdate_added: " + new Date(myDateAdded).toLocaleString() +
"\tdate_opened: " + new Date(myDateOpened).toLocaleString() +
"\nsessions:\t" + mySessions +
"\nsessions.length(): " + mySessions.length() +
"\npages_turned: " + myPagesTurned +
"\ttotal_time_spent: " + myTotalTimeSpent;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ private void serialize(StringBuilder buffer, Book book) {
" progress: " + (progress == null ? "0" : "1"));

closeTag(buffer, "entry");
Log.d("check5", "serialize - checkXML: " + buffer.toString());
Log.d("check5", "serialize - checkXML:\n" + buffer.toString());
}

@Override
Expand Down Expand Up @@ -349,8 +349,8 @@ public String serialize(BookStatistics bookStatistics) {
appendTag(buffer, "bookstatistics", true,
"book_id", String.valueOf(bookStatistics.getBookID()),
"date_added", String.valueOf(bookStatistics.getDateAdded()),
"date_closed", String.valueOf(bookStatistics.getDateClosed()),
"date_opened", String.valueOf(bookStatistics.getDateOpened()),
"sessions", bookStatistics.getSesssions(),
"pages_turned", String.valueOf(bookStatistics.getPagesTurned()),
"total_time_spent", String.valueOf(bookStatistics.getTotalTimeSpent())
);
Expand Down Expand Up @@ -1102,7 +1102,7 @@ private static final class BookStatisticsDeserializer extends DefaultHandler {
private long myBookID;
private long myDateAdded;
private long myDateOpened;
private long myDateClosed;
private String mySessions;
private int myPagesTurned;
private int myTotalTimeSpent;
private BookStatistics myBookStatistics;
Expand All @@ -1123,7 +1123,7 @@ public void endDocument() {
myBookID,
myDateAdded,
myDateOpened,
myDateClosed,
mySessions,
myPagesTurned,
myTotalTimeSpent
);
Expand All @@ -1135,7 +1135,7 @@ public void startElement(String uri, String localName, String qName, Attributes
myBookID = parseLong(attributes.getValue("book_id"));
myDateAdded = parseLong(attributes.getValue("date_added"));
myDateOpened = parseLong(attributes.getValue("date_opened"));
myDateClosed = parseLong(attributes.getValue("date_closed"));
mySessions = attributes.getValue("sessions");
myPagesTurned = parseInt(attributes.getValue("pages_turned"));
myTotalTimeSpent = parseInt(attributes.getValue("total_time_spent"));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ public static enum Types { progress, completed, reading, average, library }
private static int averageBookTimeSpent;
private static int averageSeriesTimeSpent;
private static int averagePagesPerHour;
private static int averagePagesPerSession;
private static int averageSessionDurationSeconds;
// library
private final static Filter booksFavoritedFilter = new Filter.ByLabel(Book.FAVORITE_LABEL);
private static List<Book> booksFavorited;
Expand Down Expand Up @@ -180,10 +182,25 @@ public static enum Types { progress, completed, reading, average, library }
} case average: {
averageBookTimeSpent = 0;
averagePagesPerHour = 0;
averagePagesPerSession = 0;
averageSessionDurationSeconds = 0;
for (Book book : booksReading) {
averageBookTimeSpent += book.getStatistics().getTotalTimeSpent();
averagePagesPerHour += book.getStatistics().getPagesTurned();
final BookStatistics bookStatistics = book.getStatistics();
averageBookTimeSpent += bookStatistics.getTotalTimeSpent();
averagePagesPerHour += bookStatistics.getPagesTurned();
averagePagesPerSession += bookStatistics.getNumberOfSessions();
final Map<Long, Integer> sessions = bookStatistics.getProcessedSessions();
if(sessions == null) continue;
int currBookAverageSession = 0;
for(int sessionLength : sessions.values()) {
Log.d("check4", "" + sessionLength);
currBookAverageSession += sessionLength;
}
averageSessionDurationSeconds += currBookAverageSession / sessions.values().size();
}
averageSessionDurationSeconds /= booksReading.size() < 1 ? 1 : booksReading.size();
// order matters for this calculation; careful
averagePagesPerSession = averagePagesPerHour / averagePagesPerSession;
final int hours = averageBookTimeSpent / (1000 * 60 * 60);
averagePagesPerHour /= hours < 1 ? 1 : hours;
averageBookTimeSpent /= booksReading.size() < 1 ? 1 : booksReading.size();
Expand Down Expand Up @@ -358,33 +375,38 @@ private View createAverageStatisticsView(View convertView, ViewGroup parent, Lib
final TextView nameView = ViewUtil.findTextView(view, R.id.statistics_tree_item_name);
nameView.setText(tree.getName());

int temp = averageBookTimeSpent / 1000;
final int seconds = temp % 60; temp /= 60;
final int minutes = temp % 60; temp /= 60;
final int hours = temp;
int temp = averageSessionDurationSeconds;
int seconds = temp % 60; temp /= 60;
int minutes = temp % 60; temp /= 60;
int hours = temp;
setTextViewContents(ViewUtil.findTextView(view, R.id.statistics_tree_average_left),
headingSize, String.format("%02d:%02d:%02d", hours, minutes, seconds),
1.0f, "\nTime Per Book",
1.0f, "\nTime Per Session",
smallSize, "\n(h:m:s)\n\n",
headingSize, String.valueOf("40"),
headingSize, String.valueOf(averagePagesPerSession),
1.0f, "\nPages Per Session"
);

temp = averageBookTimeSpent / 1000;
seconds = temp % 60; temp /= 60;
minutes = temp % 60; temp /= 60;
hours = temp;
setTextViewContents(ViewUtil.findTextView(view, R.id.statistics_tree_average_center),
headingSize, String.valueOf(averagePagesPerHour),
1.0f, "\nPages Per Hour\n\n"
headingSize, String.format("%02d:%02d:%02d", hours, minutes, seconds),
1.0f, "\nTime Per Book",
smallSize, "\n(h:m:s)\n\n"
);

temp = averageSeriesTimeSpent / 1000;
final int seriesSeconds = temp % 60; temp /= 60;
final int seriesMinutes = temp % 60; temp /= 60;
final int seriesHours = temp;
seconds = temp % 60; temp /= 60;
minutes = temp % 60; temp /= 60;
hours = temp;
setTextViewContents(ViewUtil.findTextView(view, R.id.statistics_tree_average_right),
headingSize, String.format("%02d:%02d:%02d", seriesHours, seriesMinutes, seriesSeconds),
headingSize, String.format("%02d:%02d:%02d", hours, minutes, seconds),
1.0f, "\nTime Per Series",
smallSize, "\n(h:m:s)\n\n",
headingSize, String.valueOf("40"),
1.0f, "\nPages Per Session"
headingSize, String.valueOf(averagePagesPerHour),
1.0f, "\nPages Per Hour"
);

return view;
Expand Down
1 change: 0 additions & 1 deletion fBReaderJ/src/main/res/layout/statistics_tree_average.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
android:layout_marginTop="-20dp"
android:layout_below="@+id/statistics_tree_item_name"
android:layout_alignParentRight="true"
android:layout_alignBottom="@+id/statistics_tree_average_left"
android:minWidth="105dp"/>

</RelativeLayout>

0 comments on commit 7ddf8ad

Please sign in to comment.