save grades to a encrypted cache file, use cache if not older than 24hr
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
313ff4741a
commit
d94f38de93
|
@ -51,7 +51,7 @@ dependencies {
|
||||||
implementation 'androidx.core:core:1.3.1'
|
implementation 'androidx.core:core:1.3.1'
|
||||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.0'
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||||
implementation 'androidx.security:security-crypto:1.1.0-alpha02'
|
implementation 'androidx.security:security-crypto:1.1.0-alpha02'
|
||||||
implementation 'com.google.android.material:material:1.2.0'
|
implementation 'com.google.android.material:material:1.2.0'
|
||||||
|
@ -66,7 +66,7 @@ dependencies {
|
||||||
implementation 'org.jsoup:jsoup:1.13.1'
|
implementation 'org.jsoup:jsoup:1.13.1'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.13'
|
testImplementation 'junit:junit:4.13'
|
||||||
androidTestImplementation 'androidx.test:core:1.2.0'
|
androidTestImplementation 'androidx.test:core:1.3.0'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,21 +24,26 @@ package org.mosad.seil0.projectlaogai.controller.cache
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.security.crypto.EncryptedFile
|
||||||
|
import androidx.security.crypto.MasterKey
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import com.google.gson.JsonParser
|
import com.google.gson.JsonParser
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import org.mosad.seil0.projectlaogai.controller.QISPOSParser
|
||||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
||||||
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
|
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController
|
||||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cCourse
|
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.cCourse
|
||||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.coursesCacheTime
|
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.coursesCacheTime
|
||||||
|
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.gradesCacheTime
|
||||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.mensaCacheTime
|
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.mensaCacheTime
|
||||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.timetableCacheTime
|
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.timetableCacheTime
|
||||||
import org.mosad.seil0.projectlaogai.util.*
|
import org.mosad.seil0.projectlaogai.util.*
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
import kotlin.collections.HashMap
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The cacheController reads and updates the cache files.
|
* The cacheController reads and updates the cache files.
|
||||||
|
@ -201,6 +206,25 @@ class CacheController(cont: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* update the encrypted grades file
|
||||||
|
*/
|
||||||
|
fun updateGrades(context: Context): Job {
|
||||||
|
val file = File(context.filesDir, "grades_encrypted")
|
||||||
|
val parser = QISPOSParser(context)
|
||||||
|
|
||||||
|
return GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
if (parser.checkQISPOSStatus() == 200) {
|
||||||
|
if (file.exists()) { file.delete() }
|
||||||
|
|
||||||
|
// save cache file and update time
|
||||||
|
saveEncrypted(context, file, Gson().toJson(parser.parseGrades()))
|
||||||
|
gradesCacheTime = System.currentTimeMillis() / 1000
|
||||||
|
Preferences.save(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* save changes in lessonMap and subjectMap,
|
* save changes in lessonMap and subjectMap,
|
||||||
* called on addSubject or removeSubject
|
* called on addSubject or removeSubject
|
||||||
|
@ -223,6 +247,22 @@ class CacheController(cont: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun saveEncrypted(context: Context, file: File, text: String) {
|
||||||
|
val masterKey = MasterKey.Builder(context)
|
||||||
|
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
||||||
|
.build()
|
||||||
|
val encryptedFile = EncryptedFile.Builder(
|
||||||
|
context,
|
||||||
|
file,
|
||||||
|
masterKey,
|
||||||
|
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
|
||||||
|
).build()
|
||||||
|
|
||||||
|
encryptedFile.openFileOutput().bufferedWriter().use {
|
||||||
|
it.write(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -372,4 +412,40 @@ class CacheController(cont: Context) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* read the encrypted grades file, don't keep them
|
||||||
|
* in CacheController for security reasons
|
||||||
|
* @return the grades as HashMap if the file exists, else a empty HashMap
|
||||||
|
*/
|
||||||
|
fun readGrades(): HashMap<String, ArrayList<GradeSubject>> {
|
||||||
|
val file = File(context.filesDir, "grades_encrypted")
|
||||||
|
|
||||||
|
// if the file does not exit, try creating it
|
||||||
|
if (!file.exists()) {
|
||||||
|
runBlocking { updateGrades(context) }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.exists()) {
|
||||||
|
val masterKey = MasterKey.Builder(context)
|
||||||
|
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
||||||
|
.build()
|
||||||
|
val encryptedFile = EncryptedFile.Builder(
|
||||||
|
context,
|
||||||
|
file,
|
||||||
|
masterKey,
|
||||||
|
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
|
||||||
|
).build()
|
||||||
|
|
||||||
|
return encryptedFile.openFileInput().bufferedReader().use {
|
||||||
|
GsonBuilder().create()
|
||||||
|
.fromJson(
|
||||||
|
it.readLine(),
|
||||||
|
object : TypeToken<HashMap<String, ArrayList<GradeSubject>>>() {}.type
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return HashMap()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -38,9 +38,8 @@ import org.mosad.seil0.projectlaogai.util.TimetableWeek
|
||||||
* * add second week
|
* * add second week
|
||||||
* * add configurable week to addSubject() and removeSubject(), updateAdditionalLessons()
|
* * add configurable week to addSubject() and removeSubject(), updateAdditionalLessons()
|
||||||
*/
|
*/
|
||||||
class TimetableController {
|
object TimetableController {
|
||||||
|
|
||||||
companion object {
|
|
||||||
val timetable = ArrayList<TimetableWeek>()
|
val timetable = ArrayList<TimetableWeek>()
|
||||||
val lessonMap = HashMap<String, Lesson>() // the key is courseName-subject-lessonID
|
val lessonMap = HashMap<String, Lesson>() // the key is courseName-subject-lessonID
|
||||||
val subjectMap = HashMap<String, ArrayList<String>>() // the key is courseName
|
val subjectMap = HashMap<String, ArrayList<String>>() // the key is courseName
|
||||||
|
@ -127,5 +126,3 @@ class TimetableController {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
|
@ -36,6 +36,7 @@ object Preferences {
|
||||||
var coursesCacheTime: Long = 0
|
var coursesCacheTime: Long = 0
|
||||||
var mensaCacheTime: Long = 0
|
var mensaCacheTime: Long = 0
|
||||||
var timetableCacheTime: Long = 0
|
var timetableCacheTime: Long = 0
|
||||||
|
var gradesCacheTime: Long = 0
|
||||||
var cColorPrimary: Int = Color.parseColor("#009688")
|
var cColorPrimary: Int = Color.parseColor("#009688")
|
||||||
var cColorAccent: Int = Color.parseColor("#0096ff")
|
var cColorAccent: Int = Color.parseColor("#0096ff")
|
||||||
var cCourse = Course(
|
var cCourse = Course(
|
||||||
|
@ -67,6 +68,9 @@ object Preferences {
|
||||||
putLong(context.getString(R.string.save_key_timetableCacheTime),
|
putLong(context.getString(R.string.save_key_timetableCacheTime),
|
||||||
timetableCacheTime
|
timetableCacheTime
|
||||||
)
|
)
|
||||||
|
putLong(context.getString(R.string.save_key_gradesCacheTime),
|
||||||
|
gradesCacheTime
|
||||||
|
)
|
||||||
apply()
|
apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,6 +170,9 @@ object Preferences {
|
||||||
timetableCacheTime = sharedPref.getLong(context.getString(
|
timetableCacheTime = sharedPref.getLong(context.getString(
|
||||||
R.string.save_key_timetableCacheTime
|
R.string.save_key_timetableCacheTime
|
||||||
), 0)
|
), 0)
|
||||||
|
gradesCacheTime = sharedPref.getLong(context.getString(
|
||||||
|
R.string.save_key_gradesCacheTime
|
||||||
|
), 0)
|
||||||
|
|
||||||
// load saved course
|
// load saved course
|
||||||
cCourse = Course(
|
cCourse = Course(
|
||||||
|
|
|
@ -35,11 +35,13 @@ import kotlinx.android.synthetic.main.fragment_grades.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import org.mosad.seil0.projectlaogai.R
|
import org.mosad.seil0.projectlaogai.R
|
||||||
import org.mosad.seil0.projectlaogai.controller.QISPOSParser
|
import org.mosad.seil0.projectlaogai.controller.QISPOSParser
|
||||||
|
import org.mosad.seil0.projectlaogai.controller.cache.CacheController
|
||||||
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
|
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences
|
||||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
||||||
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
|
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
|
||||||
import org.mosad.seil0.projectlaogai.uicomponents.GradeLinearLayout
|
import org.mosad.seil0.projectlaogai.uicomponents.GradeLinearLayout
|
||||||
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoginDialog
|
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.LoginDialog
|
||||||
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The grades fragment class
|
* The grades fragment class
|
||||||
|
@ -64,6 +66,7 @@ class GradesFragment : Fragment() {
|
||||||
|
|
||||||
parser = QISPOSParser(context!!)// init the parser
|
parser = QISPOSParser(context!!)// init the parser
|
||||||
|
|
||||||
|
// TODO if loading from cache, don't check Qispos state
|
||||||
if (checkCredentials() && checkQisposStatus()) {
|
if (checkCredentials() && checkQisposStatus()) {
|
||||||
GlobalScope.launch(Dispatchers.Default) {
|
GlobalScope.launch(Dispatchers.Default) {
|
||||||
addGrades()
|
addGrades()
|
||||||
|
@ -125,18 +128,30 @@ class GradesFragment : Fragment() {
|
||||||
return statusCode == 200
|
return statusCode == 200
|
||||||
}
|
}
|
||||||
|
|
||||||
// add the grades to the layout, async
|
/**
|
||||||
|
* add the grades to the layout, async
|
||||||
|
* TODO this is slow as hell
|
||||||
|
*/
|
||||||
private fun addGrades() = GlobalScope.launch(Dispatchers.Default) {
|
private fun addGrades() = GlobalScope.launch(Dispatchers.Default) {
|
||||||
|
val addGradesTime = measureTimeMillis {
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
refreshLayout_Grades.isRefreshing = true
|
refreshLayout_Grades.isRefreshing = true
|
||||||
}
|
}
|
||||||
|
|
||||||
val grades = parser.parseGrades()
|
// if the cache is older than 24hr, update blocking
|
||||||
|
val currentTime = System.currentTimeMillis() / 1000
|
||||||
|
if ((currentTime - Preferences.gradesCacheTime) > 86400) {
|
||||||
|
CacheController.updateGrades(context!!).join()
|
||||||
|
}
|
||||||
|
|
||||||
|
val grades = CacheController(context!!).readGrades()
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
linLayout_Grades.removeAllViews() // clear layout
|
linLayout_Grades.removeAllViews() // clear layout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO this loop takes 3/4 of the time
|
||||||
// for each semester add a new card
|
// for each semester add a new card
|
||||||
grades.forEach { semester ->
|
grades.forEach { semester ->
|
||||||
val usedSubjects = ArrayList<String>()
|
val usedSubjects = ArrayList<String>()
|
||||||
|
@ -183,8 +198,6 @@ class GradesFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
val txtViewLegal = TextView(context).apply {
|
val txtViewLegal = TextView(context).apply {
|
||||||
text = resources.getString(R.string.without_guarantee)
|
text = resources.getString(R.string.without_guarantee)
|
||||||
textAlignment = View.TEXT_ALIGNMENT_CENTER
|
textAlignment = View.TEXT_ALIGNMENT_CENTER
|
||||||
|
@ -195,6 +208,9 @@ class GradesFragment : Fragment() {
|
||||||
linLayout_Grades.addView(txtViewLegal)
|
linLayout_Grades.addView(txtViewLegal)
|
||||||
refreshLayout_Grades.isRefreshing = false
|
refreshLayout_Grades.isRefreshing = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
println("startup completed in $addGradesTime ms")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -33,7 +33,7 @@ import kotlinx.android.synthetic.main.fragment_timetable.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import org.mosad.seil0.projectlaogai.R
|
import org.mosad.seil0.projectlaogai.R
|
||||||
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
|
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController
|
||||||
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController.Companion.timetable
|
import org.mosad.seil0.projectlaogai.controller.cache.TimetableController.timetable
|
||||||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences
|
||||||
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.AddSubjectDialog
|
import org.mosad.seil0.projectlaogai.uicomponents.dialogs.AddSubjectDialog
|
||||||
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
|
import org.mosad.seil0.projectlaogai.uicomponents.DayCardView
|
||||||
|
|
|
@ -127,6 +127,7 @@
|
||||||
<string name="save_key_coursesCacheTime" translatable="false">org.mosad.seil0.projectlaogai.coursesCacheTime</string>
|
<string name="save_key_coursesCacheTime" translatable="false">org.mosad.seil0.projectlaogai.coursesCacheTime</string>
|
||||||
<string name="save_key_mensaCacheTime" translatable="false">org.mosad.seil0.projectlaogai.mensaCacheTime</string>
|
<string name="save_key_mensaCacheTime" translatable="false">org.mosad.seil0.projectlaogai.mensaCacheTime</string>
|
||||||
<string name="save_key_timetableCacheTime" translatable="false">org.mosad.seil0.projectlaogai.timetableCacheTime</string>
|
<string name="save_key_timetableCacheTime" translatable="false">org.mosad.seil0.projectlaogai.timetableCacheTime</string>
|
||||||
|
<string name="save_key_gradesCacheTime" translatable="false">org.mosad.seil0.projectlaogai.gradesCacheTime</string>
|
||||||
<string name="save_key_user_email" translatable="false">org.mosad.seil0.projectlaogai.user_email</string>
|
<string name="save_key_user_email" translatable="false">org.mosad.seil0.projectlaogai.user_email</string>
|
||||||
<string name="save_key_user_password" translatable="false">org.mosad.seil0.projectlaogai.user_password</string>
|
<string name="save_key_user_password" translatable="false">org.mosad.seil0.projectlaogai.user_password</string>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue