repo created

This commit is contained in:
Fr4nz D13trich 2025-09-18 17:54:51 +02:00
commit 1ef725ef20
2483 changed files with 278273 additions and 0 deletions

View file

@ -0,0 +1,145 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2023 Alper Ozturk <alper_ozturk@proton.me>
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.jobs.download
import android.app.Notification
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.graphics.BitmapFactory
import android.os.Build
import android.os.Handler
import android.os.Looper
import androidx.core.app.NotificationCompat
import com.owncloud.android.R
import com.owncloud.android.lib.resources.files.FileUtils
import com.owncloud.android.operations.DownloadFileOperation
import com.owncloud.android.ui.notifications.NotificationUtils
import com.owncloud.android.utils.theme.ViewThemeUtils
import java.io.File
import java.security.SecureRandom
@Suppress("TooManyFunctions")
class DownloadNotificationManager(
private val id: Int,
private val context: Context,
private val viewThemeUtils: ViewThemeUtils
) {
private var notification: Notification
private var notificationBuilder: NotificationCompat.Builder
private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
init {
notificationBuilder = NotificationUtils.newNotificationBuilder(context, viewThemeUtils).apply {
setContentTitle(context.getString(R.string.downloader_download_in_progress_ticker))
setTicker(context.getString(R.string.downloader_download_in_progress_ticker))
setSmallIcon(R.drawable.notification_icon)
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.drawable.notification_icon))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_DOWNLOAD)
}
}
notification = notificationBuilder.build()
}
@Suppress("MagicNumber")
fun prepareForStart(operation: DownloadFileOperation) {
notificationBuilder = NotificationUtils.newNotificationBuilder(context, viewThemeUtils).apply {
setSmallIcon(R.drawable.notification_icon)
setOngoing(true)
setProgress(100, 0, operation.size < 0)
setContentText(
String.format(
context.getString(R.string.downloader_download_in_progress), 0,
File(operation.savePath).name
)
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_DOWNLOAD)
}
notificationManager.notify(
id,
this.build()
)
}
}
fun prepareForResult() {
notificationBuilder
.setAutoCancel(true)
.setOngoing(false)
.setProgress(0, 0, false)
}
@Suppress("MagicNumber")
fun updateDownloadProgress(filePath: String, percent: Int, totalToTransfer: Long) {
notificationBuilder.run {
setProgress(100, percent, totalToTransfer < 0)
val fileName: String = filePath.substring(filePath.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1)
val text =
String.format(context.getString(R.string.downloader_download_in_progress), percent, fileName)
val title =
context.getString(R.string.downloader_download_in_progress_ticker)
updateNotificationText(title, text)
}
}
@Suppress("MagicNumber")
fun dismissNotification() {
Handler(Looper.getMainLooper()).postDelayed({
notificationManager.cancel(id)
}, 2000)
}
fun showNewNotification(text: String) {
val notifyId = SecureRandom().nextInt()
notificationBuilder.run {
setProgress(0, 0, false)
setContentTitle(null)
setContentText(text)
setOngoing(false)
notificationManager.notify(notifyId, this.build())
}
}
private fun updateNotificationText(title: String?, text: String) {
notificationBuilder.run {
title?.let {
setContentTitle(title)
}
setContentText(text)
notificationManager.notify(id, this.build())
}
}
fun setContentIntent(intent: Intent, flag: Int) {
notificationBuilder.setContentIntent(
PendingIntent.getActivity(
context,
System.currentTimeMillis().toInt(),
intent,
flag
)
)
}
fun getId(): Int {
return id
}
fun getNotification(): Notification {
return notificationBuilder.build()
}
}

View file

