rework CacheController and TCoRAPIController
continuous-integration/drone/push Build is passing Details

* all cache file stuff is now handled in the CacheController
* TCoRAPIController only handles API and Gson stuff
This commit is contained in:
Jannik 2020-08-07 12:14:50 +02:00
parent 8c55366ec0
commit bcfdb83d14
Signed by: Seil0
GPG Key ID: E8459F3723C52C24
6 changed files with 236 additions and 230 deletions

View File

@ -27,20 +27,27 @@ import android.util.Log
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonParser
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.*
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse
import org.mosad.seil0.projectlaogai.util.Course
import org.mosad.seil0.projectlaogai.util.MensaMenu
import org.mosad.seil0.projectlaogai.util.TimetableCourseWeek
import org.mosad.seil0.projectlaogai.util.TimetableWeek
import java.io.BufferedReader
import java.io.File
import java.io.FileReader
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.coursesCacheTime
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.mensaCacheTime
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.timetableCacheTime
import org.mosad.seil0.projectlaogai.util.*
import java.io.*
import java.util.*
import kotlin.Exception
import kotlin.collections.ArrayList
/**
* TODO rework
* * move all write functions from TCorController to CacheController
* * init: check for blocking updates, read all data from cache, check for non blocking update
* * [x] save needed data to validate cache in separate file
* * new functions:
* * [x] updateTimetable(): get Timetable object from TCoRController, write object to file
* * [x] updateMensa(): get MensaMenu object from TCoRController, write object to file
* * [ ] save additional subject and lessons
*/
class CacheController(cont: Context) {
private val className = "CacheController"
@ -52,132 +59,138 @@ class CacheController(cont: Context) {
val currentTime = System.currentTimeMillis() / 1000
// check if we need to update the mensa data before displaying it
readMensa(context)
cal.time = Date(mensaMenu.meta.updateTime * 1000)
cal.time = Date(mensaCacheTime * 1000)
// if a) it's monday and the last cache update was on sunday or b) the cache is older than 24hr, update blocking
if ((currentDay == Calendar.MONDAY && cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) || (currentTime - mensaMenu.meta.updateTime) > 86400) {
Log.i(className, "update mensa blocking")
GlobalScope.launch(Dispatchers.Default) { TCoRAPIController.getMensaMenu(context).join() }
GlobalScope.launch(Dispatchers.Default) { updateMensaMenu(context).join() }
}
// check if we need to update the timetables before displaying them
readTimetable(cCourse.courseName, 0, context)
cal.time = Date(timetables[0].meta.updateTime * 1000)
cal.time = Date(timetableCacheTime * 1000)
// if a) it`s monday and the last cache update was not on a sunday or b) the cache is older than 24hr, update blocking
if ((currentDay == Calendar.MONDAY && cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) || (currentTime - timetables[0].meta.updateTime) > 86400) {
if ((currentDay == Calendar.MONDAY && cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) || (currentTime - timetableCacheTime) > 86400) {
Log.i(className, "updating timetable after sunday!")
GlobalScope.launch(Dispatchers.Default) {
val threads = listOf(
TCoRAPIController.getTimetable(cCourse.courseName, 0, context),
TCoRAPIController.getTimetable(cCourse.courseName, 1, context)
updateTimetable(cCourse.courseName, 0, context),
updateTimetable(cCourse.courseName, 1, context)
)
threads.joinAll()
}
TCoRAPIController.getTimetable(cCourse.courseName, 0, context)
TCoRAPIController.getTimetable(cCourse.courseName, 1, context)
}
updateCourseList(context)
readStartCache(cCourse.courseName) // initially read values from cache
// check if an update is necessary, not blocking
if (currentTime - PreferencesController.coursesCacheTime > 86400)
TCoRAPIController.getCoursesList(context)
if (currentTime - coursesCacheTime > 86400)
updateCourseList(context)
if (currentTime - PreferencesController.mensaCacheTime > 10800)
TCoRAPIController.getMensaMenu(context)
if (currentTime - mensaCacheTime > 10800)
updateMensaMenu(context)
if (currentTime - PreferencesController.timetableCacheTime > 10800) {
TCoRAPIController.getTimetable(cCourse.courseName, 0, context)
TCoRAPIController.getTimetable(cCourse.courseName, 1, context)
if (currentTime - timetableCacheTime > 10800) {
updateTimetable(cCourse.courseName, 0, context)
updateTimetable(cCourse.courseName, 1, context)
}
readStartCache(cCourse.courseName)
}
companion object {
private const val className = "CacheController"
var coursesList = ArrayList<Course>()
var timetables = ArrayList<TimetableCourseWeek>()
var mensaMenu = MensaMenu()
/**
* read the courses list from the cached file
* add them to the coursesList object
* update the course list, async
*/
private fun readCoursesList(context: Context) {
fun updateCourseList(context: Context): Job {
val file = File(context.filesDir, "courses.json")
var courseListUp = CoursesList()
// make sure the file exists
if (!file.exists())
runBlocking { TCoRAPIController.getCoursesList(context).join() }
return GlobalScope.launch(Dispatchers.IO) {
try {
courseListUp = TCoRAPIController.getCourseListNEW()
} catch (ex: Exception) {
Log.e(className, "could not load course list from tcor", ex)
}
val fileReader = FileReader(file)
val bufferedReader = BufferedReader(fileReader)
val coursesObject = JsonParser.parseString(bufferedReader.readLine()).asJsonObject
// update coursesList array list
coursesList = courseListUp.courses
coursesList = Gson().fromJson(
coursesObject.getAsJsonArray("courses"),
object : TypeToken<List<Course>>() {}.type
)
}
/**
* get the MensaMenu object from the cached json,
* if cache is empty create the cache file
*/
fun readMensa(context: Context) {
val file = File(context.filesDir, "mensa.json")
// make sure the file exists
if (!file.exists())
runBlocking { TCoRAPIController.getMensaMenu(context).join() }
val fileReader = FileReader(file)
val bufferedReader = BufferedReader(fileReader)
val mensaObject = JsonParser.parseString(bufferedReader.readLine()).asJsonObject
mensaMenu = GsonBuilder().create().fromJson(mensaObject, MensaMenu().javaClass)
}
/**
* read the weeks timetable from the cached file
* @param courseName the course name (e.g AI1)
* @param week the week to read (0 for the current and so on)
*/
fun readTimetable(courseName: String, week: Int, context: Context) {
val file = File(context.filesDir, "timetable-$courseName-$week.json")
// make sure the file exists
if (!file.exists())
runBlocking { TCoRAPIController.getTimetable(courseName, week, context).join() }
val fileReader = FileReader(file)
val bufferedReader = BufferedReader(fileReader)
val timetableObject = JsonParser.parseString(bufferedReader.readLine()).asJsonObject
// make sure you add the single weeks in the exact order!
val timetable = Gson().fromJson(timetableObject, TimetableCourseWeek().javaClass)
if (timetables.size > week) {
timetables[week] = timetable
TimetableController.timetable[week] = TimetableWeek(
timetable.meta.weekIndex,
timetable.meta.weekNumberYear,
timetable.timetable.days
)
} else {
timetables.add(timetable)
TimetableController.timetable.add(
TimetableWeek(
timetable.meta.weekIndex,
timetable.meta.weekNumberYear,
timetable.timetable.days
)
)
// save cache file and update time
save(file, Gson().toJson(courseListUp))
coursesCacheTime = System.currentTimeMillis() / 1000
PreferencesController.save(context)
}
}
/**
* update the mensa menu, async
*/
fun updateMensaMenu(context: Context): Job {
val file = File(context.filesDir, "mensa.json")
return GlobalScope.launch(Dispatchers.IO) {
try {
mensaMenu = TCoRAPIController.getMensaMenu()
} catch (ex: Exception) {
Log.e(className, "could not load mensa menu from tcor", ex)
}
// save cache file and update time
save(file, Gson().toJson(mensaMenu))
mensaCacheTime = System.currentTimeMillis() / 1000
PreferencesController.save(context)
}
}
/**
* update the timetable for a week, async
* @param courseName the course name (e.g AI1)
* @param week the week to update (0 for the current and so on)
*/
fun updateTimetable(courseName: String, week: Int, context: Context): Job {
val file = File(context.filesDir, "timetable-$courseName-$week.json")
var timetable = TimetableWeek()
// try to update timetable from tcor, async
return GlobalScope.launch(Dispatchers.IO) {
try {
timetable = TCoRAPIController.getTimetable(courseName, week)
} catch (ex: Exception) {
Log.e(className, "could not load timetable from tcor", ex)
}
// update timetable in TTC
if (TimetableController.timetable.size > week) {
TimetableController.timetable[week] = timetable
} else {
TimetableController.timetable.add(timetable)
}
// save cache file and update time
save(file, Gson().toJson(timetable))
timetableCacheTime = System.currentTimeMillis() / 1000
PreferencesController.save(context)
}
}
private fun save(file: File, text: String) {
try {
val writer = BufferedWriter(FileWriter(file))
writer.write(text)
writer.close()
} catch (ex: Exception) {
Log.e(className, "failed to write file \"${file.absoluteFile}\"", ex)
}
}
}
/**
@ -187,28 +200,104 @@ class CacheController(cont: Context) {
private fun readStartCache(courseName: String) {
try {
readCoursesList(context)
} catch (e : Exception) {
Log.e(className, "Error while reading the course list", e)
} catch (ex : Exception) {
Log.e(className, "Error while reading the course list", ex)
}
try {
readMensa(context)
} catch (e : Exception) {
Log.e(className, "Error while reading the mensa menu", e)
} catch (ex : Exception) {
Log.e(className, "Error while reading the mensa menu", ex)
}
try {
readTimetable(courseName, 0, context)
} catch (e : Exception) {
Log.e(className, "Error while reading timetable week 0", e)
} catch (ex : Exception) {
Log.e(className, "Error while reading timetable week 0", ex)
}
try {
readTimetable(courseName, 1, context)
} catch (e : Exception) {
Log.e(className, "Error while reading timetable week 1", e)
} catch (ex : Exception) {
Log.e(className, "Error while reading timetable week 1", ex)
}
}
/**
* read the courses list from the cached file
* add them to the coursesList object
*/
private fun readCoursesList(context: Context) {
val file = File(context.filesDir, "courses.json")
// make sure the file exists
if (!file.exists()) {
runBlocking { updateCourseList(context).join() }
return
}
coursesList = FileReader(file).use {
GsonBuilder().create().fromJson(BufferedReader(it).readLine(), CoursesList().javaClass).courses
}
}
/**
* get the MensaMenu object from the cached json,
* if cache is empty create the cache file
*/
private fun readMensa(context: Context) {
val file = File(context.filesDir, "mensa.json")
// make sure the file exists
if (!file.exists()) {
runBlocking { updateMensaMenu(context).join() }
return
}
mensaMenu = FileReader(file).use {
GsonBuilder().create().fromJson(BufferedReader(it).readLine(), MensaMenu().javaClass)
}
}
/**
* read the weeks timetable from the cached file
* @param courseName the course name (e.g AI1)
* @param week the week to read (0 for the current and so on)
*/
private fun readTimetable(courseName: String, week: Int, context: Context) {
val file = File(context.filesDir, "timetable-$courseName-$week.json")
// if the file does not exist, call updateTimetable blocking and return
if (!file.exists()) {
runBlocking { updateTimetable(courseName, week, context).join() }
return
}
val timetableObject = FileReader(file).use {
JsonParser.parseString(BufferedReader(it).readLine()).asJsonObject
}
// if its a TimetableCourseWeek object migrate to TimetableWeek TODO remove converting at version 0.8
val timetable = if(timetableObject.has("meta")) {
Log.i(Companion.className, "trying to migrate TimetableCourseWeek to TimetableWeek")
val timetableWC = Gson().fromJson(timetableObject, TimetableCourseWeek().javaClass)
save(file, Gson().toJson(TimetableWeek(
timetableWC.meta.weekIndex,
timetableWC.meta.weekNumberYear,
timetableWC.timetable.days
)))
TimetableWeek(timetableWC.meta.weekIndex, timetableWC.meta.weekNumberYear, timetableWC.timetable.days)
} else {
Gson().fromJson(timetableObject, TimetableWeek().javaClass)
}
// update timetable in TTC
if (TimetableController.timetable.size > week) {
TimetableController.timetable[week] = timetable
} else {
TimetableController.timetable.add(timetable)
}
}
}

View File

@ -22,128 +22,58 @@
package org.mosad.seil0.projectlaogai.controller
import android.content.Context
import android.util.Log
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.JsonParser
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.*
import org.json.JSONObject
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.coursesCacheTime
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.mensaCacheTime
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.timetableCacheTime
import org.mosad.seil0.projectlaogai.util.CoursesList
import org.mosad.seil0.projectlaogai.util.Lesson
import org.mosad.seil0.projectlaogai.util.MensaMenu
import org.mosad.seil0.projectlaogai.util.TimetableCourseWeek
import java.io.BufferedWriter
import java.io.File
import java.io.FileWriter
import org.mosad.seil0.projectlaogai.util.*
import java.net.URL
/**
* This Controller calls the tcor api,
* all functions return tcor api objects
*/
class TCoRAPIController {
companion object {
private const val className = "TCoRAPIController"
private const val tcorBaseURL = "https://tcor.mosad.xyz"
/**
* Get a array of all currently available courses at the tcor API.
* Read the json object from tcor api and write it as file (cache).
* Read the json object from tcor api
*/
fun getCoursesList(context: Context): Job {
fun getCourseListNEW(): CoursesList {
val url = URL("$tcorBaseURL/courseList")
val file = File(context.filesDir, "courses.json")
return GlobalScope.launch(Dispatchers.IO) {
try {
// read data from the API
val coursesObject = JSONObject(url.readText())
// write the json object to a file
val writer = BufferedWriter(FileWriter(file))
writer.write(coursesObject.toString())
writer.close()
// update cache time
coursesCacheTime = System.currentTimeMillis() / 1000
PreferencesController.save(context)
} catch (ex: Exception) {
Log.e(className, "failed to get /courseList", ex)
// write a empty file, since it is loaded later
val writer = BufferedWriter(FileWriter(file))
writer.write(GsonBuilder().create().toJson(CoursesList()))
writer.close()
}
}
return Gson().fromJson(url.readText(), CoursesList().javaClass)
}
/**
* Get this and next weeks mensa menus from the tcor API.
* Read the json object from tcor api and write it as file (cache).
* Get current and next weeks mensa menus from the tcor API.
* Read the json object from tcor api
*/
fun getMensaMenu(context: Context): Job {
fun getMensaMenu(): MensaMenu {
val url = URL("$tcorBaseURL/mensamenu")
val file = File(context.filesDir, "mensa.json")
return GlobalScope.launch(Dispatchers.IO) {
try {
// read data from the API
val mensaObject = JSONObject(url.readText())
// write the json object to a file
val writer = BufferedWriter(FileWriter(file))
writer.write(mensaObject.toString())
writer.close()
// update cache time
mensaCacheTime = System.currentTimeMillis() / 1000
PreferencesController.save(context)
} catch (ex: Exception) {
Log.e(className, "failed to get /mensamenu", ex)
// write a empty file, since it is loaded later
val writer = BufferedWriter(FileWriter(file))
writer.write(GsonBuilder().create().toJson(MensaMenu()))
writer.close()
}
}
return Gson().fromJson(url.readText(), MensaMenu().javaClass)
}
/**
* Get the timetable for @param courseName at week @param week
* Read the json object from tcor api and write it as file (cache).
* Get the timetable for "courseName" at week "week"
* Read the json object from tcor api
* @param courseName the course name (e.g AI1)
* @param week the week to update (0 for the current and so on)
*/
fun getTimetable(courseName: String, week: Int, context: Context): Job {
fun getTimetable(courseName: String, week: Int): TimetableWeek {
val url = URL("$tcorBaseURL/timetable?course=$courseName&week=$week")
val file = File(context.filesDir, "timetable-$courseName-$week.json")
return GlobalScope.launch(Dispatchers.IO) {
try {
// read data from the API
val timetableObject = JSONObject(url.readText())
// write the json object to a file
val writer = BufferedWriter(FileWriter(file))
writer.write(timetableObject.toString())
writer.close()
// update cache time
timetableCacheTime = System.currentTimeMillis() / 1000
PreferencesController.save(context)
} catch (ex: Exception) {
Log.e(className, "failed to get /timetable", ex)
// write a empty file, since it is loaded later
val writer = BufferedWriter(FileWriter(file))
writer.write(GsonBuilder().create().toJson(TimetableCourseWeek()))
writer.close()
}
}
val timetableCW = Gson().fromJson(url.readText(), TimetableCourseWeek().javaClass)
return TimetableWeek(
timetableCW.meta.weekIndex,
timetableCW.meta.weekNumberYear,
timetableCW.timetable.days
)
}
/**
@ -179,8 +109,8 @@ class TCoRAPIController {
}
return array
}
}
}

View File

@ -37,12 +37,12 @@ class TimetableController {
companion object {
val timetable = ArrayList<TimetableWeek>()
val lessonMap = HashMap<String, Lesson>() // the key is courseName_lessonID
val subjectMap = HashMap<String, ArrayList<String>>()
val lessonMap = HashMap<String, Lesson>() // the key is courseName-subject-lessonID
val subjectMap = HashMap<String, ArrayList<String>>() // the key is courseName
/**
* add a subject to the subjects map and all it's lessons
* to the aLessons list
* add a subject to the subjectMap and all it's lessons
* to the lessonMap
* @param courseName course to which the subject belongs
* @param subject the subjects name
*/
@ -68,8 +68,8 @@ class TimetableController {
}
/**
* remove a subject from the subjects map and all it's lessons
* from the aLessons list
* remove a subject from the subjectMap and all it's lessons
* from the lessonMap
* @param courseName course to which the subject belongs
* @param subject the subjects name
*/

View File

@ -123,9 +123,7 @@ class MensaFragment : Fragment() {
* refresh the mensa cache and update the mensa screen
*/
private fun updateMensaScreen() = GlobalScope.launch(Dispatchers.Default) {
// update the cache
TCoRAPIController.getMensaMenu(context!!).join() // blocking since we want the new data
CacheController.readMensa(context!!)
CacheController.updateMensaMenu(context!!).join() // blocking since we want the new data
withContext(Dispatchers.Main) {
// remove all menus from the layout

View File

@ -54,7 +54,6 @@ import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cColorPrimary
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cShowBuffet
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
import org.mosad.seil0.projectlaogai.controller.TimetableController
import org.mosad.seil0.projectlaogai.util.DataTypes
import java.util.*
@ -166,7 +165,7 @@ class SettingsFragment : Fragment() {
getActionButton(WhichButton.POSITIVE).updateTextColor(cColorAccent)
getActionButton(WhichButton.NEGATIVE).updateTextColor(cColorAccent)
listItemsMultiChoice(items = lessons) { dialog, indices, items ->
listItemsMultiChoice(items = lessons) { _, _, items ->
items.forEach {
val list = it.split(" - ")
TimetableController.removeSubject(list[0], list[1])
@ -299,14 +298,11 @@ class SettingsFragment : Fragment() {
// update current & next weeks timetable
val threads = listOf(
TCoRAPIController.getTimetable(cCourse.courseName, 0, context),
TCoRAPIController.getTimetable(cCourse.courseName, 1, context)
CacheController.updateTimetable(cCourse.courseName, 0, context),
CacheController.updateTimetable(cCourse.courseName, 1, context)
)
threads.joinAll() // blocking since we want the new data
CacheController.readTimetable(cCourse.courseName, 0, context)
CacheController.readTimetable(cCourse.courseName, 1, context)
withContext(Dispatchers.Main) {
loadingDialog.dismiss()
}

View File

@ -36,11 +36,10 @@ import kotlinx.android.synthetic.main.fragment_timetable.*
import kotlinx.coroutines.*
import org.mosad.seil0.projectlaogai.R
import org.mosad.seil0.projectlaogai.controller.CacheController
import org.mosad.seil0.projectlaogai.controller.CacheController.Companion.timetables
import org.mosad.seil0.projectlaogai.controller.PreferencesController
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
import org.mosad.seil0.projectlaogai.controller.TimetableController
import org.mosad.seil0.projectlaogai.controller.TimetableController.Companion.timetable
import org.mosad.seil0.projectlaogai.uicomponents.AddSubjectDialog
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
import org.mosad.seil0.projectlaogai.util.NotRetardedCalendar
@ -63,10 +62,10 @@ class TimeTableFragment : Fragment() {
// init actions
initActions()
if (timetables.size > 1 && timetables[0].timetable.days.isNotEmpty() && timetables[1].timetable.days.isNotEmpty()) {
if (timetable.size > 1 && timetable[0].days.isNotEmpty() && timetable[1].days.isNotEmpty()) {
initTimetable()
println(TimetableController.timetable)
//println(timetable)
} else {
MaterialDialog(context!!)
.title(R.string.error)
@ -116,10 +115,7 @@ class TimeTableFragment : Fragment() {
}
private fun addTimetableWeek(dayBegin: Int, dayEnd: Int, week: Int) = GlobalScope.launch(Dispatchers.Main) {
//val timetable = timetables[week].timetable
//val timetableMeta = timetables[week].meta
val timetable = TimetableController.timetable[week]
val timetable = timetable[week]
for (dayIndex in dayBegin..dayEnd) {
val dayCardView = DayCardView(context!!)
@ -140,14 +136,11 @@ class TimeTableFragment : Fragment() {
private fun updateTimetableScreen() = GlobalScope.launch(Dispatchers.Default) {
// update the cache
val threads = listOf(
TCoRAPIController.getTimetable(cCourse.courseName, 0, context!!),
TCoRAPIController.getTimetable(cCourse.courseName, 1, context!!)
CacheController.updateTimetable(cCourse.courseName, 0, context!!),
CacheController.updateTimetable(cCourse.courseName, 1, context!!)
)
threads.joinAll() // blocking since we want the new data
CacheController.readTimetable(cCourse.courseName, 0, context!!)
CacheController.readTimetable(cCourse.courseName, 1, context!!)
refreshUI()
}