Skip to content

Commit

Permalink
feat: Add data saver (#1084)
Browse files Browse the repository at this point in the history
  • Loading branch information
LuftVerbot authored Jul 27, 2023
1 parent ad0e308 commit ed6744f
Show file tree
Hide file tree
Showing 15 changed files with 426 additions and 11 deletions.
154 changes: 154 additions & 0 deletions app/src/main/java/aniyomi/util/DataSaver.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package aniyomi.util

import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.source.service.SourcePreferences.DataSaver.BANDWIDTH_HERO
import eu.kanade.domain.source.service.SourcePreferences.DataSaver.NONE
import eu.kanade.domain.source.service.SourcePreferences.DataSaver.RESMUSH_IT
import eu.kanade.domain.source.service.SourcePreferences.DataSaver.WSRV_NL
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.MangaSource
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource
import okhttp3.OkHttpClient
import okhttp3.Response
import rx.Observable
import tachiyomi.core.preference.Preference
import uy.kohesive.injekt.injectLazy

interface DataSaver {

fun compress(imageUrl: String): String

companion object {
val NoOp = object : DataSaver {
override fun compress(imageUrl: String): String {
return imageUrl
}
}

fun HttpSource.fetchImage(page: Page, dataSaver: DataSaver): Observable<Response> {
val imageUrl = page.imageUrl ?: return fetchImage(page)
page.imageUrl = dataSaver.compress(imageUrl)
return fetchImage(page)
.doOnNext {
page.imageUrl = imageUrl
}
}

suspend fun HttpSource.getImage(page: Page, dataSaver: DataSaver): Response {
val imageUrl = page.imageUrl ?: return getImage(page)
page.imageUrl = dataSaver.compress(imageUrl)
return try {
getImage(page)
} finally {
page.imageUrl = imageUrl
}
}
}
}

fun DataSaver(source: MangaSource, preferences: SourcePreferences): DataSaver {
val dataSaver = preferences.dataSaver().get()
if (dataSaver != NONE && source.id.toString() in preferences.dataSaverExcludedSources().get()) {
return DataSaver.NoOp
}
return when (dataSaver) {
NONE -> DataSaver.NoOp
BANDWIDTH_HERO -> BandwidthHeroDataSaver(preferences)
WSRV_NL -> WsrvNlDataSaver(preferences)
RESMUSH_IT -> ReSmushItDataSaver(preferences)
}
}

private class BandwidthHeroDataSaver(preferences: SourcePreferences) : DataSaver {
private val dataSavedServer = preferences.dataSaverServer().get().trimEnd('/')

private val ignoreJpg = preferences.dataSaverIgnoreJpeg().get()
private val ignoreGif = preferences.dataSaverIgnoreGif().get()

private val format = preferences.dataSaverImageFormatJpeg().toIntRepresentation()
private val quality = preferences.dataSaverImageQuality().get()
private val colorBW = preferences.dataSaverColorBW().toIntRepresentation()

override fun compress(imageUrl: String): String {
return if (dataSavedServer.isNotBlank() && !imageUrl.contains(dataSavedServer)) {
when {
imageUrl.contains(".jpeg", true) || imageUrl.contains(".jpg", true) -> if (ignoreJpg) imageUrl else getUrl(imageUrl)
imageUrl.contains(".gif", true) -> if (ignoreGif) imageUrl else getUrl(imageUrl)
else -> getUrl(imageUrl)
}
} else {
imageUrl
}
}

private fun getUrl(imageUrl: String): String {
// Network Request sent for the Bandwidth Hero Proxy server
return "$dataSavedServer/?jpg=$format&l=$quality&bw=$colorBW&url=$imageUrl"
}

private fun Preference<Boolean>.toIntRepresentation() = if (get()) "1" else "0"
}

private class WsrvNlDataSaver(preferences: SourcePreferences) : DataSaver {
private val ignoreJpg = preferences.dataSaverIgnoreJpeg().get()
private val ignoreGif = preferences.dataSaverIgnoreGif().get()

private val format = preferences.dataSaverImageFormatJpeg().get()
private val quality = preferences.dataSaverImageQuality().get()

override fun compress(imageUrl: String): String {
return when {
imageUrl.contains(".jpeg", true) || imageUrl.contains(".jpg", true) -> if (ignoreJpg) imageUrl else getUrl(imageUrl)
imageUrl.contains(".gif", true) -> if (ignoreGif) imageUrl else getUrl(imageUrl)
else -> getUrl(imageUrl)
}
}

private fun getUrl(imageUrl: String): String {
// Network Request sent to wsrv
return "https://wsrv.nl/?url=$imageUrl" + if (imageUrl.contains(".webp", true) || imageUrl.contains(".gif", true)) {
if (!format) {
// Preserve output image extension for animated images(.webp and .gif)
"&q=$quality&n=-1"
} else {
// Do not preserve output Extension if User asked to convert into Jpeg
"&output=jpg&q=$quality&n=-1"
}
} else {
if (format) {
"&output=jpg&q=$quality"
} else {
"&output=webp&q=$quality"
}
}
}
}

private class ReSmushItDataSaver(preferences: SourcePreferences) : DataSaver {

private val network: NetworkHelper by injectLazy()

private val client: OkHttpClient
get() = network.client

private val ignoreJpg = preferences.dataSaverIgnoreJpeg().get()
private val ignoreGif = preferences.dataSaverIgnoreGif().get()

private val quality = preferences.dataSaverImageQuality().get()

override fun compress(imageUrl: String): String {
return when {
imageUrl.contains(".jpeg", true) || imageUrl.contains(".jpg", true) -> if (ignoreJpg) imageUrl else getUrl(imageUrl)
imageUrl.contains(".gif", true) -> if (ignoreGif) imageUrl else getUrl(imageUrl)
else -> getUrl(imageUrl)
}
}

private fun getUrl(imageUrl: String): String {
// Network Request sent to resmush
return client.newCall(GET("http://api.resmush.it/ws.php?img=$imageUrl&qlty=$quality")).execute()
.body.string().substringAfter("\"dest\":\"").substringBefore("\",")
}
}
14 changes: 14 additions & 0 deletions app/src/main/java/eu/kanade/domain/SYDomainModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package eu.kanade.domain

import eu.kanade.domain.source.manga.interactor.ToggleExcludeFromMangaDataSaver
import uy.kohesive.injekt.api.InjektModule
import uy.kohesive.injekt.api.InjektRegistrar
import uy.kohesive.injekt.api.addFactory
import uy.kohesive.injekt.api.get

class SYDomainModule : InjektModule {

override fun InjektRegistrar.registerInjectables() {
addFactory { ToggleExcludeFromMangaDataSaver(get()) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,27 @@ class GetEnabledMangaSources(
return combine(
preferences.pinnedMangaSources().changes(),
preferences.enabledLanguages().changes(),
preferences.disabledMangaSources().changes(),
preferences.lastUsedMangaSource().changes(),
combine(
preferences.disabledMangaSources().changes(),
preferences.lastUsedMangaSource().changes(),
// SY -->
preferences.dataSaverExcludedSources().changes(),
// SY <--
) { a, b, c -> Triple(a, b, c) },
repository.getMangaSources(),
) { pinnedSourceIds, enabledLanguages, disabledSources, lastUsedSource, sources ->
) { pinnedSourceIds, enabledLanguages, (disabledSources, lastUsedSource, excludedFromDataSaver), sources ->
sources
.filter { it.lang in enabledLanguages || it.id == LocalMangaSource.ID }
.filterNot { it.id.toString() in disabledSources }
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name })
.flatMap {
val flag = if ("${it.id}" in pinnedSourceIds) Pins.pinned else Pins.unpinned
val source = it.copy(pin = flag)
val source = it.copy(
pin = flag,
// SY -->
isExcludedFromDataSaver = it.id.toString() in excludedFromDataSaver,
// SY <--
)
val toFlatten = mutableListOf(source)
if (source.id == lastUsedSource) {
toFlatten.add(source.copy(isUsedLast = true, pin = source.pin - Pin.Actual))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package eu.kanade.domain.source.manga.interactor

import eu.kanade.domain.source.service.SourcePreferences
import tachiyomi.core.preference.getAndSet
import tachiyomi.domain.source.manga.model.Source

class ToggleExcludeFromMangaDataSaver(
private val preferences: SourcePreferences,
) {

fun await(source: Source) {
preferences.dataSaverExcludedSources().getAndSet {
if (source.id.toString() in it) {
it - source.id.toString()
} else {
it + source.id.toString()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,40 @@ class SourcePreferences(
fun hideInAnimeLibraryItems() = preferenceStore.getBoolean("browse_hide_in_anime_library_items", false)

fun hideInMangaLibraryItems() = preferenceStore.getBoolean("browse_hide_in_library_items", false)

// SY -->

// fun enableSourceBlacklist() = preferenceStore.getBoolean("eh_enable_source_blacklist", true)

// fun sourcesTabCategories() = preferenceStore.getStringSet("sources_tab_categories", mutableSetOf())

// fun sourcesTabCategoriesFilter() = preferenceStore.getBoolean("sources_tab_categories_filter", false)

// fun sourcesTabSourcesInCategories() = preferenceStore.getStringSet("sources_tab_source_categories", mutableSetOf())

fun dataSaver() = preferenceStore.getEnum("data_saver", DataSaver.NONE)

fun dataSaverIgnoreJpeg() = preferenceStore.getBoolean("ignore_jpeg", false)

fun dataSaverIgnoreGif() = preferenceStore.getBoolean("ignore_gif", true)

fun dataSaverImageQuality() = preferenceStore.getInt("data_saver_image_quality", 80)

fun dataSaverImageFormatJpeg() = preferenceStore.getBoolean("data_saver_image_format_jpeg", false)

fun dataSaverServer() = preferenceStore.getString("data_saver_server", "")

fun dataSaverColorBW() = preferenceStore.getBoolean("data_saver_color_bw", false)

fun dataSaverExcludedSources() = preferenceStore.getStringSet("data_saver_excluded", emptySet())

fun dataSaverDownloader() = preferenceStore.getBoolean("data_saver_downloader", true)

enum class DataSaver {
NONE,
BANDWIDTH_HERO,
WSRV_NL,
RESMUSH_IT,
}
// SY <--
}
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ fun MangaSourceOptionsDialog(
source: Source,
onClickPin: () -> Unit,
onClickDisable: () -> Unit,
// SY -->
onClickToggleDataSaver: (() -> Unit)?,
// SY <--
onDismiss: () -> Unit,
) {
AlertDialog(
Expand All @@ -185,6 +188,21 @@ fun MangaSourceOptionsDialog(
.padding(vertical = 16.dp),
)
}
// SY -->
if (onClickToggleDataSaver != null) {
Text(
text = if (source.isExcludedFromDataSaver) {
stringResource(id = R.string.data_saver_stop_exclude)
} else {
stringResource(id = R.string.data_saver_exclude)
},
modifier = Modifier
.clickable(onClick = onClickToggleDataSaver)
.fillMaxWidth()
.padding(vertical = 16.dp),
)
}
// SY <--
}
},
onDismissRequest = onDismiss,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import androidx.core.net.toUri
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.source.service.SourcePreferences.DataSaver
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.screen.debug.DebugInfoScreen
import eu.kanade.presentation.util.collectAsState
Expand Down Expand Up @@ -115,6 +117,9 @@ object SettingsAdvancedScreen : SearchableSettings {
getNetworkGroup(networkPreferences = networkPreferences),
getLibraryGroup(),
getExtensionsGroup(basePreferences = basePreferences),
// SY -->
getDataSaverGroup(),
// SY <--
)
}

Expand Down Expand Up @@ -400,4 +405,83 @@ object SettingsAdvancedScreen : SearchableSettings {
),
)
}

// SY -->
@Composable
private fun getDataSaverGroup(): Preference.PreferenceGroup {
val sourcePreferences = remember { Injekt.get<SourcePreferences>() }
val dataSaver by sourcePreferences.dataSaver().collectAsState()
return Preference.PreferenceGroup(
title = stringResource(R.string.data_saver),
preferenceItems = listOf(
Preference.PreferenceItem.ListPreference(
pref = sourcePreferences.dataSaver(),
title = stringResource(R.string.data_saver),
subtitle = stringResource(R.string.data_saver_summary),
entries = mapOf(
DataSaver.NONE to stringResource(R.string.disabled),
DataSaver.BANDWIDTH_HERO to stringResource(R.string.bandwidth_hero),
DataSaver.WSRV_NL to stringResource(R.string.wsrv),
DataSaver.RESMUSH_IT to stringResource(R.string.resmush),
),
),
Preference.PreferenceItem.EditTextPreference(
pref = sourcePreferences.dataSaverServer(),
title = stringResource(R.string.bandwidth_data_saver_server),
subtitle = stringResource(R.string.data_saver_server_summary),
enabled = dataSaver == DataSaver.BANDWIDTH_HERO,
),
Preference.PreferenceItem.SwitchPreference(
pref = sourcePreferences.dataSaverDownloader(),
title = stringResource(R.string.data_saver_downloader),
enabled = dataSaver != DataSaver.NONE,
),
Preference.PreferenceItem.SwitchPreference(
pref = sourcePreferences.dataSaverIgnoreJpeg(),
title = stringResource(R.string.data_saver_ignore_jpeg),
enabled = dataSaver != DataSaver.NONE,
),
Preference.PreferenceItem.SwitchPreference(
pref = sourcePreferences.dataSaverIgnoreGif(),
title = stringResource(R.string.data_saver_ignore_gif),
enabled = dataSaver != DataSaver.NONE,
),
Preference.PreferenceItem.ListPreference(
pref = sourcePreferences.dataSaverImageQuality(),
title = stringResource(R.string.data_saver_image_quality),
subtitle = stringResource(R.string.data_saver_image_quality_summary),
entries = listOf(
"10%",
"20%",
"40%",
"50%",
"70%",
"80%",
"90%",
"95%",
).associateBy { it.trimEnd('%').toInt() },
enabled = dataSaver != DataSaver.NONE,
),
kotlin.run {
val dataSaverImageFormatJpeg by sourcePreferences.dataSaverImageFormatJpeg().collectAsState()
Preference.PreferenceItem.SwitchPreference(
pref = sourcePreferences.dataSaverImageFormatJpeg(),
title = stringResource(R.string.data_saver_image_format),
subtitle = if (dataSaverImageFormatJpeg) {
stringResource(R.string.data_saver_image_format_summary_on)
} else {
stringResource(R.string.data_saver_image_format_summary_off)
},
enabled = dataSaver != DataSaver.NONE && dataSaver != DataSaver.RESMUSH_IT,
)
},
Preference.PreferenceItem.SwitchPreference(
pref = sourcePreferences.dataSaverColorBW(),
title = stringResource(R.string.data_saver_color_bw),
enabled = dataSaver == DataSaver.BANDWIDTH_HERO,
),
),
)
}
// SY <--
}
Loading

0 comments on commit ed6744f

Please sign in to comment.