Refactored our code to properly show the downloading online library progress.

* Added a head request to get the library length and showing the progress on behalf of that length.
* Improved the library loading process: previously, fetching the online library involved making two requests, which not only took more time to get a response from the server but also used twice the bandwidth. To address this, we have refactored our code to ensure that only one request is made at a time.
* Upgraded the retrofit, and interceptor dependencies to latest version.
This commit is contained in:
MohitMaliFtechiz 2024-09-18 15:24:08 +05:30
parent 8ebeb4e3e9
commit 10837aef76
10 changed files with 79 additions and 90 deletions

View File

@ -5,7 +5,7 @@
<ID>EmptyFunctionBlock:None.kt$None${ }</ID> <ID>EmptyFunctionBlock:None.kt$None${ }</ID>
<ID>EmptyFunctionBlock:SimplePageChangeListener.kt$SimplePageChangeListener${ }</ID> <ID>EmptyFunctionBlock:SimplePageChangeListener.kt$SimplePageChangeListener${ }</ID>
<ID>LongParameterList:ZimManageViewModel.kt$ZimManageViewModel$( booksOnFileSystem: List&lt;BookOnDisk>, activeDownloads: List&lt;DownloadModel>, allLanguages: List&lt;Language>, libraryNetworkEntity: LibraryNetworkEntity, filter: String, fileSystemState: FileSystemState )</ID> <ID>LongParameterList:ZimManageViewModel.kt$ZimManageViewModel$( booksOnFileSystem: List&lt;BookOnDisk>, activeDownloads: List&lt;DownloadModel>, allLanguages: List&lt;Language>, libraryNetworkEntity: LibraryNetworkEntity, filter: String, fileSystemState: FileSystemState )</ID>
<ID>LongParameterList:ZimManageViewModel.kt$ZimManageViewModel$( private val downloadDao: DownloadRoomDao, private val bookDao: NewBookDao, private val languageDao: NewLanguagesDao, private val storageObserver: StorageObserver, private var kiwixService: KiwixService, private val context: Application, private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver, private val bookUtils: BookUtils, private val fat32Checker: Fat32Checker, private val defaultLanguageProvider: DefaultLanguageProvider, private val dataSource: DataSource, private val connectivityManager: ConnectivityManager, private val sharedPreferenceUtil: SharedPreferenceUtil )</ID> <ID>LongParameterList:ZimManageViewModel.kt$ZimManageViewModel$( private val downloadDao: DownloadRoomDao, private val bookDao: NewBookDao, private val languageDao: NewLanguagesDao, private val storageObserver: StorageObserver, private var kiwixService: KiwixService, val context: Application, private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver, private val bookUtils: BookUtils, private val fat32Checker: Fat32Checker, private val defaultLanguageProvider: DefaultLanguageProvider, private val dataSource: DataSource, private val connectivityManager: ConnectivityManager, private val sharedPreferenceUtil: SharedPreferenceUtil )</ID>
<ID>MagicNumber:LibraryListItem.kt$LibraryListItem.LibraryDownloadItem$1000L</ID> <ID>MagicNumber:LibraryListItem.kt$LibraryListItem.LibraryDownloadItem$1000L</ID>
<ID>MagicNumber:PeerGroupHandshake.kt$PeerGroupHandshake$15000</ID> <ID>MagicNumber:PeerGroupHandshake.kt$PeerGroupHandshake$15000</ID>
<ID>MagicNumber:ShareFiles.kt$ShareFiles$24</ID> <ID>MagicNumber:ShareFiles.kt$ShareFiles$24</ID>

View File

