@ -0,0 +1,145 @@ | |||
/** | |||
* ProjectLaogai | |||
* | |||
* Copyright 2019-2020 <seil0@mosad.xyz> | |||
* | |||
* This program is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
* the Free Software Foundation; either version 3 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this program; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | |||
* MA 02110-1301, USA. | |||
* | |||
*/ | |||
package org.mosad.seil0.projectlaogai | |||
import android.content.Intent | |||
import android.graphics.Color | |||
import android.os.Bundle | |||
import android.view.View | |||
import android.widget.EditText | |||
import android.widget.LinearLayout | |||
import android.widget.TextView | |||
import androidx.appcompat.app.AppCompatActivity | |||
import androidx.core.text.HtmlCompat | |||
import androidx.viewpager.widget.ViewPager | |||
import com.afollestad.materialdialogs.callbacks.onDismiss | |||
import kotlinx.android.synthetic.main.activity_onboarding.* | |||
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences | |||
import org.mosad.seil0.projectlaogai.fragments.SettingsFragment | |||
import org.mosad.seil0.projectlaogai.onboarding.ViewPagerAdapter | |||
class OnboardingActivity : AppCompatActivity() { | |||
companion object { | |||
val layouts = intArrayOf(R.layout.fragment_on_welcome, R.layout.fragment_on_course, R.layout.fragment_on_login) | |||
} | |||
private lateinit var viewPager: ViewPager | |||
private lateinit var viewPagerAdapter: ViewPagerAdapter | |||
private lateinit var linLayoutDots: LinearLayout | |||
private lateinit var dots: Array<TextView> | |||
private lateinit var editTextEmail: EditText | |||
private lateinit var editTextPassword: EditText | |||
override fun onCreate(savedInstanceState: Bundle?) { | |||
super.onCreate(savedInstanceState) | |||
setContentView(R.layout.activity_onboarding) | |||
viewPager = findViewById(R.id.viewPager) | |||
linLayoutDots = findViewById(R.id.linLayout_Dots) | |||
addBottomDots(0) | |||
viewPagerAdapter = ViewPagerAdapter(this) | |||
viewPager.adapter = viewPagerAdapter | |||
viewPager.addOnPageChangeListener(viewPagerPageChangeListener) | |||
// we don't use the skip button, instead we use the start button to skip the last fragment | |||
btn_Skip.visibility = View.GONE | |||
} | |||
fun btnNextClick(@Suppress("UNUSED_PARAMETER")v: View) { | |||
if (viewPager.currentItem < layouts.size - 1) { | |||
viewPager.currentItem++ | |||
} else { | |||
launchHomeScreen() | |||
} | |||
} | |||
fun btnSkipClick(@Suppress("UNUSED_PARAMETER")v: View) { | |||
launchHomeScreen() | |||
} | |||
fun btnSelectCourseClick(@Suppress("UNUSED_PARAMETER")v: View) { | |||
SettingsFragment().selectCourse(this).show { | |||
onDismiss { | |||
btnNextClick(v) // show the next fragment | |||
} | |||
} | |||
} | |||
fun btnLoginClick(@Suppress("UNUSED_PARAMETER")v: View) { | |||
editTextEmail = findViewById(R.id.editText_email) | |||
editTextPassword = findViewById(R.id.editText_password) | |||
// get login credentials from gui | |||
val email = editTextEmail.text.toString() | |||
val password = editTextPassword.text.toString() | |||
// save the credentials | |||
EncryptedPreferences.saveCredentials(email, password, this) | |||
launchHomeScreen() | |||
} | |||
private fun addBottomDots(currentPage: Int) { | |||
dots = Array(layouts.size) { TextView(this) } | |||
linLayoutDots.removeAllViews() | |||
dots.forEach { | |||
it.text = HtmlCompat.fromHtml("•", HtmlCompat.FROM_HTML_MODE_LEGACY) | |||
it.textSize = 35f | |||
it.setTextColor(Color.parseColor("#cccccc")) | |||
linLayoutDots.addView(it) | |||
} | |||
if (dots.isNotEmpty()) | |||
dots[currentPage].setTextColor(Color.parseColor("#000000")) | |||
} | |||
private fun launchHomeScreen() { | |||
startActivity(Intent(this, MainActivity::class.java)) | |||
finish() | |||
} | |||
private var viewPagerPageChangeListener: ViewPager.OnPageChangeListener = object : ViewPager.OnPageChangeListener { | |||
override fun onPageSelected(position: Int) { | |||
addBottomDots(position) | |||
// changing the next button text to skip for the login fragment | |||
if (position == layouts.size - 1) { | |||
btn_Next.text = getString(R.string.skip) | |||
btn_Next.visibility = View.VISIBLE | |||
btn_Skip.visibility = View.GONE | |||
} else { | |||
btn_Next.visibility = View.GONE | |||
btn_Skip.visibility = View.GONE | |||
} | |||
} | |||
override fun onPageScrolled(arg0: Int, arg1: Float, arg2: Int) {} | |||
override fun onPageScrollStateChanged(arg0: Int) {} | |||
} | |||
} |
@ -1,164 +0,0 @@ | |||
/** | |||
* ProjectLaogai | |||
* | |||
* Copyright 2019 <seil0@mosad.xyz> | |||
* | |||
* This program is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
* the Free Software Foundation; either version 3 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this program; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | |||
* MA 02110-1301, USA. | |||
* | |||
*/ | |||
package org.mosad.seil0.projectlaogai.controller | |||
import android.content.Context | |||
import com.google.gson.Gson | |||
import com.google.gson.GsonBuilder | |||
import com.google.gson.JsonParser | |||
import com.google.gson.reflect.TypeToken | |||
import org.mosad.seil0.projectlaogai.controller.PreferencesController.Companion.cCourse | |||
import org.mosad.seil0.projectlaogai.hsoparser.* | |||
import java.io.BufferedReader | |||
import java.io.File | |||
import java.io.FileReader | |||
import java.util.* | |||
import kotlin.collections.ArrayList | |||
class CacheController(cont: Context) { | |||
private val context = cont | |||
init { | |||
val cal = Calendar.getInstance() | |||
val currentDay = Calendar.getInstance().get(Calendar.DAY_OF_WEEK) | |||
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) | |||
// 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) { | |||
println("update mensa blocking") | |||
TCoRAPIController.getMensa(context).get() | |||
} | |||
// check if we need to update the timetables before displaying them | |||
readTimetable(cCourse.courseName, 0, context) | |||
cal.time = Date(timetables[0].meta.updateTime * 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) { | |||
println("updating timetable after sunday!") | |||
val jobA = TCoRAPIController.getTimetable(cCourse.courseName, 0, context) | |||
val jobB = TCoRAPIController.getTimetable(cCourse.courseName, 1, context) | |||
jobA.get() | |||
jobB.get() | |||
} | |||
// check if an update is necessary, not blocking | |||
if(currentTime - PreferencesController.coursesCacheTime > 86400) | |||
TCoRAPIController.getCoursesList(context) | |||
if(currentTime - PreferencesController.mensaCacheTime > 10800) | |||
TCoRAPIController.getMensa(context) | |||
if(currentTime - PreferencesController.timetableCacheTime > 10800) { | |||
TCoRAPIController.getTimetable(cCourse.courseName, 0, context) | |||
TCoRAPIController.getTimetable(cCourse.courseName, 1, context) | |||
} | |||
readStartCache(cCourse.courseName) | |||
} | |||
companion object { | |||
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 | |||
*/ | |||
private fun readCoursesList(context: Context) { | |||
val file = File(context.filesDir, "courses.json") | |||
// make sure the file exists | |||
if (!file.exists()) | |||
TCoRAPIController.getCoursesList(context).get() | |||
val fileReader = FileReader(file) | |||
val bufferedReader = BufferedReader(fileReader) | |||
val coursesObject = JsonParser().parse(bufferedReader.readLine()).asJsonObject | |||
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()) { | |||
TCoRAPIController.getMensa(context).get() | |||
} | |||
val fileReader = FileReader(file) | |||
val bufferedReader = BufferedReader(fileReader) | |||
val mensaObject = JsonParser().parse(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()) | |||
TCoRAPIController.getTimetable(courseName, week, context).get() | |||
val fileReader = FileReader(file) | |||
val bufferedReader = BufferedReader(fileReader) | |||
val timetableObject = JsonParser().parse(bufferedReader.readLine()).asJsonObject | |||
// make sure you add the single weeks in the exact order! | |||
if (timetables.size > week) { | |||
timetables[week] = (Gson().fromJson(timetableObject, TimetableCourseWeek().javaClass)) | |||
} else { | |||
timetables.add(Gson().fromJson(timetableObject, TimetableCourseWeek().javaClass)) | |||
} | |||
} | |||
} | |||
/** | |||
* read coursesList, mensa (current and next week), timetable (current and next week) | |||
* @param courseName the course name (e.g AI1) | |||
*/ | |||
fun readStartCache(courseName: String) { | |||
readCoursesList(context) | |||
readMensa(context) | |||
readTimetable(courseName, 0, context) | |||
readTimetable(courseName, 1, context) | |||
} | |||
} |
@ -1,134 +0,0 @@ | |||
/** | |||
* ProjectLaogai | |||
* | |||
* Copyright 2019 <seil0@mosad.xyz> | |||
* | |||
* This program is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
* the Free Software Foundation; either version 3 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this program; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | |||
* MA 02110-1301, USA. | |||
* | |||
*/ | |||
package org.mosad.seil0.projectlaogai.controller | |||
import android.content.Context | |||
import android.graphics.Color | |||
import org.jetbrains.anko.defaultSharedPreferences | |||
import org.mosad.seil0.projectlaogai.R | |||
import org.mosad.seil0.projectlaogai.hsoparser.Course | |||
/** | |||
* The PreferencesController class | |||
* contains all preferences and global variables that exist in this app | |||
*/ | |||
class PreferencesController { | |||
companion object { | |||
var coursesCacheTime: Long = 0 | |||
var mensaCacheTime: Long = 0 | |||
var timetableCacheTime: Long = 0 | |||
var cColorPrimary: Int = Color.BLACK | |||
var cColorAccent: Int = Color.parseColor("#3F51B5") | |||
var cCourse = Course("https://www.hs-offenburg.de/index.php?id=6627&class=class&iddV=DA64F6FE-9DDB-429E-A677-05D0D40CB636&week=0", "AI3") | |||
var cShowBuffet = true | |||
var oGiants = false | |||
// the save function | |||
fun save(context: Context) { | |||
val sharedPref = context.defaultSharedPreferences | |||
// save the update times (cache) | |||
with (sharedPref.edit()) { | |||
putLong(context.getString(R.string.save_key_coursesCacheTime), | |||
coursesCacheTime | |||
) | |||
putLong(context.getString(R.string.save_key_mensaCacheTime), | |||
mensaCacheTime | |||
) | |||
putLong(context.getString(R.string.save_key_timetableCacheTime), | |||
timetableCacheTime | |||
) | |||
apply() | |||
} | |||
// save the course | |||
with (sharedPref.edit()) { | |||
putString(context.getString(R.string.save_key_course), cCourse.courseName) | |||
putString(context.getString(R.string.save_key_courseTTLink), cCourse.courseLink) | |||
apply() | |||
} | |||
// save the primary color | |||
with (sharedPref.edit()) { | |||
putInt(context.getString(R.string.save_key_colorPrimary), | |||
cColorPrimary | |||
) | |||
apply() | |||
} | |||
// save the accent color | |||
with (sharedPref.edit()) { | |||
putInt(context.getString(R.string.save_key_colorAccent), | |||
cColorAccent | |||
) | |||
apply() | |||
} | |||
// save showBuffet | |||
with (sharedPref.edit()) { | |||
putBoolean(context.getString(R.string.save_key_showBuffet), | |||
cShowBuffet | |||
) | |||
apply() | |||
} | |||
} | |||
// the load function | |||
fun load(context: Context) { | |||
val sharedPref = context.defaultSharedPreferences | |||
// load the update times (cache) | |||
coursesCacheTime = sharedPref.getLong(context.getString( | |||
R.string.save_key_coursesCacheTime | |||
), 0) | |||
mensaCacheTime = sharedPref.getLong(context.getString( | |||
R.string.save_key_mensaCacheTime | |||
), 0) | |||
timetableCacheTime = sharedPref.getLong(context.getString( | |||
R.string.save_key_timetableCacheTime | |||
), 0) | |||
// load saved course | |||
cCourse = Course( | |||
sharedPref.getString(context.getString(R.string.save_key_courseTTLink), "https://www.hs-offenburg.de/index.php?id=6627&class=class&iddV=DA64F6FE-9DDB-429E-A677-05D0D40CB636&week=0")!!, | |||
sharedPref.getString(context.getString(R.string.save_key_course), "AI3")!! | |||
) | |||
// load saved colors | |||
cColorPrimary = sharedPref.getInt(context.getString( | |||
R.string.save_key_colorPrimary | |||
), Color.BLACK) | |||
cColorAccent = sharedPref.getInt(context.getString( | |||
R.string.save_key_colorAccent | |||
), Color.parseColor("#3F51B5")) | |||
// load showBuffet | |||
cShowBuffet = sharedPref.getBoolean(context.getString( | |||
R.string.save_key_showBuffet | |||
), true) | |||
} | |||
} | |||
} |
@ -0,0 +1,236 @@ | |||
/** | |||
* ProjectLaogai | |||
* | |||
* Copyright 2019-2020 <seil0@mosad.xyz> | |||
* | |||
* This program is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
* the Free Software Foundation; either version 3 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this program; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | |||
* MA 02110-1301, USA. | |||
* | |||
*/ | |||
package org.mosad.seil0.projectlaogai.controller | |||
import android.content.Context | |||
import android.util.Log | |||
import kotlinx.coroutines.Dispatchers | |||
import kotlinx.coroutines.runBlocking | |||
import kotlinx.coroutines.withContext | |||
import org.jsoup.HttpStatusException | |||
import org.jsoup.Jsoup | |||
import org.jsoup.nodes.Element | |||
import org.mosad.seil0.projectlaogai.R | |||
import org.mosad.seil0.projectlaogai.controller.preferences.EncryptedPreferences | |||
import org.mosad.seil0.projectlaogai.util.GradeSubject | |||
import java.security.KeyStore | |||
import java.security.cert.CertificateFactory | |||
import java.security.cert.X509Certificate | |||
import java.util.* | |||
import javax.net.ssl.SSLContext | |||
import javax.net.ssl.SSLSocketFactory | |||
import javax.net.ssl.TrustManagerFactory | |||
import kotlin.collections.ArrayList | |||
import kotlin.collections.HashMap | |||
/** | |||
* Parse the qispos site the get all needed data for the grades fragment | |||
*/ | |||
class QISPOSParser(val context: Context) { | |||
private val className = this.javaClass.name | |||
private val baseURL = "https://notenverwaltung.hs-offenburg.de" | |||
private val loginPath = "/qispos/rds?state=user&type=1&category=auth.login&startpage=portal.vm&breadCrumbSource=portal" | |||
/** | |||
* check if qispos is available | |||
* @return a http status code, 999 if no HttpStatusException supplied | |||
*/ | |||
fun checkQISPOSStatus(): Int { | |||
return runBlocking { | |||
withContext(Dispatchers.IO) { | |||
val socketFactory = createSSLSocketFactory() | |||
try { | |||
val res = Jsoup.connect(baseURL + loginPath) | |||
.sslSocketFactory(socketFactory) | |||
.followRedirects(true) | |||
.referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0") | |||
.execute() | |||
res.statusCode() | |||
} catch (exHttp: HttpStatusException) { | |||
exHttp.statusCode | |||
} catch (ex: Exception) { | |||
Log.e(className, "Error while checking status", ex) | |||
999 | |||
} | |||
} | |||
} | |||
} | |||
/** | |||
* parse the html from readGrades() | |||
* @return a SortedMap, each entry is a semester, each semester has a ArrayList with subjects | |||
*/ | |||
fun parseGrades(): SortedMap<String, ArrayList<GradeSubject>> { | |||
val gradesMap = HashMap<String, ArrayList<GradeSubject>>() | |||
val gradesListHtml = readGrades() | |||
gradesListHtml?.select("table > tbody > tr")?.forEach { | |||
val row = it.select("td.tabelle1_alignleft,td.tabelle1_aligncenter,td.tabelle1_alignright") | |||
// only real subjects will be selected | |||
if(row.size >= 6 && row[0].text().length >=7) { | |||
val subject = GradeSubject( | |||
id = row[0].text(), | |||
name = row[1].text(), | |||
semester = row[2].text(), | |||
grade = if (row[3].text().isNotEmpty()) row[3].text() else row[4].text(), | |||
credits = row[5].text() | |||
) | |||
if (gradesMap.containsKey(subject.semester)) { | |||
gradesMap[subject.semester]!!.add(subject) | |||
} else { | |||
gradesMap[subject.semester] = arrayListOf(subject) | |||
} | |||
} | |||
} | |||
// return the sorted map | |||
return gradesMap.toSortedMap(compareBy<String>{ | |||
val oText = it.substringAfter(" ") | |||
if (oText.contains("/")) { | |||
oText.substringBefore("/").toInt() + 0.5 | |||
} else { | |||
oText.toDouble() | |||
} | |||
}.thenBy { it }) | |||
} | |||
/** | |||
* read the grades html from qispos | |||
* @return the grades list as html element or null | |||
*/ | |||
private fun readGrades(): Element?{ | |||
val credentials = EncryptedPreferences.readCredentials(context) | |||
val username = credentials.first.substringBefore("@") | |||
val password = credentials.second | |||
return runBlocking { | |||
withContext(Dispatchers.IO) { | |||
try { | |||
val socketFactory = createSSLSocketFactory() | |||
// login, asdf = username, fdsa = password, wtf | |||
val list = mapOf( | |||
Pair("asdf", username), | |||
Pair("fdsa", password), | |||
Pair("submit", "Anmelden") | |||
) | |||
// login and get session cookies | |||
val res = Jsoup.connect(baseURL + loginPath) | |||
.sslSocketFactory(socketFactory) | |||
.followRedirects(true) | |||
.referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0") | |||
.data(list) | |||
.postDataCharset("UTF-8") | |||
.execute() | |||
Log.i(className, "login status is: ${res.statusMessage()}: ${res.statusCode()}") | |||
val loginCookies = res.cookies() | |||
// grades root document and url | |||
val rootHtml =Jsoup.parse(res.body()) | |||
val gradesRootLink = rootHtml.select("li.menueListStyle > a.auflistung").last().attr("abs:href") | |||
// parse grades url | |||
val gradesHtml = Jsoup.connect(gradesRootLink) | |||
.sslSocketFactory(socketFactory) | |||
.followRedirects(true) | |||
.cookies(loginCookies) | |||
.referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0") | |||
.get() | |||
val gradesNextLink = gradesHtml.select("li.treelist > a.regular").attr("abs:href") | |||
val gradesNextHtml = Jsoup.connect(gradesNextLink) | |||
.sslSocketFactory(socketFactory) | |||
.followRedirects(true) | |||
.cookies(loginCookies) | |||
.referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0") | |||
.get() | |||
val gradesListLink = gradesNextHtml.selectFirst("li.treelist > ul > li").selectFirst("a").attr("abs:href") | |||
// get the grades list | |||
val gradesListHtml = Jsoup.connect(gradesListLink) | |||
.sslSocketFactory(socketFactory) | |||
.followRedirects(true) | |||
.cookies(loginCookies) | |||
.referrer("https://notenverwaltung.hs-offenburg.de/qispos/rds?state=user&type=0") | |||
.get() | |||
gradesListHtml.body() | |||
} catch (ex: Exception) { | |||
Log.e(className, "error while loading qispos", ex) | |||
null | |||
} | |||
} | |||
} | |||
} | |||
/** | |||
* since the HS has a fucked up tls setup we need to work around that | |||
*/ | |||
private fun createSSLSocketFactory(): SSLSocketFactory { | |||
// Load CAs from an InputStream | |||
// (could be from a resource or ByteArrayInputStream or ...) | |||
val cf: CertificateFactory = CertificateFactory.getInstance("X.509") | |||
val caInput = context.resources.openRawResource(R.raw.notenverwaltung_hs_offenburg_de) | |||
val ca = caInput.use { | |||
cf.generateCertificate(it) as X509Certificate | |||
} | |||
// Create a KeyStore containing our trusted CAs | |||
val keyStoreType = KeyStore.getDefaultType() | |||
val keyStore = KeyStore.getInstance(keyStoreType).apply { | |||
load(null, null) | |||
setCertificateEntry("ca", ca) | |||
} | |||
// Create a TrustManager that trusts the CAs inputStream our KeyStore | |||
val tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm() | |||
val tmf = TrustManagerFactory.getInstance(tmfAlgorithm).apply { | |||
init(keyStore) | |||
} | |||
// Create an SSLContext that uses our TrustManager | |||
val sslContext = SSLContext.getInstance("TLS").apply { | |||
init(null, tmf.trustManagers, null) | |||
} | |||
return sslContext.socketFactory | |||
} | |||
} |
@ -0,0 +1,375 @@ | |||
/** | |||
* ProjectLaogai | |||
* | |||
* Copyright 2019-2020 <seil0@mosad.xyz> | |||
* | |||
* This program is free software; you can redistribute it and/or modify | |||
* it under the terms of the GNU General Public License as published by | |||
* the Free Software Foundation; either version 3 of the License, or | |||
* (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU General Public License | |||
* along with this program; if not, write to the Free Software | |||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | |||
* MA 02110-1301, USA. | |||
* | |||
*/ | |||
package org.mosad.seil0.projectlaogai.controller.cache | |||
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.mosad.seil0.projectlaogai.controller.preferences.Preferences | |||
import org.mosad.seil0.projectlaogai.controller.TCoRAPIController | |||
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.mensaCacheTime | |||
import org.mosad.seil0.projectlaogai.controller.preferences.Preferences.timetableCacheTime | |||
import org.mosad.seil0.projectlaogai.util.* | |||
import java.io.* | |||
import java.util.* | |||
import kotlin.collections.ArrayList | |||
/** | |||
* The cacheController reads and updates the cache files. | |||
* It contains the courseList and mensaMenu object, all timetable objects | |||
* are located in TimetableController. | |||
*/ | |||
class CacheController(cont: Context) { | |||
private val className = "CacheController" | |||
private val context = cont | |||
init { | |||
val cal = Calendar.getInstance() | |||
val currentDay = Calendar.getInstance().get(Calendar.DAY_OF_WEEK) | |||
val currentTime = System.currentTimeMillis() / 1000 | |||
// check if we need to update the mensa data before displaying it | |||
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) { updateMensaMenu(context).join() } | |||
} | |||
// check if we need to update the timetable before displaying it | |||
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 - timetableCacheTime) > 86400) { | |||
Log.i(className, "updating timetable after sunday!") | |||
GlobalScope.launch(Dispatchers.Default) { | |||
val threads = listOf( | |||
updateTimetable(cCourse.courseName, 0, context), | |||
updateTimetable(cCourse.courseName, 1, context) | |||
) | |||
threads.joinAll() | |||
} | |||
} | |||
updateCourseList(context) | |||
readStartCache(cCourse.courseName) // initially read values from cache | |||
// check if an update is necessary, not blocking | |||
if (currentTime - coursesCacheTime > 86400) | |||
updateCourseList(context) | |||
if (currentTime - mensaCacheTime > 10800) | |||
updateMensaMenu(context) | |||
if (currentTime - timetableCacheTime > 10800) { | |||
TimetableController.update(context) | |||
} | |||