Merge pull request #4118 from kiwix/Fixes#4113

Fixed: Impossible to download full Wikipedia in English.
This commit is contained in:
Kelson 2024-12-08 08:44:18 +01:00 committed by GitHub
commit 154389f459
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 104 additions and 59 deletions

View File

@ -83,7 +83,7 @@ class KiwixReaderFragment : CoreReaderFragment() {
) )
} }
activity.supportActionBar?.setDisplayHomeAsUpEnabled(true) activity.supportActionBar?.setDisplayHomeAsUpEnabled(true)
toolbar?.let(activity::setupDrawerToggle) toolbar?.let { activity.setupDrawerToggle(it, true) }
setFragmentContainerBottomMarginToSizeOfNavBar() setFragmentContainerBottomMarginToSizeOfNavBar()
openPageInBookFromNavigationArguments() openPageInBookFromNavigationArguments()
} }
@ -167,7 +167,7 @@ class KiwixReaderFragment : CoreReaderFragment() {
override fun hideTabSwitcher() { override fun hideTabSwitcher() {
actionBar?.let { actionBar -> actionBar?.let { actionBar ->
actionBar.setDisplayShowTitleEnabled(true) actionBar.setDisplayShowTitleEnabled(true)
toolbar?.let { activity?.setupDrawerToggle(it) } toolbar?.let { activity?.setupDrawerToggle(it, true) }
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)

View File

@ -205,12 +205,16 @@ class ZimManageViewModel @Inject constructor(
.callTimeout(CALL_TIMEOUT, SECONDS) .callTimeout(CALL_TIMEOUT, SECONDS)
.addNetworkInterceptor(UserAgentInterceptor(USER_AGENT)) .addNetworkInterceptor(UserAgentInterceptor(USER_AGENT))
.build() .build()
try {
client.newCall(headRequest).execute().use { response -> client.newCall(headRequest).execute().use { response ->
if (response.isSuccessful) { if (response.isSuccessful) {
return@getContentLengthOfLibraryXmlFile response.header("content-length")?.toLongOrNull() return@getContentLengthOfLibraryXmlFile response.header("content-length")?.toLongOrNull()
?: DEFAULT_INT_VALUE.toLong() ?: DEFAULT_INT_VALUE.toLong()
} }
} }
} catch (ignore: Exception) {
// do nothing
}
return DEFAULT_INT_VALUE.toLong() return DEFAULT_INT_VALUE.toLong()
} }

View File

@ -18,56 +18,84 @@
package org.kiwix.kiwixmobile.core.downloader.downloadManager package org.kiwix.kiwixmobile.core.downloader.downloadManager
import android.annotation.SuppressLint
import android.app.DownloadManager import android.app.DownloadManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import kotlinx.coroutines.CoroutineScope import io.reactivex.Observable
import kotlinx.coroutines.Dispatchers import io.reactivex.disposables.Disposable
import kotlinx.coroutines.launch import io.reactivex.schedulers.Schedulers
import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao import org.kiwix.kiwixmobile.core.dao.DownloadRoomDao
import org.kiwix.kiwixmobile.core.dao.entities.DownloadRoomEntity
import org.kiwix.kiwixmobile.core.downloader.DownloadMonitor import org.kiwix.kiwixmobile.core.downloader.DownloadMonitor
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_CANCEL import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_CANCEL
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_PAUSE import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_PAUSE
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_QUERY_DOWNLOAD_STATUS import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_QUERY_DOWNLOAD_STATUS
import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_RESUME import org.kiwix.kiwixmobile.core.downloader.downloadManager.DownloadNotificationManager.Companion.ACTION_RESUME
import org.kiwix.kiwixmobile.core.extensions.registerReceiver import org.kiwix.kiwixmobile.core.extensions.isServiceRunning
import org.kiwix.kiwixmobile.core.zim_manager.ConnectivityBroadcastReceiver import org.kiwix.kiwixmobile.core.utils.files.Log
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
class DownloadManagerMonitor @Inject constructor( class DownloadManagerMonitor @Inject constructor(
val downloadRoomDao: DownloadRoomDao, val downloadRoomDao: DownloadRoomDao,
private val context: Context, private val context: Context
private val connectivityBroadcastReceiver: ConnectivityBroadcastReceiver
) : DownloadMonitor, DownloadManagerBroadcastReceiver.Callback { ) : DownloadMonitor, DownloadManagerBroadcastReceiver.Callback {
private val lock = Any() private val lock = Any()
private var monitoringDisposable: Disposable? = null
init { init {
context.registerReceiver(connectivityBroadcastReceiver) startMonitoringDownloads()
startServiceIfActiveDownloads()
trackNetworkState()
} }
@SuppressLint("CheckResult") @Suppress("MagicNumber")
private fun trackNetworkState() { fun startMonitoringDownloads() {
connectivityBroadcastReceiver.networkStates if (monitoringDisposable?.isDisposed == false) return
.distinctUntilChanged() monitoringDisposable = Observable.interval(ZERO.toLong(), 5, TimeUnit.SECONDS)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe( .subscribe(
{ {
// Start the service when the network changes so that we can try {
// track the progress accurately. synchronized(lock) {
startServiceIfActiveDownloads() // Observe downloads when the application is in the foreground.
// This is especially useful when downloads are resumed but the
// Download Manager takes some time to update the download status.
// In such cases, the foreground service may stop prematurely due to
// a lack of active downloads during this update delay.
if (downloadRoomDao.downloads().blockingFirst().isNotEmpty()) {
// Check if there are active downloads and the service is not running.
// If so, start the DownloadMonitorService to properly track download progress.
if (shouldStartService()) {
startService()
} else {
// Do nothing; it is for fixing the error when "if" is used as an expression.
}
} else {
monitoringDisposable?.dispose()
}
}
} catch (ignore: Exception) {
Log.e(
"DOWNLOAD_MONITOR",
"Couldn't get the downloads update. Original exception = $ignore"
)
}
}, },
Throwable::printStackTrace Throwable::printStackTrace
) )
} }
private fun startServiceIfActiveDownloads() { /**
CoroutineScope(Dispatchers.IO).launch { * Determines if the DownloadMonitorService should be started.
if (downloadRoomDao.downloads().blockingFirst().isNotEmpty()) { * Checks if there are active downloads and if the service is not already running.
startService() */
} private fun shouldStartService(): Boolean =
} getActiveDownloads().isNotEmpty() &&
!context.isServiceRunning(DownloadMonitorService::class.java)
private fun getActiveDownloads(): List<DownloadRoomEntity> =
downloadRoomDao.downloadRoomEntity().blockingFirst().filter {
it.status != Status.PAUSED && it.status != Status.CANCELLED
} }
override fun downloadCompleteOrCancelled(intent: Intent) { override fun downloadCompleteOrCancelled(intent: Intent) {
@ -86,24 +114,23 @@ class DownloadManagerMonitor @Inject constructor(
} }
} }
fun startMonitoringDownloads() {
startService()
}
private fun startService() { private fun startService() {
context.startService(Intent(context, DownloadMonitorService::class.java)) context.startService(Intent(context, DownloadMonitorService::class.java))
} }
fun pauseDownload(downloadId: Long) { fun pauseDownload(downloadId: Long) {
context.startService(getDownloadMonitorIntent(ACTION_PAUSE, downloadId.toInt())) context.startService(getDownloadMonitorIntent(ACTION_PAUSE, downloadId.toInt()))
startMonitoringDownloads()
} }
fun resumeDownload(downloadId: Long) { fun resumeDownload(downloadId: Long) {
context.startService(getDownloadMonitorIntent(ACTION_RESUME, downloadId.toInt())) context.startService(getDownloadMonitorIntent(ACTION_RESUME, downloadId.toInt()))
startMonitoringDownloads()
} }
fun cancelDownload(downloadId: Long) { fun cancelDownload(downloadId: Long) {
context.startService(getDownloadMonitorIntent(ACTION_CANCEL, downloadId.toInt())) context.startService(getDownloadMonitorIntent(ACTION_CANCEL, downloadId.toInt()))
startMonitoringDownloads()
} }
private fun getDownloadMonitorIntent(action: String, downloadId: Int): Intent = private fun getDownloadMonitorIntent(action: String, downloadId: Int): Intent =