@ -0,0 +1,96 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2020 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.jobs.download
import android.content.ContentResolver
import android.content.Context
import com.nextcloud.client.core.IsCancelled
import com.nextcloud.client.files.DownloadRequest
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.common.OwnCloudClient
import com.owncloud.android.operations.DownloadFileOperation
import com.owncloud.android.utils.MimeTypeUtil
import java.io.File
/**
* This runnable object encapsulates file download logic. It has been extracted to wrap
* network operation and storage manager interactions, as those pose testing challenges
* that cannot be addressed due to large number of dependencies.
*
* This design can be regarded as intermediary refactoring step.
*/
class DownloadTask(
private val context: Context,
private val contentResolver: ContentResolver,
private val clientProvider: () -> OwnCloudClient
) {
data class Result(val file: OCFile, val success: Boolean)
/**
* This class is a helper factory to to keep static dependencies
* injection out of the downloader instance.
*
* @param context Context
* @param clientProvider Provide client - this must be called on background thread
* @param contentResolver content resovler used to access file storage
*/
class Factory(
private val context: Context,
private val clientProvider: () -> OwnCloudClient,
private val contentResolver: ContentResolver
) {
fun create(): DownloadTask {
return DownloadTask(context, contentResolver, clientProvider)
}
}
// Unused progress, isCancelled arguments needed for TransferManagerTest
fun download(request: DownloadRequest, progress: (Int) -> Unit, isCancelled: IsCancelled): Result {
val op = DownloadFileOperation(request.user, request.file, context)
val client = clientProvider.invoke()
val result = op.execute(client)
return if (result.isSuccess) {
val storageManager = FileDataStorageManager(
request.user,
contentResolver
)
val file = saveDownloadedFile(op, storageManager)
Result(file, true)
} else {
Result(request.file, false)
}
}
private fun saveDownloadedFile(op: DownloadFileOperation, storageManager: FileDataStorageManager): OCFile {
val file = storageManager.getFileById(op.file.fileId) as OCFile
file.apply {
val syncDate = System.currentTimeMillis()
lastSyncDateForProperties = syncDate
lastSyncDateForData = syncDate
isUpdateThumbnailNeeded = true
modificationTimestamp = op.modificationTimestamp
modificationTimestampAtLastSyncForData = op.modificationTimestamp
etag = op.etag
mimeType = op.mimeType
storagePath = op.savePath
fileLength = File(op.savePath).length()
remoteId = op.file.remoteId
}
storageManager.saveFile(file)
if (MimeTypeUtil.isMedia(op.mimeType)) {
FileDataStorageManager.triggerMediaScan(file.storagePath)
}
return file
}
}

View file

@ -0,0 +1,12 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2023 Alper Ozturk <alper_ozturk@proton.me>
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.jobs.download
enum class FileDownloadError {
Failed, Cancelled
}

View file

