rework initial loading, don't crash on login timeout on app start

closes #25
This commit is contained in:
Jannik 2021-01-13 20:57:00 +01:00
parent 7dc120ccfe
commit 3f45d769d2
Signed by: Seil0
GPG Key ID: E8459F3723C52C24
4 changed files with 129 additions and 113 deletions

View File

@ -29,16 +29,25 @@ import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.commit import androidx.fragment.app.commit
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.callbacks.onDismiss
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.runBlocking
import org.mosad.teapod.databinding.ActivityMainBinding import org.mosad.teapod.databinding.ActivityMainBinding
import org.mosad.teapod.parser.AoDParser import org.mosad.teapod.parser.AoDParser
import org.mosad.teapod.player.PlayerActivity import org.mosad.teapod.player.PlayerActivity
import org.mosad.teapod.preferences.EncryptedPreferences import org.mosad.teapod.preferences.EncryptedPreferences
import org.mosad.teapod.preferences.Preferences import org.mosad.teapod.preferences.Preferences
import org.mosad.teapod.ui.components.LoginDialog import org.mosad.teapod.ui.components.LoginDialog
import org.mosad.teapod.ui.fragments.* import org.mosad.teapod.ui.fragments.AccountFragment
import org.mosad.teapod.ui.fragments.HomeFragment
import org.mosad.teapod.ui.fragments.LibraryFragment
import org.mosad.teapod.ui.fragments.SearchFragment
import org.mosad.teapod.util.DataTypes import org.mosad.teapod.util.DataTypes
import org.mosad.teapod.util.StorageController import org.mosad.teapod.util.StorageController
import java.net.SocketTimeoutException
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemSelectedListener { class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemSelectedListener {
@ -116,26 +125,43 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
} }
} }
/**
* initial loading and login are run in parallel, as initial loading doesn't require
* any login cookies
*/
private fun load() { private fun load() {
// running login and list in parallel does not bring any speed improvements
val time = measureTimeMillis { val time = measureTimeMillis {
Preferences.load(this) val loadingJob = AoDParser.initialLoading() // start the initial loading
// make sure credentials are set, run's async // load all saved stuff here
Preferences.load(this)
EncryptedPreferences.readCredentials(this) EncryptedPreferences.readCredentials(this)
if (EncryptedPreferences.password.isEmpty()) { StorageController.load(this)
showLoginDialog(true)
} else { try {
// try to login in, as most sites can only bee loaded once loged in // make sure credentials are set, run's async
if (!AoDParser.login()) showLoginDialog(false) if (EncryptedPreferences.password.isEmpty()) {
showLoginDialog(true)
} else {
// try to login in, as most sites can only bee loaded once loged in
if (!AoDParser.login()) showLoginDialog(false)
}
} catch (ex: SocketTimeoutException) {
Log.w(javaClass.name, "Timeout during login!")
// show waring dialog before finishing
MaterialDialog(this).show {
title(R.string.dialog_timeout_head)
message(R.string.dialog_timeout_desc)
onDismiss { exitAndRemoveTask() }
}
} }
StorageController.load(this) runBlocking { loadingJob.joinAll() } // wait for initial loading to finish
AoDParser.initialLoading()
wasInitialized = true
} }
Log.i(javaClass.name, "login and list in $time ms") Log.i(javaClass.name, "loading and login in $time ms")
wasInitialized = true
} }
private fun showLoginDialog(firstTry: Boolean) { private fun showLoginDialog(firstTry: Boolean) {
@ -186,5 +212,12 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
startActivity(restartIntent) startActivity(restartIntent)
} }
/**
* exit and remove the app from tasks
*/
fun exitAndRemoveTask() {
this.finishAndRemoveTask()
exitProcess(0)
}
} }

View File

