mirror of
https://github.com/Shabinder/SpotiFlyer.git
synced 2024-12-22 20:57:54 +01:00
Freezing Fixes, Database Adding in Background.
This commit is contained in:
parent
d8415b52ca
commit
7a3a299aa2
@ -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
|
||||
|
@ -43,7 +43,7 @@ expect val currentPlatform: AllPlatforms
|
||||
suspend fun isInternetAccessible(): Boolean {
|
||||
return withContext(dispatcherIO) {
|
||||
try {
|
||||
ktorHttpClient.head<String>("http://google.com")
|
||||
ktorHttpClient.head<String>("https://google.com")
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
)
|
||||
}
|
||||
|
@ -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"*/)
|
||||
)
|
||||
}
|
||||
|
@ -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? {
|
||||
|
||||
|
@ -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")
|
||||
|
@ -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<HttpClient>
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -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 <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
|
||||
* **/
|
||||
|
@ -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)
|
||||
|
@ -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)!!
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit f218336b5b31b365bbf34503b79f2c4f2b703d7d
|
||||
Subproject commit 41c6aca4938d13cc7364b1200bf6987c852dfdc2
|
Loading…
Reference in New Issue
Block a user