@ -0,0 +1,148 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2023 Alper Ozturk <alper_ozturk@proton.me>
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.jobs.download
import com.nextcloud.client.account.User
import com.nextcloud.client.jobs.BackgroundJobManager
import com.owncloud.android.MainApp
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.datamodel.UploadsStorageManager
import com.owncloud.android.operations.DownloadFileOperation
import com.owncloud.android.operations.DownloadType
import com.owncloud.android.utils.MimeTypeUtil
import java.io.File
import javax.inject.Inject
class FileDownloadHelper {
@Inject
lateinit var backgroundJobManager: BackgroundJobManager
@Inject
lateinit var uploadsStorageManager: UploadsStorageManager
companion object {
private var instance: FileDownloadHelper? = null
fun instance(): FileDownloadHelper {
return instance ?: synchronized(this) {
instance ?: FileDownloadHelper().also { instance = it }
}
}
}
init {
MainApp.getAppComponent().inject(this)
}
fun isDownloading(user: User?, file: OCFile?): Boolean {
if (user == null || file == null) {
return false
}
val fileStorageManager = FileDataStorageManager(user, MainApp.getAppContext().contentResolver)
val topParentId = fileStorageManager.getTopParentId(file)
val isJobScheduled = backgroundJobManager.isStartFileDownloadJobScheduled(user, file.fileId)
return isJobScheduled || if (file.isFolder) {
backgroundJobManager.isStartFileDownloadJobScheduled(user, topParentId)
} else {
FileDownloadWorker.isDownloading(user.accountName, file.fileId)
}
}
fun cancelPendingOrCurrentDownloads(user: User?, files: List<OCFile>?) {
if (user == null || files == null) return
files.forEach { file ->
FileDownloadWorker.cancelOperation(user.accountName, file.fileId)
backgroundJobManager.cancelFilesDownloadJob(user, file.fileId)
}
}
fun cancelAllDownloadsForAccount(accountName: String?, currentDownload: DownloadFileOperation?) {
if (accountName == null || currentDownload == null) return
val currentUser = currentDownload.user
val currentFile = currentDownload.file
if (!currentUser.nameEquals(accountName)) {
return
}
currentDownload.cancel()
FileDownloadWorker.cancelOperation(currentUser.accountName, currentFile.fileId)
backgroundJobManager.cancelFilesDownloadJob(currentUser, currentFile.fileId)
}
fun saveFile(
file: OCFile,
currentDownload: DownloadFileOperation?,
storageManager: FileDataStorageManager?
) {
val syncDate = System.currentTimeMillis()
file.apply {
lastSyncDateForProperties = syncDate
lastSyncDateForData = syncDate
isUpdateThumbnailNeeded = true
modificationTimestamp = currentDownload?.modificationTimestamp ?: 0L
modificationTimestampAtLastSyncForData = currentDownload?.modificationTimestamp ?: 0L
etag = currentDownload?.etag
mimeType = currentDownload?.mimeType
storagePath = currentDownload?.savePath
val savePathFile = currentDownload?.savePath?.let { File(it) }
savePathFile?.let {
fileLength = savePathFile.length()
}
remoteId = currentDownload?.file?.remoteId
}
storageManager?.saveFile(file)
if (MimeTypeUtil.isMedia(currentDownload?.mimeType)) {
FileDataStorageManager.triggerMediaScan(file.storagePath, file)
}
storageManager?.saveConflict(file, null)
}
fun downloadFileIfNotStartedBefore(user: User, file: OCFile) {
if (!isDownloading(user, file)) {
downloadFile(user, file, downloadType = DownloadType.DOWNLOAD)
}
}
fun downloadFile(user: User, file: OCFile) {
downloadFile(user, file, downloadType = DownloadType.DOWNLOAD)
}
@Suppress("LongParameterList")
fun downloadFile(
user: User,
ocFile: OCFile,
behaviour: String = "",
downloadType: DownloadType? = DownloadType.DOWNLOAD,
activityName: String = "",
packageName: String = "",
conflictUploadId: Long? = null
) {
backgroundJobManager.startFileDownloadJob(
user,
ocFile,
behaviour,
downloadType,
activityName,
packageName,
conflictUploadId
)
}
}

View file

