Browse Source

added timeout and other error handling

feature/exam_notifications
Nareshkumar Rao 4 years ago
parent
commit
09f63cb4da
  1. 4
      app/build.gradle
  2. BIN
      app/release/app-release.apk
  3. 4
      app/release/output-metadata.json
  4. 150
      app/src/main/java/com/nareshkumarrao/eiweblog/HISUtility.kt
  5. 2
      app/src/main/java/com/nareshkumarrao/eiweblog/UpdateWorker.kt
  6. 19
      app/src/main/java/com/nareshkumarrao/eiweblog/Utilities.kt
  7. 15
      app/src/main/java/com/nareshkumarrao/eiweblog/ui/main/SectionsFragment.kt
  8. 1
      app/src/main/res/layout/dialog_login.xml
  9. 2
      app/src/main/res/values-de/strings.xml
  10. 2
      app/src/main/res/values-en/strings.xml
  11. 2
      app/src/main/res/values/strings.xml
  12. 1
      images/launcher.svg

4
app/build.gradle

@ -11,8 +11,8 @@ android {
applicationId "com.nareshkumarrao.eiweblog" applicationId "com.nareshkumarrao.eiweblog"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 30 targetSdkVersion 30
versionCode 5
versionName "0.10.1"
versionCode 6
versionName "0.10.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }

BIN
app/release/app-release.apk

Binary file not shown.

4
app/release/output-metadata.json

@ -10,8 +10,8 @@
{ {
"type": "SINGLE", "type": "SINGLE",
"filters": [], "filters": [],
"versionCode": 5,
"versionName": "0.10.1",
"versionCode": 6,
"versionName": "0.10.2",
"outputFile": "app-release.apk" "outputFile": "app-release.apk"
} }
] ]

150
app/src/main/java/com/nareshkumarrao/eiweblog/HISUtility.kt

