Freezing Fixes, Database Adding in Background.

This commit is contained in:
shabinder 2021-05-04 13:29:53 +05:30
parent d8415b52ca
commit 7a3a299aa2
13 changed files with 124 additions and 44 deletions

View File

@ -17,6 +17,7 @@
package com.shabinder.common.di package com.shabinder.common.di
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import co.touchlab.stately.ensureNeverFrozen
import com.shabinder.common.database.databaseModule import com.shabinder.common.database.databaseModule
import com.shabinder.common.database.getLogger import com.shabinder.common.database.getLogger
import com.shabinder.common.di.providers.GaanaProvider import com.shabinder.common.di.providers.GaanaProvider
@ -37,6 +38,7 @@ import org.koin.core.context.startKoin
import org.koin.dsl.KoinAppDeclaration import org.koin.dsl.KoinAppDeclaration
import org.koin.dsl.module import org.koin.dsl.module
import kotlin.native.concurrent.SharedImmutable import kotlin.native.concurrent.SharedImmutable
import kotlin.native.concurrent.ThreadLocal
fun initKoin(enableNetworkLogs: Boolean = false, appDeclaration: KoinAppDeclaration = {}) = fun initKoin(enableNetworkLogs: Boolean = false, appDeclaration: KoinAppDeclaration = {}) =
startKoin { startKoin {
@ -60,7 +62,7 @@ fun commonModule(enableNetworkLogs: Boolean) = module {
single { FetchPlatformQueryResult(get(), get(), get(), get(), get(), get()) } single { FetchPlatformQueryResult(get(), get(), get(), get(), get(), get()) }
} }
@SharedImmutable @ThreadLocal
val kotlinxSerializer = KotlinxSerializer( val kotlinxSerializer = KotlinxSerializer(
Json { Json {
isLenient = true isLenient = true
@ -68,10 +70,16 @@ val kotlinxSerializer = KotlinxSerializer(
} }
) )
fun createHttpClient(enableNetworkLogs: Boolean = false, serializer: KotlinxSerializer = kotlinxSerializer) = HttpClient { fun createHttpClient(enableNetworkLogs: Boolean = false) = HttpClient {
install(JsonFeature) { // https://github.com/Kotlin/kotlinx.serialization/issues/1450
this.serializer = serializer /*install(JsonFeature) {
serializer = KotlinxSerializer(
Json {
isLenient = true
ignoreUnknownKeys = true
} }
)
}*/
// Timeout // Timeout
install(HttpTimeout) { install(HttpTimeout) {
requestTimeoutMillis = 15000L requestTimeoutMillis = 15000L

View File

@ -43,7 +43,7 @@ expect val currentPlatform: AllPlatforms
suspend fun isInternetAccessible(): Boolean { suspend fun isInternetAccessible(): Boolean {
return withContext(dispatcherIO) { return withContext(dispatcherIO) {
try { try {
ktorHttpClient.head<String>("http://google.com") ktorHttpClient.head<String>("https://google.com")
true true
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()

View File

@ -23,6 +23,10 @@ import com.shabinder.common.di.providers.YoutubeMp3
import com.shabinder.common.di.providers.YoutubeMusic import com.shabinder.common.di.providers.YoutubeMusic
import com.shabinder.common.models.PlatformQueryResult import com.shabinder.common.models.PlatformQueryResult
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class FetchPlatformQueryResult( class FetchPlatformQueryResult(
@ -54,13 +58,19 @@ class FetchPlatformQueryResult(
null null
} }
} }
result?.run { if (result != null) {
withContext(Dispatchers.Default) { addToDatabaseAsync(
db?.add( link,
folderType, title, link, coverUrl, trackList.size.toLong() result.copy() // Send a copy in order to not to freeze Result itself
) )
} }
}
return result return result
} }
private fun addToDatabaseAsync(link: String, result: PlatformQueryResult) {
GlobalScope.launch(dispatcherIO) {
db?.add(
result.folderType, result.title, link, result.coverUrl, result.trackList.size.toLong()
)
}
}
} }

View File

@ -17,6 +17,7 @@
package com.shabinder.common.di.gaana package com.shabinder.common.di.gaana
import com.shabinder.common.di.currentPlatform import com.shabinder.common.di.currentPlatform
import com.shabinder.common.di.utils.getData
import com.shabinder.common.models.AllPlatforms import com.shabinder.common.models.AllPlatforms
import com.shabinder.common.models.corsProxy import com.shabinder.common.models.corsProxy
import com.shabinder.common.models.gaana.GaanaAlbum import com.shabinder.common.models.gaana.GaanaAlbum
@ -52,7 +53,7 @@ interface GaanaRequests {
format: String = "JSON", format: String = "JSON",
limit: Int = 2000 limit: Int = 2000
): GaanaPlaylist { ): GaanaPlaylist {
return httpClient.get( return httpClient.getData(
"$BASE_URL/?type=$type&subtype=$subtype&seokey=$seokey&token=$TOKEN&format=$format&limit=$limit" "$BASE_URL/?type=$type&subtype=$subtype&seokey=$seokey&token=$TOKEN&format=$format&limit=$limit"
) )
} }
@ -69,7 +70,7 @@ interface GaanaRequests {
format: String = "JSON", format: String = "JSON",
limit: Int = 2000 limit: Int = 2000
): GaanaAlbum { ): GaanaAlbum {
return httpClient.get( return httpClient.getData(
"$BASE_URL/?type=$type&subtype=$subtype&seokey=$seokey&token=$TOKEN&format=$format&limit=$limit" "$BASE_URL/?type=$type&subtype=$subtype&seokey=$seokey&token=$TOKEN&format=$format&limit=$limit"
) )
} }
@ -85,7 +86,7 @@ interface GaanaRequests {
seokey: String, seokey: String,
format: String = "JSON", format: String = "JSON",
): GaanaSong { ): GaanaSong {
return httpClient.get( return httpClient.getData(
"$BASE_URL/?type=$type&subtype=$subtype&seokey=$seokey&token=$TOKEN&format=$format" "$BASE_URL/?type=$type&subtype=$subtype&seokey=$seokey&token=$TOKEN&format=$format"
) )
} }
@ -101,7 +102,7 @@ interface GaanaRequests {
seokey: String, seokey: String,
format: String = "JSON", format: String = "JSON",
): GaanaArtistDetails { ): GaanaArtistDetails {
return httpClient.get( return httpClient.getData(
"$BASE_URL/?type=$type&subtype=$subtype&seokey=$seokey&token=$TOKEN&format=$format" "$BASE_URL/?type=$type&subtype=$subtype&seokey=$seokey&token=$TOKEN&format=$format"
) )
} }
@ -118,7 +119,7 @@ interface GaanaRequests {
format: String = "JSON", format: String = "JSON",
limit: Int = 50 limit: Int = 50
): GaanaArtistTracks { ): GaanaArtistTracks {
return httpClient.get( return httpClient.getData(
"$BASE_URL/?type=$type&subtype=$subtype&seokey=$seokey&token=$TOKEN&format=$format&limit=$limit" "$BASE_URL/?type=$type&subtype=$subtype&seokey=$seokey&token=$TOKEN&format=$format&limit=$limit"
) )
} }

View File

@ -79,7 +79,7 @@ class GaanaProvider(
it.updateStatusIfPresent(folderType, subFolder) it.updateStatusIfPresent(folderType, subFolder)
trackList = listOf(it).toTrackDetailsList(folderType, subFolder) trackList = listOf(it).toTrackDetailsList(folderType, subFolder)
title = it.track_title title = it.track_title
coverUrl = it.artworkLink coverUrl = it.artworkLink.replace("http:","https:")
} }
} }
"album" -> { "album" -> {
@ -91,7 +91,7 @@ class GaanaProvider(
} }
trackList = it.tracks?.toTrackDetailsList(folderType, subFolder) ?: emptyList() trackList = it.tracks?.toTrackDetailsList(folderType, subFolder) ?: emptyList()
title = link title = link
coverUrl = it.custom_artworks.size_480p coverUrl = it.custom_artworks.size_480p.replace("http:","https:")
} }
} }
"playlist" -> { "playlist" -> {
@ -114,7 +114,7 @@ class GaanaProvider(
getGaanaArtistDetails(seokey = link).artist.firstOrNull() getGaanaArtistDetails(seokey = link).artist.firstOrNull()
?.also { ?.also {
title = it.name title = it.name
coverUrl = it.artworkLink ?: gaanaPlaceholderImageUrl coverUrl = it.artworkLink?.replace("http:","https:") ?: gaanaPlaceholderImageUrl
} }
getGaanaArtistTracks(seokey = link).also { getGaanaArtistTracks(seokey = link).also {
it.tracks?.forEach { track -> it.tracks?.forEach { track ->
@ -143,7 +143,7 @@ class GaanaProvider(
trackUrl = it.lyrics_url, trackUrl = it.lyrics_url,
downloaded = it.downloaded ?: DownloadStatus.NotDownloaded, downloaded = it.downloaded ?: DownloadStatus.NotDownloaded,
source = Source.Gaana, source = Source.Gaana,
albumArtURL = it.artworkLink, albumArtURL = it.artworkLink.replace("http:","https:"),
outputFilePath = dir.finalOutputDir(it.track_title, type, subFolder, dir.defaultDir()/*,".m4a"*/) outputFilePath = dir.finalOutputDir(it.track_title, type, subFolder, dir.defaultDir()/*,".m4a"*/)
) )
} }

View File

@ -17,9 +17,11 @@
package com.shabinder.common.di.providers package com.shabinder.common.di.providers
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import co.touchlab.stately.ensureNeverFrozen
import co.touchlab.stately.freeze import co.touchlab.stately.freeze
import com.shabinder.common.di.Dir import com.shabinder.common.di.Dir
import com.shabinder.common.di.TokenStore import com.shabinder.common.di.TokenStore
import com.shabinder.common.di.createHttpClient
import com.shabinder.common.di.finalOutputDir import com.shabinder.common.di.finalOutputDir
import com.shabinder.common.di.kotlinxSerializer import com.shabinder.common.di.kotlinxSerializer
import com.shabinder.common.di.ktorHttpClient import com.shabinder.common.di.ktorHttpClient
@ -33,9 +35,12 @@ import com.shabinder.common.models.spotify.Image
import com.shabinder.common.models.spotify.Source import com.shabinder.common.models.spotify.Source
import com.shabinder.common.models.spotify.Track import com.shabinder.common.models.spotify.Track
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.features.auth.*
import io.ktor.client.features.auth.providers.*
import io.ktor.client.features.defaultRequest import io.ktor.client.features.defaultRequest
import io.ktor.client.features.json.JsonFeature import io.ktor.client.features.json.JsonFeature
import io.ktor.client.request.header import io.ktor.client.request.header
import kotlin.native.concurrent.SharedImmutable
class SpotifyProvider( class SpotifyProvider(
private val tokenStore: TokenStore, private val tokenStore: TokenStore,
@ -62,14 +67,14 @@ class SpotifyProvider(
defaultRequest { defaultRequest {
header("Authorization", "Bearer ${token.access_token}") header("Authorization", "Bearer ${token.access_token}")
} }
install(JsonFeature) { /*install(JsonFeature) {
serializer = kotlinxSerializer serializer = kotlinxSerializer
} }*/
}.also { httpClientRef.value = it.freeze() } }.also { httpClientRef.value = it }
} }
} }
override val httpClientRef = NativeAtomicReference(ktorHttpClient) override val httpClientRef = NativeAtomicReference(createHttpClient(true))
suspend fun query(fullLink: String): PlatformQueryResult? { suspend fun query(fullLink: String): PlatformQueryResult? {

View File

@ -18,6 +18,7 @@ package com.shabinder.common.di.providers
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.di.gaana.corsApi import com.shabinder.common.di.gaana.corsApi
import com.shabinder.common.di.utils.postData
import com.shabinder.common.models.TrackDetails import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.YoutubeTrack import com.shabinder.common.models.YoutubeTrack
import com.shabinder.fuzzywuzzy.diffutils.FuzzySearch import com.shabinder.fuzzywuzzy.diffutils.FuzzySearch
@ -283,7 +284,7 @@ class YoutubeMusic constructor(
} }
private suspend fun getYoutubeMusicResponse(query: String): String { private suspend fun getYoutubeMusicResponse(query: String): String {
return httpClient.post("${corsApi}https://music.youtube.com/youtubei/v1/search?alt=json&key=$apiKey") { return httpClient.postData("${corsApi}https://music.youtube.com/youtubei/v1/search?alt=json&key=$apiKey") {
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
headers { headers {
append("referer", "https://music.youtube.com/search") append("referer", "https://music.youtube.com/search")

View File

@ -17,25 +17,33 @@
package com.shabinder.common.di.spotify package com.shabinder.common.di.spotify
import com.shabinder.common.di.gaana.corsApi import com.shabinder.common.di.gaana.corsApi
import com.shabinder.common.di.utils.getData
import com.shabinder.common.models.NativeAtomicReference import com.shabinder.common.models.NativeAtomicReference
import com.shabinder.common.models.spotify.Album import com.shabinder.common.models.spotify.Album
import com.shabinder.common.models.spotify.PagingObjectPlaylistTrack import com.shabinder.common.models.spotify.PagingObjectPlaylistTrack
import com.shabinder.common.models.spotify.Playlist import com.shabinder.common.models.spotify.Playlist
import com.shabinder.common.models.spotify.Track import com.shabinder.common.models.spotify.Track
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.request.get import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.descriptors.ClassSerialDescriptorBuilder
import kotlinx.serialization.json.Json
import kotlinx.serialization.serializer
private val BASE_URL get() = "${corsApi}https://api.spotify.com/v1" private val BASE_URL get() = "${corsApi}https://api.spotify.com/v1"
interface SpotifyRequests { interface SpotifyRequests {
val httpClientRef: NativeAtomicReference<HttpClient> val httpClientRef: NativeAtomicReference<HttpClient>
val httpClient get() = httpClientRef.value val httpClient:HttpClient get() = httpClientRef.value
suspend fun authenticateSpotifyClient(override: Boolean = false) suspend fun authenticateSpotifyClient(override: Boolean = false)
suspend fun getPlaylist(playlistID: String): Playlist { suspend fun getPlaylist(playlistID: String): Playlist {
return httpClient.get("$BASE_URL/playlists/$playlistID") return httpClient.getData("$BASE_URL/playlists/$playlistID")
} }
suspend fun getPlaylistTracks( suspend fun getPlaylistTracks(
@ -43,26 +51,26 @@ interface SpotifyRequests {
offset: Int = 0, offset: Int = 0,
limit: Int = 100 limit: Int = 100
): PagingObjectPlaylistTrack { ): PagingObjectPlaylistTrack {
return httpClient.get("$BASE_URL/playlists/$playlistID/tracks?offset=$offset&limit=$limit") return httpClient.getData("$BASE_URL/playlists/$playlistID/tracks?offset=$offset&limit=$limit")
} }
suspend fun getTrack(id: String?): Track { suspend fun getTrack(id: String?): Track {
return httpClient.get("$BASE_URL/tracks/$id") return httpClient.getData("$BASE_URL/tracks/$id")
} }
suspend fun getEpisode(id: String?): Track { suspend fun getEpisode(id: String?): Track {
return httpClient.get("$BASE_URL/episodes/$id") return httpClient.getData("$BASE_URL/episodes/$id")
} }
suspend fun getShow(id: String?): Track { suspend fun getShow(id: String?): Track {
return httpClient.get("$BASE_URL/shows/$id") return httpClient.getData("$BASE_URL/shows/$id")
} }
suspend fun getAlbum(id: String): Album { suspend fun getAlbum(id: String): Album {
return httpClient.get("$BASE_URL/albums/$id") return httpClient.getData("$BASE_URL/albums/$id")
} }
suspend fun getResponse(url: String): String { suspend fun getResponse(url: String): String {
return httpClient.get(url) return httpClient.getData(url)
} }
} }

View File

@ -16,6 +16,51 @@
package com.shabinder.common.di.utils package com.shabinder.common.di.utils
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.serializer
import kotlin.native.concurrent.SharedImmutable
import kotlin.native.concurrent.ThreadLocal
/*
* WorkAround: https://github.com/Kotlin/kotlinx.serialization/issues/1450
* */
@OptIn(InternalSerializationApi::class)
suspend inline fun <reified T: Any> HttpClient.getData(
urlString: String,
block: HttpRequestBuilder.() -> Unit = {}
): T {
val response = get<HttpResponse> {
url.takeFrom(urlString)
block()
}
val jsonBody = response.readText()
return json.decodeFromString(T::class.serializer(),jsonBody)
}
@OptIn(InternalSerializationApi::class)
suspend inline fun <reified T: Any> HttpClient.postData(
urlString: String,
block: HttpRequestBuilder.() -> Unit = {}
): T {
val response = post<HttpResponse> {
url.takeFrom(urlString)
block()
}
val jsonBody = response.readText()
return json.decodeFromString(T::class.serializer(),jsonBody)
}
@ThreadLocal
val json by lazy { Json {
isLenient = true
ignoreUnknownKeys = true
} }
/** /**
* Removing Illegal Chars from File Name * Removing Illegal Chars from File Name
* **/ * **/

View File

@ -18,6 +18,7 @@ package com.shabinder.common.di.youtubeMp3
import co.touchlab.kermit.Kermit import co.touchlab.kermit.Kermit
import com.shabinder.common.di.gaana.corsApi import com.shabinder.common.di.gaana.corsApi
import com.shabinder.common.di.utils.postData
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.request.forms.FormDataContent import io.ktor.client.request.forms.FormDataContent
import io.ktor.client.request.post import io.ktor.client.request.post
@ -44,7 +45,7 @@ interface Yt1sMp3 {
* Body Form= q:yt video link ,vt:format=mp3 * Body Form= q:yt video link ,vt:format=mp3
* */ * */
private suspend fun getKey(videoID: String): String { private suspend fun getKey(videoID: String): String {
val response: JsonObject? = httpClient.post("${corsApi}https://yt1s.com/api/ajaxSearch/index") { val response: JsonObject? = httpClient.postData("${corsApi}https://yt1s.com/api/ajaxSearch/index") {
body = FormDataContent( body = FormDataContent(
Parameters.build { Parameters.build {
append("q", "https://www.youtube.com/watch?v=$videoID") append("q", "https://www.youtube.com/watch?v=$videoID")
@ -56,7 +57,7 @@ interface Yt1sMp3 {
} }
private suspend fun getConvertedMp3Link(videoID: String, key: String): JsonObject? { private suspend fun getConvertedMp3Link(videoID: String, key: String): JsonObject? {
return httpClient.post("${corsApi}https://yt1s.com/api/ajaxConvert/convert") { return httpClient.postData("${corsApi}https://yt1s.com/api/ajaxConvert/convert") {
body = FormDataContent( body = FormDataContent(
Parameters.build { Parameters.build {
append("vid", videoID) append("vid", videoID)

View File

@ -31,18 +31,16 @@ actual class Dir actual constructor(
actual fun fileSeparator(): String = "/" actual fun fileSeparator(): String = "/"
// TODO Error Handling // TODO Error Handling
actual fun defaultDir(): String = defaultDirURL.path!! actual fun defaultDir(): String = defaultDirURL.path!! + fileSeparator()
val defaultDirURL: NSURL by lazy { private val defaultDirURL: NSURL by lazy {
createDirectories()
val musicDir = NSFileManager.defaultManager.URLForDirectory(NSMusicDirectory, NSUserDomainMask,null,true,null)!! val musicDir = NSFileManager.defaultManager.URLForDirectory(NSMusicDirectory, NSUserDomainMask,null,true,null)!!
musicDir.URLByAppendingPathComponent("SpotiFlyer",true)!! musicDir.URLByAppendingPathComponent("SpotiFlyer",true)!!
} }
actual fun imageCacheDir(): String = imageCacheURL.path!! actual fun imageCacheDir(): String = imageCacheURL.path!! + fileSeparator()
val imageCacheURL: NSURL by lazy { private val imageCacheURL: NSURL by lazy {
createDirectories()
val cacheDir = NSFileManager.defaultManager.URLForDirectory(NSCachesDirectory, NSUserDomainMask,null,true,null) val cacheDir = NSFileManager.defaultManager.URLForDirectory(NSCachesDirectory, NSUserDomainMask,null,true,null)
cacheDir?.URLByAppendingPathComponent("SpotiFlyer",true)!! cacheDir?.URLByAppendingPathComponent("SpotiFlyer",true)!!
} }

View File

@ -24,6 +24,7 @@ import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
import com.shabinder.common.database.getLogger import com.shabinder.common.database.getLogger
import com.shabinder.common.di.Dir import com.shabinder.common.di.Dir
import com.shabinder.common.di.FetchPlatformQueryResult import com.shabinder.common.di.FetchPlatformQueryResult
import com.shabinder.common.di.dispatcherIO
import com.shabinder.common.di.downloadTracks import com.shabinder.common.di.downloadTracks
import com.shabinder.common.list.SpotiFlyerList.State import com.shabinder.common.list.SpotiFlyerList.State
import com.shabinder.common.list.store.SpotiFlyerListStore.Intent import com.shabinder.common.list.store.SpotiFlyerListStore.Intent
@ -33,6 +34,7 @@ import com.shabinder.common.models.TrackDetails
import com.shabinder.common.models.methods import com.shabinder.common.models.methods
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.withContext
internal class SpotiFlyerListStoreProvider( internal class SpotiFlyerListStoreProvider(
private val dir: Dir, private val dir: Dir,
@ -85,6 +87,7 @@ internal class SpotiFlyerListStoreProvider(
throw Exception("An Error Occurred, Check your Link / Connection") throw Exception("An Error Occurred, Check your Link / Connection")
} }
} catch (e:Exception) { } catch (e:Exception) {
e.printStackTrace()
dispatch(Result.ErrorOccurred(e)) dispatch(Result.ErrorOccurred(e))
} }
} }

@ -1 +1 @@
Subproject commit f218336b5b31b365bbf34503b79f2c4f2b703d7d Subproject commit 41c6aca4938d13cc7364b1200bf6987c852dfdc2