DownloaderImpl.kt 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. /*
  2. * Nextcloud Android client application
  3. *
  4. * @author Chris Narkiewicz
  5. * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU Affero General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU Affero General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Affero General Public License
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. package com.nextcloud.client.files.downloader
  21. import com.nextcloud.client.core.AsyncRunner
  22. import com.nextcloud.client.core.IsCancelled
  23. import com.nextcloud.client.core.OnProgressCallback
  24. import com.nextcloud.client.core.TaskFunction
  25. import com.owncloud.android.datamodel.OCFile
  26. import java.util.UUID
  27. /**
  28. * Per-user file downloader.
  29. *
  30. * All notifications are performed on main thread. All download processes are run
  31. * in the background.
  32. *
  33. * @param runner Background task runner. It is important to provide runner that is not shared with UI code.
  34. * @param taskFactory Download task factory
  35. * @param threads maximum number of concurrent download processes
  36. */
  37. @Suppress("LongParameterList") // download operations requires those resources
  38. class DownloaderImpl(
  39. private val runner: AsyncRunner,
  40. private val taskFactory: DownloadTask.Factory,
  41. threads: Int = 1
  42. ) : Downloader {
  43. companion object {
  44. const val PROGRESS_PERCENTAGE_MAX = 100
  45. const val PROGRESS_PERCENTAGE_MIN = 0
  46. const val TEST_DOWNLOAD_PROGRESS_UPDATE_PERIOD_MS = 200L
  47. }
  48. private val registry = Registry(
  49. onStartDownload = this::onStartDownload,
  50. onDownloadChanged = this::onDownloadUpdate,
  51. maxRunning = threads
  52. )
  53. private val downloadListeners: MutableSet<(Download) -> Unit> = mutableSetOf()
  54. private val statusListeners: MutableSet<(Downloader.Status) -> Unit> = mutableSetOf()
  55. override val isRunning: Boolean get() = registry.isRunning
  56. override val status: Downloader.Status
  57. get() = Downloader.Status(
  58. pending = registry.pending,
  59. running = registry.running,
  60. completed = registry.completed
  61. )
  62. override fun registerDownloadListener(listener: (Download) -> Unit) {
  63. downloadListeners.add(listener)
  64. }
  65. override fun removeDownloadListener(listener: (Download) -> Unit) {
  66. downloadListeners.remove(listener)
  67. }
  68. override fun registerStatusListener(listener: (Downloader.Status) -> Unit) {
  69. statusListeners.add(listener)
  70. }
  71. override fun removeStatusListener(listener: (Downloader.Status) -> Unit) {
  72. statusListeners.remove(listener)
  73. }
  74. override fun download(request: Request) {
  75. registry.add(request)
  76. registry.startNext()
  77. }
  78. override fun getDownload(uuid: UUID): Download? = registry.getDownload(uuid)
  79. override fun getDownload(file: OCFile): Download? = registry.getDownload(file)
  80. private fun onStartDownload(uuid: UUID, request: Request) {
  81. val downloadTask = createDownloadTask(request)
  82. runner.postTask(
  83. task = downloadTask,
  84. onProgress = { progress: Int -> registry.progress(uuid, progress) },
  85. onResult = { result -> registry.complete(uuid, result.success, result.file); registry.startNext() },
  86. onError = { registry.complete(uuid, false); registry.startNext() }
  87. )
  88. }
  89. private fun createDownloadTask(request: Request): TaskFunction<DownloadTask.Result, Int> {
  90. return if (request.test) {
  91. { progress: OnProgressCallback<Int>, isCancelled: IsCancelled ->
  92. testDownloadTask(request.file, progress, isCancelled)
  93. }
  94. } else {
  95. val downloadTask = taskFactory.create()
  96. val wrapper: TaskFunction<DownloadTask.Result, Int> = { progress: ((Int) -> Unit), isCancelled ->
  97. downloadTask.download(request, progress, isCancelled)
  98. }
  99. wrapper
  100. }
  101. }
  102. private fun onDownloadUpdate(download: Download) {
  103. downloadListeners.forEach { it.invoke(download) }
  104. if (statusListeners.isNotEmpty()) {
  105. val status = this.status
  106. statusListeners.forEach { it.invoke(status) }
  107. }
  108. }
  109. /**
  110. * Test download task is used only to simulate download process without
  111. * any network traffic. It is used for development.
  112. */
  113. private fun testDownloadTask(
  114. file: OCFile,
  115. onProgress: OnProgressCallback<Int>,
  116. isCancelled: IsCancelled
  117. ): DownloadTask.Result {
  118. for (i in PROGRESS_PERCENTAGE_MIN..PROGRESS_PERCENTAGE_MAX) {
  119. onProgress(i)
  120. if (isCancelled()) {
  121. return DownloadTask.Result(file, false)
  122. }
  123. Thread.sleep(TEST_DOWNLOAD_PROGRESS_UPDATE_PERIOD_MS)
  124. }
  125. return DownloadTask.Result(file, true)
  126. }
  127. }