@ -87,7 +87,6 @@ import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog
import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog.YesNoDialog.WifiOnly import org.kiwix.kiwixmobile.core.utils.dialog.KiwixDialog.YesNoDialog.WifiOnly
import org.kiwix.kiwixmobile.databinding.FragmentDestinationDownloadBinding import org.kiwix.kiwixmobile.databinding.FragmentDestinationDownloadBinding
import org.kiwix.kiwixmobile.zimManager.NetworkState import org.kiwix.kiwixmobile.zimManager.NetworkState
import org.kiwix.kiwixmobile.zimManager.OnlineLibraryStatus
import org.kiwix.kiwixmobile.zimManager.ZimManageViewModel import org.kiwix.kiwixmobile.zimManager.ZimManageViewModel
import org.kiwix.kiwixmobile.zimManager.libraryView.AvailableSpaceCalculator import org.kiwix.kiwixmobile.zimManager.libraryView.AvailableSpaceCalculator
import org.kiwix.kiwixmobile.zimManager.libraryView.adapter.LibraryAdapter import org.kiwix.kiwixmobile.zimManager.libraryView.adapter.LibraryAdapter
@ -106,7 +105,7 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions {
@Inject lateinit var availableSpaceCalculator: AvailableSpaceCalculator @Inject lateinit var availableSpaceCalculator: AvailableSpaceCalculator
@Inject lateinit var alertDialogShower: AlertDialogShower @Inject lateinit var alertDialogShower: AlertDialogShower
private var fragmentDestinationDownloadBinding: FragmentDestinationDownloadBinding? = null private var fragmentDestinationDownloadBinding: FragmentDestinationDownloadBinding? = null
private val lock = Any()
private var downloadBookItem: LibraryListItem.BookItem? = null private var downloadBookItem: LibraryListItem.BookItem? = null
private val zimManageViewModel by lazy { private val zimManageViewModel by lazy {
requireActivity().viewModel<ZimManageViewModel>(viewModelFactory) requireActivity().viewModel<ZimManageViewModel>(viewModelFactory)
@ -302,10 +301,11 @@ class OnlineLibraryFragment : BaseFragment(), FragmentActivityExtensions {
} }
} }
private fun onLibraryStatusChanged(onlineLibraryStatus: OnlineLibraryStatus) { private fun onLibraryStatusChanged(libraryStatus: String) {
fragmentDestinationDownloadBinding?.apply { synchronized(lock) {
onlineLibraryProgressBar.progress = onlineLibraryStatus.progress fragmentDestinationDownloadBinding?.apply {
onlineLibraryProgressStatusText.text = onlineLibraryStatus.status onlineLibraryProgressStatusText.text = libraryStatus
}
} }
} }

View File

