StudIPParser.kt now stops fetching items that don't provide full information, and tells the user how many items were skipped

This commit is contained in:
denizk0461
2023-05-03 11:10:34 +02:00
parent 7f0659d094
commit ae8db7ce48
6 changed files with 68 additions and 25 deletions
@@ -102,11 +102,17 @@ class FetcherActivity : FragmentActivity() {
) { p0 ->
try {
// Decode HTML and parse it into a list of StudIPEvent.kt
// TODO add a tutorial for the user to know what to do
viewModel.parse(URLDecoder.decode(p0, "UTF-8"))
val elementsNotFetched = viewModel.parse(URLDecoder.decode(p0, "UTF-8"))
// Notify the user that the fetch was successful
showToast(this, getString(R.string.toast_fetch_finished))
/*
* Notify the user that the fetch was successful, and tell the user how many
* items could not be fetched.
*/
showToast(this, when (elementsNotFetched) {
0 -> getString(R.string.toast_fetch_finished_zero)
1 -> getString(R.string.toast_fetch_finished_one)
else -> getString(R.string.toast_fetch_finished_other, elementsNotFetched)
})
// Close the activity
finish()
@@ -23,15 +23,17 @@ import kotlin.jvm.Throws
*/
/**
* Converts a time stamp string to its numeric value in minutes.
* Converts a time stamp string to its numeric value in minutes. Returns 0 on error.
* Example: 13:20. (20 minutes) + (13 hours * 60 minutes) = 800 minutes.
*
* @return minute value of the string
*/
fun String.parseToMinutes(): Int {
val parts = split(":")
return (parts[0].toInt() * 60) + parts[1].toInt()
}
fun String.parseToMinutes(): Int = try {
val parts = split(":")
(parts[0].toInt() * 60) + parts[1].toInt()
} catch (e: IndexOutOfBoundsException) {
0
}
/**
* Retrieves a specified colour customised to the currently applied theme.
@@ -17,16 +17,20 @@ class StudIPParser(application: Application) {
private val repo: AppRepository = AppRepository.getRepositoryInstance(application)
/**
* Parse a given HTML string. HTML must be of a Stud.IP timetable.
* Parse a given HTML string. HTML must be of a Stud.IP timetable. May skip elements that don't
* provide full information on an event (title, timeslot, lecturers, room).
*
* @param html website content of the Stud.IP timetable
* @throws IOException when an error in fetching or the database transaction occurs
*/
@Throws(IOException::class)
fun parse(html: String) {
fun parse(html: String): Int {
// Primary key value to uniquely identify entries in the database
var id = 0
// Counts how many elements could not be successfully fetched
var elementsNotFetched = 0
// Parse the HTML using Jsoup to traverse the document
val doc = Jsoup.parse(html)
@@ -43,7 +47,7 @@ class StudIPParser(application: Application) {
doc.getDateIndices().forEach { (index, element) ->
// Iterate through all events scheduled for a given day
element.getElementsByClass("schedule_entry").forEach { entry ->
element.getElementsByClass("schedule_entry").forEach loop@{ entry ->
/*
* Retrieve the event's header. This will contain the event's title as well as the
* lecturers holding the event.
@@ -57,10 +61,21 @@ class StudIPParser(application: Application) {
val delimiter = entryHeader.lastIndexOf('(')
// Retrieve the title of the event from the header string
val parsedTitle = entryHeader.substring(0 until delimiter).trim()
val parsedTitle = try {
entryHeader.substring(0 until delimiter).trim()
} catch (e: StringIndexOutOfBoundsException) {
elementsNotFetched += 1
return@loop
}
// Retrieve the lecturer name(s) from the header string
val parsedLecturers = entryHeader.substring(delimiter + 1 until entryHeader.length - 1).trim()
val parsedLecturers = try {
entryHeader
.substring(delimiter + 1 until entryHeader.length - 1).trim()
} catch (e: StringIndexOutOfBoundsException) {
elementsNotFetched += 1
return@loop
}
/*
* Retrieve further event information. This will contain both the time slot the
@@ -72,18 +87,18 @@ class StudIPParser(application: Application) {
// Retrieve the time slot and split it into start and end time stamps
val timeSlot = entryInfo[0].split(" - ")
// Time the event starts at
val eventStart = timeSlot[0]
// Time the course starts at
val eventStart = timeSlot.getOrQuestionMark(0)
// Construct the newly scraped Stud.IP event
val event = StudIPEvent(
eventId = id,
title = parsedTitle,
lecturer = parsedLecturers,
room = entryInfo[1],
room = entryInfo.getOrQuestionMark(1),
day = index,
timeslotStart = eventStart,
timeslotEnd = timeSlot[1],
timeslotEnd = timeSlot.getOrQuestionMark(1),
timeslotId = eventStart.parseToMinutes(),
)
@@ -100,6 +115,9 @@ class StudIPParser(application: Application) {
// After fetching has finished, save the list of new items into persistent storage
repo.insertEvents(newEvents)
// Return the count of elements that were not able to be fetched
return elementsNotFetched
}
/**
@@ -141,6 +159,11 @@ class StudIPParser(application: Application) {
// Add the days found to the list
list.add(Pair(
dayIndex,
/*
* The index of the columns doesn't seem to depend on the other elements in the
* table. Friday will always have the index 4 ("calendar_view_1_column_4") even if
* Monday through Thursday are hidden from the schedule.
*/
getElementById("calendar_view_1_column_${dayIndex}") ?: continue,
))
}
@@ -148,4 +171,10 @@ class StudIPParser(application: Application) {
// Return the list of columns
return list
}
private fun List<String>.getOrQuestionMark(index: Int): String = try {
get(index)
} catch (e: IndexOutOfBoundsException) {
"?"
}
}
@@ -23,9 +23,9 @@ class FetcherViewModel(app: Application) : AppViewModel(app) {
* @param html source code of the schedule website
*/
@Throws(IOException::class)
fun parse(html: String) {
doAsync {
fun parse(html: String): Int =
returnBlocking {
parser.parse(html)
}
}
}
+5 -2
View File
@@ -23,6 +23,11 @@
<string name="fetch_error_webpage_snack">Du musst deinen Stundenplan öffnen!</string>
<string name="canteen_fetch_error">Mensaplan konnte nicht heruntergeladen werden!</string>
<!-- plurals string resource cannot be used here because Android doesn't treat 0 differently from 2 -->
<string name="toast_fetch_finished_zero">Dein Stundenplan wurde aktualisiert!</string>
<string name="toast_fetch_finished_one">Dein Stundenplan wurde aktualisiert, doch eine Veranstaltung konnte nicht heruntergeladen werden.</string>
<string name="toast_fetch_finished_other">Dein Stundenplan wurde aktualisiert, doch %d Veranstaltungen konnten nicht heruntergeladen werden.</string>
<string name="fetch_bar_close_title">Schließen</string>
<string name="fetch_bar_close_desc">Schließe die Webseite, ohne etwas zu speichern</string>
<string name="fetch_bar_refresh_title">Aktualisieren</string>
@@ -134,8 +139,6 @@
<string name="nav_food">Mensa</string>
<string name="nav_settings">Einstellungen</string>
<string name="toast_fetch_finished">Dein Stundenplan wurde aktualisiert!</string>
<string name="settings_general">Allgemein</string>
<string name="settings_schedule">Stud.IP-Stundenplan</string>
<string name="settings_canteen">Mensenangebote</string>
+5 -2
View File
@@ -23,6 +23,11 @@
<string name="fetch_error_webpage_snack">You must navigate to your schedule!</string>
<string name="canteen_fetch_error">Couldn\'t retrieve the canteen plan!</string>
<!-- plurals resource cannot be used here; view German translation for details -->
<string name="toast_fetch_finished_zero">Your schedule has been refreshed!</string>
<string name="toast_fetch_finished_one">Your schedule has been refreshed, but one item could not be saved.</string>
<string name="toast_fetch_finished_other">Your schedule has been refreshed, but %d items could not be saved.</string>
<string name="fetch_bar_close_title">Close</string>
<string name="fetch_bar_close_desc">Close the website without saving anything</string>
<string name="fetch_bar_refresh_title">Refresh</string>
@@ -138,8 +143,6 @@
<string name="nav_food">Canteens</string>
<string name="nav_settings">Settings</string>
<string name="toast_fetch_finished">Your schedule has been refreshed!</string>
<string name="settings_general">General</string>
<string name="settings_schedule">Stud.IP Schedule</string>
<string name="settings_canteen">Canteen Offers</string>