@ -1,17 +1,20 @@
package com.nareshkumarrao.eiweblog package com.nareshkumarrao.eiweblog
import android.app.Activity
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build import android.os.Build
import android.widget.Toast
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import org.jsoup.Connection import org.jsoup.Connection
import org.jsoup.Jsoup import org.jsoup.Jsoup
import java.net.SocketTimeoutException
data class ExamRow(val name: String, val grade: String, val attempt: String, val date: String) data class ExamRow(val name: String, val grade: String, val attempt: String, val date: String)
@ -34,17 +37,17 @@ internal object HISUtility {
return return
} }
val newRows: MutableList<ExamRow> = mutableListOf()
fetchExamRows(context) { examRows ->
if (examRows != null) {
for (examRow in examRows) {
if (!savedRows.contains(examRow)) {
newRows.add(examRow)
}
val newRows: MutableList<ExamRow> = mutableListOf()
fetchExamRows(context) { examRows ->
if (examRows != null) {
for (examRow in examRows) {
if (!savedRows.contains(examRow)) {
newRows.add(examRow)
} }
} }
callback(newRows)
} }
callback(newRows)
}
} }
@ -77,75 +80,118 @@ internal object HISUtility {
?: return null ?: return null
val runnable = Runnable { val runnable = Runnable {
val postData: MutableMap<String, String> = mutableMapOf()
postData["asdf"] = username
postData["fdsa"] = password
val loginPage = Jsoup.connect(context.getString(R.string.ossc_login_post))
try {
val postData: MutableMap<String, String> = mutableMapOf()
postData["asdf"] = username
postData["fdsa"] = password
val loginPage = Jsoup.connect(context.getString(R.string.ossc_login_post))
.method(Connection.Method.POST) .method(Connection.Method.POST)
.userAgent("Mozilla") .userAgent("Mozilla")
.data(postData) .data(postData)
.timeout(60000)
.execute() .execute()
val selectNotenspiegel = Jsoup.connect(context.getString(R.string.ossc_select_noten))
.userAgent("Mozilla")
.cookies(loginPage.cookies())
.get()
val notenspiegelURL = selectNotenspiegel.select("a[href]:containsOwn(Notenspiegel)").first()?.attr("href")
?: kotlin.run {
callback(null)
return@Runnable
}
val selectNotenspiegel =
Jsoup.connect(context.getString(R.string.ossc_select_noten))
.userAgent("Mozilla")
.cookies(loginPage.cookies())
.timeout(60000)
.get()
val notenspiegelURL =
selectNotenspiegel.select("a[href]:containsOwn(Notenspiegel)").first()
?.attr("href")
?: kotlin.run {
callback(null)
return@Runnable
}
val selectStudiengangUnhide = Jsoup.connect(notenspiegelURL)
val selectStudiengangUnhide = Jsoup.connect(notenspiegelURL)
.userAgent("Mozilla") .userAgent("Mozilla")
.cookies(loginPage.cookies()) .cookies(loginPage.cookies())
.timeout(60000)
.get() .get()
val selectStudiengangUnhideURL = selectStudiengangUnhide.select("a[href]:containsOwn(Abschluss)").first().attr("href")
val selectStudiengangUnhideURL =
selectStudiengangUnhide.select("a[href]:containsOwn(Abschluss)").first()
.attr("href")
val selectStudiengang = Jsoup.connect(selectStudiengangUnhideURL)
val selectStudiengang = Jsoup.connect(selectStudiengangUnhideURL)
.userAgent("Mozilla") .userAgent("Mozilla")
.cookies(loginPage.cookies()) .cookies(loginPage.cookies())
.timeout(60000)
.get() .get()
val studiengangURL = selectStudiengang.select("a[href]:containsOwn(Leistungen anzeigen)").first().attr("href")
val studiengangURL =
selectStudiengang.select("a[href]:containsOwn(Leistungen anzeigen)").first()
.attr("href")
val notenSpiegelPage = Jsoup.connect(studiengangURL)
val notenSpiegelPage = Jsoup.connect(studiengangURL)
.userAgent("Mozilla") .userAgent("Mozilla")
.cookies(loginPage.cookies()) .cookies(loginPage.cookies())
.timeout(60000)
.get() .get()
val allGradesRows = notenSpiegelPage.select("div.fixedContainer > table > tbody > tr")
val examRows: MutableList<ExamRow> = mutableListOf()
for (row in allGradesRows) {
if (row.select("td.tabelle1_alignleft").size < 1) {
continue
val allGradesRows =
notenSpiegelPage.select("div.fixedContainer > table > tbody > tr")
val examRows: MutableList<ExamRow> = mutableListOf()
for (row in allGradesRows) {
if (row.select("td.tabelle1_alignleft").size < 1) {
continue
}
val columns = row.select("td")
if (columns.size < 1) {
continue
}
val examRow = ExamRow(
columns[1].text(),
columns[3].text(),
columns[6].text(),
columns[7].text()
)
examRows.add(examRow)
} }
val columns = row.select("td")
if (columns.size < 1) {
continue
saveExamRows(context, examRows)
callback(examRows)
} catch (e: Exception) {
if (context is Activity) context.runOnUiThread {
when (e) {
is SocketTimeoutException -> {
Toast.makeText(
context,
context.getString(R.string.ossc_timeout_message),
Toast.LENGTH_LONG
).show()
}
else -> {
Toast.makeText(context, e.localizedMessage, Toast.LENGTH_LONG).show()
}
}
context.finish()
} }
val examRow = ExamRow(columns[1].text(), columns[3].text(), columns[6].text(), columns[7].text())
examRows.add(examRow)
} }
saveExamRows(context, examRows)
callback(examRows)
} }
return Thread(runnable).start() return Thread(runnable).start()
} }
fun sendNotification(context: Context?, examRow: ExamRow, id:Int) {
fun sendNotification(context: Context?, examRow: ExamRow, id: Int) {
val intent = Intent(context, NotificationSettingsActivity::class.java).apply { val intent = Intent(context, NotificationSettingsActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
} }
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, 0) val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, 0)
val builder = NotificationCompat.Builder(context!!, context.getString(R.string.grades_notification_channel_id))
.setSmallIcon(R.drawable.ic_stat_name)
.setContentTitle(context.getString(R.string.exam_results_notification))
.setStyle(NotificationCompat.BigTextStyle()
.bigText("${examRow.name}: ${examRow.grade}"))
val builder = NotificationCompat.Builder(
context!!,
context.getString(R.string.grades_notification_channel_id)
)
.setSmallIcon(R.drawable.ic_stat_name)
.setContentTitle(context.getString(R.string.exam_results_notification))
.setStyle(
NotificationCompat.BigTextStyle()
.bigText("${examRow.name}: ${examRow.grade}")
)
.setContentIntent(pendingIntent) .setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_DEFAULT) .setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setAutoCancel(true) .setAutoCancel(true)
@ -155,15 +201,21 @@ internal object HISUtility {
} }
fun createNotificationChannel(context: Context?){
fun createNotificationChannel(context: Context?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = context?.getString(R.string.grades_notification_channel_name) val name = context?.getString(R.string.grades_notification_channel_name)
val descriptionText = context?.getString(R.string.grades_notification_channel_description)
val descriptionText =
context?.getString(R.string.grades_notification_channel_description)
val importance = NotificationManager.IMPORTANCE_DEFAULT val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel = NotificationChannel(context?.getString(R.string.grades_notification_channel_id), name, importance).apply {
val channel = NotificationChannel(
context?.getString(R.string.grades_notification_channel_id),
name,
importance
).apply {
description = descriptionText description = descriptionText
} }
val notificationManager: NotificationManager = context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notificationManager: NotificationManager =
context?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel) notificationManager.createNotificationChannel(channel)
} }
} }