@ -18,18 +18,27 @@
package org.kiwix.kiwixmobile.zimManager package org.kiwix.kiwixmobile.zimManager
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.data.remote.OnlineLibraryProgressListener import org.kiwix.kiwixmobile.core.data.remote.OnlineLibraryProgressListener
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DEFAULT_INT_VALUE
import org.kiwix.kiwixmobile.core.downloader.downloadManager.HUNDERED
import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO
class AppProgressListenerProvider( class AppProgressListenerProvider(
private val zimManageViewModel: ZimManageViewModel private val zimManageViewModel: ZimManageViewModel
) : OnlineLibraryProgressListener { ) : OnlineLibraryProgressListener {
@Suppress("MagicNumber") @Suppress("MagicNumber")
override fun onProgress(bytesRead: Long, contentLength: Long, done: Boolean) { override fun onProgress(bytesRead: Long, contentLength: Long) {
val progress = if (contentLength == -1L) 0 else (bytesRead * 100 / contentLength).toInt() val progress =
if (contentLength == DEFAULT_INT_VALUE.toLong()) {
ZERO
} else {
(bytesRead * HUNDERED / contentLength).toInt() * 3
}
zimManageViewModel.downloadProgress.postValue( zimManageViewModel.downloadProgress.postValue(
OnlineLibraryStatus( zimManageViewModel.context.getString(
progress, R.string.downloading_library,
"Downloading online content" zimManageViewModel.context.getString(R.string.percentage, progress)
) )
) )
} }

View File

@ -1,21 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2024 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.zimManager
data class OnlineLibraryStatus(val progress: Int, val status: String)

View File

@ -34,6 +34,7 @@ import io.reactivex.processors.BehaviorProcessor
import io.reactivex.processors.PublishProcessor import io.reactivex.processors.PublishProcessor
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import okhttp3.logging.HttpLoggingInterceptor.Level.BASIC import okhttp3.logging.HttpLoggingInterceptor.Level.BASIC
import okhttp3.logging.HttpLoggingInterceptor.Level.NONE import okhttp3.logging.HttpLoggingInterceptor.Level.NONE
@ -47,6 +48,7 @@ import org.kiwix.kiwixmobile.core.dao.NewBookDao
import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao import org.kiwix.kiwixmobile.core.dao.NewLanguagesDao
import org.kiwix.kiwixmobile.core.data.DataSource import org.kiwix.kiwixmobile.core.data.DataSource
import org.kiwix.kiwixmobile.core.data.remote.KiwixService import org.kiwix.kiwixmobile.core.data.remote.KiwixService
import org.kiwix.kiwixmobile.core.data.remote.KiwixService.Companion.LIBRARY_NETWORK_PATH
import org.kiwix.kiwixmobile.core.data.remote.ProgressResponseBody import org.kiwix.kiwixmobile.core.data.remote.ProgressResponseBody
import org.kiwix.kiwixmobile.core.data.remote.UserAgentInterceptor import org.kiwix.kiwixmobile.core.data.remote.UserAgentInterceptor
import org.kiwix.kiwixmobile.core.di.modules.CALL_TIMEOUT import org.kiwix.kiwixmobile.core.di.modules.CALL_TIMEOUT
@ -106,7 +108,7 @@ class ZimManageViewModel @Inject constructor(
private val languageDao: NewLanguagesDao, private val languageDao: NewLanguagesDao,
private val storageObserver: StorageObserver, private val storageObserver: StorageObserver,
private var kiwixService: KiwixService, private var kiwixService: KiwixService,
private val context: Application, val context: Application,
private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver, private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver,
private val bookUtils: BookUtils, private val bookUtils: BookUtils,
private val fat32Checker: Fat32Checker, private val fat32Checker: Fat32Checker,
@ -140,7 +142,7 @@ class ZimManageViewModel @Inject constructor(
val requestFiltering = BehaviorProcessor.createDefault("") val requestFiltering = BehaviorProcessor.createDefault("")
private var compositeDisposable: CompositeDisposable? = CompositeDisposable() private var compositeDisposable: CompositeDisposable? = CompositeDisposable()
val downloadProgress = MutableLiveData<OnlineLibraryStatus>() val downloadProgress = MutableLiveData<String>()
init { init {
// add listener to retrofit to get updates of downloading online library // add listener to retrofit to get updates of downloading online library
@ -150,6 +152,7 @@ class ZimManageViewModel @Inject constructor(
} }
private fun createKiwixServiceWithProgressListener(): KiwixService { private fun createKiwixServiceWithProgressListener(): KiwixService {
val contentLength = getContentLengthOfLibraryXmlFile()
val customOkHttpClient = OkHttpClient().newBuilder() val customOkHttpClient = OkHttpClient().newBuilder()
.followRedirects(true) .followRedirects(true)
.followSslRedirects(true) .followSslRedirects(true)
@ -168,7 +171,8 @@ class ZimManageViewModel @Inject constructor(
.body( .body(
ProgressResponseBody( ProgressResponseBody(
originalResponse.body!!, originalResponse.body!!,
AppProgressListenerProvider(this) AppProgressListenerProvider(this),
contentLength
) )
) )
.build() .build()
@ -177,6 +181,29 @@ class ZimManageViewModel @Inject constructor(
return KiwixService.ServiceCreator.newHackListService(customOkHttpClient, KIWIX_DOWNLOAD_URL) return KiwixService.ServiceCreator.newHackListService(customOkHttpClient, KIWIX_DOWNLOAD_URL)
} }
private fun getContentLengthOfLibraryXmlFile(): Long {
val headRequest = Request.Builder()
.url("$KIWIX_DOWNLOAD_URL$LIBRARY_NETWORK_PATH")
.head()
.header("Accept-Encoding", "identity")
.build()
val client = OkHttpClient().newBuilder()
.followRedirects(true)
.followSslRedirects(true)
.connectTimeout(CONNECTION_TIMEOUT, SECONDS)
.readTimeout(READ_TIMEOUT, SECONDS)
.callTimeout(CALL_TIMEOUT, SECONDS)
.addNetworkInterceptor(UserAgentInterceptor(USER_AGENT))
.build()
client.newCall(headRequest).execute().use { response ->
if (response.isSuccessful) {
return@getContentLengthOfLibraryXmlFile response.header("content-length")?.toLongOrNull()
?: -1L
}
}
return -1L
}
@VisibleForTesting @VisibleForTesting
fun onClearedExposed() { fun onClearedExposed() {
onCleared() onCleared()
@ -300,37 +327,22 @@ class ZimManageViewModel @Inject constructor(
} }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(Schedulers.io()) .observeOn(Schedulers.io())
.flatMap { .concatMap {
kiwixService.library kiwixService.library
.toFlowable() .toFlowable()
.retry(5) .retry(5)
.doOnSubscribe { .doOnSubscribe {
downloadProgress.postValue(OnlineLibraryStatus(0, "Downloading library 0%")) downloadProgress.postValue(context.getString(R.string.start_server_label))
} }
.map { response -> .map { response ->
downloadProgress.postValue( downloadProgress.postValue(context.getString(R.string.parsing_remote_library))
OnlineLibraryStatus(
0,
"Downloading library... parsing response"
)
)
response response
} }
.doFinally { .doFinally {
downloadProgress.postValue( downloadProgress.postValue(context.getString(R.string.parsing_remote_library))
OnlineLibraryStatus(
0,
"Remote library downloaded, parsing data"
)
)
} }
.onErrorReturn { .onErrorReturn {
it.printStackTrace() it.printStackTrace()
downloadProgress.postValue(
OnlineLibraryStatus(
0, "Failed to download the library"
)
)
LibraryNetworkEntity().apply { book = LinkedList() } LibraryNetworkEntity().apply { book = LinkedList() }
} }
} }

View File

@ -67,12 +67,12 @@
tools:ignore="RequiredSize" /> tools:ignore="RequiredSize" />
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:id="@+id/progressCardView" android:id="@+id/onlineLibraryProgressLayout"
android:layout_width="wrap_content" android:layout_width="140dp"
android:layout_height="wrap_content" android:layout_height="100dp"
android:layout_margin="16dp" android:layout_margin="16dp"
android:visibility="visible" android:visibility="gone"
app:cardCornerRadius="4dp" app:cardCornerRadius="8dp"
app:cardElevation="4dp" app:cardElevation="4dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -80,30 +80,28 @@
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
<LinearLayout <LinearLayout
android:id="@+id/onlineLibraryProgressLayout"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center" android:gravity="center"
android:orientation="vertical" android:orientation="vertical"
android:padding="@dimen/activity_horizontal_margin" android:padding="@dimen/find_in_page_button_padding">
android:visibility="gone">
<ProgressBar <ProgressBar
android:id="@+id/onlineLibraryProgressBar" android:id="@+id/onlineLibraryProgressBar"
android:layout_width="wrap_content" android:layout_width="40dp"
android:layout_height="wrap_content" android:layout_height="40dp" />
android:layout_marginTop="@dimen/activity_horizontal_margin" />
<TextView <TextView
android:id="@+id/onlineLibraryProgressStatusText" android:id="@+id/onlineLibraryProgressStatusText"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:layout_marginBottom="@dimen/activity_horizontal_margin"
android:gravity="center" android:gravity="center"
android:text="@string/reaching_remote_library" android:text="@string/reaching_remote_library"
android:textColor="@color/mine_shaft_gray900" android:textColor="@color/mine_shaft_gray900"
android:textSize="12sp" /> android:textSize="10sp"
tools:ignore="SmallSp" />
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -20,9 +20,9 @@ object Versions {
const val tracing: String = "1.1.0" const val tracing: String = "1.1.0"
const val com_squareup_retrofit2: String = "2.9.0" const val com_squareup_retrofit2: String = "2.11.0"
const val com_squareup_okhttp3: String = "4.10.0" const val com_squareup_okhttp3: String = "4.12.0"
const val org_jetbrains_kotlin: String = "1.9.20" const val org_jetbrains_kotlin: String = "1.9.20"

View File

@ -19,5 +19,5 @@
package org.kiwix.kiwixmobile.core.data.remote package org.kiwix.kiwixmobile.core.data.remote
interface OnlineLibraryProgressListener { interface OnlineLibraryProgressListener {
fun onProgress(bytesRead: Long, contentLength: Long, done: Boolean) fun onProgress(bytesRead: Long, contentLength: Long)
} }

View File

@ -25,11 +25,13 @@ import okio.BufferedSource
import okio.ForwardingSource import okio.ForwardingSource
import okio.Source import okio.Source
import okio.buffer import okio.buffer
import org.kiwix.kiwixmobile.core.utils.files.Log import org.kiwix.kiwixmobile.core.downloader.downloadManager.DEFAULT_INT_VALUE
import org.kiwix.kiwixmobile.core.downloader.downloadManager.ZERO
class ProgressResponseBody( class ProgressResponseBody(
private val responseBody: ResponseBody, private val responseBody: ResponseBody,
private val progressListener: OnlineLibraryProgressListener private val progressListener: OnlineLibraryProgressListener,
private val contentLength: Long
) : ResponseBody() { ) : ResponseBody() {
private lateinit var bufferedSource: BufferedSource private lateinit var bufferedSource: BufferedSource
@ -37,7 +39,6 @@ class ProgressResponseBody(
override fun contentType(): MediaType? = responseBody.contentType() override fun contentType(): MediaType? = responseBody.contentType()
override fun contentLength(): Long = responseBody.contentLength() override fun contentLength(): Long = responseBody.contentLength()
override fun source(): BufferedSource { override fun source(): BufferedSource {
if (!::bufferedSource.isInitialized) { if (!::bufferedSource.isInitialized) {
bufferedSource = source(responseBody.source()).buffer() bufferedSource = source(responseBody.source()).buffer()
@ -47,24 +48,11 @@ class ProgressResponseBody(
private fun source(source: Source): Source { private fun source(source: Source): Source {
return object : ForwardingSource(source) { return object : ForwardingSource(source) {
var totalBytesRead = 0L var totalBytesRead = ZERO.toLong()
override fun read(sink: Buffer, byteCount: Long): Long { override fun read(sink: Buffer, byteCount: Long): Long {
val bytesRead = super.read(sink, byteCount) val bytesRead = super.read(sink, byteCount)
totalBytesRead += if (bytesRead != -1L) bytesRead else 0 totalBytesRead += if (bytesRead != DEFAULT_INT_VALUE.toLong()) bytesRead else ZERO.toLong()
val isDone = bytesRead == -1L progressListener.onProgress(totalBytesRead, contentLength)
progressListener.onProgress(
totalBytesRead,
responseBody.contentLength(),
isDone
)
.also {
Log.e(
"PROGRESS",
"onProgress: ${contentLength()} and byteRead = $totalBytesRead\n" +
" sink ${bytesRead == -1L} \n byteRead = $bytesRead " +
"\n bufferedSource = ${bufferedSource.isOpen}"
)
}
return bytesRead return bytesRead
} }
} }

View File

@ -349,6 +349,9 @@
<string name="allow">Allow</string> <string name="allow">Allow</string>
<string name="swipe_down_for_library">Swipe Down for Library</string> <string name="swipe_down_for_library">Swipe Down for Library</string>
<string name="reaching_remote_library">Reaching remote library</string> <string name="reaching_remote_library">Reaching remote library</string>
<string name="parsing_remote_library">Parsing remote library</string>
<string name="starting_downloading_remote_library">Starting download of the online library</string>
<string name="downloading_library">Downloading library %s</string>
<string-array name="pref_dark_modes_entries"> <string-array name="pref_dark_modes_entries">
<item>@string/on</item> <item>@string/on</item>
<item>@string/off</item> <item>@string/off</item>