@ -0,0 +1,84 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2023 Alper Ozturk <alper_ozturk@proton.me>
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.jobs.download
import android.content.Context
import android.content.Intent
import com.nextcloud.client.account.User
import com.owncloud.android.authentication.AuthenticatorActivity
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.operations.DownloadFileOperation
import com.owncloud.android.ui.activity.FileActivity
import com.owncloud.android.ui.activity.FileDisplayActivity
import com.owncloud.android.ui.dialog.SendShareDialog
import com.owncloud.android.ui.fragment.OCFileListFragment
import com.owncloud.android.ui.preview.PreviewImageActivity
import com.owncloud.android.ui.preview.PreviewImageFragment
class FileDownloadIntents(private val context: Context) {
fun newDownloadIntent(
download: DownloadFileOperation,
linkedToRemotePath: String
): Intent {
return Intent(FileDownloadWorker.getDownloadAddedMessage()).apply {
putExtra(FileDownloadWorker.EXTRA_ACCOUNT_NAME, download.user.accountName)
putExtra(FileDownloadWorker.EXTRA_REMOTE_PATH, download.remotePath)
putExtra(FileDownloadWorker.EXTRA_LINKED_TO_PATH, linkedToRemotePath)
setPackage(context.packageName)
}
}
fun downloadFinishedIntent(
download: DownloadFileOperation,
downloadResult: RemoteOperationResult<*>,
unlinkedFromRemotePath: String?
): Intent {
return Intent(FileDownloadWorker.getDownloadFinishMessage()).apply {
putExtra(FileDownloadWorker.EXTRA_DOWNLOAD_RESULT, downloadResult.isSuccess)
putExtra(FileDownloadWorker.EXTRA_ACCOUNT_NAME, download.user.accountName)
putExtra(FileDownloadWorker.EXTRA_REMOTE_PATH, download.remotePath)
putExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR, download.behaviour)
putExtra(SendShareDialog.ACTIVITY_NAME, download.activityName)
putExtra(SendShareDialog.PACKAGE_NAME, download.packageName)
if (unlinkedFromRemotePath != null) {
putExtra(FileDownloadWorker.EXTRA_LINKED_TO_PATH, unlinkedFromRemotePath)
}
setPackage(context.packageName)
}
}
fun credentialContentIntent(user: User): Intent {
return Intent(context, AuthenticatorActivity::class.java).apply {
putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, user.toPlatformAccount())
putExtra(
AuthenticatorActivity.EXTRA_ACTION,
AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN
)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
addFlags(Intent.FLAG_FROM_BACKGROUND)
}
}
fun detailsIntent(operation: DownloadFileOperation?): Intent {
return if (operation != null) {
if (PreviewImageFragment.canBePreviewed(operation.file)) {
Intent(context, PreviewImageActivity::class.java)
} else {
Intent(context, FileDisplayActivity::class.java)
}.apply {
putExtra(FileActivity.EXTRA_FILE, operation.file)
putExtra(FileActivity.EXTRA_USER, operation.user)
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
}
} else {
Intent()
}
}
}

View file