2
app/src/main/java/com/nareshkumarrao/eiweblog/UpdateWorker.kt

@ -16,11 +16,13 @@ class UpdateWorker(private val context: Context, workerParams: WorkerParameters)
if(weblogNotificationsEnabled!!){ if(weblogNotificationsEnabled!!){
Utilities.weblogList(context) { articles -> Utilities.weblogList(context) { articles ->
articles ?: return@weblogList
val lastArticle = Utilities.getLatestRelevantArticle(articles)!! val lastArticle = Utilities.getLatestRelevantArticle(articles)!!
val hashString = lastArticle.title + lastArticle.content + lastArticle.date val hashString = lastArticle.title + lastArticle.content + lastArticle.date
val oldHash = md5(hashString) val oldHash = md5(hashString)
Utilities.fetchWeblogXML(applicationContext){newArticles -> Utilities.fetchWeblogXML(applicationContext){newArticles ->
newArticles ?: return@fetchWeblogXML
val lastNewArticle = Utilities.getLatestRelevantArticle(newArticles)!! val lastNewArticle = Utilities.getLatestRelevantArticle(newArticles)!!
val newHashString = lastNewArticle.title + lastNewArticle.content + lastNewArticle.date val newHashString = lastNewArticle.title + lastNewArticle.content + lastNewArticle.date
val newHash = md5(newHashString) val newHash = md5(newHashString)

19
app/src/main/java/com/nareshkumarrao/eiweblog/Utilities.kt

@ -22,22 +22,22 @@ import java.io.StringReader
internal object Utilities { internal object Utilities {
fun weblogList(context: Context?, function: (d: List<Article>) -> Unit){
fun weblogList(context: Context?, function: (d: List<Article>?) -> Unit) {
val sharedPref = context?.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE) val sharedPref = context?.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE)
val weblogResponse = sharedPref?.getString(context.getString(R.string.weblog_response_key), null) val weblogResponse = sharedPref?.getString(context.getString(R.string.weblog_response_key), null)
if (weblogResponse == null){
if (weblogResponse == null) {
fetchWeblogXML(context, function) fetchWeblogXML(context, function)
return return
} }
val parser: XmlPullParser = Xml.newPullParser() val parser: XmlPullParser = Xml.newPullParser()
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false) parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
parser.setInput( StringReader(weblogResponse) )
parser.setInput(StringReader(weblogResponse))
parser.nextTag() parser.nextTag()
function(parseXML(parser)) function(parseXML(parser))
} }
fun fetchWeblogXML(context: Context?, function: (d: List<Article>) -> Unit) {
fun fetchWeblogXML(context: Context?, callback: (d: List<Article>?) -> Unit) {
val queue = Volley.newRequestQueue(context) val queue = Volley.newRequestQueue(context)
@ -49,7 +49,7 @@ internal object Utilities {
val sharedPref = context?.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE) val sharedPref = context?.getSharedPreferences(context.getString(R.string.preference_file_key), Context.MODE_PRIVATE)
if (sharedPref != null) { if (sharedPref != null) {
with (sharedPref.edit()) {
with(sharedPref.edit()) {
putString(context.getString(R.string.weblog_response_key), responseStr) putString(context.getString(R.string.weblog_response_key), responseStr)
apply() apply()
} }
@ -58,14 +58,17 @@ internal object Utilities {
val parser: XmlPullParser = Xml.newPullParser() val parser: XmlPullParser = Xml.newPullParser()
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false) parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
//Log.d("XMLLIST", responseStr ) //Log.d("XMLLIST", responseStr )
parser.setInput( StringReader(responseStr) )
parser.setInput(StringReader(responseStr))
parser.nextTag() parser.nextTag()
val articles = parseXML(parser) val articles = parseXML(parser)
function(articles)
callback(articles)
}, },
{ error -> Log.e("XMLLIST", error.toString()) })
{ error ->
Log.e("XMLLIST", error.toString())
callback(null)
})
queue.add(stringRequest) queue.add(stringRequest)
} }

15
app/src/main/java/com/nareshkumarrao/eiweblog/ui/main/SectionsFragment.kt

@ -4,6 +4,7 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -48,12 +49,18 @@ class SectionsFragment : Fragment() {
} }
} }
private fun updateView(get_articles: List<Article>){
this.swipeRefreshLayout?.isRefreshing=false
private fun updateView(get_articles: List<Article>?) {
this.swipeRefreshLayout?.isRefreshing = false
if (get_articles == null) {
Toast.makeText(context, getString(R.string.load_weblog_error_message), Toast.LENGTH_LONG).show()
return
}
val articles: MutableList<Article> = mutableListOf() val articles: MutableList<Article> = mutableListOf()
val title = arguments?.getString(ARG_SECTION_NAME) ?: return val title = arguments?.getString(ARG_SECTION_NAME) ?: return
for (article in get_articles){
if(article.category == title){
for (article in get_articles) {
if (article.category == title) {
articles.add(article) articles.add(article)
} }
} }

1
app/src/main/res/layout/dialog_login.xml

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">

2
app/src/main/res/values-de/strings.xml

@ -32,4 +32,6 @@
<string name="enable_grades_notifications_text">Benachrichtige mich bei neuem Klausurergebnis</string> <string name="enable_grades_notifications_text">Benachrichtige mich bei neuem Klausurergebnis</string>
<string name="login_dialog_title">Anmelden bei OSSC</string> <string name="login_dialog_title">Anmelden bei OSSC</string>
<string name="error">FEHLER!</string> <string name="error">FEHLER!</string>
<string name="ossc_timeout_message">Timeout-Fehler bei OSSC. Versuch mal später.</string>
<string name="load_weblog_error_message">Weblog kann nicht aktualisiert werden. Versuch mal später.</string>
</resources> </resources>

2
app/src/main/res/values-en/strings.xml

@ -33,4 +33,6 @@
<string name="enable_grades_notifications_text">Notify me when I get new grades</string> <string name="enable_grades_notifications_text">Notify me when I get new grades</string>
<string name="login_dialog_title">Login to OSSC</string> <string name="login_dialog_title">Login to OSSC</string>
<string name="error">ERROR!</string> <string name="error">ERROR!</string>
<string name="ossc_timeout_message">OSSC has timed out. Try again later.</string>
<string name="load_weblog_error_message">Could not load new weblog data. Try again later.</string>
</resources> </resources>

2
app/src/main/res/values/strings.xml

@ -54,4 +54,6 @@
<string name="enable_grades_notifications_key" translatable="false">com.nareshkumarrao.eiweblog.grades_notifications.key</string> <string name="enable_grades_notifications_key" translatable="false">com.nareshkumarrao.eiweblog.grades_notifications.key</string>
<string name="login_dialog_title">Login to OSSC</string> <string name="login_dialog_title">Login to OSSC</string>
<string name="error">ERROR!</string> <string name="error">ERROR!</string>
<string name="ossc_timeout_message">OSSC has timed out. Try again later.</string>
<string name="load_weblog_error_message">Could not load new weblog data. Try again later.</string>
</resources> </resources>

1
images/launcher.svg

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--suppress ALL -->
<svg <svg
xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#" xmlns:cc="http://creativecommons.org/ns#"

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Loading…
Cancel
Save