View File

@ -188,8 +188,8 @@ class DownloadMonitorService : Service() {
val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
val reason = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON)) val reason = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON))
val bytesDownloaded = val bytesDownloaded =
cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)) cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
val totalBytes = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)) val totalBytes = cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
val progress = calculateProgress(bytesDownloaded, totalBytes) val progress = calculateProgress(bytesDownloaded, totalBytes)
val etaInMilliSeconds = calculateETA(downloadId, bytesDownloaded, totalBytes) val etaInMilliSeconds = calculateETA(downloadId, bytesDownloaded, totalBytes)
@ -243,8 +243,8 @@ class DownloadMonitorService : Service() {
reason: Int, reason: Int,
progress: Int, progress: Int,
etaInMilliSeconds: Long, etaInMilliSeconds: Long,
bytesDownloaded: Int, bytesDownloaded: Long,
totalBytes: Int totalBytes: Long
) { ) {
val error = mapDownloadError(reason) val error = mapDownloadError(reason)
updateDownloadStatus( updateDownloadStatus(
@ -261,8 +261,8 @@ class DownloadMonitorService : Service() {
private fun handlePausedDownload( private fun handlePausedDownload(
downloadId: Long, downloadId: Long,
progress: Int, progress: Int,
bytesDownloaded: Int, bytesDownloaded: Long,
totalSizeOfDownload: Int, totalSizeOfDownload: Long,
reason: Int reason: Int
) { ) {
val pauseReason = mapDownloadPauseReason(reason) val pauseReason = mapDownloadPauseReason(reason)
@ -288,8 +288,8 @@ class DownloadMonitorService : Service() {
downloadId: Long, downloadId: Long,
progress: Int, progress: Int,
etaInMilliSeconds: Long, etaInMilliSeconds: Long,
bytesDownloaded: Int, bytesDownloaded: Long,
totalSizeOfDownload: Int totalSizeOfDownload: Long
) { ) {
updateDownloadStatus( updateDownloadStatus(
downloadId, downloadId,
@ -317,14 +317,14 @@ class DownloadMonitorService : Service() {
downloadInfoMap.remove(downloadId) downloadInfoMap.remove(downloadId)
} }
private fun calculateProgress(bytesDownloaded: Int, totalBytes: Int): Int = private fun calculateProgress(bytesDownloaded: Long, totalBytes: Long): Int =
if (totalBytes > ZERO) { if (totalBytes > ZERO) {
(bytesDownloaded / totalBytes.toDouble()).times(HUNDERED).toInt() (bytesDownloaded / totalBytes.toDouble()).times(HUNDERED).toInt()
} else { } else {
ZERO ZERO
} }
private fun calculateETA(downloadedFileId: Long, bytesDownloaded: Int, totalBytes: Int): Long { private fun calculateETA(downloadedFileId: Long, bytesDownloaded: Long, totalBytes: Long): Long {
val currentTime = System.currentTimeMillis() val currentTime = System.currentTimeMillis()
val downloadInfo = downloadInfoMap.getOrPut(downloadedFileId) { val downloadInfo = downloadInfoMap.getOrPut(downloadedFileId) {
DownloadInfo(startTime = currentTime, initialBytesDownloaded = bytesDownloaded) DownloadInfo(startTime = currentTime, initialBytesDownloaded = bytesDownloaded)
@ -376,8 +376,8 @@ class DownloadMonitorService : Service() {
error: Error, error: Error,
progress: Int = DEFAULT_INT_VALUE, progress: Int = DEFAULT_INT_VALUE,
etaInMilliSeconds: Long = DEFAULT_INT_VALUE.toLong(), etaInMilliSeconds: Long = DEFAULT_INT_VALUE.toLong(),
bytesDownloaded: Int = DEFAULT_INT_VALUE, bytesDownloaded: Long = DEFAULT_INT_VALUE.toLong(),
totalSizeOfDownload: Int = DEFAULT_INT_VALUE totalSizeOfDownload: Long = DEFAULT_INT_VALUE.toLong()
) { ) {
synchronized(lock) { synchronized(lock) {
updater.onNext { updater.onNext {
@ -392,11 +392,11 @@ class DownloadMonitorService : Service() {
this.progress = progress this.progress = progress
} }
this.etaInMilliSeconds = etaInMilliSeconds this.etaInMilliSeconds = etaInMilliSeconds
if (bytesDownloaded != DEFAULT_INT_VALUE) { if (bytesDownloaded != DEFAULT_INT_VALUE.toLong()) {
this.bytesDownloaded = bytesDownloaded.toLong() this.bytesDownloaded = bytesDownloaded
} }
if (totalSizeOfDownload != DEFAULT_INT_VALUE) { if (totalSizeOfDownload != DEFAULT_INT_VALUE.toLong()) {
this.totalSizeOfDownload = totalSizeOfDownload.toLong() this.totalSizeOfDownload = totalSizeOfDownload
} }
} }
downloadRoomDao.update(downloadModel) downloadRoomDao.update(downloadModel)
@ -614,7 +614,7 @@ class DownloadMonitorService : Service() {
data class DownloadInfo( data class DownloadInfo(
var startTime: Long, var startTime: Long,
var initialBytesDownloaded: Int var initialBytesDownloaded: Long
) )
interface AssignNewForegroundServiceNotification { interface AssignNewForegroundServiceNotification {

View File

@ -100,8 +100,8 @@ object ActivityExtensions {
val Activity.cachedComponent: CoreActivityComponent val Activity.cachedComponent: CoreActivityComponent
get() = coreMainActivity.cachedComponent get() = coreMainActivity.cachedComponent
fun Activity.setupDrawerToggle(toolbar: Toolbar) = fun Activity.setupDrawerToggle(toolbar: Toolbar, shouldEnableRightDrawer: Boolean = false) =
coreMainActivity.setupDrawerToggle(toolbar) coreMainActivity.setupDrawerToggle(toolbar, shouldEnableRightDrawer)
fun Activity.navigate(fragmentId: Int) { fun Activity.navigate(fragmentId: Int) {
coreMainActivity.navigate(fragmentId) coreMainActivity.navigate(fragmentId)

View File

@ -18,6 +18,8 @@
package org.kiwix.kiwixmobile.core.extensions package org.kiwix.kiwixmobile.core.extensions
import android.app.ActivityManager
import android.app.Service
import android.content.Context import android.content.Context
import android.content.Context.RECEIVER_NOT_EXPORTED import android.content.Context.RECEIVER_NOT_EXPORTED
import android.content.Intent import android.content.Intent
@ -113,3 +115,11 @@ fun Context.getBitmapFromDrawable(drawable: Drawable): Bitmap {
return bitmap return bitmap
} }
@Suppress("Deprecation")
fun Context.isServiceRunning(serviceClass: Class<out Service>): Boolean {
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val services = activityManager.getRunningServices(Int.MAX_VALUE)
return services.any { it.service.className == serviceClass.name && it.foreground }
}

View File

@ -220,7 +220,7 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
override fun onSupportNavigateUp(): Boolean = override fun onSupportNavigateUp(): Boolean =
navController.navigateUp() || super.onSupportNavigateUp() navController.navigateUp() || super.onSupportNavigateUp()
open fun setupDrawerToggle(toolbar: Toolbar) { open fun setupDrawerToggle(toolbar: Toolbar, shouldEnableRightDrawer: Boolean = false) {
// Set the initial contentDescription to the hamburger icon. // Set the initial contentDescription to the hamburger icon.
// This method is called from various locations after modifying the navigationIcon. // This method is called from various locations after modifying the navigationIcon.
// For example, we previously changed this icon/contentDescription to the "+" button // For example, we previously changed this icon/contentDescription to the "+" button
@ -241,9 +241,11 @@ abstract class CoreMainActivity : BaseActivity(), WebViewProvider {
it.syncState() it.syncState()
} }
drawerContainerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) drawerContainerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
if (shouldEnableRightDrawer) {
// Enable the right drawer // Enable the right drawer
drawerContainerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END) drawerContainerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END)
} }
}
open fun disableDrawer() { open fun disableDrawer() {
drawerToggle?.isDrawerIndicatorEnabled = false drawerToggle?.isDrawerIndicatorEnabled = false

View File

@ -893,7 +893,9 @@ abstract class CoreReaderFragment :
* to verify proper functionality. * to verify proper functionality.
*/ */
open fun setUpDrawerToggle(toolbar: Toolbar) { open fun setUpDrawerToggle(toolbar: Toolbar) {
toolbar.let((requireActivity() as CoreMainActivity)::setupDrawerToggle) toolbar.let {
(requireActivity() as CoreMainActivity).setupDrawerToggle(it, true)
}
} }
/** /**

View File

@ -98,8 +98,8 @@ class CustomMainActivity : CoreMainActivity() {
} }
} }
override fun setupDrawerToggle(toolbar: Toolbar) { override fun setupDrawerToggle(toolbar: Toolbar, shouldEnableRightDrawer: Boolean) {
super.setupDrawerToggle(toolbar) super.setupDrawerToggle(toolbar, shouldEnableRightDrawer)
activityCustomMainBinding.drawerNavView.apply { activityCustomMainBinding.drawerNavView.apply {
/** /**
* Hide the 'ZimHostFragment' option from the navigation menu * Hide the 'ZimHostFragment' option from the navigation menu