@ -0,0 +1,466 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2023 Alper Ozturk <alper_ozturk@proton.me>
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.nextcloud.client.jobs.download
import android.accounts.Account
import android.accounts.AccountManager
import android.accounts.OnAccountsUpdateListener
import android.app.PendingIntent
import android.content.Context
import android.util.Pair
import androidx.core.util.component1
import androidx.core.util.component2
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.nextcloud.client.account.User
import com.nextcloud.client.account.UserAccountManager
import com.nextcloud.model.WorkerState
import com.nextcloud.model.WorkerStateLiveData
import com.nextcloud.utils.ForegroundServiceHelper
import com.owncloud.android.R
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.ForegroundServiceType
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.files.services.IndexedForest
import com.owncloud.android.lib.common.OwnCloudAccount
import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
import com.owncloud.android.lib.common.network.OnDatatransferProgressListener
import com.owncloud.android.lib.common.operations.RemoteOperationResult
import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.operations.DownloadFileOperation
import com.owncloud.android.operations.DownloadType
import com.owncloud.android.utils.theme.ViewThemeUtils
import java.security.SecureRandom
import java.util.AbstractList
import java.util.Optional
import java.util.Vector
@Suppress("LongParameterList", "TooManyFunctions")
class FileDownloadWorker(
viewThemeUtils: ViewThemeUtils,
private val accountManager: UserAccountManager,
private var localBroadcastManager: LocalBroadcastManager,
private val context: Context,
params: WorkerParameters
) : Worker(context, params), OnAccountsUpdateListener, OnDatatransferProgressListener {
companion object {
private val TAG = FileDownloadWorker::class.java.simpleName
private val pendingDownloads = IndexedForest<DownloadFileOperation>()
fun cancelOperation(accountName: String, fileId: Long) {
pendingDownloads.all.forEach {
it.value?.payload?.cancelMatchingOperation(accountName, fileId)
}
}
fun isDownloading(accountName: String, fileId: Long): Boolean {
return pendingDownloads.all.any { it.value?.payload?.isMatching(accountName, fileId) == true }
}
const val FILE_REMOTE_PATH = "FILE_REMOTE_PATH"
const val ACCOUNT_NAME = "ACCOUNT_NAME"
const val BEHAVIOUR = "BEHAVIOUR"
const val DOWNLOAD_TYPE = "DOWNLOAD_TYPE"
const val ACTIVITY_NAME = "ACTIVITY_NAME"
const val PACKAGE_NAME = "PACKAGE_NAME"
const val CONFLICT_UPLOAD_ID = "CONFLICT_UPLOAD_ID"
const val EXTRA_DOWNLOAD_RESULT = "EXTRA_DOWNLOAD_RESULT"
const val EXTRA_REMOTE_PATH = "EXTRA_REMOTE_PATH"
const val EXTRA_LINKED_TO_PATH = "EXTRA_LINKED_TO_PATH"
const val EXTRA_ACCOUNT_NAME = "EXTRA_ACCOUNT_NAME"
fun getDownloadAddedMessage(): String {
return FileDownloadWorker::class.java.name + "DOWNLOAD_ADDED"
}
fun getDownloadFinishMessage(): String {
return FileDownloadWorker::class.java.name + "DOWNLOAD_FINISH"
}
}
private var currentDownload: DownloadFileOperation? = null
private var conflictUploadId: Long? = null
private var lastPercent = 0
private val intents = FileDownloadIntents(context)
private var notificationManager = DownloadNotificationManager(
SecureRandom().nextInt(),
context,
viewThemeUtils
)
private var downloadProgressListener = FileDownloadProgressListener()
private var user: User? = null
private var currentUser = Optional.empty<User>()
private var currentUserFileStorageManager: FileDataStorageManager? = null
private var fileDataStorageManager: FileDataStorageManager? = null
private var downloadError: FileDownloadError? = null
@Suppress("TooGenericExceptionCaught")
override fun doWork(): Result {
return try {
val requestDownloads = getRequestDownloads()
addAccountUpdateListener()
val foregroundInfo = ForegroundServiceHelper.createWorkerForegroundInfo(
notificationManager.getId(),
notificationManager.getNotification(),
ForegroundServiceType.DataSync
)
setForegroundAsync(foregroundInfo)
requestDownloads.forEach {
downloadFile(it)
}
downloadError?.let {
showDownloadErrorNotification(it)
notificationManager.dismissNotification()
}
setIdleWorkerState()
Log_OC.e(TAG, "FilesDownloadWorker successfully completed")
Result.success()
} catch (t: Throwable) {
notificationManager.dismissNotification()
notificationManager.showNewNotification(context.getString(R.string.downloader_unexpected_error))
Log_OC.e(TAG, "Error caught at FilesDownloadWorker(): " + t.localizedMessage)
setIdleWorkerState()
Result.failure()
}
}
override fun onStopped() {
Log_OC.e(TAG, "FilesDownloadWorker stopped")
notificationManager.dismissNotification()
setIdleWorkerState()
super.onStopped()
}
private fun setWorkerState(user: User?) {
WorkerStateLiveData.instance().setWorkState(WorkerState.Download(user, currentDownload))
}
private fun setIdleWorkerState() {
WorkerStateLiveData.instance().setWorkState(WorkerState.Idle)
}
private fun removePendingDownload(accountName: String?) {
pendingDownloads.remove(accountName)
}
private fun getRequestDownloads(): AbstractList<String> {
setUser()
val files = getFiles()
val downloadType = getDownloadType()
conflictUploadId = inputData.keyValueMap[CONFLICT_UPLOAD_ID] as Long?
val behaviour = inputData.keyValueMap[BEHAVIOUR] as String? ?: ""
val activityName = inputData.keyValueMap[ACTIVITY_NAME] as String? ?: ""
val packageName = inputData.keyValueMap[PACKAGE_NAME] as String? ?: ""
val requestedDownloads: AbstractList<String> = Vector()
return try {
files.forEach { file ->
val operation = DownloadFileOperation(
user,
file,
behaviour,
activityName,
packageName,
context,
downloadType
)
operation.addDownloadDataTransferProgressListener(this)
operation.addDownloadDataTransferProgressListener(downloadProgressListener)
val (downloadKey, linkedToRemotePath) = pendingDownloads.putIfAbsent(
user?.accountName,
file.remotePath,
operation
) ?: Pair(null, null)
downloadKey?.let {
requestedDownloads.add(downloadKey)
}
linkedToRemotePath?.let {
localBroadcastManager.sendBroadcast(intents.newDownloadIntent(operation, linkedToRemotePath))
}
}
requestedDownloads
} catch (e: IllegalArgumentException) {
Log_OC.e(TAG, "Not enough information provided in intent: " + e.message)
requestedDownloads
}
}
private fun setUser() {
val accountName = inputData.keyValueMap[ACCOUNT_NAME] as String
user = accountManager.getUser(accountName).get()
fileDataStorageManager = FileDataStorageManager(user, context.contentResolver)
}
private fun getFiles(): List<OCFile> {
val remotePath = inputData.keyValueMap[FILE_REMOTE_PATH] as String?
val file = fileDataStorageManager?.getFileByEncryptedRemotePath(remotePath) ?: return listOf()
return if (file.isFolder) {
fileDataStorageManager?.getAllFilesRecursivelyInsideFolder(file) ?: listOf()
} else {
listOf(file)
}
}
private fun getDownloadType(): DownloadType? {
val typeAsString = inputData.keyValueMap[DOWNLOAD_TYPE] as String?
return if (typeAsString != null) {
if (typeAsString == DownloadType.DOWNLOAD.toString()) {
DownloadType.DOWNLOAD
} else {
DownloadType.EXPORT
}
} else {
null
}
}
private fun addAccountUpdateListener() {
val am = AccountManager.get(context)
am.addOnAccountsUpdatedListener(this, null, false)
}
@Suppress("TooGenericExceptionCaught", "DEPRECATION")
private fun downloadFile(downloadKey: String) {
currentDownload = pendingDownloads.get(downloadKey)
if (currentDownload == null) {
return
}
setWorkerState(user)
Log_OC.e(TAG, "FilesDownloadWorker downloading: $downloadKey")
val isAccountExist = accountManager.exists(currentDownload?.user?.toPlatformAccount())
if (!isAccountExist) {
removePendingDownload(currentDownload?.user?.accountName)
return
}
notifyDownloadStart(currentDownload!!)
var downloadResult: RemoteOperationResult<*>? = null
try {
val ocAccount = getOCAccountForDownload()
val downloadClient =
OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, context)
downloadResult = currentDownload?.execute(downloadClient)
if (downloadResult?.isSuccess == true && currentDownload?.downloadType === DownloadType.DOWNLOAD) {
getCurrentFile()?.let {
FileDownloadHelper.instance().saveFile(it, currentDownload, currentUserFileStorageManager)
}
}
} catch (e: Exception) {
Log_OC.e(TAG, "Error downloading", e)
downloadResult = RemoteOperationResult<Any?>(e)
} finally {
cleanupDownloadProcess(downloadResult)
}
}
private fun notifyDownloadStart(download: DownloadFileOperation) {
lastPercent = 0
notificationManager.run {
prepareForStart(download)
setContentIntent(intents.detailsIntent(download), PendingIntent.FLAG_IMMUTABLE)
}
}
@Suppress("DEPRECATION")
private fun getOCAccountForDownload(): OwnCloudAccount {
val currentDownloadAccount = currentDownload?.user?.toPlatformAccount()
val currentDownloadUser = accountManager.getUser(currentDownloadAccount?.name)
if (currentUser != currentDownloadUser) {
currentUser = currentDownloadUser
currentUserFileStorageManager = FileDataStorageManager(currentUser.get(), context.contentResolver)
}
return currentDownloadUser.get().toOwnCloudAccount()
}
private fun getCurrentFile(): OCFile? {
var file: OCFile? = currentDownload?.file?.fileId?.let { currentUserFileStorageManager?.getFileById(it) }
if (file == null) {
file = currentUserFileStorageManager?.getFileByDecryptedRemotePath(currentDownload?.file?.remotePath)
}
if (file == null) {
Log_OC.e(this, "Could not save " + currentDownload?.file?.remotePath)
return null
}
return file
}
private fun cleanupDownloadProcess(result: RemoteOperationResult<*>?) {
result?.let {
checkDownloadError(it)
}
val removeResult = pendingDownloads.removePayload(
currentDownload?.user?.accountName,
currentDownload?.remotePath
)
val downloadResult = result ?: RemoteOperationResult<Any?>(RuntimeException("Error downloading…"))
currentDownload?.run {
notifyDownloadResult(this, downloadResult)
val downloadFinishedIntent = intents.downloadFinishedIntent(
this,
downloadResult,
removeResult.second
)
localBroadcastManager.sendBroadcast(downloadFinishedIntent)
}
}
private fun checkDownloadError(result: RemoteOperationResult<*>) {
if (result.isSuccess || downloadError != null) {
return
}
downloadError = if (result.isCancelled) {
FileDownloadError.Cancelled
} else {
FileDownloadError.Failed
}
}
private fun showDownloadErrorNotification(downloadError: FileDownloadError) {
val text = when (downloadError) {
FileDownloadError.Cancelled -> {
context.getString(R.string.downloader_file_download_cancelled)
}
FileDownloadError.Failed -> {
context.getString(R.string.downloader_file_download_failed)
}
}
notificationManager.showNewNotification(text)
}
private fun notifyDownloadResult(
download: DownloadFileOperation,
downloadResult: RemoteOperationResult<*>
) {
if (downloadResult.isCancelled) {
return
}
val needsToUpdateCredentials = (ResultCode.UNAUTHORIZED == downloadResult.code)
notificationManager.run {
prepareForResult()
if (needsToUpdateCredentials) {
showNewNotification(context.getString(R.string.downloader_download_failed_credentials_error))
setContentIntent(
intents.credentialContentIntent(download.user),
PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
)
} else {
setContentIntent(intents.detailsIntent(null), PendingIntent.FLAG_IMMUTABLE)
}
}
}
@Suppress("DEPRECATION")
override fun onAccountsUpdated(accounts: Array<out Account>?) {
if (!accountManager.exists(currentDownload?.user?.toPlatformAccount())) {
currentDownload?.cancel()
}
}
@Suppress("MagicNumber")
override fun onTransferProgress(
progressRate: Long,
totalTransferredSoFar: Long,
totalToTransfer: Long,
filePath: String
) {
val percent: Int = (100.0 * totalTransferredSoFar.toDouble() / totalToTransfer.toDouble()).toInt()
if (percent != lastPercent) {
notificationManager.run {
updateDownloadProgress(filePath, percent, totalToTransfer)
}
}
lastPercent = percent
}
inner class FileDownloadProgressListener : OnDatatransferProgressListener {
private val boundListeners: MutableMap<Long, OnDatatransferProgressListener> = HashMap()
fun isDownloading(user: User?, file: OCFile?): Boolean {
return FileDownloadHelper.instance().isDownloading(user, file)
}
fun addDataTransferProgressListener(listener: OnDatatransferProgressListener?, file: OCFile?) {
if (file == null || listener == null) {
return
}
boundListeners[file.fileId] = listener
}
fun removeDataTransferProgressListener(listener: OnDatatransferProgressListener?, file: OCFile?) {
if (file == null || listener == null) {
return
}
val fileId = file.fileId
if (boundListeners[fileId] === listener) {
boundListeners.remove(fileId)
}
}
override fun onTransferProgress(
progressRate: Long,
totalTransferredSoFar: Long,
totalToTransfer: Long,
fileName: String
) {
val listener = boundListeners[currentDownload?.file?.fileId]
listener?.onTransferProgress(
progressRate,
totalTransferredSoFar,
totalToTransfer,
fileName
)
}
}
}