Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Data Saver #1084

Merged
merged 15 commits into from
Jul 27, 2023
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