Option to slowdown downloads and configure parallelism
This commit is contained in:
@@ -141,6 +141,12 @@ class AppSettings(context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val isDownloadsSlowdownEnabled: Boolean
|
||||||
|
get() = prefs.getBoolean(KEY_DOWNLOADS_SLOWDOWN, false)
|
||||||
|
|
||||||
|
val downloadsParallelism: Int
|
||||||
|
get() = prefs.getInt(KEY_DOWNLOADS_PARALLELISM, 2)
|
||||||
|
|
||||||
val isSuggestionsEnabled: Boolean
|
val isSuggestionsEnabled: Boolean
|
||||||
get() = prefs.getBoolean(KEY_SUGGESTIONS, false)
|
get() = prefs.getBoolean(KEY_SUGGESTIONS, false)
|
||||||
|
|
||||||
@@ -261,6 +267,8 @@ class AppSettings(context: Context) {
|
|||||||
const val KEY_SUGGESTIONS_EXCLUDE_NSFW = "suggestions_exclude_nsfw"
|
const val KEY_SUGGESTIONS_EXCLUDE_NSFW = "suggestions_exclude_nsfw"
|
||||||
const val KEY_SUGGESTIONS_EXCLUDE_TAGS = "suggestions_exclude_tags"
|
const val KEY_SUGGESTIONS_EXCLUDE_TAGS = "suggestions_exclude_tags"
|
||||||
const val KEY_SEARCH_SINGLE_SOURCE = "search_single_source"
|
const val KEY_SEARCH_SINGLE_SOURCE = "search_single_source"
|
||||||
|
const val KEY_DOWNLOADS_PARALLELISM = "downloads_parallelism"
|
||||||
|
const val KEY_DOWNLOADS_SLOWDOWN = "downloads_slowdown"
|
||||||
|
|
||||||
// About
|
// About
|
||||||
const val KEY_APP_UPDATE = "app_update"
|
const val KEY_APP_UPDATE = "app_update"
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import org.koitharu.kotatsu.BuildConfig
|
|||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.network.CommonHeaders
|
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.local.data.PagesCache
|
import org.koitharu.kotatsu.local.data.PagesCache
|
||||||
import org.koitharu.kotatsu.local.domain.CbzMangaOutput
|
import org.koitharu.kotatsu.local.domain.CbzMangaOutput
|
||||||
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||||
@@ -30,8 +31,8 @@ import org.koitharu.kotatsu.utils.progress.ProgressJob
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
private const val MAX_DOWNLOAD_ATTEMPTS = 3
|
private const val MAX_DOWNLOAD_ATTEMPTS = 3
|
||||||
private const val MAX_PARALLEL_DOWNLOADS = 2
|
|
||||||
private const val DOWNLOAD_ERROR_DELAY = 500L
|
private const val DOWNLOAD_ERROR_DELAY = 500L
|
||||||
|
private const val SLOWDOWN_DELAY = 200L
|
||||||
|
|
||||||
class DownloadManager(
|
class DownloadManager(
|
||||||
private val coroutineScope: CoroutineScope,
|
private val coroutineScope: CoroutineScope,
|
||||||
@@ -40,9 +41,10 @@ class DownloadManager(
|
|||||||
private val okHttp: OkHttpClient,
|
private val okHttp: OkHttpClient,
|
||||||
private val cache: PagesCache,
|
private val cache: PagesCache,
|
||||||
private val localMangaRepository: LocalMangaRepository,
|
private val localMangaRepository: LocalMangaRepository,
|
||||||
|
private val settings: AppSettings,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val connectivityManager = context.applicationContext.getSystemService(
|
private val connectivityManager = context.getSystemService(
|
||||||
Context.CONNECTIVITY_SERVICE
|
Context.CONNECTIVITY_SERVICE
|
||||||
) as ConnectivityManager
|
) as ConnectivityManager
|
||||||
private val coverWidth = context.resources.getDimensionPixelSize(
|
private val coverWidth = context.resources.getDimensionPixelSize(
|
||||||
@@ -51,7 +53,7 @@ class DownloadManager(
|
|||||||
private val coverHeight = context.resources.getDimensionPixelSize(
|
private val coverHeight = context.resources.getDimensionPixelSize(
|
||||||
androidx.core.R.dimen.compat_notification_large_icon_max_height
|
androidx.core.R.dimen.compat_notification_large_icon_max_height
|
||||||
)
|
)
|
||||||
private val semaphore = Semaphore(MAX_PARALLEL_DOWNLOADS)
|
private val semaphore = Semaphore(settings.downloadsParallelism)
|
||||||
|
|
||||||
fun downloadManga(
|
fun downloadManga(
|
||||||
manga: Manga,
|
manga: Manga,
|
||||||
@@ -141,6 +143,10 @@ class DownloadManager(
|
|||||||
totalPages = pages.size,
|
totalPages = pages.size,
|
||||||
currentPage = pageIndex,
|
currentPage = pageIndex,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (settings.isDownloadsSlowdownEnabled) {
|
||||||
|
delay(SLOWDOWN_DELAY)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
outState.value = DownloadState.PostProcessing(startId, data, cover)
|
outState.value = DownloadState.PostProcessing(startId, data, cover)
|
||||||
@@ -206,4 +212,24 @@ class DownloadManager(
|
|||||||
error = throwable,
|
error = throwable,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Factory(
|
||||||
|
private val context: Context,
|
||||||
|
private val imageLoader: ImageLoader,
|
||||||
|
private val okHttp: OkHttpClient,
|
||||||
|
private val cache: PagesCache,
|
||||||
|
private val localMangaRepository: LocalMangaRepository,
|
||||||
|
private val settings: AppSettings,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun create(coroutineScope: CoroutineScope) = DownloadManager(
|
||||||
|
coroutineScope = coroutineScope,
|
||||||
|
context = context,
|
||||||
|
imageLoader = imageLoader,
|
||||||
|
okHttp = okHttp,
|
||||||
|
cache = cache,
|
||||||
|
localMangaRepository = localMangaRepository,
|
||||||
|
settings = settings,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -49,13 +49,8 @@ class DownloadService : BaseService() {
|
|||||||
notificationSwitcher = ForegroundNotificationSwitcher(this)
|
notificationSwitcher = ForegroundNotificationSwitcher(this)
|
||||||
val wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager)
|
val wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager)
|
||||||
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "kotatsu:downloading")
|
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "kotatsu:downloading")
|
||||||
downloadManager = DownloadManager(
|
downloadManager = get<DownloadManager.Factory>().create(
|
||||||
coroutineScope = lifecycleScope + WakeLockNode(wakeLock, TimeUnit.HOURS.toMillis(1)),
|
coroutineScope = lifecycleScope + WakeLockNode(wakeLock, TimeUnit.HOURS.toMillis(1)),
|
||||||
context = this,
|
|
||||||
imageLoader = get(),
|
|
||||||
okHttp = get(),
|
|
||||||
cache = get(),
|
|
||||||
localMangaRepository = get(),
|
|
||||||
)
|
)
|
||||||
DownloadNotification.createChannel(this)
|
DownloadNotification.createChannel(this)
|
||||||
registerReceiver(controlReceiver, IntentFilter(ACTION_DOWNLOAD_CANCEL))
|
registerReceiver(controlReceiver, IntentFilter(ACTION_DOWNLOAD_CANCEL))
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package org.koitharu.kotatsu.local
|
|||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
import org.koitharu.kotatsu.download.domain.DownloadManager
|
||||||
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
||||||
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||||
import org.koitharu.kotatsu.local.ui.LocalListViewModel
|
import org.koitharu.kotatsu.local.ui.LocalListViewModel
|
||||||
@@ -16,5 +17,7 @@ val localModule
|
|||||||
|
|
||||||
factory { ExternalStorageHelper(androidContext()) }
|
factory { ExternalStorageHelper(androidContext()) }
|
||||||
|
|
||||||
|
factory { DownloadManager.Factory(androidContext(), get(), get(), get(), get(), get()) }
|
||||||
|
|
||||||
viewModel { LocalListViewModel(get(), get(), get(), get()) }
|
viewModel { LocalListViewModel(get(), get(), get(), get()) }
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,6 @@ import android.content.SharedPreferences
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import java.io.File
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
@@ -13,8 +12,10 @@ import org.koitharu.kotatsu.base.ui.dialog.StorageSelectDialog
|
|||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
import org.koitharu.kotatsu.local.data.LocalStorageManager
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
import org.koitharu.kotatsu.settings.utils.SliderPreference
|
||||||
import org.koitharu.kotatsu.utils.ext.getStorageName
|
import org.koitharu.kotatsu.utils.ext.getStorageName
|
||||||
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
|
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
class ContentSettingsFragment :
|
class ContentSettingsFragment :
|
||||||
BasePreferenceFragment(R.string.content),
|
BasePreferenceFragment(R.string.content),
|
||||||
@@ -29,6 +30,13 @@ class ContentSettingsFragment :
|
|||||||
findPreference<Preference>(AppSettings.KEY_SUGGESTIONS)?.setSummary(
|
findPreference<Preference>(AppSettings.KEY_SUGGESTIONS)?.setSummary(
|
||||||
if (settings.isSuggestionsEnabled) R.string.enabled else R.string.disabled
|
if (settings.isSuggestionsEnabled) R.string.enabled else R.string.disabled
|
||||||
)
|
)
|
||||||
|
findPreference<SliderPreference>(AppSettings.KEY_DOWNLOADS_PARALLELISM)?.run {
|
||||||
|
summary = value.toString()
|
||||||
|
setOnPreferenceChangeListener { preference, newValue ->
|
||||||
|
preference.summary = newValue.toString()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
bindRemoteSourcesSummary()
|
bindRemoteSourcesSummary()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ suspend fun ConnectivityManager.waitForNetwork(): Network {
|
|||||||
return suspendCancellableCoroutine { cont ->
|
return suspendCancellableCoroutine { cont ->
|
||||||
val callback = object : ConnectivityManager.NetworkCallback() {
|
val callback = object : ConnectivityManager.NetworkCallback() {
|
||||||
override fun onAvailable(network: Network) {
|
override fun onAvailable(network: Network) {
|
||||||
|
unregisterNetworkCallback(this)
|
||||||
if (cont.isActive) {
|
if (cont.isActive) {
|
||||||
cont.resume(network)
|
cont.resume(network)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,12 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?android:attr/selectableItemBackground"
|
|
||||||
android:baselineAligned="false"
|
android:baselineAligned="false"
|
||||||
android:clipChildren="false"
|
android:clipChildren="false"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:orientation="horizontal"
|
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||||
|
android:orientation="horizontal"
|
||||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||||
tools:ignore="PrivateResource">
|
tools:ignore="PrivateResource">
|
||||||
@@ -27,17 +26,18 @@
|
|||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<RelativeLayout
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:orientation="horizontal"
|
||||||
android:layout_weight="1">
|
android:baselineAligned="true"
|
||||||
|
android:baselineAlignedChildIndex="0"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@android:id/title"
|
android:id="@android:id/title"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentStart="true"
|
|
||||||
android:layout_toStartOf="@android:id/summary"
|
|
||||||
android:ellipsize="marquee"
|
android:ellipsize="marquee"
|
||||||
android:labelFor="@id/seekbar"
|
android:labelFor="@id/seekbar"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
@@ -49,12 +49,11 @@
|
|||||||
style="@style/PreferenceSummaryTextStyle"
|
style="@style/PreferenceSummaryTextStyle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:textAlignment="viewStart"
|
android:textAlignment="viewStart"
|
||||||
android:textColor="?android:attr/textColorSecondary" />
|
android:textColor="?android:attr/textColorSecondary" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<com.google.android.material.slider.Slider
|
<com.google.android.material.slider.Slider
|
||||||
android:id="@+id/slider"
|
android:id="@+id/slider"
|
||||||
|
|||||||
@@ -271,4 +271,7 @@
|
|||||||
<string name="text_delete_local_manga_batch">Удалить выбранную мангу с накопителя?</string>
|
<string name="text_delete_local_manga_batch">Удалить выбранную мангу с накопителя?</string>
|
||||||
<string name="removal_completed">Удаление завершено</string>
|
<string name="removal_completed">Удаление завершено</string>
|
||||||
<string name="batch_manga_save_confirm">Загрузить выбранную мангу со всеми главами? Это может привести к большому расходу трафика и места на накопителе</string>
|
<string name="batch_manga_save_confirm">Загрузить выбранную мангу со всеми главами? Это может привести к большому расходу трафика и места на накопителе</string>
|
||||||
|
<string name="parallel_downloads">Загружать параллельно</string>
|
||||||
|
<string name="download_slowdown">Замедление загрузки</string>
|
||||||
|
<string name="download_slowdown_summary">Помогает избежать блокировки IP-адреса</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -274,4 +274,7 @@
|
|||||||
<string name="text_delete_local_manga_batch">Delete selected items from device permanently?</string>
|
<string name="text_delete_local_manga_batch">Delete selected items from device permanently?</string>
|
||||||
<string name="removal_completed">Removal completed</string>
|
<string name="removal_completed">Removal completed</string>
|
||||||
<string name="batch_manga_save_confirm">Are you sure you want to download all selected manga with all its chapters? This action can consume a lot of traffic and storage</string>
|
<string name="batch_manga_save_confirm">Are you sure you want to download all selected manga with all its chapters? This action can consume a lot of traffic and storage</string>
|
||||||
|
<string name="parallel_downloads">Parallel downloads</string>
|
||||||
|
<string name="download_slowdown">Download slowdown</string>
|
||||||
|
<string name="download_slowdown_summary">Helps avoid blocking your IP address</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -8,17 +8,32 @@
|
|||||||
android:key="remote_sources"
|
android:key="remote_sources"
|
||||||
android:title="@string/remote_sources" />
|
android:title="@string/remote_sources" />
|
||||||
|
|
||||||
<Preference
|
|
||||||
android:key="local_storage"
|
|
||||||
android:persistent="false"
|
|
||||||
android:title="@string/manga_save_location" />
|
|
||||||
|
|
||||||
<PreferenceScreen
|
<PreferenceScreen
|
||||||
android:fragment="org.koitharu.kotatsu.settings.SuggestionsSettingsFragment"
|
android:fragment="org.koitharu.kotatsu.settings.SuggestionsSettingsFragment"
|
||||||
android:key="suggestions"
|
android:key="suggestions"
|
||||||
android:persistent="false"
|
android:persistent="false"
|
||||||
android:title="@string/suggestions" />
|
android:title="@string/suggestions" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:key="local_storage"
|
||||||
|
android:persistent="false"
|
||||||
|
android:title="@string/manga_save_location"
|
||||||
|
app:allowDividerAbove="true" />
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:key="downloads_slowdown"
|
||||||
|
android:summary="@string/download_slowdown_summary"
|
||||||
|
android:title="@string/download_slowdown" />
|
||||||
|
|
||||||
|
<org.koitharu.kotatsu.settings.utils.SliderPreference
|
||||||
|
android:key="downloads_parallelism"
|
||||||
|
android:stepSize="1"
|
||||||
|
android:title="@string/parallel_downloads"
|
||||||
|
android:valueFrom="1"
|
||||||
|
android:valueTo="5"
|
||||||
|
app:defaultValue="2" />
|
||||||
|
|
||||||
<PreferenceScreen
|
<PreferenceScreen
|
||||||
android:fragment="org.koitharu.kotatsu.settings.backup.BackupSettingsFragment"
|
android:fragment="org.koitharu.kotatsu.settings.backup.BackupSettingsFragment"
|
||||||
android:title="@string/backup_restore"
|
android:title="@string/backup_restore"
|
||||||
|
|||||||
Reference in New Issue
Block a user