From 7a3a299aa2eb01130f0fe74621dfdb3100c825e2 Mon Sep 17 00:00:00 2001 From: shabinder Date: Tue, 4 May 2021 13:29:53 +0530 Subject: [PATCH] Freezing Fixes, Database Adding in Background. --- .../kotlin/com/shabinder/common/di/DI.kt | 18 +++++--- .../kotlin/com/shabinder/common/di/Expect.kt | 2 +- .../common/di/FetchPlatformQueryResult.kt | 22 ++++++--- .../common/di/gaana/GaanaRequests.kt | 11 ++--- .../common/di/providers/GaanaProvider.kt | 8 ++-- .../common/di/providers/SpotifyProvider.kt | 13 ++++-- .../common/di/providers/YoutubeMusic.kt | 3 +- .../common/di/spotify/SpotifyRequests.kt | 26 +++++++---- .../com/shabinder/common/di/utils/Utils.kt | 45 +++++++++++++++++++ .../shabinder/common/di/youtubeMp3/Yt1sMp3.kt | 5 ++- .../kotlin/com.shabinder.common.di/IOSDir.kt | 10 ++--- .../list/store/SpotiFlyerListStoreProvider.kt | 3 ++ spotiflyer-ios | 2 +- 13 files changed, 124 insertions(+), 44 deletions(-) diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/DI.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/DI.kt index 01ad1987..5e61d3ed 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/DI.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/DI.kt @@ -17,6 +17,7 @@ package com.shabinder.common.di import co.touchlab.kermit.Kermit +import co.touchlab.stately.ensureNeverFrozen import com.shabinder.common.database.databaseModule import com.shabinder.common.database.getLogger 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.module import kotlin.native.concurrent.SharedImmutable +import kotlin.native.concurrent.ThreadLocal fun initKoin(enableNetworkLogs: Boolean = false, appDeclaration: KoinAppDeclaration = {}) = startKoin { @@ -60,7 +62,7 @@ fun commonModule(enableNetworkLogs: Boolean) = module { single { FetchPlatformQueryResult(get(), get(), get(), get(), get(), get()) } } -@SharedImmutable +@ThreadLocal val kotlinxSerializer = KotlinxSerializer( Json { isLenient = true @@ -68,10 +70,16 @@ val kotlinxSerializer = KotlinxSerializer( } ) -fun createHttpClient(enableNetworkLogs: Boolean = false, serializer: KotlinxSerializer = kotlinxSerializer) = HttpClient { - install(JsonFeature) { - this.serializer = serializer - } +fun createHttpClient(enableNetworkLogs: Boolean = false) = HttpClient { + // https://github.com/Kotlin/kotlinx.serialization/issues/1450 + /*install(JsonFeature) { + serializer = KotlinxSerializer( + Json { + isLenient = true + ignoreUnknownKeys = true + } + ) + }*/ // Timeout install(HttpTimeout) { requestTimeoutMillis = 15000L diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Expect.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Expect.kt index 4d2a6628..e6c00993 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Expect.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/Expect.kt @@ -43,7 +43,7 @@ expect val currentPlatform: AllPlatforms suspend fun isInternetAccessible(): Boolean { return withContext(dispatcherIO) { try { - ktorHttpClient.head("http://google.com") + ktorHttpClient.head("https://google.com") true } catch (e: Exception) { e.printStackTrace() diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/FetchPlatformQueryResult.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/FetchPlatformQueryResult.kt index d0ccce03..ef1b6f27 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/FetchPlatformQueryResult.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/FetchPlatformQueryResult.kt @@ -23,6 +23,10 @@ import com.shabinder.common.di.providers.YoutubeMp3 import com.shabinder.common.di.providers.YoutubeMusic import com.shabinder.common.models.PlatformQueryResult import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class FetchPlatformQueryResult( @@ -54,13 +58,19 @@ class FetchPlatformQueryResult( null } } - result?.run { - withContext(Dispatchers.Default) { - db?.add( - folderType, title, link, coverUrl, trackList.size.toLong() - ) - } + if (result != null) { + addToDatabaseAsync( + link, + result.copy() // Send a copy in order to not to freeze Result itself + ) } 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() + ) + } + } } diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/gaana/GaanaRequests.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/gaana/GaanaRequests.kt index e84ea778..07277bb7 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/gaana/GaanaRequests.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/gaana/GaanaRequests.kt @@ -17,6 +17,7 @@ package com.shabinder.common.di.gaana import com.shabinder.common.di.currentPlatform +import com.shabinder.common.di.utils.getData import com.shabinder.common.models.AllPlatforms import com.shabinder.common.models.corsProxy import com.shabinder.common.models.gaana.GaanaAlbum @@ -52,7 +53,7 @@ interface GaanaRequests { format: String = "JSON", limit: Int = 2000 ): GaanaPlaylist { - return httpClient.get( + return httpClient.getData( "$BASE_URL/?type=$type&subtype=$subtype&seokey=$seokey&token=$TOKEN&format=$format&limit=$limit" ) } @@ -69,7 +70,7 @@ interface GaanaRequests { format: String = "JSON", limit: Int = 2000 ): GaanaAlbum { - return httpClient.get( + return httpClient.getData( "$BASE_URL/?type=$type&subtype=$subtype&seokey=$seokey&token=$TOKEN&format=$format&limit=$limit" ) } @@ -85,7 +86,7 @@ interface GaanaRequests { seokey: String, format: String = "JSON", ): GaanaSong { - return httpClient.get( + return httpClient.getData( "$BASE_URL/?type=$type&subtype=$subtype&seokey=$seokey&token=$TOKEN&format=$format" ) } @@ -101,7 +102,7 @@ interface GaanaRequests { seokey: String, format: String = "JSON", ): GaanaArtistDetails { - return httpClient.get( + return httpClient.getData( "$BASE_URL/?type=$type&subtype=$subtype&seokey=$seokey&token=$TOKEN&format=$format" ) } @@ -118,7 +119,7 @@ interface GaanaRequests { format: String = "JSON", limit: Int = 50 ): GaanaArtistTracks { - return httpClient.get( + return httpClient.getData( "$BASE_URL/?type=$type&subtype=$subtype&seokey=$seokey&token=$TOKEN&format=$format&limit=$limit" ) } diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/GaanaProvider.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/GaanaProvider.kt index cb4076d7..69959a54 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/GaanaProvider.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/GaanaProvider.kt @@ -79,7 +79,7 @@ class GaanaProvider( it.updateStatusIfPresent(folderType, subFolder) trackList = listOf(it).toTrackDetailsList(folderType, subFolder) title = it.track_title - coverUrl = it.artworkLink + coverUrl = it.artworkLink.replace("http:","https:") } } "album" -> { @@ -91,7 +91,7 @@ class GaanaProvider( } trackList = it.tracks?.toTrackDetailsList(folderType, subFolder) ?: emptyList() title = link - coverUrl = it.custom_artworks.size_480p + coverUrl = it.custom_artworks.size_480p.replace("http:","https:") } } "playlist" -> { @@ -114,7 +114,7 @@ class GaanaProvider( getGaanaArtistDetails(seokey = link).artist.firstOrNull() ?.also { title = it.name - coverUrl = it.artworkLink ?: gaanaPlaceholderImageUrl + coverUrl = it.artworkLink?.replace("http:","https:") ?: gaanaPlaceholderImageUrl } getGaanaArtistTracks(seokey = link).also { it.tracks?.forEach { track -> @@ -143,7 +143,7 @@ class GaanaProvider( trackUrl = it.lyrics_url, downloaded = it.downloaded ?: DownloadStatus.NotDownloaded, source = Source.Gaana, - albumArtURL = it.artworkLink, + albumArtURL = it.artworkLink.replace("http:","https:"), outputFilePath = dir.finalOutputDir(it.track_title, type, subFolder, dir.defaultDir()/*,".m4a"*/) ) } diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/SpotifyProvider.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/SpotifyProvider.kt index 276f3031..ab40a511 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/SpotifyProvider.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/SpotifyProvider.kt @@ -17,9 +17,11 @@ package com.shabinder.common.di.providers import co.touchlab.kermit.Kermit +import co.touchlab.stately.ensureNeverFrozen import co.touchlab.stately.freeze import com.shabinder.common.di.Dir import com.shabinder.common.di.TokenStore +import com.shabinder.common.di.createHttpClient import com.shabinder.common.di.finalOutputDir import com.shabinder.common.di.kotlinxSerializer 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.Track 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.json.JsonFeature import io.ktor.client.request.header +import kotlin.native.concurrent.SharedImmutable class SpotifyProvider( private val tokenStore: TokenStore, @@ -62,14 +67,14 @@ class SpotifyProvider( defaultRequest { header("Authorization", "Bearer ${token.access_token}") } - install(JsonFeature) { + /*install(JsonFeature) { 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? { diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMusic.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMusic.kt index c8e3032c..696ad3a8 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMusic.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/providers/YoutubeMusic.kt @@ -18,6 +18,7 @@ package com.shabinder.common.di.providers import co.touchlab.kermit.Kermit 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.YoutubeTrack import com.shabinder.fuzzywuzzy.diffutils.FuzzySearch @@ -283,7 +284,7 @@ class YoutubeMusic constructor( } 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) headers { append("referer", "https://music.youtube.com/search") diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/spotify/SpotifyRequests.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/spotify/SpotifyRequests.kt index 9faeab37..8a4edc75 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/spotify/SpotifyRequests.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/spotify/SpotifyRequests.kt @@ -17,25 +17,33 @@ package com.shabinder.common.di.spotify 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.spotify.Album import com.shabinder.common.models.spotify.PagingObjectPlaylistTrack import com.shabinder.common.models.spotify.Playlist import com.shabinder.common.models.spotify.Track 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" interface SpotifyRequests { val httpClientRef: NativeAtomicReference - val httpClient get() = httpClientRef.value + val httpClient:HttpClient get() = httpClientRef.value suspend fun authenticateSpotifyClient(override: Boolean = false) suspend fun getPlaylist(playlistID: String): Playlist { - return httpClient.get("$BASE_URL/playlists/$playlistID") + return httpClient.getData("$BASE_URL/playlists/$playlistID") } suspend fun getPlaylistTracks( @@ -43,26 +51,26 @@ interface SpotifyRequests { offset: Int = 0, limit: Int = 100 ): 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 { - return httpClient.get("$BASE_URL/tracks/$id") + return httpClient.getData("$BASE_URL/tracks/$id") } 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 { - return httpClient.get("$BASE_URL/shows/$id") + return httpClient.getData("$BASE_URL/shows/$id") } 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 { - return httpClient.get(url) + return httpClient.getData(url) } } diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/utils/Utils.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/utils/Utils.kt index 8c096aca..88fcc775 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/utils/Utils.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/utils/Utils.kt @@ -16,6 +16,51 @@ 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 HttpClient.getData( + urlString: String, + block: HttpRequestBuilder.() -> Unit = {} +): T { + val response = get { + url.takeFrom(urlString) + block() + } + val jsonBody = response.readText() + return json.decodeFromString(T::class.serializer(),jsonBody) +} + +@OptIn(InternalSerializationApi::class) +suspend inline fun HttpClient.postData( + urlString: String, + block: HttpRequestBuilder.() -> Unit = {} +): T { + val response = post { + 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 * **/ diff --git a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/youtubeMp3/Yt1sMp3.kt b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/youtubeMp3/Yt1sMp3.kt index 4a1613a5..c4be4f59 100644 --- a/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/youtubeMp3/Yt1sMp3.kt +++ b/common/dependency-injection/src/commonMain/kotlin/com/shabinder/common/di/youtubeMp3/Yt1sMp3.kt @@ -18,6 +18,7 @@ package com.shabinder.common.di.youtubeMp3 import co.touchlab.kermit.Kermit import com.shabinder.common.di.gaana.corsApi +import com.shabinder.common.di.utils.postData import io.ktor.client.HttpClient import io.ktor.client.request.forms.FormDataContent import io.ktor.client.request.post @@ -44,7 +45,7 @@ interface Yt1sMp3 { * Body Form= q:yt video link ,vt:format=mp3 * */ 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( Parameters.build { append("q", "https://www.youtube.com/watch?v=$videoID") @@ -56,7 +57,7 @@ interface Yt1sMp3 { } 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( Parameters.build { append("vid", videoID) diff --git a/common/dependency-injection/src/iosMain/kotlin/com.shabinder.common.di/IOSDir.kt b/common/dependency-injection/src/iosMain/kotlin/com.shabinder.common.di/IOSDir.kt index 04ea6488..3a2ea657 100644 --- a/common/dependency-injection/src/iosMain/kotlin/com.shabinder.common.di/IOSDir.kt +++ b/common/dependency-injection/src/iosMain/kotlin/com.shabinder.common.di/IOSDir.kt @@ -31,18 +31,16 @@ actual class Dir actual constructor( actual fun fileSeparator(): String = "/" // TODO Error Handling - actual fun defaultDir(): String = defaultDirURL.path!! + actual fun defaultDir(): String = defaultDirURL.path!! + fileSeparator() - val defaultDirURL: NSURL by lazy { - createDirectories() + private val defaultDirURL: NSURL by lazy { val musicDir = NSFileManager.defaultManager.URLForDirectory(NSMusicDirectory, NSUserDomainMask,null,true,null)!! musicDir.URLByAppendingPathComponent("SpotiFlyer",true)!! } - actual fun imageCacheDir(): String = imageCacheURL.path!! + actual fun imageCacheDir(): String = imageCacheURL.path!! + fileSeparator() - val imageCacheURL: NSURL by lazy { - createDirectories() + private val imageCacheURL: NSURL by lazy { val cacheDir = NSFileManager.defaultManager.URLForDirectory(NSCachesDirectory, NSUserDomainMask,null,true,null) cacheDir?.URLByAppendingPathComponent("SpotiFlyer",true)!! } diff --git a/common/list/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt b/common/list/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt index 44fe981d..eeef1b1d 100644 --- a/common/list/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt +++ b/common/list/src/commonMain/kotlin/com/shabinder/common/list/store/SpotiFlyerListStoreProvider.kt @@ -24,6 +24,7 @@ import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor import com.shabinder.common.database.getLogger import com.shabinder.common.di.Dir import com.shabinder.common.di.FetchPlatformQueryResult +import com.shabinder.common.di.dispatcherIO import com.shabinder.common.di.downloadTracks import com.shabinder.common.list.SpotiFlyerList.State 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 kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.withContext internal class SpotiFlyerListStoreProvider( private val dir: Dir, @@ -85,6 +87,7 @@ internal class SpotiFlyerListStoreProvider( throw Exception("An Error Occurred, Check your Link / Connection") } } catch (e:Exception) { + e.printStackTrace() dispatch(Result.ErrorOccurred(e)) } } diff --git a/spotiflyer-ios b/spotiflyer-ios index f218336b..41c6aca4 160000 --- a/spotiflyer-ios +++ b/spotiflyer-ios @@ -1 +1 @@ -Subproject commit f218336b5b31b365bbf34503b79f2c4f2b703d7d +Subproject commit 41c6aca4938d13cc7364b1200bf6987c852dfdc2