@ -78,7 +78,7 @@ object AoDParser {
val resLogin = Jsoup.connect(baseUrl + loginPath) val resLogin = Jsoup.connect(baseUrl + loginPath)
.method(Connection.Method.POST) .method(Connection.Method.POST)
.timeout(60000) // login can take some time .timeout(60000) // login can take some time default is 60000 (60 sec)
.data(data) .data(data)
.postDataCharset("UTF-8") .postDataCharset("UTF-8")
.cookies(authCookies) .cookies(authCookies)
@ -96,20 +96,11 @@ object AoDParser {
/** /**
* initially load all media and home screen data * initially load all media and home screen data
* -> blocking
*/ */
fun initialLoading() = runBlocking { fun initialLoading() = listOf(
val loadHomeJob = GlobalScope.async { loadHome(),
loadHome()
}
val listJob = GlobalScope.async {
listAnimes() listAnimes()
} )
loadHomeJob.await()
listJob.await()
}
/** /**
* get a media by it's ID (int) * get a media by it's ID (int)
@ -134,7 +125,7 @@ object AoDParser {
} }
// TODO don't use jsoup here // TODO don't use jsoup here
fun sendCallback(callbackPath: String) = GlobalScope.launch(Dispatchers.IO) { private fun sendCallback(callbackPath: String) = GlobalScope.launch(Dispatchers.IO) {
val headers = mutableMapOf( val headers = mutableMapOf(
Pair("Accept", "application/json, text/javascript, */*; q=0.01"), Pair("Accept", "application/json, text/javascript, */*; q=0.01"),
Pair("Accept-Language", "de,en-US;q=0.7,en;q=0.3"), Pair("Accept-Language", "de,en-US;q=0.7,en;q=0.3"),
@ -158,112 +149,99 @@ object AoDParser {
/** /**
* load all media from aod into itemMediaList and mediaList * load all media from aod into itemMediaList and mediaList
*/ */
private fun listAnimes() = runBlocking { private fun listAnimes() = GlobalScope.launch(Dispatchers.IO) {
if (sessionCookies.isEmpty()) login() val resAnimes = Jsoup.connect(baseUrl + libraryPath).get()
//println(resAnimes)
withContext(Dispatchers.Default) { itemMediaList.clear()
val resAnimes = Jsoup.connect(baseUrl + libraryPath) mediaList.clear()
.cookies(sessionCookies) resAnimes.select("div.animebox").forEach {
.get() val type = if (it.select("p.animebox-link").select("a").text().toLowerCase(Locale.ROOT) == "zur serie") {
MediaType.TVSHOW
//println(resAnimes) } else {
MediaType.MOVIE
itemMediaList.clear()
mediaList.clear()
resAnimes.select("div.animebox").forEach {
val type = if (it.select("p.animebox-link").select("a").text().toLowerCase(Locale.ROOT) == "zur serie") {
MediaType.TVSHOW
} else {
MediaType.MOVIE
}
val mediaTitle = it.select("h3.animebox-title").text()
val mediaLink = it.select("p.animebox-link").select("a").attr("href")
val mediaImage = it.select("p.animebox-image").select("img").attr("src")
val mediaShortText = it.select("p.animebox-shorttext").text()
val mediaId = mediaLink.substringAfterLast("/").toInt()
itemMediaList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
mediaList.add(Media(mediaId, mediaLink, type).apply {
info.title = mediaTitle
info.posterUrl = mediaImage
info.shortDesc = mediaShortText
})
} }
val mediaTitle = it.select("h3.animebox-title").text()
val mediaLink = it.select("p.animebox-link").select("a").attr("href")
val mediaImage = it.select("p.animebox-image").select("img").attr("src")
val mediaShortText = it.select("p.animebox-shorttext").text()
val mediaId = mediaLink.substringAfterLast("/").toInt()
Log.i(javaClass.name, "Total library size is: ${mediaList.size}") itemMediaList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
mediaList.add(Media(mediaId, mediaLink, type).apply {
info.title = mediaTitle
info.posterUrl = mediaImage
info.shortDesc = mediaShortText
})
} }
Log.i(javaClass.name, "Total library size is: ${mediaList.size}")
} }
/** /**
* load new episodes, titles and highlights * load new episodes, titles and highlights
*/ */
private fun loadHome() = runBlocking { private fun loadHome() = GlobalScope.launch(Dispatchers.IO) {
if (sessionCookies.isEmpty()) login() val resHome = Jsoup.connect(baseUrl).get()
withContext(Dispatchers.Default) { // get highlights from AoD
val resHome = Jsoup.connect(baseUrl) highlightsList.clear()
.cookies(sessionCookies) resHome.select("#aod-highlights").select("div.news-item").forEach {
.get() val mediaId = it.select("div.news-item-text").select("a.serienlink")
.attr("href").substringAfterLast("/").toIntOrNull()
val mediaTitle = it.select("div.news-title").select("h2").text()
val mediaImage = it.select("img").attr("src")
// get highlights from AoD if (mediaId != null) {
highlightsList.clear() highlightsList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
resHome.select("#aod-highlights").select("div.news-item").forEach {
val mediaId = it.select("div.news-item-text").select("a.serienlink")
.attr("href").substringAfterLast("/").toIntOrNull()
val mediaTitle = it.select("div.news-title").select("h2").text()
val mediaImage = it.select("img").attr("src")
if (mediaId != null) {
highlightsList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
}
} }
}
// get all new episodes from AoD // get all new episodes from AoD
newEpisodesList.clear() newEpisodesList.clear()
resHome.select("h2:contains(Neue Episoden)").next().select("li").forEach { resHome.select("h2:contains(Neue Episoden)").next().select("li").forEach {
val mediaId = it.select("a.thumbs").attr("href") val mediaId = it.select("a.thumbs").attr("href")
.substringAfterLast("/").toIntOrNull() .substringAfterLast("/").toIntOrNull()
val mediaImage = it.select("a.thumbs > img").attr("src") val mediaImage = it.select("a.thumbs > img").attr("src")
val mediaTitle = "${it.select("a").text()} - ${it.select("span.neweps").text()}" val mediaTitle = "${it.select("a").text()} - ${it.select("span.neweps").text()}"
if (mediaId != null) { if (mediaId != null) {
newEpisodesList.add(ItemMedia(mediaId, mediaTitle, mediaImage)) newEpisodesList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
}
} }
}
// get new simulcasts from AoD // get new simulcasts from AoD
newSimulcastsList.clear() newSimulcastsList.clear()
resHome.select("h2:contains(Neue Simulcasts)").next().select("li").forEach { resHome.select("h2:contains(Neue Simulcasts)").next().select("li").forEach {
val mediaId = it.select("a.thumbs").attr("href") val mediaId = it.select("a.thumbs").attr("href")
.substringAfterLast("/").toIntOrNull() .substringAfterLast("/").toIntOrNull()
val mediaImage = it.select("a.thumbs > img").attr("src") val mediaImage = it.select("a.thumbs > img").attr("src")
val mediaTitle = it.select("a").text() val mediaTitle = it.select("a").text()
if (mediaId != null) { if (mediaId != null) {
newSimulcastsList.add(ItemMedia(mediaId, mediaTitle, mediaImage)) newSimulcastsList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
}
} }
}
// get new titles from AoD // get new titles from AoD
newTitlesList.clear() newTitlesList.clear()
resHome.select("h2:contains(Neue Anime-Titel)").next().select("li").forEach { resHome.select("h2:contains(Neue Anime-Titel)").next().select("li").forEach {
val mediaId = it.select("a.thumbs").attr("href") val mediaId = it.select("a.thumbs").attr("href")
.substringAfterLast("/").toIntOrNull() .substringAfterLast("/").toIntOrNull()
val mediaImage = it.select("a.thumbs > img").attr("src") val mediaImage = it.select("a.thumbs > img").attr("src")
val mediaTitle = it.select("a").text() val mediaTitle = it.select("a").text()
if (mediaId != null) { if (mediaId != null) {
newTitlesList.add(ItemMedia(mediaId, mediaTitle, mediaImage)) newTitlesList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
}
} }
}
// if highlights is empty, add a random new title // if highlights is empty, add a random new title
if (highlightsList.isEmpty()) { if (highlightsList.isEmpty()) {
if (newTitlesList.isNotEmpty()) { if (newTitlesList.isNotEmpty()) {
highlightsList.add(newTitlesList[Random.nextInt(0, newTitlesList.size)]) highlightsList.add(newTitlesList[Random.nextInt(0, newTitlesList.size)])
} else { } else {
highlightsList.add(ItemMedia(0,"", "")) highlightsList.add(ItemMedia(0,"", ""))
}
} }
} }
} }
@ -272,7 +250,7 @@ object AoDParser {
* load streams for the media path, movies have one episode * load streams for the media path, movies have one episode
* @param media is used as call ba reference * @param media is used as call ba reference
*/ */
private suspend fun loadStreams(media: Media) = GlobalScope.launch(Dispatchers.IO) { private fun loadStreams(media: Media) = GlobalScope.launch(Dispatchers.IO) {
if (sessionCookies.isEmpty()) login() if (sessionCookies.isEmpty()) login()
if (!loginSuccess) { if (!loginSuccess) {
@ -363,6 +341,7 @@ object AoDParser {
} }
} }
} }
Log.i(javaClass.name, "media loaded successfully")
} }
/** /**

View File

@ -59,6 +59,8 @@
<!-- dialogs --> <!-- dialogs -->
<string name="save">speichern</string> <string name="save">speichern</string>
<string name="cancel">@android:string/cancel</string> <string name="cancel">@android:string/cancel</string>
<string name="dialog_timeout_head">Anmelden fehlgeschlagen</string>
<string name="dialog_timeout_desc">Der Server scheint langsam zu antworten. Bitte versuche es später noch einmal.</string>
<!-- etc --> <!-- etc -->
<string name="login">Login</string> <string name="login">Login</string>

View File

@ -75,6 +75,8 @@
<!-- dialogs --> <!-- dialogs -->
<string name="save">save</string> <string name="save">save</string>
<string name="cancel">@android:string/cancel</string> <string name="cancel">@android:string/cancel</string>
<string name="dialog_timeout_head">Login failed</string>
<string name="dialog_timeout_desc">Looks like the server is taking to long to respond. Please try again later.</string>
<!-- etc --> <!-- etc -->
<string name="login">Login</string> <string name="login">Login</string>