rework initial loading, don't crash on login timeout on app start
closes #25
This commit is contained in:
		@ -29,16 +29,25 @@ import android.view.MenuItem
 | 
			
		||||
import androidx.appcompat.app.AppCompatActivity
 | 
			
		||||
import androidx.fragment.app.Fragment
 | 
			
		||||
import androidx.fragment.app.commit
 | 
			
		||||
import com.afollestad.materialdialogs.MaterialDialog
 | 
			
		||||
import com.afollestad.materialdialogs.callbacks.onDismiss
 | 
			
		||||
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.parser.AoDParser
 | 
			
		||||
import org.mosad.teapod.player.PlayerActivity
 | 
			
		||||
import org.mosad.teapod.preferences.EncryptedPreferences
 | 
			
		||||
import org.mosad.teapod.preferences.Preferences
 | 
			
		||||
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.StorageController
 | 
			
		||||
import java.net.SocketTimeoutException
 | 
			
		||||
import kotlin.system.exitProcess
 | 
			
		||||
import kotlin.system.measureTimeMillis
 | 
			
		||||
 | 
			
		||||
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() {
 | 
			
		||||
        // running login and list in parallel does not bring any speed improvements
 | 
			
		||||
        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)
 | 
			
		||||
            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)
 | 
			
		||||
            StorageController.load(this)
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                // make sure credentials are set, run's async
 | 
			
		||||
                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)
 | 
			
		||||
            AoDParser.initialLoading()
 | 
			
		||||
 | 
			
		||||
            wasInitialized = true
 | 
			
		||||
            runBlocking { loadingJob.joinAll() } // wait for initial loading to finish
 | 
			
		||||
        }
 | 
			
		||||
        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) {
 | 
			
		||||
@ -186,5 +212,12 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
 | 
			
		||||
        startActivity(restartIntent)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * exit and remove the app from tasks
 | 
			
		||||
     */
 | 
			
		||||
    fun exitAndRemoveTask() {
 | 
			
		||||
        this.finishAndRemoveTask()
 | 
			
		||||
        exitProcess(0)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -78,7 +78,7 @@ object AoDParser {
 | 
			
		||||
 | 
			
		||||
            val resLogin = Jsoup.connect(baseUrl + loginPath)
 | 
			
		||||
                .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)
 | 
			
		||||
                .postDataCharset("UTF-8")
 | 
			
		||||
                .cookies(authCookies)
 | 
			
		||||
@ -96,20 +96,11 @@ object AoDParser {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * initially load all media and home screen data
 | 
			
		||||
     * -> blocking
 | 
			
		||||
     */
 | 
			
		||||
    fun initialLoading() = runBlocking {
 | 
			
		||||
        val loadHomeJob = GlobalScope.async {
 | 
			
		||||
            loadHome()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val listJob = GlobalScope.async {
 | 
			
		||||
    fun initialLoading() = listOf(
 | 
			
		||||
            loadHome(),
 | 
			
		||||
            listAnimes()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        loadHomeJob.await()
 | 
			
		||||
        listJob.await()
 | 
			
		||||
    }
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * get a media by it's ID (int)
 | 
			
		||||
@ -134,7 +125,7 @@ object AoDParser {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 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(
 | 
			
		||||
            Pair("Accept", "application/json, text/javascript, */*; q=0.01"),
 | 
			
		||||
            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
 | 
			
		||||
     */
 | 
			
		||||
    private fun listAnimes()  = runBlocking {
 | 
			
		||||
        if (sessionCookies.isEmpty()) login()
 | 
			
		||||
    private fun listAnimes() = GlobalScope.launch(Dispatchers.IO) {
 | 
			
		||||
        val resAnimes = Jsoup.connect(baseUrl + libraryPath).get()
 | 
			
		||||
        //println(resAnimes)
 | 
			
		||||
 | 
			
		||||
        withContext(Dispatchers.Default) {
 | 
			
		||||
            val resAnimes = Jsoup.connect(baseUrl + libraryPath)
 | 
			
		||||
                .cookies(sessionCookies)
 | 
			
		||||
                .get()
 | 
			
		||||
 | 
			
		||||
            //println(resAnimes)
 | 
			
		||||
 | 
			
		||||
            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
 | 
			
		||||
                })
 | 
			
		||||
        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()
 | 
			
		||||
 | 
			
		||||
            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
 | 
			
		||||
     */
 | 
			
		||||
    private fun loadHome() = runBlocking {
 | 
			
		||||
        if (sessionCookies.isEmpty()) login()
 | 
			
		||||
    private fun loadHome() = GlobalScope.launch(Dispatchers.IO) {
 | 
			
		||||
         val resHome = Jsoup.connect(baseUrl).get()
 | 
			
		||||
 | 
			
		||||
        withContext(Dispatchers.Default) {
 | 
			
		||||
            val resHome = Jsoup.connect(baseUrl)
 | 
			
		||||
                .cookies(sessionCookies)
 | 
			
		||||
                .get()
 | 
			
		||||
        // get highlights from AoD
 | 
			
		||||
        highlightsList.clear()
 | 
			
		||||
        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")
 | 
			
		||||
 | 
			
		||||
            // get highlights from AoD
 | 
			
		||||
            highlightsList.clear()
 | 
			
		||||
            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))
 | 
			
		||||
                }
 | 
			
		||||
            if (mediaId != null) {
 | 
			
		||||
                highlightsList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            // get all new episodes from AoD
 | 
			
		||||
            newEpisodesList.clear()
 | 
			
		||||
            resHome.select("h2:contains(Neue Episoden)").next().select("li").forEach {
 | 
			
		||||
                val mediaId = it.select("a.thumbs").attr("href")
 | 
			
		||||
                    .substringAfterLast("/").toIntOrNull()
 | 
			
		||||
                val mediaImage = it.select("a.thumbs > img").attr("src")
 | 
			
		||||
                val mediaTitle = "${it.select("a").text()} - ${it.select("span.neweps").text()}"
 | 
			
		||||
        // get all new episodes from AoD
 | 
			
		||||
        newEpisodesList.clear()
 | 
			
		||||
        resHome.select("h2:contains(Neue Episoden)").next().select("li").forEach {
 | 
			
		||||
            val mediaId = it.select("a.thumbs").attr("href")
 | 
			
		||||
                .substringAfterLast("/").toIntOrNull()
 | 
			
		||||
            val mediaImage = it.select("a.thumbs > img").attr("src")
 | 
			
		||||
            val mediaTitle = "${it.select("a").text()} - ${it.select("span.neweps").text()}"
 | 
			
		||||
 | 
			
		||||
                if (mediaId != null) {
 | 
			
		||||
                    newEpisodesList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
 | 
			
		||||
                }
 | 
			
		||||
            if (mediaId != null) {
 | 
			
		||||
                newEpisodesList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            // get new simulcasts from AoD
 | 
			
		||||
            newSimulcastsList.clear()
 | 
			
		||||
            resHome.select("h2:contains(Neue Simulcasts)").next().select("li").forEach {
 | 
			
		||||
                val mediaId = it.select("a.thumbs").attr("href")
 | 
			
		||||
                    .substringAfterLast("/").toIntOrNull()
 | 
			
		||||
                val mediaImage = it.select("a.thumbs > img").attr("src")
 | 
			
		||||
                val mediaTitle = it.select("a").text()
 | 
			
		||||
        // get new simulcasts from AoD
 | 
			
		||||
        newSimulcastsList.clear()
 | 
			
		||||
        resHome.select("h2:contains(Neue Simulcasts)").next().select("li").forEach {
 | 
			
		||||
            val mediaId = it.select("a.thumbs").attr("href")
 | 
			
		||||
                .substringAfterLast("/").toIntOrNull()
 | 
			
		||||
            val mediaImage = it.select("a.thumbs > img").attr("src")
 | 
			
		||||
            val mediaTitle = it.select("a").text()
 | 
			
		||||
 | 
			
		||||
                if (mediaId != null) {
 | 
			
		||||
                    newSimulcastsList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
 | 
			
		||||
                }
 | 
			
		||||
            if (mediaId != null) {
 | 
			
		||||
                newSimulcastsList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            // get new titles from AoD
 | 
			
		||||
            newTitlesList.clear()
 | 
			
		||||
            resHome.select("h2:contains(Neue Anime-Titel)").next().select("li").forEach {
 | 
			
		||||
                val mediaId = it.select("a.thumbs").attr("href")
 | 
			
		||||
                    .substringAfterLast("/").toIntOrNull()
 | 
			
		||||
                val mediaImage = it.select("a.thumbs > img").attr("src")
 | 
			
		||||
                val mediaTitle = it.select("a").text()
 | 
			
		||||
        // get new titles from AoD
 | 
			
		||||
        newTitlesList.clear()
 | 
			
		||||
        resHome.select("h2:contains(Neue Anime-Titel)").next().select("li").forEach {
 | 
			
		||||
            val mediaId = it.select("a.thumbs").attr("href")
 | 
			
		||||
                .substringAfterLast("/").toIntOrNull()
 | 
			
		||||
            val mediaImage = it.select("a.thumbs > img").attr("src")
 | 
			
		||||
            val mediaTitle = it.select("a").text()
 | 
			
		||||
 | 
			
		||||
                if (mediaId != null) {
 | 
			
		||||
                    newTitlesList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
 | 
			
		||||
                }
 | 
			
		||||
            if (mediaId != null) {
 | 
			
		||||
                newTitlesList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            // if highlights is empty, add a random new title
 | 
			
		||||
            if (highlightsList.isEmpty()) {
 | 
			
		||||
                if (newTitlesList.isNotEmpty()) {
 | 
			
		||||
                    highlightsList.add(newTitlesList[Random.nextInt(0, newTitlesList.size)])
 | 
			
		||||
                } else {
 | 
			
		||||
                    highlightsList.add(ItemMedia(0,"", ""))
 | 
			
		||||
                }
 | 
			
		||||
        // if highlights is empty, add a random new title
 | 
			
		||||
        if (highlightsList.isEmpty()) {
 | 
			
		||||
            if (newTitlesList.isNotEmpty()) {
 | 
			
		||||
                highlightsList.add(newTitlesList[Random.nextInt(0, newTitlesList.size)])
 | 
			
		||||
            } else {
 | 
			
		||||
                highlightsList.add(ItemMedia(0,"", ""))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -272,7 +250,7 @@ object AoDParser {
 | 
			
		||||
     * load streams for the media path, movies have one episode
 | 
			
		||||
     * @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 (!loginSuccess) {
 | 
			
		||||
@ -363,6 +341,7 @@ object AoDParser {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Log.i(javaClass.name, "media loaded successfully")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -59,6 +59,8 @@
 | 
			
		||||
    <!-- dialogs -->
 | 
			
		||||
    <string name="save">speichern</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 -->
 | 
			
		||||
    <string name="login">Login</string>
 | 
			
		||||
 | 
			
		||||
@ -75,6 +75,8 @@
 | 
			
		||||
    <!-- dialogs -->
 | 
			
		||||
    <string name="save">save</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 -->
 | 
			
		||||
    <string name="login">Login</string>
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user