222 lines
9.2 KiB
Kotlin
222 lines
9.2 KiB
Kotlin
package com.adlerosn.brasilfurfest.schedule.managers
|
|
|
|
import android.content.Context
|
|
import android.os.FileObserver
|
|
import android.util.Log
|
|
import com.adlerosn.brasilfurfest.helper.*
|
|
import com.adlerosn.brasilfurfest.helper.observables.Observable
|
|
import com.adlerosn.brasilfurfest.helper.observables.Observer
|
|
import com.adlerosn.brasilfurfest.notification.NotificationFirer
|
|
import com.adlerosn.brasilfurfest.schedule.abstractDataTypes.convention.Announcement
|
|
import com.adlerosn.brasilfurfest.schedule.abstractDataTypes.convention.Convention
|
|
import com.adlerosn.brasilfurfest.schedule.abstractDataTypes.convention.ConventionSeries
|
|
import com.adlerosn.brasilfurfest.schedule.abstractDataTypes.convention.Event
|
|
import com.adlerosn.brasilfurfest.schedule.abstractDataTypes.managed.AttendeeConFavorites
|
|
import com.adlerosn.brasilfurfest.schedule.abstractDataTypes.managed.AttendeeConsFavorites
|
|
import com.google.gson.GsonBuilder
|
|
import org.jetbrains.anko.doAsync
|
|
import java.io.File
|
|
import java.io.Serializable
|
|
import java.util.*
|
|
|
|
class ScheduleManager(context: Context) : UncomplicatedObservable<Any?>(), Observer<Observable<Any?>, Any?>, Serializable {
|
|
override fun update(observable: Observable<Any?>, args: Any?) = onAttendeeIntentionsChanged()
|
|
|
|
private val uniqueIdentifierFile = File(context.filesDir, "rduid.txt")
|
|
val uniqueIdentifier: String
|
|
|
|
val cacheManager = CacheManager(context)
|
|
private val cachedChoices = File(context.filesDir, "schedule_choices.json")
|
|
private val cachedChoicesCanonicalPath = cachedChoices.canonicalPath
|
|
|
|
private val fireNotificationsClosure = { NotificationFirer().fire(context) }
|
|
|
|
private inner class InnerFileObserver(path: String, private val closure: ()->Any?) : FixedFileObserver(path){
|
|
override fun onEvent(event: Int, path: String) {
|
|
val mask = FileObserver.CLOSE_WRITE
|
|
if ((event and mask) != 0) {
|
|
closure()
|
|
}
|
|
}
|
|
}
|
|
|
|
private var diskChangesToNotLoad: Int = 0
|
|
private fun incrementDiskChangesToNotLoad(howMany: Int = 1){
|
|
synchronized(::diskChangesToNotLoad) {
|
|
diskChangesToNotLoad+=howMany
|
|
}
|
|
}
|
|
private fun notifyDiskChanges() {
|
|
synchronized(::diskChangesToNotLoad) {
|
|
if(diskChangesToNotLoad > 0)
|
|
diskChangesToNotLoad-=1
|
|
else
|
|
loadFromDisk()
|
|
}
|
|
setChanged()
|
|
notifyObservers()
|
|
}
|
|
|
|
private val fileObserver: InnerFileObserver
|
|
private val scheduleObserver: InnerFileObserver
|
|
|
|
fun updateConventionSeries() =
|
|
cacheManager[RemoteAssets.json.lastPathPart()]!!.first
|
|
.let { ConventionJsonReader.readFromInputStream(it) }
|
|
.also { conventionSeries = it }
|
|
|
|
var conventionSeries: ConventionSeries = updateConventionSeries()
|
|
val convention get() = conventionSeries.featured
|
|
val attendeeConventionsFavorites = AttendeeConsFavorites()
|
|
val attendeeFavorites
|
|
get(): AttendeeConFavorites {
|
|
lateinit var favorites: AttendeeConFavorites
|
|
synchronized(::attendeeConventionsFavorites) {
|
|
favorites = attendeeConventionsFavorites[convention]
|
|
}
|
|
return favorites
|
|
}
|
|
val conventionTime get() = GregorianCalendar().apply { timeInMillis = System.currentTimeMillis() } // .GregorianCalendar(2018,7,19,13,15)
|
|
private val jsonSerializer get() = GsonBuilder()
|
|
//.setExclusionStrategies(ObservableFieldsExclusionStrategy())
|
|
.registerTypeAdapter(Convention::class.java, ConventionInstanceCreator(convention))
|
|
.create()
|
|
// val nextNotificationTime: Long get(){
|
|
// val early: Long = convention.notificationFireMinutesBefore*60*1000.toLong()
|
|
// val now = conventionTime
|
|
// val thisRange = now.dayRange.split(convention.splitDayIn).first { conventionTime in it }
|
|
// val diff = thisRange.finish.timeInMillis - thisRange.start.timeInMillis
|
|
// val nextEnd = thisRange.start.timeInMillis+(1*diff)
|
|
// val nextNextEnd = thisRange.start.timeInMillis+(2*diff)
|
|
// val earlyNextEnd = nextEnd-early
|
|
// val earlyNextNextEnd = nextNextEnd-early
|
|
// return if(now.timeInMillis < earlyNextEnd) earlyNextEnd else earlyNextNextEnd
|
|
// }
|
|
|
|
init {
|
|
loadFromDisk()
|
|
if(!cachedChoices.exists())
|
|
saveToDisk(false)
|
|
uniqueIdentifier = initUniqueIdentifierFile()
|
|
fileObserver = (InnerFileObserver(cachedChoicesCanonicalPath) {notifyDiskChanges()}).apply { startWatching() }
|
|
scheduleObserver = (InnerFileObserver(cacheManager.getFileFor(RemoteAssets.json).absolutePath) {notifyScheduleChanged()}).apply { startWatching() }
|
|
}
|
|
|
|
private fun notifyScheduleChanged() {
|
|
conventionSeries = updateConventionSeries()
|
|
setChanged()
|
|
notifyObservers()
|
|
fireNotificationsClosure()
|
|
}
|
|
|
|
private val readAnnouncementsFile = File(context.filesDir, "announcements_read.txt")
|
|
val unreadAnnouncements: List<Announcement> get() {
|
|
if(!readAnnouncementsFile.exists())
|
|
return listOf()
|
|
val readAnnouncementUUIDs = readAnnouncementsFile.readLines()
|
|
return conventionSeries.announcements.filter { it.uuid !in readAnnouncementUUIDs }
|
|
}
|
|
fun setAnnouncementsRead() {
|
|
readAnnouncementsFile.writeText(
|
|
conventionSeries.announcements.map {
|
|
it.uuid
|
|
}.joinToString(separator = "\n")
|
|
)
|
|
}
|
|
|
|
|
|
private fun initUniqueIdentifierFile(): String{
|
|
if(!uniqueIdentifierFile.exists())
|
|
uniqueIdentifierFile.writeText(UUID.randomUUID().toString())
|
|
return uniqueIdentifierFile.readText()
|
|
}
|
|
|
|
private fun readObject(ois: java.io.ObjectInputStream){
|
|
ois.defaultReadObject()
|
|
fileObserver.stopWatching()
|
|
fileObserver.startWatching()
|
|
scheduleObserver.stopWatching()
|
|
scheduleObserver.startWatching()
|
|
loadFromDisk()
|
|
}
|
|
|
|
private fun loadFromDisk() {
|
|
if (cachedChoices.exists()) {
|
|
synchronized(::attendeeConventionsFavorites) {
|
|
val json = cachedChoices.readText()
|
|
val stateFromDisk = try {
|
|
jsonSerializer.fromJson(json, attendeeConventionsFavorites::class.java)
|
|
} catch (e: com.google.gson.JsonSyntaxException) {
|
|
Log.e("Schedule manager", "Malformed JSON", e)
|
|
AttendeeConsFavorites()
|
|
}
|
|
if (stateFromDisk != null) {
|
|
stateFromDisk.recursivelyRemoveObservers()
|
|
attendeeConventionsFavorites.recursivelyRemoveObservers()
|
|
attendeeConventionsFavorites.clear()
|
|
attendeeConventionsFavorites.putAll(stateFromDisk)
|
|
attendeeConventionsFavorites.observeAllChildren()
|
|
attendeeConventionsFavorites.addObserver(this)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
private fun saveToDisk(async: Boolean = true) {
|
|
synchronized(::attendeeConventionsFavorites) {
|
|
attendeeConventionsFavorites.recursivelyRemoveObservers()
|
|
val json = jsonSerializer.toJson(attendeeConventionsFavorites)
|
|
attendeeConventionsFavorites.observeAllChildren()
|
|
attendeeConventionsFavorites.addObserver(this)
|
|
incrementDiskChangesToNotLoad()
|
|
val closure = { cachedChoices.bufferedWriter().use { it.write(json) } }
|
|
if(async)
|
|
doAsync { closure() }
|
|
else
|
|
closure()
|
|
}
|
|
}
|
|
|
|
fun onAttendeeIntentionsChanged() {
|
|
saveToDisk()
|
|
}
|
|
|
|
fun clearIntentions() {
|
|
attendeeConventionsFavorites.clear()
|
|
saveToDisk()
|
|
}
|
|
|
|
val currentSplashAsset: String get() = (
|
|
conventionSeries.banners.firstOrNull { conventionTime in it.lifetime }?.banner
|
|
?: conventionSeries.defaultBanner
|
|
).solved!!
|
|
|
|
val editionHasStarted: Boolean get() = conventionTime.timeInMillis > convention.officialLifespan.start.timeInMillis
|
|
val editionHasEnded: Boolean get() = conventionTime.timeInMillis > convention.officialLifespan.finish.timeInMillis
|
|
|
|
val nextEditionStartTime : Long? get(){
|
|
val currentTime = conventionTime.timeInMillis
|
|
val timeUntilThis = convention.officialLifespan.start.timeInMillis - currentTime
|
|
val timeUntilNext = (convention.nextEdition?.timeInMillis ?: Long.MAX_VALUE) - currentTime
|
|
val stillHappening = editionHasStarted and !editionHasEnded
|
|
return when {
|
|
timeUntilThis > 0 -> convention.officialLifespan.start.timeInMillis
|
|
stillHappening -> null
|
|
timeUntilNext > 0 -> convention.nextEdition?.timeInMillis
|
|
else -> null
|
|
}
|
|
}
|
|
|
|
val nextEditionStartCountdown : Triple<Int, Int, Int>?
|
|
get() = nextEditionStartTime?.let { buildDayHourMinuteCountdownTriple(it - conventionTime.timeInMillis) }
|
|
|
|
private fun buildDayHourMinuteCountdownTriple(millisUntil: Long): Triple<Int, Int, Int>{
|
|
return (millisUntil/1000).let {
|
|
Triple((it/(3600*24)).toInt(), ((it/3600)%24).toInt(), ((it/60)%60).toInt())
|
|
}
|
|
}
|
|
|
|
fun canAttend(event: Event) = attendeeFavorites.canAttend(event)
|
|
}
|