conventionschedule-android/app/src/main/java/com/adlerosn/brasilfurfest/schedule/managers/ScheduleManager.kt

222 lines
9.2 KiB
Kotlin
Raw Normal View History

2018-12-13 02:04:29 +00:00
package com.adlerosn.brasilfurfest.schedule.managers
2018-07-09 17:16:33 +00:00
import android.content.Context
2018-07-13 21:09:43 +00:00
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
2018-07-13 21:09:43 +00:00
import com.adlerosn.brasilfurfest.schedule.abstractDataTypes.convention.Convention
import com.adlerosn.brasilfurfest.schedule.abstractDataTypes.convention.ConventionSeries
2018-12-28 00:28:48 +00:00
import com.adlerosn.brasilfurfest.schedule.abstractDataTypes.convention.Event
2018-07-18 15:01:16 +00:00
import com.adlerosn.brasilfurfest.schedule.abstractDataTypes.managed.AttendeeConFavorites
2018-07-13 21:09:43 +00:00
import com.adlerosn.brasilfurfest.schedule.abstractDataTypes.managed.AttendeeConsFavorites
import com.google.gson.GsonBuilder
2018-12-12 04:30:34 +00:00
import org.jetbrains.anko.doAsync
2018-12-28 00:28:48 +00:00
import java.io.File
import java.io.Serializable
2018-07-13 21:09:43 +00:00
import java.util.*
2018-07-09 17:16:33 +00:00
class ScheduleManager(context: Context) : UncomplicatedObservable<Any?>(), Observer<Observable<Any?>, Any?>, Serializable {
override fun update(observable: Observable<Any?>, args: Any?) = onAttendeeIntentionsChanged()
2018-07-09 17:16:33 +00:00
private val uniqueIdentifierFile = File(context.filesDir, "rduid.txt")
val uniqueIdentifier: String
val cacheManager = CacheManager(context)
2018-07-14 19:43:24 +00:00
private val cachedChoices = File(context.filesDir, "schedule_choices.json")
private val cachedChoicesCanonicalPath = cachedChoices.canonicalPath
2018-07-13 21:09:43 +00:00
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) {
2018-07-13 21:09:43 +00:00
val mask = FileObserver.CLOSE_WRITE
if ((event and mask) != 0) {
closure()
2018-07-13 21:09:43 +00:00
}
}
}
2018-12-12 04:30:34 +00:00
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
2018-12-12 04:30:34 +00:00
fun updateConventionSeries() =
2018-12-28 00:28:48 +00:00
cacheManager[RemoteAssets.json.lastPathPart()]!!.first
.let { ConventionJsonReader.readFromInputStream(it) }
.also { conventionSeries = it }
var conventionSeries: ConventionSeries = updateConventionSeries()
2018-12-20 16:10:07 +00:00
val convention get() = conventionSeries.featured
val attendeeConventionsFavorites = AttendeeConsFavorites()
2018-12-28 00:28:48 +00:00
val attendeeFavorites
get(): AttendeeConFavorites {
lateinit var favorites: AttendeeConFavorites
synchronized(::attendeeConventionsFavorites) {
favorites = attendeeConventionsFavorites[convention]
}
return favorites
2018-07-18 15:01:16 +00:00
}
2018-12-28 00:28:48 +00:00
val conventionTime get() = GregorianCalendar().apply { timeInMillis = System.currentTimeMillis() } // .GregorianCalendar(2018,7,19,13,15)
2018-07-26 02:29:15 +00:00
private val jsonSerializer get() = GsonBuilder()
2018-07-13 21:09:43 +00:00
//.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
// }
2019-04-29 19:25:23 +00:00
2018-07-09 17:16:33 +00:00
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()
2018-07-09 17:16:33 +00:00
}
2018-12-12 04:30:34 +00:00
private fun readObject(ois: java.io.ObjectInputStream){
ois.defaultReadObject()
2018-07-14 19:43:24 +00:00
fileObserver.stopWatching()
2018-07-13 21:09:43 +00:00
fileObserver.startWatching()
scheduleObserver.stopWatching()
scheduleObserver.startWatching()
2018-07-13 21:09:43 +00:00
loadFromDisk()
2018-07-09 17:16:33 +00:00
}
2018-07-13 21:09:43 +00:00
private fun loadFromDisk() {
2018-07-09 17:16:33 +00:00
if (cachedChoices.exists()) {
2018-12-28 00:28:48 +00:00
synchronized(::attendeeConventionsFavorites) {
2018-12-12 04:30:34 +00:00
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)
2018-12-12 04:30:34 +00:00
AttendeeConsFavorites()
}
2018-07-18 15:01:16 +00:00
if (stateFromDisk != null) {
stateFromDisk.recursivelyRemoveObservers()
attendeeConventionsFavorites.recursivelyRemoveObservers()
attendeeConventionsFavorites.clear()
attendeeConventionsFavorites.putAll(stateFromDisk)
attendeeConventionsFavorites.observeAllChildren()
attendeeConventionsFavorites.addObserver(this)
2018-12-12 04:30:34 +00:00
}
2018-07-09 17:16:33 +00:00
}
}
}
private fun saveToDisk(async: Boolean = true) {
2018-12-28 00:28:48 +00:00
synchronized(::attendeeConventionsFavorites) {
2018-07-13 21:09:43 +00:00
attendeeConventionsFavorites.recursivelyRemoveObservers()
val json = jsonSerializer.toJson(attendeeConventionsFavorites)
attendeeConventionsFavorites.observeAllChildren()
attendeeConventionsFavorites.addObserver(this)
2018-12-12 04:30:34 +00:00
incrementDiskChangesToNotLoad()
val closure = { cachedChoices.bufferedWriter().use { it.write(json) } }
if(async)
doAsync { closure() }
else
closure()
2018-07-13 21:09:43 +00:00
}
2018-07-09 17:16:33 +00:00
}
fun onAttendeeIntentionsChanged() {
2018-07-13 21:09:43 +00:00
saveToDisk()
2018-07-09 17:16:33 +00:00
}
2018-07-13 21:09:43 +00:00
fun clearIntentions() {
attendeeConventionsFavorites.clear()
saveToDisk()
2018-07-09 17:16:33 +00:00
}
2018-07-18 15:01:16 +00:00
2018-12-20 17:12:29 +00:00
val currentSplashAsset: String get() = (
2018-12-28 00:28:48 +00:00
conventionSeries.banners.firstOrNull { conventionTime in it.lifetime }?.banner
2018-12-20 17:12:29 +00:00
?: conventionSeries.defaultBanner
2018-12-28 00:28:48 +00:00
).solved!!
2018-07-26 02:29:15 +00:00
2018-12-20 17:12:29 +00:00
val editionHasStarted: Boolean get() = conventionTime.timeInMillis > convention.officialLifespan.start.timeInMillis
val editionHasEnded: Boolean get() = conventionTime.timeInMillis > convention.officialLifespan.finish.timeInMillis
2018-07-26 02:29:15 +00:00
2018-07-27 03:39:36 +00:00
val nextEditionStartTime : Long? get(){
2018-07-26 02:29:15 +00:00
val currentTime = conventionTime.timeInMillis
2018-12-20 17:12:29 +00:00
val timeUntilThis = convention.officialLifespan.start.timeInMillis - currentTime
2018-07-26 02:29:15 +00:00
val timeUntilNext = (convention.nextEdition?.timeInMillis ?: Long.MAX_VALUE) - currentTime
val stillHappening = editionHasStarted and !editionHasEnded
return when {
2018-12-20 17:12:29 +00:00
timeUntilThis > 0 -> convention.officialLifespan.start.timeInMillis
2018-07-26 02:29:15 +00:00
stillHappening -> null
2018-07-27 03:39:36 +00:00
timeUntilNext > 0 -> convention.nextEdition?.timeInMillis
2018-07-26 02:29:15 +00:00
else -> null
}
}
2018-07-27 03:39:36 +00:00
val nextEditionStartCountdown : Triple<Int, Int, Int>?
get() = nextEditionStartTime?.let { buildDayHourMinuteCountdownTriple(it - conventionTime.timeInMillis) }
2018-07-26 02:29:15 +00:00
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())
}
}
2018-12-28 00:28:48 +00:00
fun canAttend(event: Event) = attendeeFavorites.canAttend(event)
2018-07-18 15:01:16 +00:00
}