Compare commits
33 Commits
@ -0,0 +1,56 @@ |
||||
package net.pokeranalytics.android |
||||
|
||||
import android.app.Service |
||||
import android.content.Intent |
||||
import android.os.Binder |
||||
import android.os.IBinder |
||||
import io.realm.Realm |
||||
import timber.log.Timber |
||||
|
||||
class RealmWriteService : Service() { |
||||
|
||||
private lateinit var realm: Realm |
||||
|
||||
private val binder = LocalBinder() |
||||
|
||||
override fun onBind(intent: Intent?): IBinder { |
||||
return binder |
||||
} |
||||
|
||||
inner class LocalBinder : Binder() { |
||||
fun getService(): RealmWriteService = this@RealmWriteService |
||||
} |
||||
|
||||
override fun onCreate() { |
||||
super.onCreate() |
||||
this.realm = Realm.getDefaultInstance() |
||||
} |
||||
|
||||
override fun onDestroy() { |
||||
super.onDestroy() |
||||
|
||||
Timber.d(">>>> Service destroyed : realm close") |
||||
this.realm.close() |
||||
} |
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { |
||||
return super.onStartCommand(intent, flags, startId) |
||||
} |
||||
|
||||
fun executeRealmAsyncTransaction(handler: (Realm) -> (Unit)) { |
||||
|
||||
Timber.d(">>>> Launch async transaction...") |
||||
|
||||
this.realm.executeTransactionAsync({ asyncRealm -> |
||||
handler(asyncRealm) |
||||
Timber.d(">> transaction handler done") |
||||
}, { |
||||
Timber.d(">> onSuccess, refreshing...") |
||||
this.realm.refresh() |
||||
}, { |
||||
Timber.d(">> transaction failed: $it") |
||||
}) |
||||
|
||||
} |
||||
|
||||
} |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,61 @@ |
||||
package net.pokeranalytics.android.model.realm |
||||
|
||||
import io.realm.RealmObject |
||||
import io.realm.RealmResults |
||||
import io.realm.annotations.LinkingObjects |
||||
import io.realm.annotations.PrimaryKey |
||||
import io.realm.annotations.RealmClass |
||||
import net.pokeranalytics.android.model.filter.Filterable |
||||
import net.pokeranalytics.android.model.filter.QueryCondition |
||||
import java.util.* |
||||
|
||||
|
||||
@RealmClass |
||||
open class FlatTimeInterval : RealmObject(), Filterable { |
||||
|
||||
@PrimaryKey |
||||
var id = UUID.randomUUID().toString() |
||||
|
||||
/** |
||||
* The start date of the session |
||||
*/ |
||||
var startDate: Date = Date() |
||||
set(value) { |
||||
field = value |
||||
this.computeDuration() |
||||
} |
||||
|
||||
/** |
||||
* The start date of the session |
||||
*/ |
||||
var endDate: Date = Date() |
||||
set(value) { |
||||
field = value |
||||
this.computeDuration() |
||||
} |
||||
|
||||
/** |
||||
* the net duration of the session, automatically calculated |
||||
*/ |
||||
var duration: Long = 0L |
||||
|
||||
@LinkingObjects("flatTimeIntervals") |
||||
val sessions: RealmResults<Session>? = null |
||||
|
||||
private fun computeDuration() { |
||||
duration = endDate.time - startDate.time |
||||
} |
||||
|
||||
companion object { |
||||
|
||||
fun fieldNameForQueryType(queryCondition: Class <out QueryCondition>): String? { |
||||
Session.fieldNameForQueryType(queryCondition)?.let { |
||||
return "sessions.$it" |
||||
} |
||||
return null |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
@ -1,288 +0,0 @@ |
||||
//package net.pokeranalytics.android.model.realm |
||||
// |
||||
//import io.realm.RealmObject |
||||
//import io.realm.RealmQuery |
||||
//import io.realm.RealmResults |
||||
//import io.realm.annotations.Ignore |
||||
//import io.realm.annotations.LinkingObjects |
||||
//import net.pokeranalytics.android.exceptions.ModelException |
||||
//import timber.log.Timber |
||||
//import java.util.* |
||||
// |
||||
//open class TimeFrame : RealmObject() { |
||||
// |
||||
// // A start date |
||||
// var startDate: Date = Date() |
||||
// private set(value) { |
||||
// field = value |
||||
// this.computeNetDuration() |
||||
// } |
||||
// |
||||
// // An end date |
||||
// var endDate: Date? = null |
||||
// private set(value) { |
||||
// field = value |
||||
// this.computeNetDuration() |
||||
// } |
||||
// |
||||
// // The latest pause date |
||||
// var pauseDate: Date? = null |
||||
// set(value) { |
||||
// field?.let { |
||||
// if (value == null && field != null) { |
||||
// breakDuration += Date().time - it.time |
||||
// } |
||||
// } |
||||
// field = value |
||||
// this.computeNetDuration() |
||||
// } |
||||
// |
||||
// // The break netDuration |
||||
// var breakDuration: Long = 0L |
||||
// set(value) { |
||||
// field = value |
||||
// this.computeNetDuration() |
||||
// } |
||||
// |
||||
// // the total netDuration |
||||
// var netDuration: Long = 0L |
||||
// private set |
||||
// |
||||
// var hourlyDuration: Double = 0.0 |
||||
// get() { |
||||
// return this.netDuration / 3600000.0 // 3.6 millions of milliseconds |
||||
// } |
||||
// |
||||
// // Session |
||||
// @LinkingObjects("timeFrame") |
||||
// private val endedSessions: RealmResults<Session>? = null // we should have only one session |
||||
// |
||||
// @Ignore |
||||
// var session: Session? = null |
||||
// get() = if (this.endedSessions != null && this.endedSessions.isEmpty()) null else this.endedSessions?.first() |
||||
// |
||||
// // Group |
||||
// @LinkingObjects("timeFrame") |
||||
// private val sets: RealmResults<SessionSet>? = null // we should have only one sessionGroup |
||||
// |
||||
// @Ignore |
||||
// var set: SessionSet? = null |
||||
// get() = this.sets?.first() |
||||
// |
||||
// fun setStart(startDate: Date) { |
||||
// this.startDate = startDate |
||||
// this.session?.let { |
||||
// this.notifySessionDateChange(it) |
||||
// } |
||||
// } |
||||
// |
||||
// fun setEnd(endDate: Date?) { |
||||
// this.endDate = endDate |
||||
// this.session?.let { |
||||
// this.notifySessionDateChange(it) |
||||
// } |
||||
// } |
||||
// |
||||
// fun setDate(startDate: Date, endDate: Date?) { |
||||
// this.startDate = startDate |
||||
// this.endDate = endDate |
||||
// |
||||
// this.session?.let { |
||||
// this.notifySessionDateChange(it) |
||||
// } |
||||
// } |
||||
// |
||||
// /** |
||||
// * Computes the net netDuration of the session |
||||
// */ |
||||
// private fun computeNetDuration() { |
||||
// var endDate: Date = this.endDate ?: Date() |
||||
// this.netDuration = endDate.time - this.startDate.time - this.breakDuration |
||||
// } |
||||
// |
||||
// /** |
||||
// * Queries all time frames that might be impacted by the date change |
||||
// * Makes all necessary changes to keep sequential time frames |
||||
// */ |
||||
// fun notifySessionDateChange(owner: Session) { |
||||
// |
||||
// var query: RealmQuery<SessionSet> = this.realm.where(SessionSet::class.java) |
||||
// query.isNotNull("timeFrame") |
||||
// |
||||
//// Timber.d("this> sd = : ${this.startDate}, ed = ${this.endDate}") |
||||
// |
||||
// val sets = realm.where(SessionSet::class.java).findAll() |
||||
//// Timber.d("set count = ${sets.size}") |
||||
// |
||||
// if (this.endDate == null) { |
||||
// query.greaterThanOrEqualTo("timeFrame.startDate", this.startDate) |
||||
// .or() |
||||
// .greaterThanOrEqualTo("timeFrame.endDate", this.startDate) |
||||
// .or() |
||||
// .isNull("timeFrame.endDate") |
||||
// } else { |
||||
// val endDate = this.endDate!! |
||||
// query |
||||
// .lessThanOrEqualTo("timeFrame.startDate", this.startDate) |
||||
// .greaterThanOrEqualTo("timeFrame.endDate", this.startDate) |
||||
// .or() |
||||
// .lessThanOrEqualTo("timeFrame.startDate", endDate) |
||||
// .greaterThanOrEqualTo("timeFrame.endDate", endDate) |
||||
// .or() |
||||
// .greaterThanOrEqualTo("timeFrame.startDate", this.startDate) |
||||
// .lessThanOrEqualTo("timeFrame.endDate", endDate) |
||||
// .or() |
||||
// .isNull("timeFrame.endDate") |
||||
// .lessThanOrEqualTo("timeFrame.startDate", endDate) |
||||
// } |
||||
// |
||||
// val sessionGroups = query.findAll() |
||||
// |
||||
// this.updateTimeFrames(sessionGroups, owner) |
||||
// |
||||
// } |
||||
// |
||||
// /** |
||||
// * Update Time frames from sets |
||||
// */ |
||||
// private fun updateTimeFrames(sessionSets: RealmResults<SessionSet>, owner: Session) { |
||||
// |
||||
// when (sessionSets.size) { |
||||
// 0 -> this.createOrUpdateSessionSet(owner) |
||||
// 1 -> this.updateSessionGroup(owner, sessionSets.first()!!) |
||||
// else -> this.mergeSessionGroups(owner, sessionSets) |
||||
// } |
||||
// |
||||
// } |
||||
// |
||||
// /** |
||||
// * Creates the session sessionGroup when the session has none |
||||
// */ |
||||
// private fun createOrUpdateSessionSet(owner: Session) { |
||||
// |
||||
// val set = owner.sessionSet |
||||
// if (set != null) { |
||||
// set.timeFrame?.startDate = this.startDate |
||||
// set.timeFrame?.endDate = this.endDate |
||||
// } else { |
||||
// this.createSessionSet(owner) |
||||
// } |
||||
// |
||||
//// Timber.d("sd = : ${set.timeFrame?.startDate}, ed = ${set.timeFrame?.endDate}") |
||||
// Timber.d("netDuration 1 = : ${set?.timeFrame?.netDuration}") |
||||
// |
||||
// } |
||||
// |
||||
// fun createSessionSet(owner: Session) { |
||||
// val set: SessionSet = SessionSet.newInstanceForResult(this.realm) |
||||
// set.timeFrame?.let { |
||||
// it.startDate = this.startDate |
||||
// it.endDate = this.endDate |
||||
// } ?: run { |
||||
// throw ModelException("TimeFrame should never be null here") |
||||
// } |
||||
// |
||||
// owner.sessionSet = set |
||||
// } |
||||
// |
||||
// |
||||
// /** |
||||
// * Single SessionSet update, the session might be the owner |
||||
// * Changes the sessionGroup timeframe using the current timeframe dates |
||||
// */ |
||||
// private fun updateSessionGroup(owner: Session, sessionSet: SessionSet) { |
||||
// |
||||
// var timeFrame: TimeFrame = sessionSet.timeFrame!! // tested in the query |
||||
//// timeFrame.setDate(this.startDate, this.endDate) |
||||
// |
||||
// val sisterSessions = sessionSet.endedSessions!! // shouldn't crash ever |
||||
// |
||||
// // if we have only one session in the set and that it corresponds to the set |
||||
// if (sessionSet.endedSessions?.size == 1 && sessionSet.endedSessions?.first() == owner) { |
||||
// timeFrame.setDate(this.startDate, this.endDate) |
||||
// } else { // there are 2+ endedSessions to manage and possible splits |
||||
// |
||||
// val endDate = this.endDate |
||||
// |
||||
// // case where all endedSessions are over but the set is not, we might have a split, so we delete the set and save everything again |
||||
// if (endDate != null && sisterSessions.all { it.timeFrame?.endDate != null } && timeFrame.endDate == null) { |
||||
// var endedSessions = mutableListOf<Session>(owner) |
||||
// sessionSet.endedSessions?.forEach { endedSessions.add(it) } |
||||
// sessionSet.deleteFromRealm() |
||||
// endedSessions.forEach { it.timeFrame?.notifySessionDateChange(it) } |
||||
// } else { |
||||
// |
||||
// if (this.startDate.before(timeFrame.startDate)) { |
||||
// timeFrame.startDate = this.startDate |
||||
// } |
||||
// if (endDate != null && timeFrame.endDate != null && endDate.after(timeFrame.endDate)) { |
||||
// timeFrame.endDate = endDate |
||||
// } else if (endDate == null) { |
||||
// timeFrame.endDate = null |
||||
// } |
||||
// |
||||
// owner.sessionSet = sessionSet |
||||
// |
||||
//// Timber.d("sd = : ${sessionSet.timeFrame?.startDate}, ed = ${sessionSet.timeFrame?.endDate}") |
||||
// Timber.d("netDuration 2 = : ${sessionSet.timeFrame?.netDuration}") |
||||
// } |
||||
// |
||||
// } |
||||
// |
||||
// } |
||||
// |
||||
// /** |
||||
// * Multiple session sets update: |
||||
// * Merges all sets into one (delete all then create a new one) |
||||
// */ |
||||
// private fun mergeSessionGroups(owner: Session, sessionSets: RealmResults<SessionSet>) { |
||||
// |
||||
// var startDate: Date = this.startDate |
||||
// var endDate: Date? = this.endDate |
||||
// |
||||
// // find earlier and later dates from all sets |
||||
// val timeFrames = sessionSets.mapNotNull { it.timeFrame } |
||||
// timeFrames.forEach { tf -> |
||||
// if (tf.startDate.before(startDate)) { |
||||
// startDate = tf.startDate |
||||
// } |
||||
// |
||||
// endDate?.let { ed -> |
||||
// tf.endDate?.let { tfed -> |
||||
// if (tfed.after(ed)) { |
||||
// endDate = tfed |
||||
// } |
||||
// } |
||||
// } ?: run { |
||||
// endDate = tf.endDate |
||||
// } |
||||
// |
||||
// } |
||||
// |
||||
// // get all endedSessions from sets |
||||
// var endedSessions = mutableSetOf<Session>() |
||||
// sessionSets.forEach { set -> |
||||
// set.endedSessions?.asIterable()?.let { endedSessions.addAll(it) } |
||||
// } |
||||
// |
||||
// // delete all sets |
||||
// sessionSets.deleteAllFromRealm() |
||||
// |
||||
// // Create a new sets |
||||
// val set: SessionSet = SessionSet.newInstanceForResult(this.realm) |
||||
// set.timeFrame?.let { |
||||
// it.setDate(startDate, endDate) |
||||
// } ?: run { |
||||
// throw ModelException("TimeFrame should never be null here") |
||||
// } |
||||
// |
||||
// // Add the session linked to this timeframe to the new sessionGroup |
||||
// owner.sessionSet = set |
||||
// |
||||
// // Add all orphan endedSessions |
||||
// endedSessions.forEach { it.sessionSet = set } |
||||
// Timber.d("netDuration 3 = : ${set.timeFrame?.netDuration}") |
||||
// |
||||
// } |
||||
// |
||||
//} |
||||
@ -1,209 +0,0 @@ |
||||
package net.pokeranalytics.android.model.utils |
||||
|
||||
import io.realm.RealmQuery |
||||
import io.realm.RealmResults |
||||
import net.pokeranalytics.android.exceptions.ModelException |
||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
||||
import net.pokeranalytics.android.model.realm.Session |
||||
import net.pokeranalytics.android.model.realm.SessionSet |
||||
import kotlin.math.max |
||||
|
||||
class CorruptSessionSetException(message: String) : Exception(message) |
||||
|
||||
/** |
||||
* The manager is in charge of updating the abstract concept of timeline, |
||||
* representing the sequenced time frames where the user plays. |
||||
*/ |
||||
class SessionSetManager { |
||||
|
||||
companion object { |
||||
|
||||
/** |
||||
* Updates the global timeline using the updated [session] |
||||
*/ |
||||
fun updateTimeline(session: Session) { |
||||
|
||||
if (!session.realm.isInTransaction) { |
||||
throw PAIllegalStateException("realm should be in transaction at this point") |
||||
} |
||||
|
||||
if (session.startDate == null) { |
||||
throw ModelException("Start date should never be null here") |
||||
} |
||||
if (session.endDate == null) { |
||||
throw ModelException("End date should never be null here") |
||||
} |
||||
|
||||
val sessionSets = this.matchingSets(session) |
||||
cleanupSessionSets(session, sessionSets) |
||||
|
||||
|
||||
} |
||||
|
||||
private fun matchingSets(session: Session) : RealmResults<SessionSet> { |
||||
val realm = session.realm |
||||
val endDate = session.endDate!! // tested above |
||||
val startDate = session.startDate!! |
||||
|
||||
val query: RealmQuery<SessionSet> = realm.where(SessionSet::class.java) |
||||
|
||||
query |
||||
.lessThanOrEqualTo("startDate", startDate) |
||||
.greaterThanOrEqualTo("endDate", startDate) |
||||
.or() |
||||
.lessThanOrEqualTo("startDate", endDate) |
||||
.greaterThanOrEqualTo("endDate", endDate) |
||||
.or() |
||||
.greaterThanOrEqualTo("startDate", startDate) |
||||
.lessThanOrEqualTo("endDate", endDate) |
||||
|
||||
return query.findAll() |
||||
} |
||||
|
||||
/** |
||||
* Multiple session sets update: |
||||
* Merges or splits session sets |
||||
* Does that by deleting then recreating |
||||
*/ |
||||
private fun cleanupSessionSets(session: Session, sessionSets: RealmResults<SessionSet>) { |
||||
|
||||
// get all endedSessions from sets |
||||
val allImpactedSessions = mutableSetOf<Session>() |
||||
sessionSets.forEach { set -> |
||||
set.sessions?.asIterable()?.let { allImpactedSessions.addAll(it) } |
||||
} |
||||
allImpactedSessions.add(session) |
||||
|
||||
// delete all sets |
||||
sessionSets.deleteAllFromRealm() |
||||
|
||||
allImpactedSessions.forEach { impactedSession -> |
||||
val sets = matchingSets(impactedSession) |
||||
this.updateTimeFrames(sets, impactedSession) |
||||
} |
||||
|
||||
// Timber.d("netDuration 3 = : ${set.timeFrame?.netDuration}") |
||||
|
||||
} |
||||
|
||||
|
||||
/** |
||||
* Update the global timeline using the impacted [sessionSets] and the updated [session] |
||||
*/ |
||||
private fun updateTimeFrames(sessionSets: RealmResults<SessionSet>, session: Session) { |
||||
|
||||
when (sessionSets.size) { |
||||
0 -> this.createOrUpdateSessionSet(session) |
||||
else -> this.mergeSessionGroups(session, sessionSets) |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Creates or update the session set for the [session] |
||||
*/ |
||||
private fun createOrUpdateSessionSet(session: Session) { |
||||
|
||||
val set = session.sessionSet |
||||
if (set != null) { |
||||
set.startDate = session.startDate!! // tested above |
||||
set.endDate = session.endDate!! |
||||
} else { |
||||
this.createSessionSet(session) |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Create a set and affect it to the [session] |
||||
*/ |
||||
private fun createSessionSet(session: Session) { |
||||
val set: SessionSet = SessionSet.newInstance(session.realm) |
||||
set.startDate = session.startDate!! |
||||
set.endDate = session.endDate!! |
||||
set.breakDuration = session.breakDuration |
||||
session.sessionSet = set |
||||
set.computeStats() |
||||
} |
||||
|
||||
/** |
||||
* Multiple session sets update: |
||||
* Merges all sets into one (delete all then create a new one) |
||||
*/ |
||||
private fun mergeSessionGroups(session: Session, sessionSets: RealmResults<SessionSet>) { |
||||
|
||||
var startDate = session.startDate!! |
||||
var endDate = session.endDate!! |
||||
|
||||
// get all endedSessions from sets |
||||
val sessions = mutableSetOf<Session>() |
||||
sessionSets.forEach { set -> |
||||
set.sessions?.asIterable()?.let { sessions.addAll(it) } |
||||
} |
||||
|
||||
// find earlier and later dates from all sets |
||||
sessions.forEach { s -> |
||||
|
||||
if (s.startDate != null && s.endDate != null) { |
||||
val start = s.startDate!! |
||||
val end = s.endDate!! |
||||
if (start.before(startDate)) { |
||||
startDate = start |
||||
} |
||||
if (end.after(endDate)) { |
||||
endDate = end |
||||
} |
||||
} else { |
||||
throw CorruptSessionSetException("Set contains unfinished sessions!") |
||||
} |
||||
} |
||||
|
||||
// delete all sets |
||||
sessionSets.deleteAllFromRealm() |
||||
|
||||
// Create a new set |
||||
val set: SessionSet = SessionSet.newInstance(session.realm) |
||||
set.startDate = startDate |
||||
set.endDate = endDate |
||||
|
||||
// Add the session linked to this timeframe to the new sessionGroup |
||||
session.sessionSet = set |
||||
|
||||
// Add all orphan endedSessions |
||||
sessions.forEach { s -> |
||||
s.sessionSet = set |
||||
set.breakDuration = max(set.breakDuration, s.breakDuration) |
||||
} |
||||
set.computeStats() |
||||
|
||||
// Timber.d("netDuration 3 = : ${set.timeFrame?.netDuration}") |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Removes the [session] from the timeline |
||||
*/ |
||||
fun removeFromTimeline(session: Session) { |
||||
|
||||
if (!session.realm.isInTransaction) { |
||||
throw PAIllegalStateException("realm should be in transaction at this point") |
||||
} |
||||
|
||||
val sessionSet = session.sessionSet |
||||
if (sessionSet != null) { |
||||
|
||||
val sessions = mutableSetOf<Session>() |
||||
sessionSet.sessions?.asIterable()?.let { sessions.addAll(it) } |
||||
sessions.remove(session) |
||||
|
||||
sessionSet.deleteFromRealm() |
||||
|
||||
sessions.forEach { |
||||
updateTimeline(it) |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,534 @@ |
||||
package net.pokeranalytics.android.model.utils |
||||
|
||||
import io.realm.Realm |
||||
import io.realm.RealmModel |
||||
import io.realm.RealmQuery |
||||
import io.realm.RealmResults |
||||
import net.pokeranalytics.android.exceptions.ModelException |
||||
import net.pokeranalytics.android.model.realm.FlatTimeInterval |
||||
import net.pokeranalytics.android.model.realm.Session |
||||
import net.pokeranalytics.android.model.realm.SessionSet |
||||
import net.pokeranalytics.android.util.extensions.findById |
||||
import net.pokeranalytics.android.util.extensions.max |
||||
import net.pokeranalytics.android.util.extensions.min |
||||
import timber.log.Timber |
||||
import java.util.* |
||||
|
||||
class CorruptSessionSetException(message: String) : Exception(message) |
||||
|
||||
/** |
||||
* The TimeManager pre-computes time related data: |
||||
* - SessionSet: All overlapping sessions are grouped into a SessionSet, |
||||
* used to calculate the number of sessions and break durations |
||||
* - FlatTimeInterval: Sessions time intervals are breaked down into smaller intervals |
||||
* when overlapping occurs to get faster duration calculations |
||||
*/ |
||||
object TimeManager { |
||||
|
||||
var sessions: RealmResults<Session>? = null |
||||
|
||||
private val sessionIdsToProcess = mutableSetOf<String>() |
||||
|
||||
private var start: Date? = null |
||||
private var end: Date? = null |
||||
|
||||
fun configure() {} // launch init |
||||
|
||||
fun startChanged(session: Session, date: Date?) { |
||||
this.start = min(this.start, date) |
||||
this.end = max(this.end, session.endDate) |
||||
this.sessionIdsToProcess.add(session.id) |
||||
} |
||||
|
||||
fun endChanged(session: Session, date: Date?) { |
||||
this.end = max(this.end, date) |
||||
this.start = min(this.start, session.startDate) |
||||
this.sessionIdsToProcess.add(session.id) |
||||
} |
||||
|
||||
fun sessionDateChanged(session: Session) { |
||||
this.start = min(this.start, session.startDate) |
||||
this.end = max(this.end, session.endDate) |
||||
this.sessionIdsToProcess.add(session.id) |
||||
} |
||||
|
||||
init { |
||||
|
||||
val realm = Realm.getDefaultInstance() |
||||
|
||||
sessions = realm.where(Session::class.java).findAllAsync() |
||||
sessions?.addChangeListener { _, _ -> |
||||
|
||||
if (sessionIdsToProcess.isNotEmpty()) { |
||||
|
||||
realm.executeTransactionAsync({ asyncRealm -> |
||||
|
||||
val sessions = sessionIdsToProcess.mapNotNull { asyncRealm.findById<Session>(it) } |
||||
sessionIdsToProcess.clear() |
||||
|
||||
for (session in sessions) { |
||||
Timber.d("Session id = ${session.id}") |
||||
Timber.d("Session time intervals count = ${session.flatTimeIntervals.size}") |
||||
session.flatTimeIntervals.deleteAllFromRealm() |
||||
val fti = FlatTimeInterval() |
||||
session.flatTimeIntervals.add(fti) |
||||
asyncRealm.insertOrUpdate(session) |
||||
} |
||||
|
||||
}, { |
||||
Timber.d("executeTransactionAsync onSuccess listener...") |
||||
val timeIntervals = realm.where(FlatTimeInterval::class.java).findAll() |
||||
Timber.d("Total timeIntervals count = ${timeIntervals.size}") |
||||
|
||||
timeIntervals.forEach { |
||||
Timber.d(">>> Time interval session count = ${it.sessions?.size}, session id = ${it.sessions?.firstOrNull()?.id}") |
||||
} |
||||
|
||||
}, {}) |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
// sessions?.addChangeListener { _, _ -> |
||||
// |
||||
// Timber.d("...sessions change at ${Date().time}") |
||||
// |
||||
// val start = this.start |
||||
// val end = this.end |
||||
// if (start != null && end != null) { |
||||
// |
||||
// Timber.d("...process date changes from $start to $end") |
||||
// |
||||
// this.start = null |
||||
// this.end = null |
||||
// |
||||
// realm.executeTransactionAsync ({ asyncRealm -> |
||||
// processSessions(asyncRealm, start, end) |
||||
// cleanUp() |
||||
// }, { |
||||
// Timber.d(">>>>> ON SUCCESS") |
||||
// |
||||
// realm.where(FlatTimeInterval::class.java).findAll().forEach { |
||||
// Timber.d("######## sessions count = ${it.sessions?.size}") |
||||
// } |
||||
// |
||||
// }, { |
||||
// Timber.d("Transaction failed : $it") |
||||
// }) |
||||
// } |
||||
// } |
||||
|
||||
realm.close() |
||||
} |
||||
|
||||
private fun cleanUp() { |
||||
this.start = null |
||||
this.end = null |
||||
this.sessionIdsToProcess.clear() |
||||
} |
||||
|
||||
private fun processSessions(realm: Realm, start: Date, end: Date) { |
||||
|
||||
Timber.d("***** processSessions, process count = ${sessionIdsToProcess.size}") |
||||
|
||||
// val start = this.start |
||||
// val end = this.end |
||||
|
||||
val sessions = sessionIdsToProcess.mapNotNull { realm.findById<Session>(it) } |
||||
for (session in sessions) { |
||||
|
||||
// Session Sets |
||||
val startDate = session.startDate |
||||
val endDate = session.endDate |
||||
if (startDate != null && endDate != null) { |
||||
updateTimeline(session) |
||||
} else if (session.sessionSet != null) { |
||||
removeFromTimeline(session) |
||||
} |
||||
|
||||
} |
||||
|
||||
// FlatTimeIntervals |
||||
processFlatTimeInterval(realm, sessions.toSet(), start, end) |
||||
|
||||
val ftis = realm.where(FlatTimeInterval::class.java).findAll() |
||||
Timber.d("*** FTIs count = ${ftis.size}") |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Updates the global timeline using the updated [session] |
||||
*/ |
||||
fun updateTimeline(session: Session) { |
||||
|
||||
// if (!session.realm.isInTransaction) { |
||||
// throw PAIllegalStateException("realm should be in transaction at this point") |
||||
// } |
||||
|
||||
if (session.startDate == null) { |
||||
throw ModelException("Start date should never be null here") |
||||
} |
||||
if (session.endDate == null) { |
||||
throw ModelException("End date should never be null here") |
||||
} |
||||
|
||||
val start = session.startDate!! |
||||
val end = session.endDate!! |
||||
|
||||
val sessionSets = this.matchingData<SessionSet>(session.realm, start, end) |
||||
cleanupSessionSets(session, sessionSets) |
||||
|
||||
} |
||||
|
||||
// private fun matchingSets(session: Session): RealmResults<SessionSet> { |
||||
// val realm = session.realm |
||||
// val endDate = session.endDate!! // tested above |
||||
// val startDate = session.startDate!! |
||||
// |
||||
// val query: RealmQuery<SessionSet> = realm.where(SessionSet::class.java) |
||||
// |
||||
// query |
||||
// .lessThanOrEqualTo("startDate", startDate) |
||||
// .greaterThanOrEqualTo("endDate", startDate) |
||||
// .or() |
||||
// .lessThanOrEqualTo("startDate", endDate) |
||||
// .greaterThanOrEqualTo("endDate", endDate) |
||||
// .or() |
||||
// .greaterThanOrEqualTo("startDate", startDate) |
||||
// .lessThanOrEqualTo("endDate", endDate) |
||||
// |
||||
// return query.findAll() |
||||
// } |
||||
|
||||
private inline fun <reified T : RealmModel> matchingData(realm: Realm, startDate: Date, endDate: Date): RealmResults<T> { |
||||
|
||||
val query: RealmQuery<T> = realm.where(T::class.java) |
||||
|
||||
query |
||||
.lessThanOrEqualTo("startDate", startDate) |
||||
.greaterThanOrEqualTo("endDate", startDate) |
||||
.or() |
||||
.lessThanOrEqualTo("startDate", endDate) |
||||
.greaterThanOrEqualTo("endDate", endDate) |
||||
.or() |
||||
.greaterThanOrEqualTo("startDate", startDate) |
||||
.lessThanOrEqualTo("endDate", endDate) |
||||
|
||||
return query.findAll() |
||||
} |
||||
|
||||
/** |
||||
* Multiple session sets update: |
||||
* Merges or splits session sets |
||||
* Does that by deleting then recreating |
||||
*/ |
||||
private fun cleanupSessionSets(session: Session, sessionSets: RealmResults<SessionSet>) { |
||||
|
||||
// get all endedSessions from sets |
||||
val allImpactedSessions = mutableSetOf<Session>() |
||||
sessionSets.forEach { set -> |
||||
set.sessions?.asIterable()?.let { allImpactedSessions.addAll(it) } |
||||
} |
||||
allImpactedSessions.add(session) |
||||
|
||||
// delete all sets |
||||
sessionSets.deleteAllFromRealm() |
||||
|
||||
allImpactedSessions.forEach { impactedSession -> |
||||
val sets = matchingData<SessionSet>(impactedSession.realm, impactedSession.startDate!!, impactedSession.endDate!!) |
||||
this.updateTimeFrames(sets, impactedSession) |
||||
} |
||||
|
||||
// Timber.d("netDuration 3 = : ${set.timeFrame?.netDuration}") |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Update the global timeline using the impacted [sessionSets] and the updated [session] |
||||
*/ |
||||
private fun updateTimeFrames(sessionSets: RealmResults<SessionSet>, session: Session) { |
||||
when (sessionSets.size) { |
||||
0 -> this.createOrUpdateSessionSet(session) |
||||
else -> this.mergeSessionGroups(session, sessionSets) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Creates or update the session set for the [session] |
||||
*/ |
||||
private fun createOrUpdateSessionSet(session: Session) { |
||||
|
||||
val set = session.sessionSet |
||||
if (set != null) { |
||||
set.startDate = session.startDate!! // tested above |
||||
set.endDate = session.endDate!! |
||||
} else { |
||||
this.createSessionSet(session) |
||||
} |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Create a set and affect it to the [session] |
||||
*/ |
||||
private fun createSessionSet(session: Session) { |
||||
val set = SessionSet.newInstance(session.realm) |
||||
set.startDate = session.startDate!! |
||||
set.endDate = session.endDate!! |
||||
set.breakDuration = session.breakDuration |
||||
session.sessionSet = set |
||||
set.computeStats() |
||||
} |
||||
|
||||
/** |
||||
* Multiple session sets update: |
||||
* Merges all sets into one (delete all then create a new one) |
||||
*/ |
||||
private fun mergeSessionGroups(session: Session, sessionSets: RealmResults<SessionSet>) { |
||||
|
||||
var startDate = session.startDate!! |
||||
var endDate = session.endDate!! |
||||
|
||||
// get all endedSessions from sets |
||||
val sessions = mutableSetOf<Session>() |
||||
sessionSets.forEach { set -> |
||||
set.sessions?.asIterable()?.let { sessions.addAll(it) } |
||||
} |
||||
|
||||
// find earlier and later dates from all sets |
||||
sessions.forEach { s -> |
||||
|
||||
if (s.startDate != null && s.endDate != null) { |
||||
val start = s.startDate!! |
||||
val end = s.endDate!! |
||||
if (start.before(startDate)) { |
||||
startDate = start |
||||
} |
||||
if (end.after(endDate)) { |
||||
endDate = end |
||||
} |
||||
} else { |
||||
throw CorruptSessionSetException("Set contains unfinished sessions!") |
||||
} |
||||
} |
||||
|
||||
// delete all sets |
||||
sessionSets.deleteAllFromRealm() |
||||
|
||||
// Create a new set |
||||
val set = SessionSet.newInstance(session.realm) |
||||
set.startDate = startDate |
||||
set.endDate = endDate |
||||
|
||||
// Add the session linked to this timeframe to the new sessionGroup |
||||
session.sessionSet = set |
||||
|
||||
// Add all orphan endedSessions |
||||
sessions.forEach { s -> |
||||
s.sessionSet = set |
||||
} |
||||
set.computeStats() |
||||
|
||||
// Timber.d("netDuration 3 = : ${set.timeFrame?.netDuration}") |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Removes the [session] from the timeline |
||||
*/ |
||||
fun removeFromTimeline(session: Session) { |
||||
|
||||
// if (!session.realm.isInTransaction) { |
||||
// throw PAIllegalStateException("realm should be in transaction at this point") |
||||
// } |
||||
|
||||
val sessionSet = session.sessionSet |
||||
if (sessionSet != null) { |
||||
|
||||
val sessions = mutableSetOf<Session>() |
||||
sessionSet.sessions?.asIterable()?.let { sessions.addAll(it) } |
||||
sessions.remove(session) |
||||
|
||||
sessionSet.deleteFromRealm() |
||||
|
||||
sessions.forEach { |
||||
updateTimeline(it) |
||||
} |
||||
} |
||||
} |
||||
|
||||
private fun processFlatTimeInterval(realm: Realm, changedSessions: Set<Session>, start: Date, end: Date) { |
||||
|
||||
// Timber.d("***************************************************") |
||||
// Timber.d("*** processFlatTimeInterval, from: $start, to $end") |
||||
// Timber.d("***************************************************") |
||||
|
||||
val sessions = matchingData<Session>(realm, start, end) |
||||
val intervalsStore = IntervalsStore(sessions.toSet()) |
||||
intervalsStore.processSessions(changedSessions) |
||||
|
||||
Timber.d("*** sessions count = ${intervalsStore.sessions.size}") |
||||
Timber.d("*** ftis to delete: ${intervalsStore.intervals.size}") |
||||
for (fti in intervalsStore.intervals) { |
||||
fti.deleteFromRealm() |
||||
} |
||||
|
||||
// intervalsStore.intervals.forEach { it.deleteFromRealm() } |
||||
|
||||
val intervals = SessionInterval.intervalMap(intervalsStore.sessions) |
||||
|
||||
for (interval in intervals) { |
||||
|
||||
val sortedDates = interval.dates.sorted() |
||||
for (i in (0 until sortedDates.size - 1)) { |
||||
|
||||
val s = sortedDates[i] |
||||
val e = sortedDates[i + 1] |
||||
|
||||
val matchingSessions = interval.sessions.filter { |
||||
val sd = it.startDate |
||||
val ed = it.endDate |
||||
(sd != null && ed != null && sd <= s && ed >= e) |
||||
} |
||||
if (matchingSessions.isNotEmpty()) { |
||||
// Timber.d("**** Create FTI: $s - $e") |
||||
val fti = FlatTimeInterval() |
||||
fti.startDate = s |
||||
fti.endDate = e |
||||
for (session in matchingSessions) { |
||||
session.flatTimeIntervals.add(fti) |
||||
realm.insertOrUpdate(session) |
||||
} |
||||
realm.insertOrUpdate(fti) |
||||
} else { |
||||
Timber.w("The FTI has no sessions") |
||||
} |
||||
} |
||||
} |
||||
|
||||
sessions.forEach { |
||||
Timber.d("ending process...session FTI count = ${it.flatTimeIntervals.size}") |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
class IntervalsStore(sessionSet: Set<Session>) { |
||||
|
||||
var start: Date = Date() |
||||
var end: Date = Date(0L) |
||||
|
||||
val intervals = mutableSetOf<FlatTimeInterval>() |
||||
|
||||
val sessions = mutableSetOf<Session>() |
||||
|
||||
private val sessionIds: MutableSet<String> = mutableSetOf() |
||||
|
||||
init { |
||||
processSessions(sessionSet) |
||||
} |
||||
|
||||
fun processSessions(sessions: Set<Session>) { |
||||
this.sessions.addAll(sessions) |
||||
for (session in sessions) { |
||||
// Timber.d("PROCESS > s = ${session.startDate} / e = ${session.endDate} ") |
||||
loadIntervals(session) |
||||
} |
||||
} |
||||
|
||||
private fun loadIntervals(session: Session) { |
||||
|
||||
if (sessionIds.contains(session.id)) { |
||||
return |
||||
} |
||||
|
||||
session.startDate?.let { this.start = min(this.start, it) } |
||||
session.endDate?.let { this.end = max(this.end, it) } |
||||
|
||||
this.sessionIds.add(session.id) |
||||
|
||||
Timber.d("session FTI count = ${session.flatTimeIntervals.size}") |
||||
for (fti in session.flatTimeIntervals) { |
||||
this.intervals.add(fti) |
||||
|
||||
fti.sessions?.let { sessions -> |
||||
processSessions(sessions.toSet()) |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
class SessionInterval(session: Session) { |
||||
|
||||
var start: Date |
||||
var end: Date? |
||||
|
||||
var sessions: MutableSet<Session> = mutableSetOf() |
||||
val dates: MutableSet<Date> = mutableSetOf() |
||||
|
||||
val duration: Long |
||||
get() { |
||||
val endDate = end ?: Date() |
||||
return endDate.time - start.time |
||||
} |
||||
|
||||
init { |
||||
this.start = session.startDate!! |
||||
this.end = session.endDate |
||||
|
||||
// Timber.d("INTERVAL init: s = $start, e = $end") |
||||
|
||||
this.addSession(session) |
||||
} |
||||
|
||||
private fun addSession(session: Session) { |
||||
this.sessions.add(session) |
||||
|
||||
session.startDate?.let { this.dates.add(it) } |
||||
session.endDate?.let { endDate -> |
||||
this.dates.add(endDate) |
||||
if (endDate > end) { |
||||
end = endDate |
||||
} |
||||
} |
||||
} |
||||
|
||||
companion object { |
||||
|
||||
fun intervalMap(sessions: Set<Session>): List<SessionInterval> { |
||||
|
||||
val sorted = sessions.sortedBy { it.startDate } |
||||
val intervals = mutableListOf<SessionInterval>() |
||||
|
||||
sorted.firstOrNull()?.let { firstSession -> |
||||
|
||||
var currentInterval = SessionInterval(firstSession) |
||||
intervals.add(currentInterval) |
||||
|
||||
val remainingSessions = sorted.drop(1) |
||||
for (session in remainingSessions) { |
||||
val start = session.startDate!! |
||||
val currentEnd = currentInterval.end |
||||
if (currentEnd != null && start > currentEnd) { |
||||
val interval = SessionInterval(session) |
||||
currentInterval = interval |
||||
intervals.add(interval) |
||||
} else { |
||||
currentInterval.addSession(session) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// intervals.forEach { |
||||
// Timber.d("s = ${it.start}, e = ${it.end}") |
||||
// } |
||||
|
||||
return intervals |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue